use crate::plugin::{Plugin, String, Vec};
use crate::synth::{fm_sine, karplus_strong, midi_to_hz, sine, vibrato_sine};
use crate::{
plugin::{overlay_samples, SampleStepConfig},
synth::{envelope, noise_burst},
DrumPattern, DrumStep,
};
use crate::{BassNote, MelodyNote, StemSample};
pub const PERSIAN_BPM_BASE: i32 = 76;
pub const PERSIAN_BPM_VARIATION: i32 = 16;
pub const PERSIAN_SCALE: [u8; 5] = [0, 1, 4, 5, 7];
pub const PERSIAN_CHORD_PROGRESSIONS: [[usize; 4]; 4] = [
[0, 0, 2, 0], [0, 1, 0, 2], [0, 3, 0, 3], [0, 2, 0, 0], ];
pub const PERSIAN_KICK_PATTERNS: [[u8; 16]; 3] = [
[1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0], ];
pub const PERSIAN_SNARE_PATTERNS: [[u8; 16]; 3] = [
[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, 1, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0], ];
pub const PERSIAN_HAT_PATTERNS: [[u8; 16]; 3] = [
[0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0], [1, 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, 0, 0, 0, 0], ];
#[derive(Debug, Clone)]
pub struct PersianPlugin {}
impl Plugin for PersianPlugin {
fn mode(&self) -> &'static str {
"persian"
}
fn mode_string(&self) -> String {
String::from("persian")
}
fn scale(&self) -> &[u8] {
&PERSIAN_SCALE
}
fn chord_progression_from_seed(&self, seed: u32) -> Vec<usize> {
let idx = (seed as usize) % PERSIAN_CHORD_PROGRESSIONS.len();
PERSIAN_CHORD_PROGRESSIONS[idx].to_vec()
}
fn tempo_from_seed(&self, seed: u32) -> i32 {
let variation =
((seed >> 8) % PERSIAN_BPM_VARIATION as u32) as i32 - PERSIAN_BPM_VARIATION / 2;
PERSIAN_BPM_BASE + variation }
fn drum_pattern_from_seed(&self, seed: &[u8; 8]) -> DrumPattern {
let kick = PERSIAN_KICK_PATTERNS[(seed[0] % 3) as usize];
let snare = PERSIAN_SNARE_PATTERNS[(seed[1] % 3) as usize];
let hat = PERSIAN_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: snare[offset] == 1,
hat: hat[offset] == 1,
open_hat: false,
offset,
};
}
if seed[3] & 0x01 != 0 {
steps[8].hat = true;
steps[8].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 hz = midi_to_hz(note.midi);
let str1 = karplus_strong(sr, hz * 0.9992, dur, 0.24);
let str2 = karplus_strong(sr, hz * 1.0008, dur, 0.24);
let str1 = envelope(sr, &str1, 0.010, 0.0, 1.0, 0.050);
let str2 = envelope(sr, &str2, 0.010, 0.0, 1.0, 0.050);
let h1 = sine(sr, hz, dur, 0.14);
let h2 = sine(sr, hz * 2.0, dur, 0.05);
let h3 = sine(sr, hz * 3.0, dur, 0.04);
let h4 = sine(sr, hz * 4.0, dur, 0.09);
let h1 = envelope(sr, &h1, 0.012, 0.065, 0.48, 0.095);
let h2 = envelope(sr, &h2, 0.012, 0.080, 0.28, 0.075);
let h3 = envelope(sr, &h3, 0.012, 0.095, 0.16, 0.060);
let h4 = envelope(sr, &h4, 0.012, 0.115, 0.08, 0.055);
let lyre = karplus_strong(sr, midi_to_hz(note.harmony_midi), dur, 0.07);
let lyre = envelope(sr, &lyre, 0.003, 0.0, 1.0, 0.030);
(0..total)
.map(|i| StemSample {
value: str1.get(i).copied().unwrap_or(0.0)
+ str2.get(i).copied().unwrap_or(0.0)
+ h1.get(i).copied().unwrap_or(0.0)
+ h2.get(i).copied().unwrap_or(0.0)
+ h3.get(i).copied().unwrap_or(0.0)
+ h4.get(i).copied().unwrap_or(0.0)
+ lyre.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 harp = karplus_strong(sr, hz, dur, 0.42);
let harp = envelope(sr, &harp, 0.002, 0.0, 1.0, 0.030);
let oud = sine(sr, hz, dur, 0.14);
let oud = envelope(sr, &oud, 0.008, 0.080, 0.24, 0.100);
(0..total.min(harp.len()))
.map(|i| StemSample {
value: harp[i] + oud.get(i).copied().unwrap_or(0.0),
byte_index: note.byte_index,
})
.collect()
}
fn sample_voice_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);
let ney = fm_sine(sr, hz, dur, 0.090, 1.0, 0.35);
let ney = envelope(sr, &ney, 0.050, 0.060, 0.78, 0.120);
let vib = vibrato_sine(sr, hz, dur, 0.068, 6.5, 0.030);
let vib = envelope(sr, &vib, 0.065, 0.080, 0.70, 0.110);
(0..total)
.map(|i| StemSample {
value: ney.get(i).copied().unwrap_or(0.0) + vib.get(i).copied().unwrap_or(0.0),
byte_index: note.byte_index,
})
.collect()
}
fn sample_step(&self, step: &DrumStep, config: SampleStepConfig, samples: &mut Vec<f32>) {
if step.kick {
let dom_hz = 155.0 + f64::from(config.color % 35);
let dur_k = (0.085_f64).min(config.step_duration * 0.90);
let body = sine(config.sample_rate, dom_hz, dur_k, 0.48);
let snap = noise_burst(
config.sample_rate,
config.random,
(0.018_f64).min(dur_k),
0.18,
);
overlay_samples(
&envelope(config.sample_rate, &body, 0.001, 0.038, 0.0, 0.040),
samples,
);
overlay_samples(
&envelope(config.sample_rate, &snap, 0.0, 0.008, 0.0, 0.008),
samples,
);
}
if step.snare {
let tak_hz = 615.0 + f64::from(config.color % 90);
let dur_t = (0.040_f64).min(config.step_duration * 0.55);
let body = sine(config.sample_rate, tak_hz, dur_t, 0.20);
let noise = noise_burst(config.sample_rate, config.random, dur_t, 0.14);
overlay_samples(
&envelope(config.sample_rate, &body, 0.0, 0.016, 0.0, 0.020),
samples,
);
overlay_samples(
&envelope(config.sample_rate, &noise, 0.0, 0.010, 0.0, 0.014),
samples,
);
}
if step.hat {
if step.open_hat {
let d = (0.180_f64).min(config.step_duration * 3.5);
let noise = noise_burst(config.sample_rate, config.random, d, 0.12);
let ring = sine(config.sample_rate, 3000.0, d, 0.08);
overlay_samples(
&envelope(config.sample_rate, &noise, 0.0, 0.020, 0.20, 0.090),
samples,
);
overlay_samples(
&envelope(config.sample_rate, &ring, 0.0, 0.045, 0.12, 0.100),
samples,
);
} else {
let d = (0.025_f64).min(config.step_duration * 0.50);
let noise = noise_burst(config.sample_rate, config.random, d, 0.10);
let ting = sine(config.sample_rate, 3400.0, d, 0.06);
overlay_samples(
&envelope(config.sample_rate, &noise, 0.0, 0.010, 0.0, 0.008),
samples,
);
overlay_samples(
&envelope(config.sample_rate, &ting, 0.0, 0.012, 0.0, 0.008),
samples,
);
}
}
}
}