use crate::chord::{Chord, interval::Interval, note::Note};
static MAX_MIDI_CODE: u8 = 79;
static MIN_MIDI_CODE: u8 = 51;
#[derive(Debug, PartialEq, Eq, Clone)]
struct MidiNote {
base: u8,
available: Vec<u8>,
int: Interval,
}
impl MidiNote {
pub fn new(note: &Note, int: Interval) -> MidiNote {
let mut candidates = Vec::new();
let mut candidate = note.to_midi_code();
while candidate <= MAX_MIDI_CODE {
if candidate >= MIN_MIDI_CODE {
candidates.push(candidate);
}
candidate += 12;
}
MidiNote {
base: int.st() % 12,
int,
available: candidates,
}
}
}
fn notes_pool(ch: &Chord) -> Vec<MidiNote> {
let mut midi_notes = Vec::new();
for (n, i) in ch.notes.iter().zip(ch.normalized_intervals.clone()) {
midi_notes.push(MidiNote::new(n, i))
}
midi_notes
}
pub type MidiCodesVoicing = Vec<u8>;
fn nearest_lead(pl: u8, pool: &mut Vec<MidiNote>) -> u8 {
let mut not_allowed: Vec<Interval> = Vec::new();
for i in 0..pool.len() {
let curr = &pool[i];
let next = &pool[(i + 1) % pool.len()];
let dist = (curr.base as i32 - next.base as i32).abs();
if dist == 1 || dist == 11 {
not_allowed.push(next.int);
}
}
let mut min = (u8::MAX, Interval::Unison, 0);
for i in pool.iter() {
if not_allowed.contains(&i.int) {
continue;
}
for n in &i.available {
let dist = (pl as i16 - *n as i16).unsigned_abs() as u8;
if dist < min.0 {
min.0 = dist;
min.1 = i.int;
min.2 = *n;
}
}
}
let mut found = (false, 0);
for (i, el) in pool.iter().enumerate() {
for e in &el.available {
if min.2 == *e {
found = (true, i);
}
}
}
if found.0 {
pool.remove(found.1);
}
min.2
}
fn guide_notes(pool: &mut [MidiNote], v: &mut MidiCodesVoicing) {
let binding = pool.to_owned();
let mut guides: Vec<&MidiNote> = binding
.iter()
.filter(|g| {
matches!(
g.int,
Interval::MinorThird
| Interval::MajorThird
| Interval::PerfectFourth
| Interval::AugmentedFourth
| Interval::DiminishedFifth
| Interval::AugmentedFifth
| Interval::DiminishedSeventh
| Interval::MinorSeventh
)
})
.collect();
let has_sixth_or_dim_seventh = pool
.iter()
.any(|x| x.int == Interval::MajorSixth || x.int == Interval::DiminishedSeventh);
let has_minor_seventh = pool.iter().any(|x| x.int == Interval::MinorSeventh);
if !has_minor_seventh && has_sixth_or_dim_seventh {
let sixth = pool.iter().find(|&x| x.int == Interval::MajorSixth);
if let Some(s) = sixth {
guides.push(s);
}
} else if !has_sixth_or_dim_seventh {
let maj_seventh = pool.iter().find(|&x| x.int == Interval::MajorSeventh);
if let Some(s) = maj_seventh {
guides.push(s);
}
}
let mut min = (u8::MAX, Interval::Unison);
while !guides.is_empty() {
for g in &guides {
for n in &g.available {
if *n < min.0 && *n >= MIN_MIDI_CODE {
min = (*n, g.int);
}
}
}
if min.0 == u8::MAX {
break;
}
v.push(min.0);
guides.retain(|i| i.int != min.1);
min = (u8::MAX, Interval::Unison);
}
}
fn non_guide_notes(pool: &mut [MidiNote], v: &mut MidiCodesVoicing, lead: u8) {
let binding = pool.to_owned();
let mut ts: Vec<&MidiNote> = binding
.iter()
.filter(|g| {
matches!(
g.int,
Interval::FlatNinth
| Interval::Ninth
| Interval::SharpNinth
| Interval::Eleventh
| Interval::SharpEleventh
| Interval::FlatThirteenth
| Interval::Thirteenth
)
})
.collect();
if pool.len() < 6 {
let fifth = pool.iter().find(|&x| x.int == Interval::PerfectFifth);
if let Some(f) = fifth {
ts.push(f);
}
let fifth = pool.iter().find(|&x| x.int == Interval::DiminishedFifth);
if let Some(f) = fifth {
ts.push(f);
}
}
if pool.len() < 3 {
let fifth = pool.iter().find(|&x| x.int == Interval::Unison);
if let Some(f) = fifth {
ts.push(f);
}
}
let has_sixth_or_dim_seventh = pool
.iter()
.any(|x| x.int == Interval::MajorSixth || x.int == Interval::DiminishedSeventh);
let has_minor_seventh = pool.iter().any(|x| x.int == Interval::MinorSeventh);
if has_sixth_or_dim_seventh && has_minor_seventh {
let sixth = pool.iter().find(|&x| x.int == Interval::MajorSixth);
if let Some(s) = sixth {
ts.push(s);
}
} else if has_sixth_or_dim_seventh {
let maj_seventh = pool.iter().find(|&x| x.int == Interval::MajorSeventh);
if let Some(s) = maj_seventh {
ts.push(s);
}
}
let mut max = (u8::MIN, Interval::Unison);
while !ts.is_empty() {
for g in &ts {
for n in &g.available {
if *n > max.0 && *n < lead && *n >= MIN_MIDI_CODE {
max = (*n, g.int);
}
}
}
if max.0 == u8::MIN {
break;
}
v.push(max.0);
ts.retain(|i| i.int != max.1);
max = (u8::MIN, Interval::Unison);
}
}
pub fn generate_voicing(ch: &Chord, lead_note: Option<u8>) -> MidiCodesVoicing {
let prev_lead = lead_note.unwrap_or(MAX_MIDI_CODE);
let mut res = Vec::new();
let mut pool = notes_pool(ch);
pool.sort_by_key(|f| f.base);
if ch.bass.is_some() {
res.push(ch.bass.as_ref().unwrap().to_midi_code() - 12);
res.push(ch.root.to_midi_code());
} else {
res.push(ch.root.to_midi_code() - 12);
}
let lead = nearest_lead(prev_lead, &mut pool);
guide_notes(&mut pool, &mut res);
non_guide_notes(&mut pool, &mut res, lead);
res.push(lead);
res
}