use crate::plugin::{Plugin, SampleStepConfig, SectionDef, StemMask, String, Vec};
use crate::synth::{midi_to_hz, sine, triangle};
use crate::{
plugin::overlay_samples,
synth::{envelope, noise_burst, square},
DrumStep,
};
use crate::{BassNote, MelodyNote, StemSample};
const ROCK_SECTIONS: [SectionDef; 10] = [
SectionDef {
beats: 16,
stems: StemMask { voice: false, square: true, triangle: true, noise: false, sfx: false },
silence_after: 0.8,
},
SectionDef {
beats: 32,
stems: StemMask { voice: false, square: true, triangle: true, noise: true, sfx: false },
silence_after: 0.0,
},
SectionDef {
beats: 16,
stems: StemMask::ALL,
silence_after: 0.0,
},
SectionDef {
beats: 32,
stems: StemMask::ALL,
silence_after: 0.5,
},
SectionDef {
beats: 32,
stems: StemMask { voice: false, square: true, triangle: true, noise: true, sfx: false },
silence_after: 0.0,
},
SectionDef {
beats: 16,
stems: StemMask::ALL,
silence_after: 0.0,
},
SectionDef {
beats: 32,
stems: StemMask::ALL,
silence_after: 0.5,
},
SectionDef {
beats: 16,
stems: StemMask { voice: false, square: false, triangle: true, noise: true, sfx: false },
silence_after: 0.8,
},
SectionDef {
beats: 32,
stems: StemMask::ALL,
silence_after: 0.0,
},
SectionDef {
beats: 16,
stems: StemMask { voice: false, square: true, triangle: true, noise: false, sfx: false },
silence_after: 1.5,
},
];
pub const ROCK_BPM_BASE: i32 = 130;
pub const ROCK_BPM_VARIATION: i32 = 30;
pub const ROCK_CHORD_PROGRESSIONS: [[usize; 4]; 4] = [
[0, 3, 2, 3], [0, 2, 3, 2], [0, 3, 4, 3], [0, 2, 4, 2], ];
#[derive(Debug, Clone)]
pub struct RockPlugin {}
impl Plugin for RockPlugin {
fn mode(&self) -> &'static str {
"rock"
}
fn mode_string(&self) -> String {
String::from("rock")
}
fn chord_progression_from_seed(&self, seed: u32) -> Vec<usize> {
let idx = (seed as usize) % ROCK_CHORD_PROGRESSIONS.len();
ROCK_CHORD_PROGRESSIONS[idx].to_vec()
}
fn tempo_from_seed(&self, seed: u32) -> i32 {
let variation = ((seed >> 8) % ROCK_BPM_VARIATION as u32) as i32 - ROCK_BPM_VARIATION / 2;
ROCK_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 midi = (note.midi - 12.0).max(21.0);
let root = square(sr, midi_to_hz(midi), dur, 0.30, 0.5);
let fifth = square(sr, midi_to_hz(midi + 7.0), dur, 0.20, 0.5);
let root = envelope(sr, &root, 0.002, 0.05, 0.80, 0.04);
let fifth = envelope(sr, &fifth, 0.002, 0.05, 0.80, 0.04);
(0..total)
.map(|i| {
let s = root.get(i).copied().unwrap_or(0.0) + fifth.get(i).copied().unwrap_or(0.0);
StemSample {
value: s / (1.0 + s.abs()),
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 raw = triangle(sr, midi_to_hz(note.midi), dur, 0.30);
let driven: Vec<f32> = raw
.iter()
.map(|&s| {
let d = s * 1.5;
d / (1.0 + d.abs())
})
.collect();
let with_env = envelope(sr, &driven, 0.002, 0.04, 0.60, 0.04);
(0..total.min(with_env.len()))
.map(|i| StemSample {
value: with_env[i],
byte_index: note.byte_index,
})
.collect()
}
fn sample_step(&self, step: &DrumStep, config: SampleStepConfig, samples: &mut Vec<f32>) {
if step.kick {
let freq = 65.0 + f64::from(config.color % 10);
let dur_sec = (0.14_f64).min(config.step_duration * 2.0);
let raw = square(config.sample_rate, freq, dur_sec, 0.55, 0.3);
overlay_samples(
&envelope(config.sample_rate, &raw, 0.001, 0.06, 0.0, 0.015),
samples,
);
let sub = sine(config.sample_rate, 50.0, dur_sec, 0.30);
overlay_samples(
&envelope(config.sample_rate, &sub, 0.001, 0.09, 0.0, 0.02),
samples,
);
}
if step.snare {
let accent = config.pattern == 4 || config.pattern == 12;
let amp = if accent { 0.35 } else { 0.15 };
let dur_n = (0.09_f64).min(config.step_duration);
let raw = noise_burst(config.sample_rate, config.random, dur_n, amp);
overlay_samples(
&envelope(config.sample_rate, &raw, 0.001, 0.04, 0.0, 0.02),
samples,
);
if accent {
let tone = sine(config.sample_rate, 200.0, dur_n, 0.15);
overlay_samples(
&envelope(config.sample_rate, &tone, 0.001, 0.03, 0.0, 0.015),
samples,
);
}
}
if step.hat {
overlay_samples(
&if step.open_hat {
let d = (0.12_f64).min(config.step_duration * 3.0);
let s = noise_burst(config.sample_rate, config.random, d, 0.18);
envelope(config.sample_rate, &s, 0.001, 0.06, 0.15, 0.04)
} else {
let amp = if config.pattern % 2 == 0 { 0.14 } else { 0.08 };
let d = (0.022_f64).min(config.step_duration * 0.5);
noise_burst(config.sample_rate, config.random, d, amp)
},
samples,
);
}
}
fn section_defs(&self) -> &'static [SectionDef] {
&ROCK_SECTIONS
}
}