use crate::{err, Meter, Note, Result, Scale, MAX_LENGTH, MAX_PLAYBACK_NOTES}; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] pub enum ChordQuality { Major, Minor, Major7, Minor7, Sus2, Sus4, } impl ChordQuality { pub const ALL: [Self; 6] = [ Self::Major, Self::Minor, Self::Major7, Self::Minor7, Self::Sus2, Self::Sus4, ]; pub fn name(self) -> &'static str { match self { Self::Major => "Major", Self::Minor => "Minor", Self::Major7 => "Major 7", Self::Minor7 => "Minor 7", Self::Sus2 => "Sus 2", Self::Sus4 => "Sus 4", } } pub fn mask(self, root: u8) -> u16 { let intervals: &[u8] = match self { Self::Major => &[0, 4, 7], Self::Minor => &[0, 3, 7], Self::Major7 => &[0, 4, 7, 11], Self::Minor7 => &[0, 3, 7, 10], Self::Sus2 => &[0, 2, 7], Self::Sus4 => &[0, 5, 7], }; intervals .iter() .fold(0, |mask, i| mask | 1 << ((root + i) % 12)) } } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct HarmonySpan { pub start: u32, pub duration: u32, pub tones: u16, pub bass: u8, } #[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)] pub struct Harmony { pub length: u32, pub spans: Vec, } impl Harmony { pub fn validate(&self) -> Result<()> { if self.spans.is_empty() { return if self.length == 0 { Ok(()) } else { Err(err("Empty harmony must have zero length.")) }; } if self.length == 0 || self.length > MAX_LENGTH || self.spans.len() > 256 { return Err(err("Harmony must fit 32 bars and at most 256 changes.")); } let mut end = 0; for span in &self.spans { if span.duration == 0 || span.start < end || span.start.saturating_add(span.duration) > self.length || span.tones == 0 || span.tones > 0xfff || span.bass > 127 { return Err(err("Invalid or overlapping harmonic regions.")); } end = span.start + span.duration; } Ok(()) } pub fn at(&self, tick: u32) -> Option<&HarmonySpan> { if self.length == 0 { return None; } let tick = tick % self.length; self.spans .iter() .find(|s| tick >= s.start && tick < s.start + s.duration) } pub fn from_midi(notes: &[Note], length: u32) -> Result { if notes.is_empty() || notes.len() > MAX_PLAYBACK_NOTES || length == 0 || length > MAX_LENGTH { return Err(err("Import a harmony track of up to 32 bars.")); } let mut edges = std::collections::BTreeSet::new(); for n in notes { if n.pitch > 127 || n.duration == 0 || n.end() > length { return Err(err("Invalid harmony note.")); } edges.insert(n.start); edges.insert(n.end()); } let edges: Vec<_> = edges.into_iter().collect(); let mut spans: Vec = Vec::new(); for pair in edges.windows(2) { let active: Vec<_> = notes .iter() .filter(|n| n.start <= pair[0] && n.end() > pair[0]) .collect(); if active.is_empty() { continue; } let tones = active.iter().fold(0, |mask, n| mask | 1 << (n.pitch % 12)); let bass = active.iter().map(|n| n.pitch).min().unwrap(); if let Some(last) = spans.last_mut() { if last.start + last.duration == pair[0] && last.tones == tones && last.bass == bass { last.duration += pair[1] - pair[0]; continue; } } spans.push(HarmonySpan { start: pair[0], duration: pair[1] - pair[0], tones, bass, }); } let context = Self { length, spans }; context.validate()?; Ok(context) } pub fn progression(root: u8, scale: Scale, meter: Meter) -> Self { let minor = scale.minor(); let bar = meter.bar(); let chords = if minor { [ (0, ChordQuality::Minor), (8, ChordQuality::Major), (3, ChordQuality::Major), (10, ChordQuality::Major), ] } else { [ (0, ChordQuality::Major), (9, ChordQuality::Minor), (5, ChordQuality::Major), (7, ChordQuality::Major), ] }; Self { length: 4 * bar, spans: chords .into_iter() .enumerate() .map(|(i, (step, quality))| { let root = (root + step) % 12; HarmonySpan { start: i as u32 * bar, duration: bar, tones: quality.mask(root), bass: 48 + root, } }) .collect(), } } }