// "Pop Music Generator" - Rust Edition // David Westgate 2024 // Initial python implementation (and most comments) from Bart Massey // https://github.com/pdx-cs-sound/popgen/blob/main/popgen.py use clap::Parser; use rand::Rng; use regex::Regex; use rodio::{OutputStream, buffer::SamplesBuffer, Sink}; use std::{f32::consts::PI, vec}; const C5: &str = "C[5]"; // 11 canonical note names. const NAMES: [&str; 12] = [ "C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", "A", "Bb", "B", ]; const NO_OUTPUT: &str = ""; const REGEX_BASE: &str = r"([A-G]b?)(\[([0-8])\])?"; // Relative notes of a major scale. const MAJOR_SCALE: [u8; 7] = [0, 2, 4, 5, 7, 9, 11]; // Major chord scale tones — one-based. const MAJOR_CHORD: [u8; 3] = [1, 3, 5]; // Root note offset for each chord in scale tones — one-based. const CHORD_LOOP: [u8; 4] = [8, 5, 6, 4]; // Given a MIDI key number and an optional number of beats of // note duration, return a sine wave for that note. fn make_note(key: i8, n: Option, beat_samples: u32, samplerate: u16) -> Vec { todo!() } // Given a scale note with root note 0, return a key offset // from the corresponding root MIDI key. fn note_to_key_offset(note: i8) -> i8 { todo!() } // Given a position within a chord, return a scale note // offset — zero-based. fn chord_to_note_offset(posn: i8) -> i8 { todo!() } fn pick_notes(chord_root: u8, position: &mut i8, rng: &mut rand::prelude::ThreadRng) -> [i8; 4] { todo!() } // Turn a note name into a corresponding MIDI key number. // Format is name with optional bracketed octave, for example // "D" or "Eb[5]". Default is octave 4 if no octave is // specified. fn parse_note(note_str: &str, regex: Regex) -> u32 { todo!() } #[derive(Parser, Debug)] #[command(version, about, long_about = None)] struct Args { #[arg(short, long, default_value_t = 90)] bpm: u8, #[arg(short, long, default_value_t = 48_000)] samplerate: u16, #[arg(short, long, default_value_t = C5.to_string())] root: String, #[arg(long, default_value_t = 2)] bassoctave: u32, #[arg(long, default_value_t = 5)] balance: i8, #[arg(short, long, default_value_t = -3)] gain: i8, #[arg(short, long, default_value_t = NO_OUTPUT.to_string())] output: String, } 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(); let mut position: i8 = 0; // Tempo in beats per minute. let bpm: u8 = args.bpm; // Audio sample rate in samples per second. let samplerate: u16 = args.samplerate; let melody_gain = args.balance; // MIDI key where melody goes. let melody_root: u32 = parse_note(&args.root, note_name_regexp); let melody_root_i8: i8 = melody_root.try_into().unwrap(); // Bass MIDI key is below melody root. let bass_root: i8 = ((melody_root as i32) - (12i32 * args.bassoctave as i32)) .try_into() .unwrap(); }