use crate::plugin::chiptune::classical_section_defs_from_seed;
use crate::plugin::{Plugin, SectionDef, String, Vec};
use crate::synth::{fm_sine, midi_to_hz, pitch_sweep_sine, sine};
use crate::{
plugin::{overlay_samples, SampleStepConfig},
synth::{envelope, noise_burst},
DrumStep,
};
use crate::{BassNote, MelodyNote, StemSample};
pub const LOFI_BPM_BASE: i32 = 99;
pub const LOFI_BPM_VARIATION: i32 = 18;
pub const LOFI_CHORD_PROGRESSIONS: [[usize; 4]; 4] = [
[0, 4, 3, 4], [0, 2, 4, 3], [0, 3, 2, 4], [0, 2, 3, 1], ];
#[derive(Debug, Clone)]
pub struct LofiPlugin {}
impl Plugin for LofiPlugin {
fn mode(&self) -> &'static str {
"lofi"
}
fn mode_string(&self) -> String {
String::from("lofi")
}
fn has_sfx(&self) -> bool {
true
}
fn chord_progression_from_seed(&self, seed: u32) -> Vec<usize> {
let idx = (seed as usize) % LOFI_CHORD_PROGRESSIONS.len();
LOFI_CHORD_PROGRESSIONS[idx].to_vec()
}
fn tempo_from_seed(&self, seed: u32) -> i32 {
let variation = ((seed >> 8) % LOFI_BPM_VARIATION as u32) as i32 - LOFI_BPM_VARIATION / 2;
LOFI_BPM_BASE + variation }
fn sample_melody_note(&self, note: MelodyNote, sample_rate: u32) -> Vec<StemSample> {
let sr = sample_rate as f64;
let total = (sr * note.duration).floor() as usize;
if total == 0 {
return Vec::new();
}
let dur = total as f64 / sr;
let hz = midi_to_hz(note.midi - 12.0);
let harm_hz = midi_to_hz(note.harmony_midi - 12.0);
let tine_attack = fm_sine(sr, hz, dur, 0.25, 2.0, 2.8);
let tine_attack = envelope(sr, &tine_attack, 0.001, 0.055, 0.0, 0.005);
let tine_body = fm_sine(sr, hz, dur, 0.20, 2.0, 0.45);
let tine_body = envelope(sr, &tine_body, 0.003, 0.10, 0.55, 0.12);
let harm = fm_sine(sr, harm_hz, dur, 0.09, 2.0, 0.45);
let harm = envelope(sr, &harm, 0.003, 0.10, 0.55, 0.12);
let brass = fm_sine(sr, harm_hz, dur, 0.11, 1.0, 1.8);
let brass = envelope(sr, &brass, 0.040, 0.08, 0.65, 0.15);
let flute_f = sine(sr, hz, dur, 0.16);
let flute_h2 = sine(sr, hz * 2.0, dur, 0.012);
let flute_h3 = sine(sr, hz * 3.0, dur, 0.004);
let flute_sum: alloc::vec::Vec<f32> = (0..total)
.map(|i| {
flute_f.get(i).copied().unwrap_or(0.0)
+ flute_h2.get(i).copied().unwrap_or(0.0)
+ flute_h3.get(i).copied().unwrap_or(0.0)
})
.collect();
let flute = envelope(sr, &flute_sum, 0.020, 0.060, 0.85, 0.12);
let pad = fm_sine(sr, harm_hz, dur, 0.055, 1.0, 0.70);
let pad = envelope(sr, &pad, 0.080, 0.0, 0.65, 0.18);
(0..total)
.map(|i| StemSample {
value: tine_attack.get(i).copied().unwrap_or(0.0)
+ tine_body.get(i).copied().unwrap_or(0.0)
+ harm.get(i).copied().unwrap_or(0.0)
+ brass.get(i).copied().unwrap_or(0.0)
+ flute.get(i).copied().unwrap_or(0.0)
+ pad.get(i).copied().unwrap_or(0.0),
byte_index: note.byte_index,
})
.collect()
}
fn sample_bass_note(&self, note: BassNote, sample_rate: u32) -> Vec<StemSample> {
let sr = sample_rate as f64;
let total = (sr * note.duration).floor() as usize;
if total == 0 {
return Vec::new();
}
let dur = total as f64 / sr;
let hz = midi_to_hz(note.midi);
let body = fm_sine(sr, hz, dur, 0.28, 2.0, 0.30);
let body = envelope(sr, &body, 0.005, 0.09, 0.55, 0.12);
let sub = sine(sr, hz * 0.5, dur, 0.12);
let sub = envelope(sr, &sub, 0.005, 0.12, 0.50, 0.15);
(0..total)
.map(|i| StemSample {
value: body.get(i).copied().unwrap_or(0.0) + sub.get(i).copied().unwrap_or(0.0),
byte_index: note.byte_index,
})
.collect()
}
fn sample_sfx_note(&self, note: MelodyNote, sample_rate: u32) -> Vec<StemSample> {
let sr = sample_rate as f64;
let total = (sr * note.duration).floor() as usize;
if total == 0 {
return Vec::new();
}
let seed = note.byte_index as u64;
let mut state = seed
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
let mut out: alloc::vec::Vec<f32> = (0..total)
.map(|_| {
state = state
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
((state >> 33) as f32 / 2147483648.0_f32 * 2.0 - 1.0) * 0.006
})
.collect();
let h = seed
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
if (h >> 33) as f32 / 2147483648.0_f32 < 0.08 {
let amp = 0.15 + ((h >> 17) & 0xFF) as f32 / 255.0 * 0.08; let dur = 0.002 + ((h >> 9) & 0xFF) as f64 / 255.0 * 0.003; let n = (sr * dur).floor() as usize;
let raw: alloc::vec::Vec<f32> = (0..n)
.map(|i| {
let a = seed
.wrapping_add(i as u64 ^ 0xDEAD_BEEF)
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
((a >> 33) as f32 / 2147483648.0_f32 * 2.0 - 1.0) * amp
})
.collect();
let shaped = envelope(sr, &raw, 0.0, dur * 0.6, 0.0, dur * 0.4);
for (i, &v) in shaped.iter().enumerate() {
if i < out.len() {
out[i] += v;
}
}
}
out.iter()
.map(|&v| StemSample {
value: v,
byte_index: note.byte_index,
})
.collect()
}
fn sample_step(&self, step: &DrumStep, config: SampleStepConfig, samples: &mut Vec<f32>) {
if step.kick {
let dur_sec = (0.15_f64).min(config.step_duration * 2.5);
let raw = pitch_sweep_sine(config.sample_rate, 100.0, 45.0, dur_sec, 0.35, 30.0);
overlay_samples(
&envelope(config.sample_rate, &raw, 0.001, 0.07, 0.0, 0.04),
samples,
);
}
if step.snare {
let accent = config.pattern == 4 || config.pattern == 12;
let amp = if accent { 0.14 } else { 0.07 };
let dur_n = (0.10_f64).min(config.step_duration);
let noise = noise_burst(config.sample_rate, config.random, dur_n, amp);
let tone = sine(config.sample_rate, 180.0, dur_n, amp * 0.4);
let mixed: alloc::vec::Vec<f32> = noise
.iter()
.zip(tone.iter())
.map(|(&n, &t)| n + t)
.collect();
overlay_samples(
&envelope(config.sample_rate, &mixed, 0.001, 0.07, 0.0, 0.025),
samples,
);
}
if step.hat {
overlay_samples(
&if step.open_hat {
let d = (0.07_f64).min(config.step_duration * 2.5);
let s = noise_burst(config.sample_rate, config.random, d, 0.07);
envelope(config.sample_rate, &s, 0.001, 0.04, 0.20, 0.03)
} else {
let amp = if config.pattern % 2 == 0 { 0.05 } else { 0.03 };
let d = (0.012_f64).min(config.step_duration * 0.35);
noise_burst(config.sample_rate, config.random, d, amp)
},
samples,
);
}
}
fn section_defs_from_seed(&self, seed: u32) -> Vec<SectionDef> {
classical_section_defs_from_seed(seed)
.iter_mut()
.map(|s| {
s.stems.sfx = true;
*s
})
.collect()
}
}