use crate::plugin::{Plugin, String, Vec};
use crate::synth::{fm_sine, midi_to_hz, pitch_sweep_sine};
use crate::{
plugin::{overlay_samples, SampleStepConfig},
synth::{envelope, noise_burst},
DrumPattern, DrumStep,
};
use crate::{BassNote, MelodyNote, StemSample};
pub const TRAP_BPM_BASE: i32 = 140;
pub const TRAP_BPM_VARIATION: i32 = 30;
pub const TRAP_KICK_PATTERNS: [[u8; 16]; 3] = [
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], ];
pub const TRAP_HAT_PATTERNS: [[u8; 16]; 3] = [
[1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1], [1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1], [1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1], ];
pub const TRAP_CHORD_PROGRESSIONS: [[usize; 4]; 4] = [
[0, 1, 0, 4], [0, 4, 1, 3], [0, 1, 3, 4], [0, 3, 1, 0], ];
#[derive(Debug, Clone)]
pub struct TrapPlugin {}
impl Plugin for TrapPlugin {
fn mode(&self) -> &'static str {
"trap"
}
fn mode_string(&self) -> String {
String::from("trap")
}
fn chord_progression_from_seed(&self, seed: u32) -> Vec<usize> {
let idx = (seed as usize) % TRAP_CHORD_PROGRESSIONS.len();
TRAP_CHORD_PROGRESSIONS[idx].to_vec()
}
fn tempo_from_seed(&self, seed: u32) -> i32 {
let variation = ((seed >> 8) % TRAP_BPM_VARIATION as u32) as i32 - TRAP_BPM_VARIATION / 2;
TRAP_BPM_BASE + variation }
fn drum_pattern_from_seed(&self, seed: &[u8; 8]) -> DrumPattern {
let kick = TRAP_KICK_PATTERNS[(seed[0] % 3) as usize];
let hat = TRAP_HAT_PATTERNS[(seed[2] % 3) as usize];
let mut steps = [DrumStep::default(); 16];
for offset in 0..16 {
steps[offset] = DrumStep {
kick: kick[offset] == 1,
snare: offset == 4 || offset == 12, hat: hat[offset] == 1,
open_hat: false,
offset,
};
}
let oh = if seed[5] & 0x01 != 0 { 6 } else { 14 };
steps[oh].hat = true;
steps[oh].open_hat = true;
DrumPattern { steps }
}
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 harmony_midi = (note.harmony_midi - 12.0).max(21.0);
let mel = fm_sine(sr, midi_to_hz(midi), dur, 0.25, 4.0, 2.5);
let mel = envelope(sr, &mel, 0.002, 0.15, 0.10, 0.05);
let harm = fm_sine(sr, midi_to_hz(harmony_midi), dur, 0.10, 4.0, 2.5);
let harm = envelope(sr, &harm, 0.002, 0.15, 0.10, 0.05);
(0..total)
.map(|i| StemSample {
value: mel.get(i).copied().unwrap_or(0.0) + harm.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 target_hz = midi_to_hz(note.midi);
let raw = pitch_sweep_sine(sr, target_hz * 2.2, target_hz, dur, 0.60, 12.0);
let with_env = envelope(sr, &raw, 0.003, 0.20, 0.70, 0.12);
(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 raw = pitch_sweep_sine(
config.sample_rate,
180.0,
42.0,
config.step_duration,
0.80,
22.0,
);
overlay_samples(
&envelope(
config.sample_rate,
&raw,
0.001,
config.step_duration * 0.55,
0.0,
config.step_duration * 0.35,
),
samples,
);
}
if step.snare {
let accent = config.pattern == 4 || config.pattern == 12;
let amp = if accent { 0.40 } else { 0.18 };
let dur_n = (0.07_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.0, 0.030, 0.0, 0.020),
samples,
);
if accent {
let raw2 = noise_burst(config.sample_rate, config.random, dur_n * 0.6, amp * 0.7);
let env2 = envelope(config.sample_rate, &raw2, 0.0, 0.025, 0.0, 0.015);
let offset = (config.sample_rate * 0.008) as usize;
for i in 0..env2.len() {
let dst = i + offset;
if dst < samples.len() {
samples[dst] += env2[i];
}
}
}
}
if step.hat {
overlay_samples(
&if step.open_hat {
let d = (0.10_f64).min(config.step_duration * 2.5);
let s = noise_burst(config.sample_rate, config.random, d, 0.20);
envelope(config.sample_rate, &s, 0.0, 0.04, 0.25, 0.05)
} else {
let amp = if config.pattern % 2 == 0 { 0.16 } else { 0.10 };
let d = (0.020_f64).min(config.step_duration * 0.45);
noise_burst(config.sample_rate, config.random, d, amp)
},
samples,
);
}
}
}