use super::*;
pub(crate) const BASS_LINES: [[i32; 8]; 4] = [
[45, 47, 45, 43, 52, 53, 45, 45], [45, 50, 48, 43, 41, 52, 45, 43], [45, 41, 48, 43, 50, 52, 47, 43], [45, 41, 48, 43, 50, 52, 47, 43], ];
pub(crate) fn bass_root_note(progression: usize, step: usize) -> i32 {
BASS_LINES[progression % BASS_LINES.len()][step % 8]
}
pub(crate) const BASS_RHYTHMS: [[bool; 16]; 4] = [
[
true, false, false, false, true, false, false, false, true, false, false, false, true,
false, false, false,
],
[
true, false, false, true, false, false, true, false, true, true, false, false, true, true,
false, false,
],
[
true, false, true, false, true, false, true, false, true, false, true, false, true, false,
true, false,
],
[
true, false, false, true, false, false, true, false, true, false, false, true, false,
false, true, false,
],
];
pub(crate) const MAX_BASS_VOICES: usize = 3;
pub(crate) const BASS_STEP_BEATS: f32 = 0.25;
pub(crate) struct BassEngine {
pub(crate) sample_rate: f32,
pub(crate) chord_trigger: GridTrigger,
pub(crate) step_index: usize,
pub(crate) step_trigger: GridTrigger,
pub(crate) rhythm_step: usize,
pub(crate) voices: Vec<BassVoice>,
}
impl BassEngine {
pub(crate) fn new(sample_rate: f32) -> Self {
Self {
sample_rate,
chord_trigger: GridTrigger::after_start(),
step_index: 0,
step_trigger: GridTrigger::new(),
rhythm_step: BASS_RHYTHMS[0].len() - 1,
voices: Vec::with_capacity(MAX_BASS_VOICES),
}
}
pub(crate) fn next(
&mut self,
c: &BassControls,
pad: &PadControls,
tune: f32,
timing: TimingContext,
) -> (f32, f32) {
let progression = (pad.progression.round() as i64).rem_euclid(4) as usize;
if self.chord_trigger.pop(timing, pad.chord_bars * 4.0, 0.0) {
self.step_index = (self.step_index + 1) % 8;
}
let loop_len = (c.interval_beats / BASS_STEP_BEATS)
.round()
.clamp(1.0, 32.0) as usize;
if self
.step_trigger
.pop(timing, BASS_STEP_BEATS, c.offset_beats)
{
self.rhythm_step = (self.rhythm_step + 1) % loop_len;
let rhythm = (c.rhythm.round() as usize) % BASS_RHYTHMS.len();
let hit = self.rhythm_step < BASS_RHYTHMS[rhythm].len()
&& BASS_RHYTHMS[rhythm][self.rhythm_step];
if hit {
let note =
bass_root_note(progression, self.step_index) + (c.octave.round() as i32) * 12;
let hz = midi_to_hz(note) * tune_ratio(tune);
for voice in &mut self.voices {
voice.release();
}
if self.voices.len() >= MAX_BASS_VOICES {
let remove_count = self.voices.len() + 1 - MAX_BASS_VOICES;
self.voices.drain(0..remove_count);
}
self.voices.push(BassVoice::new(
hz,
c.attack_time,
c.decay_time,
c.drive,
self.sample_rate,
));
}
}
let mut l = 0.0f32;
let mut r = 0.0f32;
for voice in &mut self.voices {
let (vl, vr) = voice.next();
l += vl;
r += vr;
}
self.voices.retain(|v| !v.is_done());
(l * c.level, r * c.level)
}
}
pub(crate) struct BassVoice {
pub(crate) osc: SineOscillator,
pub(crate) envelope: Adsr,
pub(crate) drive: f32,
}
impl BassVoice {
pub(crate) fn new(hz: f32, attack_time: f32, decay_time: f32, drive: f32, sample_rate: f32) -> Self {
Self {
osc: SineOscillator::new(hz, sample_rate),
envelope: Adsr::new(attack_time, decay_time, 0.0, decay_time, sample_rate),
drive,
}
}
pub(crate) fn next(&mut self) -> (f32, f32) {
let mut s = self.osc.next() * self.envelope.next();
if self.drive > 0.0 {
let driven = s * (1.0 + self.drive * 8.0);
s = driven / (1.0 + driven.abs()) * (1.0 + self.drive * 0.5);
}
StereoPanner::equal_power(s, 0.0)
}
pub(crate) fn release(&mut self) {
self.envelope.note_off();
}
pub(crate) fn is_done(&self) -> bool {
self.envelope.is_done()
}
}