use super::*;
pub(crate) const ARP_RATE_BEATS_MIN: f32 = 0.125;
pub(crate) const ARP_RATE_BEATS_MAX: f32 = 4.0;
pub(crate) const ARP_OCTAVES_MIN: f32 = 1.0;
pub(crate) const ARP_OCTAVES_MAX: f32 = 3.0;
pub(crate) const ARP_CHORD_TONES: usize = 4;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum ArpPattern {
Up,
Down,
UpDown,
Random,
}
pub(crate) fn arp_pattern_index(value: f32) -> usize {
(value.round() as i64).rem_euclid(4) as usize
}
pub(crate) fn arp_pattern_from_control(value: f32) -> ArpPattern {
match arp_pattern_index(value) {
0 => ArpPattern::Up,
1 => ArpPattern::Down,
2 => ArpPattern::UpDown,
_ => ArpPattern::Random,
}
}
pub(crate) fn arp_pattern_label(value: f32) -> &'static str {
match arp_pattern_from_control(value) {
ArpPattern::Up => "Up",
ArpPattern::Down => "Down",
ArpPattern::UpDown => "Up-Down",
ArpPattern::Random => "Random",
}
}
pub(crate) fn arp_octave_span(value: f32) -> usize {
(value.round() as i32).clamp(ARP_OCTAVES_MIN as i32, ARP_OCTAVES_MAX as i32) as usize
}
pub(crate) fn arp_cycle_notes(chord: [i32; ARP_CHORD_TONES], octaves: usize) -> Vec<i32> {
let mut notes = Vec::with_capacity(ARP_CHORD_TONES * octaves.max(1));
for octave in 0..octaves.max(1) {
for tone in chord {
notes.push(tone + 12 * octave as i32);
}
}
notes.sort_unstable();
notes
}
pub(crate) fn arp_ping_pong_advance(pos: usize, dir: i32, len: usize) -> (usize, i32) {
if len <= 1 {
return (0, dir);
}
let proposed = pos as i32 + dir;
if proposed < 0 {
(1, 1)
} else if proposed >= len as i32 {
(len - 2, -1)
} else {
(proposed as usize, dir)
}
}
pub(crate) fn arp_advance(
pos: usize,
pattern: ArpPattern,
len: usize,
dir: i32,
rng: &mut StdRng,
) -> (usize, i32) {
match pattern {
ArpPattern::Up => ((pos + 1) % len, dir),
ArpPattern::Down => ((pos + len - 1) % len, dir),
ArpPattern::UpDown => arp_ping_pong_advance(pos, dir, len),
ArpPattern::Random => (rng.gen_range(0..len), dir),
}
}
pub(crate) struct ArpEngine {
pub(crate) sample_rate: f32,
pub(crate) chord_trigger: GridTrigger,
pub(crate) step_index: usize,
pub(crate) active_chord_count: Option<usize>,
pub(crate) active_progression: Option<usize>,
pub(crate) note_trigger: GridTrigger,
pub(crate) cycle_pos: usize,
pub(crate) ping_pong_dir: i32,
pub(crate) voices: Vec<TonalVoice>,
pub(crate) rng: StdRng,
}
impl ArpEngine {
pub(crate) fn new(sample_rate: f32) -> Self {
Self {
sample_rate,
chord_trigger: GridTrigger::after_start(),
step_index: 0,
active_chord_count: None,
active_progression: None,
note_trigger: GridTrigger::new(),
cycle_pos: 0,
ping_pong_dir: 1,
voices: Vec::with_capacity(8),
rng: StdRng::from_entropy(),
}
}
pub(crate) fn next(
&mut self,
c: &ArpControls,
pad: &PadControls,
tune: f32,
timing: TimingContext,
) -> (f32, f32) {
let active_chord_count = self.active_chord_count.get_or_insert(pad_chord_count(pad));
let progression = progression_index(pad.progression);
let active_progression = self.active_progression.get_or_insert(progression);
advance_pad_progression(
&mut self.step_index,
active_chord_count,
active_progression,
pad_chord_count(pad),
progression,
self.chord_trigger.pop(timing, pad.chord_bars * 4.0, 0.0),
);
let rate_beats = c.rate_beats.clamp(ARP_RATE_BEATS_MIN, ARP_RATE_BEATS_MAX);
if self
.note_trigger
.pop_swung(timing, rate_beats, c.offset_beats, c.swing)
{
let chord = pad_chord_tones(pad, *active_progression, self.step_index);
let octaves = arp_octave_span(c.octaves);
let notes = arp_cycle_notes(chord, octaves);
let len = notes.len().max(1);
self.cycle_pos = self.cycle_pos.min(len - 1);
let pattern = arp_pattern_from_control(c.pattern);
let note = notes[self.cycle_pos];
let (next_pos, next_dir) = arp_advance(
self.cycle_pos,
pattern,
len,
self.ping_pong_dir,
&mut self.rng,
);
self.cycle_pos = next_pos;
self.ping_pong_dir = next_dir;
let hz = midi_to_hz(note) * tune_ratio(tune);
let pan = self.rng.gen_range(-0.4f32..0.4);
if c.gain != 0.0 {
self.voices.push(TonalVoice::new(
tonal_synth_type_index(c.voice_type),
TonalNote {
midi: note,
hz,
pan,
level: c.gain,
sample_rate: self.sample_rate,
attack_time: c.attack,
decay_time: c.decay,
},
));
}
}
mix_and_retain(&mut self.voices, TonalVoice::next, TonalVoice::is_done)
}
}