use crate::instruments::drums::DrumType;
pub const PPQ: u16 = 480;
pub const DEFAULT_VELOCITY: u8 = 80;
pub fn frequency_to_midi_note(freq: f32) -> u8 {
if freq <= 0.0 {
return 0;
}
let note = 69.0 + 12.0 * (freq / 440.0).log2();
note.round().clamp(0.0, 127.0) as u8
}
pub fn midi_note_to_frequency(note: u8) -> f32 {
440.0 * 2.0_f32.powf((note as f32 - 69.0) / 12.0)
}
pub fn seconds_to_ticks(time: f32, tempo: f32, ppq: u16) -> u32 {
let beats = time * (tempo / 60.0);
let ticks = beats * ppq as f32;
ticks.round() as u32
}
pub fn ticks_to_seconds(ticks: u32, tempo: f32, ppq: u16) -> f32 {
let beats = ticks as f32 / ppq as f32;
beats / (tempo / 60.0)
}
pub struct TempoMap {
changes: Vec<(f32, f32)>, ppq: u16,
}
impl TempoMap {
pub fn new(initial_bpm: f32, ppq: u16) -> Self {
Self {
changes: vec![(0.0, initial_bpm)],
ppq,
}
}
pub fn add_change(&mut self, time: f32, bpm: f32) {
self.changes.push((time, bpm));
}
pub fn finalize(&mut self) {
self.changes
.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal));
self.changes.dedup_by(|a, b| (a.0 - b.0).abs() < 0.001);
}
pub fn seconds_to_ticks(&self, time: f32) -> u32 {
if time <= 0.0 {
return 0;
}
let mut accumulated_ticks = 0u32;
let mut prev_time = 0.0;
let mut prev_bpm = self.changes[0].1;
for &(change_time, change_bpm) in &self.changes {
if change_time >= time {
let duration = time - prev_time;
accumulated_ticks += seconds_to_ticks(duration, prev_bpm, self.ppq);
return accumulated_ticks;
}
let duration = change_time - prev_time;
accumulated_ticks += seconds_to_ticks(duration, prev_bpm, self.ppq);
prev_time = change_time;
prev_bpm = change_bpm;
}
let duration = time - prev_time;
accumulated_ticks += seconds_to_ticks(duration, prev_bpm, self.ppq);
accumulated_ticks
}
}
pub fn drum_type_to_midi_note(drum_type: DrumType) -> u8 {
match drum_type {
DrumType::Kick => 36, DrumType::Kick808 => 35, DrumType::SubKick => 35,
DrumType::Snare => 38, DrumType::Snare808 => 40,
DrumType::HiHatClosed => 42, DrumType::HiHat808Closed => 42, DrumType::HiHatOpen => 46, DrumType::HiHat808Open => 46,
DrumType::Clap => 39, DrumType::Clap808 => 39,
DrumType::Tom => 47, DrumType::TomHigh => 50, DrumType::TomLow => 45,
DrumType::Rimshot => 37, DrumType::Cowbell => 56,
DrumType::Crash => 49, DrumType::Ride => 51, DrumType::China => 52, DrumType::Splash => 55,
DrumType::Tambourine => 54, DrumType::Shaker => 70,
DrumType::BassDrop => 35, DrumType::Boom => 35,
DrumType::Claves => 75, DrumType::Triangle => 81, DrumType::SideStick => 37, DrumType::WoodBlock => 77,
DrumType::Kick909 => 36, DrumType::Snare909 => 40,
DrumType::CongaHigh => 62, DrumType::CongaLow => 64, DrumType::BongoHigh => 60, DrumType::BongoLow => 61,
DrumType::RideBell => 53,
DrumType::FloorTomLow => 41, DrumType::FloorTomHigh => 43,
DrumType::HiHatPedal => 44,
DrumType::Crash2 => 57,
DrumType::Vibraslap => 58,
DrumType::TimbaleHigh => 65, DrumType::TimbaleLow => 66, DrumType::AgogoHigh => 67, DrumType::AgogoLow => 68,
DrumType::Cabasa => 69, DrumType::GuiroShort => 73, DrumType::GuiroLong => 74,
DrumType::WoodBlockHigh => 76,
DrumType::Timpani => 47, DrumType::Gong => 52, DrumType::Chimes => 84,
DrumType::Djembe => 60, DrumType::TablaBayan => 58, DrumType::TablaDayan => 77, DrumType::Cajon => 38,
DrumType::Fingersnap => 37, DrumType::Maracas => 70, DrumType::Castanet => 85, DrumType::SleighBells => 83,
DrumType::LaserZap => 35, DrumType::ReverseCymbal => 49, DrumType::WhiteNoiseHit => 39, DrumType::StickClick => 37,
DrumType::KickTight => 36, DrumType::KickDeep => 35, DrumType::KickAcoustic => 36, DrumType::KickClick => 36,
DrumType::SnareRim => 37, DrumType::SnareTight => 38, DrumType::SnareLoose => 38, DrumType::SnarePiccolo => 40,
DrumType::HiHatHalfOpen => 46, DrumType::HiHatSizzle => 46,
DrumType::ClapDry => 39, DrumType::ClapRoom => 39, DrumType::ClapGroup => 39, DrumType::ClapSnare => 39,
DrumType::CrashShort => 49, DrumType::RideTip => 51,
DrumType::EggShaker => 70, DrumType::TubeShaker => 70,
DrumType::Tom808Low => 45, DrumType::Tom808Mid => 47, DrumType::Tom808High => 48, DrumType::Cowbell808 => 56, DrumType::Clave808 => 75,
DrumType::HiHat909Closed => 42, DrumType::HiHat909Open => 46, DrumType::Clap909 => 39, DrumType::Cowbell909 => 56, DrumType::Rim909 => 37,
DrumType::ReverseSnare => 38, DrumType::CymbalSwell => 55, }
}
pub fn midi_note_to_drum_type(midi_note: u8) -> Option<DrumType> {
match midi_note {
35 => Some(DrumType::Kick808), 36 => Some(DrumType::Kick),
38 => Some(DrumType::Snare), 40 => Some(DrumType::Snare808),
42 => Some(DrumType::HiHatClosed), 46 => Some(DrumType::HiHatOpen),
37 => Some(DrumType::Rimshot), 39 => Some(DrumType::Clap),
45 => Some(DrumType::TomLow), 47 => Some(DrumType::Tom), 48 => Some(DrumType::Tom808High), 50 => Some(DrumType::TomHigh),
49 => Some(DrumType::Crash), 51 => Some(DrumType::Ride), 52 => Some(DrumType::China), 55 => Some(DrumType::Splash),
54 => Some(DrumType::Tambourine), 56 => Some(DrumType::Cowbell), 70 => Some(DrumType::Shaker),
75 => Some(DrumType::Claves), 77 => Some(DrumType::WoodBlock), 81 => Some(DrumType::Triangle),
60 => Some(DrumType::BongoHigh), 61 => Some(DrumType::BongoLow), 62 => Some(DrumType::CongaHigh), 64 => Some(DrumType::CongaLow),
53 => Some(DrumType::RideBell),
41 => Some(DrumType::FloorTomLow), 43 => Some(DrumType::FloorTomHigh),
44 => Some(DrumType::HiHatPedal),
57 => Some(DrumType::Crash2),
58 => Some(DrumType::Vibraslap),
65 => Some(DrumType::TimbaleHigh), 66 => Some(DrumType::TimbaleLow), 67 => Some(DrumType::AgogoHigh), 68 => Some(DrumType::AgogoLow),
69 => Some(DrumType::Cabasa), 73 => Some(DrumType::GuiroShort), 74 => Some(DrumType::GuiroLong),
76 => Some(DrumType::WoodBlockHigh),
83 => Some(DrumType::SleighBells), 84 => Some(DrumType::Chimes), 85 => Some(DrumType::Castanet),
_ => None,
}
}
pub fn volume_to_velocity(volume: f32) -> u8 {
(volume.clamp(0.0, 1.0) * 127.0).round() as u8
}
pub fn velocity_to_volume(velocity: u8) -> f32 {
velocity as f32 / 127.0
}
pub fn semitones_to_pitch_bend(semitones: f32, range: f32) -> u16 {
let bend_value = 8192.0 + (semitones / range) * 8192.0;
bend_value.round().clamp(0.0, 16383.0) as u16
}
pub fn pitch_bend_to_semitones_from_signed(bend_value: i16, range: f32) -> f32 {
(bend_value as f32 / 8192.0) * range
}
pub fn mod_value_to_cc(value: f32, bipolar: bool) -> u8 {
if bipolar {
((value + 1.0) * 63.5).round().clamp(0.0, 127.0) as u8
} else {
(value * 127.0).round().clamp(0.0, 127.0) as u8
}
}
pub fn gm_program_to_instrument(program: u8) -> crate::instruments::Instrument {
use crate::instruments::Instrument;
match program {
0 => Instrument::acoustic_piano(), 1 => Instrument::acoustic_piano(), 2 => Instrument::electric_piano(), 3 => Instrument::honky_tonk_piano(), 4 => Instrument::electric_piano(), 5 => Instrument::stage_73(), 6 => Instrument::harpsichord(), 7 => Instrument::clavinet(),
8 => Instrument::celesta(), 9 => Instrument::glockenspiel(), 10 => Instrument::music_box(), 11 => Instrument::vibraphone(), 12 => Instrument::marimba(), 13 => Instrument::xylophone(), 14 => Instrument::tubular_bells(), 15 => Instrument::dulcimer(),
16 => Instrument::hammond_organ(), 17 => Instrument::organ(), 18 => Instrument::church_organ(), 19 => Instrument::church_organ(), 20 => Instrument::reed_organ(), 21 => Instrument::accordion(), 22 => Instrument::accordion(), 23 => Instrument::accordion(),
24 => Instrument::acoustic_guitar(), 25 => Instrument::acoustic_guitar(), 26 => Instrument::electric_guitar_clean(), 27 => Instrument::electric_guitar_clean(), 28 => Instrument::guitar_palm_muted(), 29 => Instrument::electric_guitar_distorted(), 30 => Instrument::electric_guitar_distorted(), 31 => Instrument::guitar_harmonics(),
32 => Instrument::upright_bass(), 33 => Instrument::fingerstyle_bass(), 34 => Instrument::slap_bass(), 35 => Instrument::fretless_bass(), 36 => Instrument::slap_bass(), 37 => Instrument::slap_bass(), 38 => Instrument::synth_bass(), 39 => Instrument::synth_bass(),
40 => Instrument::violin(), 41 => Instrument::viola(), 42 => Instrument::cello(), 43 => Instrument::double_bass(), 44 => Instrument::tremolo_strings(), 45 => Instrument::pizzicato_strings(), 46 => Instrument::harp(), 47 => Instrument::timpani(),
48 => Instrument::strings(), 49 => Instrument::slow_strings(), 50 => Instrument::strings(), 51 => Instrument::strings(), 52 => Instrument::choir_aahs(), 53 => Instrument::choir_oohs(), 54 => Instrument::synth_voice(), 55 => Instrument::strings(),
56 => Instrument::solo_trumpet(), 57 => Instrument::trombone(), 58 => Instrument::tuba(), 59 => Instrument::muted_trumpet(), 60 => Instrument::french_horn(), 61 => Instrument::brass_section(), 62 => Instrument::prophet_brass(), 63 => Instrument::analog_brass(),
64 => Instrument::soprano_sax(), 65 => Instrument::alto_sax(), 66 => Instrument::tenor_sax(), 67 => Instrument::baritone_sax(), 68 => Instrument::oboe(), 69 => Instrument::english_horn(), 70 => Instrument::bassoon(), 71 => Instrument::clarinet(),
72 => Instrument::piccolo(), 73 => Instrument::flute(), 74 => Instrument::flute(), 75 => Instrument::pan_flute(), 76 => Instrument::didgeridoo(), 77 => Instrument::shakuhachi(), 78 => Instrument::shakuhachi(), 79 => Instrument::uilleann_pipes(),
80 => Instrument::square_lead(), 81 => Instrument::saw_lead(), 82 => Instrument::synth_voice(), 83 => Instrument::chiptune(), 84 => Instrument::synth_lead(), 85 => Instrument::synth_voice(), 86 => Instrument::supersaw(), 87 => Instrument::saw_lead(),
88 => Instrument::warm_pad(), 89 => Instrument::ambient_pad(), 90 => Instrument::vocal_pad(), 91 => Instrument::choir_oohs(), 92 => Instrument::shimmer_pad(), 93 => Instrument::ambient_pad(), 94 => Instrument::warm_pad(), 95 => Instrument::juno_pad(),
96 => Instrument::cosmic_rays(), 97 => Instrument::wind_chimes(), 98 => Instrument::glass_harmonica(), 99 => Instrument::ambient_pad(), 100 => Instrument::shimmer_pad(), 101 => Instrument::granular_pad(), 102 => Instrument::cosmic_rays(), 103 => Instrument::glitch(),
104 => Instrument::sitar(), 105 => Instrument::banjo(), 106 => Instrument::shamisen(), 107 => Instrument::koto(), 108 => Instrument::kalimba(), 109 => Instrument::bagpipes(), 110 => Instrument::erhu(), 111 => Instrument::duduk(),
112 => Instrument::steel_drums(), 113 => Instrument::cowbell(), 114 => Instrument::steel_drums(), 115 => Instrument::taiko_drum(), 116 => Instrument::taiko(), 117 => Instrument::timpani(), 118 => Instrument::djembe(), 119 => Instrument::metallic_perc(),
120 => Instrument::guitar_harmonics(), 121 => Instrument::glitch(), 122 => Instrument::wind_chimes(), 123 => Instrument::cosmic_rays(), 124 => Instrument::glitch(), 125 => Instrument::impact(), 126 => Instrument::riser(), 127 => Instrument::laser(),
_ => Instrument::acoustic_piano(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_frequency_to_midi_note() {
assert_eq!(frequency_to_midi_note(440.0), 69); assert_eq!(frequency_to_midi_note(261.63), 60); assert_eq!(frequency_to_midi_note(523.25), 72);
assert_eq!(frequency_to_midi_note(0.0), 0);
assert_eq!(frequency_to_midi_note(-100.0), 0);
assert_eq!(frequency_to_midi_note(20000.0), 127); }
#[test]
fn test_seconds_to_ticks() {
assert_eq!(seconds_to_ticks(0.5, 120.0, 480), 480);
assert_eq!(seconds_to_ticks(1.0, 120.0, 480), 960);
assert_eq!(seconds_to_ticks(1.0, 60.0, 480), 480);
}
#[test]
fn test_drum_type_to_midi_note() {
assert_eq!(drum_type_to_midi_note(DrumType::Kick), 36);
assert_eq!(drum_type_to_midi_note(DrumType::Snare), 38);
assert_eq!(drum_type_to_midi_note(DrumType::HiHatClosed), 42);
assert_eq!(drum_type_to_midi_note(DrumType::HiHatOpen), 46);
assert_eq!(drum_type_to_midi_note(DrumType::Clap), 39);
}
#[test]
fn test_volume_to_velocity() {
assert_eq!(volume_to_velocity(0.0), 0);
assert_eq!(volume_to_velocity(1.0), 127);
assert_eq!(volume_to_velocity(0.5), 64); assert_eq!(volume_to_velocity(1.5), 127); assert_eq!(volume_to_velocity(-0.5), 0); }
#[test]
fn test_semitones_to_pitch_bend() {
assert_eq!(semitones_to_pitch_bend(0.0, 2.0), 8192);
assert_eq!(semitones_to_pitch_bend(2.0, 2.0), 16383);
assert_eq!(semitones_to_pitch_bend(-2.0, 2.0), 0);
assert_eq!(semitones_to_pitch_bend(1.0, 2.0), 12288);
assert_eq!(semitones_to_pitch_bend(-1.0, 2.0), 4096);
assert_eq!(semitones_to_pitch_bend(10.0, 2.0), 16383); assert_eq!(semitones_to_pitch_bend(-10.0, 2.0), 0); }
#[test]
fn test_semitones_to_pitch_bend_different_range() {
assert_eq!(semitones_to_pitch_bend(0.0, 12.0), 8192);
assert_eq!(semitones_to_pitch_bend(12.0, 12.0), 16383);
assert_eq!(semitones_to_pitch_bend(-12.0, 12.0), 0);
assert_eq!(semitones_to_pitch_bend(6.0, 12.0), 12288);
}
#[test]
fn test_pitch_bend_fractional_semitones() {
let bend_quarter_tone = semitones_to_pitch_bend(0.5, 2.0);
assert!(bend_quarter_tone > 8192 && bend_quarter_tone < 12288);
assert_eq!(bend_quarter_tone, 10240);
let bend_eighth_tone = semitones_to_pitch_bend(0.25, 2.0);
assert!(bend_eighth_tone > 8192 && bend_eighth_tone < bend_quarter_tone);
}
#[test]
fn test_mod_value_to_cc_unipolar() {
assert_eq!(mod_value_to_cc(0.0, false), 0);
assert_eq!(mod_value_to_cc(1.0, false), 127);
assert_eq!(mod_value_to_cc(0.5, false), 64);
assert_eq!(mod_value_to_cc(-0.5, false), 0);
assert_eq!(mod_value_to_cc(1.5, false), 127);
}
#[test]
fn test_mod_value_to_cc_bipolar() {
assert_eq!(mod_value_to_cc(-1.0, true), 0);
assert_eq!(mod_value_to_cc(0.0, true), 64);
assert_eq!(mod_value_to_cc(1.0, true), 127);
assert_eq!(mod_value_to_cc(0.5, true), 95); assert_eq!(mod_value_to_cc(-0.5, true), 32);
assert_eq!(mod_value_to_cc(-2.0, true), 0);
assert_eq!(mod_value_to_cc(2.0, true), 127);
}
#[test]
fn test_midi_note_to_frequency() {
assert_eq!(midi_note_to_frequency(69), 440.0); assert!((midi_note_to_frequency(60) - 261.63).abs() < 0.01); assert!((midi_note_to_frequency(72) - 523.25).abs() < 0.01);
let c4 = midi_note_to_frequency(60);
let c5 = midi_note_to_frequency(72);
assert!((c5 / c4 - 2.0).abs() < 0.001); }
#[test]
fn test_midi_note_frequency_roundtrip() {
for midi_note in 21..108 {
let freq = midi_note_to_frequency(midi_note);
let converted_back = frequency_to_midi_note(freq);
assert_eq!(converted_back, midi_note);
}
}
#[test]
fn test_ticks_to_seconds() {
assert_eq!(ticks_to_seconds(480, 120.0, 480), 0.5);
assert_eq!(ticks_to_seconds(960, 120.0, 480), 1.0);
assert_eq!(ticks_to_seconds(480, 60.0, 480), 1.0);
}
#[test]
fn test_ticks_seconds_roundtrip() {
let tempo = 120.0;
let ppq = 480;
for seconds in [0.25, 0.5, 1.0, 2.0, 4.0] {
let ticks = seconds_to_ticks(seconds, tempo, ppq);
let converted_back = ticks_to_seconds(ticks, tempo, ppq);
assert!((converted_back - seconds).abs() < 0.001);
}
}
#[test]
fn test_midi_note_to_drum_type() {
assert_eq!(midi_note_to_drum_type(36), Some(DrumType::Kick));
assert_eq!(midi_note_to_drum_type(38), Some(DrumType::Snare));
assert_eq!(midi_note_to_drum_type(42), Some(DrumType::HiHatClosed));
assert_eq!(midi_note_to_drum_type(46), Some(DrumType::HiHatOpen));
assert_eq!(midi_note_to_drum_type(39), Some(DrumType::Clap));
assert_eq!(midi_note_to_drum_type(49), Some(DrumType::Crash));
assert_eq!(midi_note_to_drum_type(0), None);
assert_eq!(midi_note_to_drum_type(100), None);
}
#[test]
fn test_drum_type_midi_note_roundtrip() {
let drums = [
DrumType::Kick,
DrumType::Snare,
DrumType::HiHatClosed,
DrumType::HiHatOpen,
DrumType::Clap,
DrumType::Crash,
DrumType::Ride,
];
for drum in drums {
let midi_note = drum_type_to_midi_note(drum);
let converted_back = midi_note_to_drum_type(midi_note);
assert!(converted_back.is_some());
}
}
#[test]
fn test_tempo_map_single_tempo() {
let tempo_map = TempoMap::new(120.0, 480);
assert_eq!(tempo_map.seconds_to_ticks(0.0), 0);
assert_eq!(tempo_map.seconds_to_ticks(0.5), 480); assert_eq!(tempo_map.seconds_to_ticks(1.0), 960); assert_eq!(tempo_map.seconds_to_ticks(2.0), 1920); }
#[test]
fn test_tempo_map_multiple_tempos() {
let mut tempo_map = TempoMap::new(120.0, 480);
tempo_map.add_change(2.0, 60.0);
tempo_map.finalize();
assert_eq!(tempo_map.seconds_to_ticks(0.0), 0);
assert_eq!(tempo_map.seconds_to_ticks(0.5), 480); assert_eq!(tempo_map.seconds_to_ticks(1.0), 960); assert_eq!(tempo_map.seconds_to_ticks(2.0), 1920);
assert_eq!(tempo_map.seconds_to_ticks(3.0), 2400);
assert_eq!(tempo_map.seconds_to_ticks(4.0), 2880);
}
#[test]
fn test_tempo_map_complex_scenario() {
let mut tempo_map = TempoMap::new(120.0, 480);
tempo_map.add_change(1.0, 90.0); tempo_map.add_change(3.0, 180.0); tempo_map.finalize();
assert_eq!(tempo_map.seconds_to_ticks(1.0), 960);
assert_eq!(tempo_map.seconds_to_ticks(3.0), 2400);
assert_eq!(tempo_map.seconds_to_ticks(4.0), 3840);
}
#[test]
fn test_tempo_map_tempo_speedup() {
let mut tempo_map = TempoMap::new(60.0, 480);
assert_eq!(tempo_map.seconds_to_ticks(1.0), 480);
tempo_map.add_change(2.0, 120.0);
tempo_map.finalize();
assert_eq!(tempo_map.seconds_to_ticks(2.0), 960);
assert_eq!(tempo_map.seconds_to_ticks(3.0), 1920);
}
#[test]
fn test_tempo_map_edge_cases() {
let tempo_map = TempoMap::new(120.0, 480);
assert_eq!(tempo_map.seconds_to_ticks(0.0), 0);
assert_eq!(tempo_map.seconds_to_ticks(-1.0), 0);
assert!(tempo_map.seconds_to_ticks(0.001) > 0);
}
}