touches on popgen
This commit is contained in:
parent
21d85e1ee4
commit
f8eaf6c753
@ -9,6 +9,7 @@ path = "src/popgen.rs"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.5.23", features = ["derive"] }
|
||||
hound = "3.5.1"
|
||||
rand = "0.8.5"
|
||||
regex = "1.11.1"
|
||||
rodio = "0.20.1"
|
||||
|
BIN
code/popgen/out0.wav
Normal file
BIN
code/popgen/out0.wav
Normal file
Binary file not shown.
BIN
code/popgen/out1.wav
Normal file
BIN
code/popgen/out1.wav
Normal file
Binary file not shown.
@ -4,9 +4,10 @@
|
||||
// https://github.com/pdx-cs-sound/popgen/blob/main/popgen.py
|
||||
|
||||
use clap::Parser;
|
||||
use hound;
|
||||
use rand::Rng;
|
||||
use regex::Regex;
|
||||
use rodio::{OutputStream, buffer::SamplesBuffer, Sink};
|
||||
use rodio::{buffer::SamplesBuffer, OutputStream, Sink};
|
||||
use std::{f32::consts::PI, vec};
|
||||
|
||||
const C5: &str = "C[5]";
|
||||
@ -93,11 +94,10 @@ fn chord_to_note_offset(posn: i8) -> i8 {
|
||||
floor_div(posn, 3) * 7 + (major_chord as i8) - 1
|
||||
}
|
||||
|
||||
// Choose four random notes (one measure) with proper chord offset from the base note
|
||||
fn pick_notes(chord_root: u8, position: &mut i8, rng: &mut rand::prelude::ThreadRng) -> [i8; 4] {
|
||||
let mut notes: [i8; 4] = [0; 4]; //Vec would work too, but less memory efficient
|
||||
|
||||
let mut p: i8 = *position;
|
||||
|
||||
for note in &mut notes {
|
||||
let chord_note_offset: i8 = chord_to_note_offset(p);
|
||||
let chord_note: i8 = note_to_key_offset((chord_root as i8) + chord_note_offset);
|
||||
@ -130,6 +130,7 @@ fn parse_note(note_str: &str, regex: Regex) -> u32 {
|
||||
let mut it: std::str::Chars<'_> = chars.into_iter();
|
||||
let base_note: char = it.next().unwrap();
|
||||
let mut flat: bool = false;
|
||||
// Logic to Handle all valid cases, like C, C[4], Db, Db[6], etc.
|
||||
match it.next() {
|
||||
Some(c) => {
|
||||
if c == 'b' {
|
||||
@ -155,6 +156,7 @@ fn parse_note(note_str: &str, regex: Regex) -> u32 {
|
||||
None => {}
|
||||
}
|
||||
|
||||
// Different string format for flat vs not
|
||||
let index: usize = match flat {
|
||||
true => NAMES.iter().position(|&n| n == format!("{}b", base_note)),
|
||||
false => NAMES.iter().position(|&n| n == base_note.to_string()),
|
||||
@ -186,27 +188,35 @@ struct Args {
|
||||
#[arg(short, long, default_value_t = -3)]
|
||||
gain: i8,
|
||||
|
||||
#[arg(short, long, default_value_t = NO_OUTPUT.to_string())]
|
||||
#[arg(short, long, default_value_t = NO_OUTPUT.to_string())] // Ideally there is a better way than this
|
||||
output: String,
|
||||
}
|
||||
|
||||
// Rodio example https://github.com/RustAudio/rodio/blob/master/examples/basic.rs
|
||||
fn play(samples: Vec<f32>) {
|
||||
|
||||
let (_stream, stream_handle) = OutputStream::try_default().unwrap();
|
||||
let source = SamplesBuffer::new(1,48_000, samples);
|
||||
|
||||
let sink = Sink::try_new(&stream_handle).unwrap();
|
||||
|
||||
fn play(samples: Vec<f32>, samplerate: u16, gain: i8) {
|
||||
// TODO: apply gain
|
||||
let (_stream, stream_handle) = OutputStream::try_default().unwrap();
|
||||
let source: SamplesBuffer<f32> = SamplesBuffer::new(1, samplerate as u32, samples);
|
||||
let sink: Sink = Sink::try_new(&stream_handle).unwrap(); //sink makes it easy to keep the program asleep so the whole song plays
|
||||
sink.append(source);
|
||||
|
||||
sink.sleep_until_end();
|
||||
}
|
||||
|
||||
|
||||
fn save(samples: Vec<f32>, samplerate: u16, output: &str, gain: i8) {
|
||||
// TODO: apply gain
|
||||
let spec: hound::WavSpec = hound::WavSpec {
|
||||
channels: 1,
|
||||
sample_rate: samplerate as u32,
|
||||
bits_per_sample: 32,
|
||||
sample_format: hound::SampleFormat::Float,
|
||||
};
|
||||
let mut writer = hound::WavWriter::create(output, spec).unwrap();
|
||||
for s in samples {
|
||||
writer.write_sample(s).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("Hello, pop!");
|
||||
let args: Args = Args::parse();
|
||||
let note_name_regexp: Regex = Regex::new(REGEX_BASE).unwrap();
|
||||
let mut rng: rand::prelude::ThreadRng = rand::thread_rng();
|
||||
@ -235,35 +245,43 @@ fn main() {
|
||||
let mut sound: Vec<f32> = vec![];
|
||||
|
||||
for chord_root in CHORD_LOOP {
|
||||
let notes = pick_notes(chord_root - 1, &mut position, &mut rng);
|
||||
let notes: [i8; 4] = pick_notes(chord_root - 1, &mut position, &mut rng);
|
||||
|
||||
let mut melody: Vec<Vec<f32>> = vec![];
|
||||
for note in notes {
|
||||
let melody_note: Vec<f32> = make_note(note +melody_root_i8 , None, beat_samples, samplerate);
|
||||
let melody_note: Vec<f32> =
|
||||
make_note(note + melody_root_i8, None, beat_samples, samplerate);
|
||||
melody.push(melody_note);
|
||||
}
|
||||
|
||||
let bass_note: i8 = note_to_key_offset((chord_root - 1) as i8);
|
||||
let bass: Vec<f32> = make_note(bass_note + bass_root, Some(4), beat_samples, samplerate);
|
||||
let bass_gain = 1 - melody_gain;
|
||||
|
||||
// Scale the waveforms
|
||||
let bass_gain: i8 = 1 - melody_gain;
|
||||
|
||||
// Unlike pythons array arithmetic, here we must unravel and mutate our data structures to apply the gains in the correct place.
|
||||
let flattened_melody: Vec<f32> = melody.into_iter().flatten().collect();
|
||||
let scaled_melody: Vec<f32> = flattened_melody.iter().map(|&x| x * melody_gain as f32).collect();
|
||||
|
||||
let scaled_melody: Vec<f32> = flattened_melody
|
||||
.iter()
|
||||
.map(|&x| x * melody_gain as f32)
|
||||
.collect();
|
||||
let scaled_bass: Vec<f32> = bass.iter().map(|&x| x * bass_gain as f32).collect();
|
||||
|
||||
let paired_notes: Vec<(f32, f32)> = scaled_melody.iter().zip(scaled_bass.iter()).map(|(&m, &b)| (m, b)).collect();
|
||||
let paired_notes: Vec<(f32, f32)> = scaled_melody
|
||||
.iter()
|
||||
.zip(scaled_bass.iter())
|
||||
.map(|(&m, &b)| (m, b))
|
||||
.collect();
|
||||
|
||||
// Finally, combine back together
|
||||
let combined_waveform: Vec<f32> = paired_notes.iter().map(|(m, b)| m + b).collect();
|
||||
|
||||
sound.extend(combined_waveform);
|
||||
|
||||
}
|
||||
if args.output.eq(NO_OUTPUT) {
|
||||
play(sound);
|
||||
}
|
||||
else {
|
||||
todo!()
|
||||
play(sound, samplerate, args.gain);
|
||||
} else {
|
||||
save(sound, samplerate, &args.output, args.gain);
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user