120 lines
3.0 KiB
Rust
120 lines
3.0 KiB
Rust
// "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<u8>, beat_samples: u32, samplerate: u16) -> Vec<f32> {
|
|
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();
|
|
|
|
|
|
|
|
}
|
|
|