use alloc::boxed::Box;
use alloc::vec::Vec;
use getset::{Getters, Setters};
use crate::constants::{CHORD_BEATS, HARMONY_BASE, MELODY_BASE};
use crate::plugin::Plugin;
use crate::{MelodyNote, Note, SongMetadata};
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
pub enum ArpeggioPattern {
#[default]
Up,
Down,
UpDown,
}
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
pub struct ArpeggiatorConfig {
pub subdivisions: u8,
pub pattern: ArpeggioPattern,
}
#[derive(Default, Debug, Clone, PartialEq, Getters, Setters)]
pub struct Arpeggiator {
#[getset(get = "pub", get_mut = "pub", set = "pub")]
config: ArpeggiatorConfig,
melody_time: f64,
}
impl Arpeggiator {
pub fn with_config(self, config: ArpeggiatorConfig) -> Self {
Self { config, ..self }
}
pub fn reset(&mut self) {
self.melody_time = 0.0
}
pub fn expand_note(
&mut self,
metadata: &SongMetadata,
plugin: &Box<dyn Plugin>,
note: &Note,
) -> Vec<Note> {
if let Note::Melody(note) = note {
let n = self.config.subdivisions.max(1) as usize;
let sub_dur = note.duration / n as f64;
let scale = plugin.scale();
let chord_idx = (self.melody_time / metadata.timing.beat_duration / CHORD_BEATS as f64)
as usize
% metadata.chord_progression.len();
let chord_deg = metadata.chord_progression[chord_idx];
self.melody_time += note.duration;
let num_tones = 3usize.min(scale.len());
(0..n)
.map(|i| {
let offset = self.tone_index(i, num_tones);
let deg = (chord_deg + offset) % scale.len();
let harm_deg = if deg > 0 { deg - 1 } else { scale.len() - 1 };
let midi = (MELODY_BASE
+ metadata.root_semitone as f64
+ scale[deg] as f64
+ (note.octave * 12) as f64)
.max(21.0)
.min(108.0);
let harmony_midi = (HARMONY_BASE
+ metadata.root_semitone as f64
+ scale[harm_deg] as f64
+ (note.octave * 12) as f64)
.max(21.0)
.min(108.0);
Note::Melody(MelodyNote {
midi,
harmony_midi,
duration: sub_dur,
degree: deg,
octave: note.octave,
byte_index: note.byte_index,
})
})
.collect()
} else {
let mut notes: Vec<Note> = Default::default();
notes.push(*note);
notes
}
}
pub fn expand_notes(
&mut self,
metadata: &SongMetadata,
plugin: &Box<dyn Plugin>,
notes: &[Note],
) -> Vec<Note> {
notes
.iter()
.map(|n| self.expand_note(metadata, plugin, n))
.flatten()
.collect()
}
fn tone_index(&self, i: usize, num_tones: usize) -> usize {
match self.config.pattern {
ArpeggioPattern::Up => i % num_tones,
ArpeggioPattern::Down => (num_tones - 1) - i % num_tones,
ArpeggioPattern::UpDown => {
let cycle = (num_tones - 1) * 2;
let pos = i % cycle;
if pos < num_tones {
pos
} else {
cycle - pos
}
}
}
}
}