tunes/midi/
convert.rs

1//! MIDI conversion utilities
2//!
3//! This module provides functions for converting between Tunes internal
4//! representations and MIDI values (note numbers, velocities, ticks, etc.)
5
6use crate::instruments::drums::DrumType;
7
8/// MIDI ticks per quarter note (standard resolution)
9pub const PPQ: u16 = 480;
10
11/// Default MIDI velocity for notes without explicit velocity
12pub const DEFAULT_VELOCITY: u8 = 80;
13
14/// Convert frequency (Hz) to MIDI note number
15///
16/// Uses equal temperament tuning: MIDI note = 69 + 12 * log2(freq / 440)
17/// Returns 0-127, clamped to valid MIDI range.
18///
19/// # Examples
20/// ```
21/// # use tunes::midi::frequency_to_midi_note;
22/// assert_eq!(frequency_to_midi_note(440.0), 69); // A4
23/// assert_eq!(frequency_to_midi_note(261.63), 60); // C4
24/// ```
25pub fn frequency_to_midi_note(freq: f32) -> u8 {
26    if freq <= 0.0 {
27        return 0;
28    }
29
30    // MIDI note number = 69 + 12 * log2(freq / 440)
31    let note = 69.0 + 12.0 * (freq / 440.0).log2();
32    note.round().clamp(0.0, 127.0) as u8
33}
34
35/// Convert MIDI note number to frequency (Hz)
36///
37/// Uses equal temperament tuning: freq = 440 * 2^((note - 69) / 12)
38///
39/// # Examples
40/// ```
41/// # use tunes::midi::midi_note_to_frequency;
42/// assert_eq!(midi_note_to_frequency(69), 440.0); // A4
43/// assert!((midi_note_to_frequency(60) - 261.63).abs() < 0.01); // C4
44/// ```
45pub fn midi_note_to_frequency(note: u8) -> f32 {
46    // freq = 440 * 2^((note - 69) / 12)
47    440.0 * 2.0_f32.powf((note as f32 - 69.0) / 12.0)
48}
49
50/// Convert time in seconds to MIDI ticks
51///
52/// # Arguments
53/// * `time` - Time in seconds
54/// * `tempo` - Tempo in BPM
55/// * `ppq` - Pulses per quarter note (ticks per beat)
56pub fn seconds_to_ticks(time: f32, tempo: f32, ppq: u16) -> u32 {
57    // Beats = time * (bpm / 60)
58    // Ticks = beats * ppq
59    let beats = time * (tempo / 60.0);
60    let ticks = beats * ppq as f32;
61    ticks.round() as u32
62}
63
64/// Convert MIDI ticks to time in seconds
65///
66/// # Arguments
67/// * `ticks` - MIDI ticks
68/// * `tempo` - Tempo in BPM
69/// * `ppq` - Pulses per quarter note (ticks per beat)
70pub fn ticks_to_seconds(ticks: u32, tempo: f32, ppq: u16) -> f32 {
71    // Beats = ticks / ppq
72    // Time = beats / (bpm / 60)
73    let beats = ticks as f32 / ppq as f32;
74    beats / (tempo / 60.0)
75}
76
77/// Helper struct to track tempo changes and convert time to ticks accurately
78///
79/// This struct maintains a sorted list of tempo changes and calculates MIDI ticks
80/// by integrating through tempo segments, ensuring accurate timing even with
81/// multiple tempo changes.
82pub struct TempoMap {
83    changes: Vec<(f32, f32)>, // (time in seconds, bpm)
84    ppq: u16,
85}
86
87impl TempoMap {
88    /// Create a new TempoMap with an initial tempo
89    pub fn new(initial_bpm: f32, ppq: u16) -> Self {
90        Self {
91            changes: vec![(0.0, initial_bpm)],
92            ppq,
93        }
94    }
95
96    /// Add a tempo change at a specific time
97    pub fn add_change(&mut self, time: f32, bpm: f32) {
98        self.changes.push((time, bpm));
99    }
100
101    /// Sort tempo changes by time (must be called after all changes are added)
102    pub fn finalize(&mut self) {
103        self.changes
104            .sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal));
105        // Remove duplicates at same time (keep last)
106        self.changes.dedup_by(|a, b| (a.0 - b.0).abs() < 0.001);
107    }
108
109    /// Convert time in seconds to MIDI ticks, accounting for tempo changes
110    ///
111    /// This method integrates through tempo segments:
112    /// - For each tempo segment, calculate ticks for time spent in that segment
113    /// - Sum up ticks from all segments up to the target time
114    ///
115    /// # Example
116    /// ```text
117    /// Tempo changes: [(0.0, 120.0), (2.0, 60.0)]
118    /// Converting 3.0 seconds:
119    /// - First 2 seconds at 120 BPM = 1920 ticks
120    /// - Next 1 second at 60 BPM = 480 ticks
121    /// - Total = 2400 ticks
122    /// ```
123    pub fn seconds_to_ticks(&self, time: f32) -> u32 {
124        if time <= 0.0 {
125            return 0;
126        }
127
128        let mut accumulated_ticks = 0u32;
129        let mut prev_time = 0.0;
130        let mut prev_bpm = self.changes[0].1;
131
132        for &(change_time, change_bpm) in &self.changes {
133            if change_time >= time {
134                // Target time is before this tempo change
135                // Calculate ticks from prev_time to time at prev_bpm
136                let duration = time - prev_time;
137                accumulated_ticks += seconds_to_ticks(duration, prev_bpm, self.ppq);
138                return accumulated_ticks;
139            }
140
141            // Calculate ticks from prev_time to change_time at prev_bpm
142            let duration = change_time - prev_time;
143            accumulated_ticks += seconds_to_ticks(duration, prev_bpm, self.ppq);
144
145            prev_time = change_time;
146            prev_bpm = change_bpm;
147        }
148
149        // Time is after all tempo changes - use last tempo
150        let duration = time - prev_time;
151        accumulated_ticks += seconds_to_ticks(duration, prev_bpm, self.ppq);
152        accumulated_ticks
153    }
154}
155
156/// Convert DrumType to General MIDI percussion note number
157///
158/// General MIDI defines percussion on channel 10 with specific note numbers.
159/// See: https://en.wikipedia.org/wiki/General_MIDI#Percussion
160pub fn drum_type_to_midi_note(drum_type: DrumType) -> u8 {
161    match drum_type {
162        // Kick drums
163        DrumType::Kick => 36,    // Bass Drum 1
164        DrumType::Kick808 => 35, // Acoustic Bass Drum
165        DrumType::SubKick => 35, // Acoustic Bass Drum
166
167        // Snare drums
168        DrumType::Snare => 38,    // Acoustic Snare
169        DrumType::Snare808 => 40, // Electric Snare
170
171        // Hi-hats
172        DrumType::HiHatClosed => 42,    // Closed Hi-Hat
173        DrumType::HiHat808Closed => 42, // Closed Hi-Hat
174        DrumType::HiHatOpen => 46,      // Open Hi-Hat
175        DrumType::HiHat808Open => 46,   // Open Hi-Hat
176
177        // Claps
178        DrumType::Clap => 39,    // Hand Clap
179        DrumType::Clap808 => 39, // Hand Clap
180
181        // Toms
182        DrumType::Tom => 47,     // Low-Mid Tom
183        DrumType::TomHigh => 50, // High Tom
184        DrumType::TomLow => 45,  // Low Tom
185
186        // Percussion
187        DrumType::Rimshot => 37, // Side Stick
188        DrumType::Cowbell => 56, // Cowbell
189
190        // Cymbals
191        DrumType::Crash => 49,  // Crash Cymbal 1
192        DrumType::Ride => 51,   // Ride Cymbal 1
193        DrumType::China => 52,  // Chinese Cymbal
194        DrumType::Splash => 55, // Splash Cymbal
195
196        // Shakers/Percussion
197        DrumType::Tambourine => 54, // Tambourine
198        DrumType::Shaker => 70,     // Maracas
199
200        // Special effects (map to toms as fallback)
201        DrumType::BassDrop => 35, // Acoustic Bass Drum
202        DrumType::Boom => 35,     // Acoustic Bass Drum
203
204        // Simple percussion
205        DrumType::Claves => 75,    // Claves
206        DrumType::Triangle => 81,  // Open Triangle
207        DrumType::SideStick => 37, // Side Stick
208        DrumType::WoodBlock => 77, // Low Wood Block
209
210        // 909 electronic drums
211        DrumType::Kick909 => 36,  // Bass Drum 1
212        DrumType::Snare909 => 40, // Electric Snare
213
214        // Latin percussion
215        DrumType::CongaHigh => 62, // Mute Hi Conga
216        DrumType::CongaLow => 64,  // Low Conga
217        DrumType::BongoHigh => 60, // Hi Bongo
218        DrumType::BongoLow => 61,  // Low Bongo
219
220        // Utility
221        DrumType::RideBell => 53, // Ride Bell
222
223        // Additional toms
224        DrumType::FloorTomLow => 41,  // Low Floor Tom
225        DrumType::FloorTomHigh => 43, // High Floor Tom
226
227        // Additional hi-hat
228        DrumType::HiHatPedal => 44, // Pedal Hi-Hat
229
230        // Additional cymbals
231        DrumType::Crash2 => 57, // Crash Cymbal 2
232
233        // Special effects
234        DrumType::Vibraslap => 58, // Vibraslap
235
236        // Additional Latin percussion
237        DrumType::TimbaleHigh => 65, // High Timbale
238        DrumType::TimbaleLow => 66,  // Low Timbale
239        DrumType::AgogoHigh => 67,   // High Agogo
240        DrumType::AgogoLow => 68,    // Low Agogo
241
242        // Additional shakers/scrapers
243        DrumType::Cabasa => 69,     // Cabasa
244        DrumType::GuiroShort => 73, // Short Guiro
245        DrumType::GuiroLong => 74,  // Long Guiro
246
247        // Additional wood percussion
248        DrumType::WoodBlockHigh => 76, // Hi Wood Block
249
250        // Orchestral percussion (no direct GM mapping, use approximations)
251        DrumType::Timpani => 47, // Low-Mid Tom (closest approximation)
252        DrumType::Gong => 52,    // Chinese Cymbal
253        DrumType::Chimes => 84,  // Belltree (GM note 84)
254
255        // World percussion (no direct GM mapping)
256        DrumType::Djembe => 60,     // Hi Bongo (similar hand drum)
257        DrumType::TablaBayan => 58, // Vibraslap (as placeholder)
258        DrumType::TablaDayan => 77, // Low Wood Block (sharp attack)
259        DrumType::Cajon => 38,      // Acoustic Snare (similar character)
260
261        // Hand percussion
262        DrumType::Fingersnap => 37,  // Side Stick (similar click)
263        DrumType::Maracas => 70,     // Maracas (GM standard)
264        DrumType::Castanet => 85,    // Castanets (GM note 85)
265        DrumType::SleighBells => 83, // Jingle Bell (GM note 83)
266
267        // Electronic / Effects (no GM equivalents, use generic)
268        DrumType::LaserZap => 35,      // Bass Drum (placeholder)
269        DrumType::ReverseCymbal => 49, // Crash Cymbal
270        DrumType::WhiteNoiseHit => 39, // Hand Clap
271        DrumType::StickClick => 37,    // Side Stick
272
273        // Kick variations (all map to kick notes)
274        DrumType::KickTight => 36,    // Bass Drum 1
275        DrumType::KickDeep => 35,     // Acoustic Bass Drum
276        DrumType::KickAcoustic => 36, // Bass Drum 1
277        DrumType::KickClick => 36,    // Bass Drum 1
278
279        // Snare variations (all map to snare notes)
280        DrumType::SnareRim => 37,     // Side Stick (rim sound)
281        DrumType::SnareTight => 38,   // Acoustic Snare
282        DrumType::SnareLoose => 38,   // Acoustic Snare
283        DrumType::SnarePiccolo => 40, // Electric Snare
284
285        // Hi-hat variations
286        DrumType::HiHatHalfOpen => 46, // Open Hi-Hat
287        DrumType::HiHatSizzle => 46,   // Open Hi-Hat
288
289        // Clap variations (all map to clap)
290        DrumType::ClapDry => 39,   // Hand Clap
291        DrumType::ClapRoom => 39,  // Hand Clap
292        DrumType::ClapGroup => 39, // Hand Clap
293        DrumType::ClapSnare => 39, // Hand Clap
294
295        // Cymbal variations
296        DrumType::CrashShort => 49, // Crash Cymbal 1
297        DrumType::RideTip => 51,    // Ride Cymbal 1
298
299        // Shaker variations
300        DrumType::EggShaker => 70,  // Maracas
301        DrumType::TubeShaker => 70, // Maracas
302
303        // 808 Kit Completion
304        DrumType::Tom808Low => 45,  // Low Tom
305        DrumType::Tom808Mid => 47,  // Low-Mid Tom
306        DrumType::Tom808High => 48, // Hi-Mid Tom
307        DrumType::Cowbell808 => 56, // Cowbell
308        DrumType::Clave808 => 75,   // Claves
309
310        // 909 Kit Completion
311        DrumType::HiHat909Closed => 42, // Closed Hi-Hat
312        DrumType::HiHat909Open => 46,   // Open Hi-Hat
313        DrumType::Clap909 => 39,        // Hand Clap
314        DrumType::Cowbell909 => 56,     // Cowbell
315        DrumType::Rim909 => 37,         // Side Stick
316
317        // Transition Effects
318        DrumType::ReverseSnare => 38, // Acoustic Snare
319        DrumType::CymbalSwell => 55,  // Splash Cymbal
320    }
321}
322
323/// Convert General MIDI percussion note number to DrumType
324///
325/// Maps standard MIDI percussion notes (channel 10) to tunes DrumType.
326/// Returns None for unsupported MIDI percussion notes.
327///
328/// # Arguments
329/// * `midi_note` - MIDI note number (typically 35-81 for GM percussion)
330///
331/// # Examples
332/// ```
333/// # use tunes::midi::midi_note_to_drum_type;
334/// # use tunes::instruments::drums::DrumType;
335/// assert_eq!(midi_note_to_drum_type(36), Some(DrumType::Kick));
336/// assert_eq!(midi_note_to_drum_type(38), Some(DrumType::Snare));
337/// assert_eq!(midi_note_to_drum_type(42), Some(DrumType::HiHatClosed));
338/// ```
339pub fn midi_note_to_drum_type(midi_note: u8) -> Option<DrumType> {
340    match midi_note {
341        // Kick drums
342        35 => Some(DrumType::Kick808), // Acoustic Bass Drum
343        36 => Some(DrumType::Kick),    // Bass Drum 1
344
345        // Snare drums
346        38 => Some(DrumType::Snare),    // Acoustic Snare
347        40 => Some(DrumType::Snare808), // Electric Snare
348
349        // Hi-hats
350        42 => Some(DrumType::HiHatClosed), // Closed Hi-Hat
351        46 => Some(DrumType::HiHatOpen),   // Open Hi-Hat
352
353        // Claps and rimshots
354        37 => Some(DrumType::Rimshot), // Side Stick
355        39 => Some(DrumType::Clap),    // Hand Clap
356
357        // Toms
358        45 => Some(DrumType::TomLow),     // Low Tom
359        47 => Some(DrumType::Tom),        // Low-Mid Tom
360        48 => Some(DrumType::Tom808High), // Hi-Mid Tom (808 variant)
361        50 => Some(DrumType::TomHigh),    // High Tom
362
363        // Cymbals
364        49 => Some(DrumType::Crash),  // Crash Cymbal 1
365        51 => Some(DrumType::Ride),   // Ride Cymbal 1
366        52 => Some(DrumType::China),  // Chinese Cymbal
367        55 => Some(DrumType::Splash), // Splash Cymbal
368
369        // Percussion
370        54 => Some(DrumType::Tambourine), // Tambourine
371        56 => Some(DrumType::Cowbell),    // Cowbell
372        70 => Some(DrumType::Shaker),     // Maracas
373
374        // Simple percussion
375        75 => Some(DrumType::Claves),    // Claves
376        77 => Some(DrumType::WoodBlock), // Low Wood Block
377        81 => Some(DrumType::Triangle),  // Open Triangle
378
379        // Latin percussion
380        60 => Some(DrumType::BongoHigh), // Hi Bongo
381        61 => Some(DrumType::BongoLow),  // Low Bongo
382        62 => Some(DrumType::CongaHigh), // Mute Hi Conga
383        64 => Some(DrumType::CongaLow),  // Low Conga
384
385        // Ride bell
386        53 => Some(DrumType::RideBell), // Ride Bell
387
388        // Additional toms
389        41 => Some(DrumType::FloorTomLow),  // Low Floor Tom
390        43 => Some(DrumType::FloorTomHigh), // High Floor Tom
391
392        // Additional hi-hat
393        44 => Some(DrumType::HiHatPedal), // Pedal Hi-Hat
394
395        // Additional cymbals
396        57 => Some(DrumType::Crash2), // Crash Cymbal 2
397
398        // Special effects
399        58 => Some(DrumType::Vibraslap), // Vibraslap
400
401        // Additional Latin percussion
402        65 => Some(DrumType::TimbaleHigh), // High Timbale
403        66 => Some(DrumType::TimbaleLow),  // Low Timbale
404        67 => Some(DrumType::AgogoHigh),   // High Agogo
405        68 => Some(DrumType::AgogoLow),    // Low Agogo
406
407        // Additional shakers/scrapers
408        69 => Some(DrumType::Cabasa),     // Cabasa
409        73 => Some(DrumType::GuiroShort), // Short Guiro
410        74 => Some(DrumType::GuiroLong),  // Long Guiro
411
412        // Additional wood percussion
413        76 => Some(DrumType::WoodBlockHigh), // Hi Wood Block
414
415        // Additional GM percussion (orchestral/hand percussion)
416        83 => Some(DrumType::SleighBells), // Jingle Bell
417        84 => Some(DrumType::Chimes),      // Belltree
418        85 => Some(DrumType::Castanet),    // Castanets
419
420        // Unsupported MIDI percussion notes
421        _ => None,
422    }
423}
424
425/// Convert volume (0.0-1.0) to MIDI velocity (0-127)
426pub fn volume_to_velocity(volume: f32) -> u8 {
427    (volume.clamp(0.0, 1.0) * 127.0).round() as u8
428}
429
430/// Convert MIDI velocity (0-127) to volume (0.0-1.0)
431pub fn velocity_to_volume(velocity: u8) -> f32 {
432    velocity as f32 / 127.0
433}
434
435/// Convert pitch bend in semitones to MIDI pitch bend value (14-bit)
436///
437/// MIDI pitch bend is a 14-bit value (0-16383) with center at 8192.
438/// Standard pitch bend range is ±2 semitones.
439///
440/// # Arguments
441/// * `semitones` - Pitch bend amount in semitones (positive = up, negative = down)
442/// * `range` - Pitch bend range in semitones (default is 2.0 for ±2 semitones)
443///
444/// # Returns
445/// 14-bit pitch bend value (0-16383), clamped to valid range
446pub fn semitones_to_pitch_bend(semitones: f32, range: f32) -> u16 {
447    // Center value is 8192 (no bend)
448    // Each semitone within the range corresponds to ±8192/range units
449    let bend_value = 8192.0 + (semitones / range) * 8192.0;
450    bend_value.round().clamp(0.0, 16383.0) as u16
451}
452
453/// Convert MIDI pitch bend value (14-bit) to semitones
454///
455/// MIDI pitch bend is a 14-bit value (0-16383) with center at 8192.
456/// Standard pitch bend range is ±2 semitones.
457///
458/// Note: The midly library returns pitch bend as a signed i16 value
459/// relative to center (-8192 to +8191), not the raw 14-bit value.
460///
461/// # Arguments
462/// * `bend_value` - Pitch bend value from midly (signed, relative to center)
463/// * `range` - Pitch bend range in semitones (default is 2.0 for ±2 semitones)
464///
465/// # Returns
466/// Pitch bend in semitones (positive = up, negative = down)
467pub fn pitch_bend_to_semitones_from_signed(bend_value: i16, range: f32) -> f32 {
468    // midly returns pitch bend as signed value relative to center
469    // -8192 to +8191, where 0 = center (no bend)
470    (bend_value as f32 / 8192.0) * range
471}
472
473/// Convert a modulation LFO value to MIDI CC value (0-127)
474///
475/// For unipolar modulation (volume): 0.0 -> 0, 1.0 -> 127
476/// For bipolar modulation (pitch, pan): -1.0 -> 0, 0.0 -> 64, 1.0 -> 127
477pub fn mod_value_to_cc(value: f32, bipolar: bool) -> u8 {
478    if bipolar {
479        // Bipolar: -1.0 to 1.0 -> 0 to 127
480        ((value + 1.0) * 63.5).round().clamp(0.0, 127.0) as u8
481    } else {
482        // Unipolar: 0.0 to 1.0 -> 0 to 127
483        (value * 127.0).round().clamp(0.0, 127.0) as u8
484    }
485}
486
487/// Map General MIDI program number (0-127) to an Instrument preset
488///
489/// This provides automatic instrument selection when importing MIDI files.
490/// The mapping follows the General MIDI standard and uses the best available
491/// preset from the library's 160+ instruments.
492///
493/// # General MIDI Program Categories
494/// - 0-7: Piano
495/// - 8-15: Chromatic Percussion
496/// - 16-23: Organ
497/// - 24-31: Guitar
498/// - 32-39: Bass
499/// - 40-47: Strings
500/// - 48-55: Ensemble
501/// - 56-63: Brass
502/// - 64-71: Reed
503/// - 72-79: Pipe
504/// - 80-87: Synth Lead
505/// - 88-95: Synth Pad
506/// - 96-103: Synth Effects
507/// - 104-111: Ethnic
508/// - 112-119: Percussive
509/// - 120-127: Sound Effects
510pub fn gm_program_to_instrument(program: u8) -> crate::instruments::Instrument {
511    use crate::instruments::Instrument;
512
513    match program {
514        // Piano (0-7)
515        0 => Instrument::acoustic_piano(),      // Acoustic Grand Piano
516        1 => Instrument::acoustic_piano(),      // Bright Acoustic Piano
517        2 => Instrument::electric_piano(),      // Electric Grand Piano
518        3 => Instrument::honky_tonk_piano(),    // Honky-tonk Piano
519        4 => Instrument::electric_piano(),      // Electric Piano 1 (Rhodes)
520        5 => Instrument::stage_73(),            // Electric Piano 2 (Chorus)
521        6 => Instrument::harpsichord(),         // Harpsichord
522        7 => Instrument::clavinet(),            // Clavinet
523
524        // Chromatic Percussion (8-15)
525        8 => Instrument::celesta(),             // Celesta
526        9 => Instrument::glockenspiel(),        // Glockenspiel
527        10 => Instrument::music_box(),          // Music Box
528        11 => Instrument::vibraphone(),         // Vibraphone
529        12 => Instrument::marimba(),            // Marimba
530        13 => Instrument::xylophone(),          // Xylophone
531        14 => Instrument::tubular_bells(),      // Tubular Bells
532        15 => Instrument::dulcimer(),           // Dulcimer
533
534        // Organ (16-23)
535        16 => Instrument::hammond_organ(),      // Drawbar Organ
536        17 => Instrument::organ(),              // Percussive Organ
537        18 => Instrument::church_organ(),       // Rock Organ
538        19 => Instrument::church_organ(),       // Church Organ
539        20 => Instrument::reed_organ(),         // Reed Organ
540        21 => Instrument::accordion(),          // Accordion
541        22 => Instrument::accordion(),          // Harmonica
542        23 => Instrument::accordion(),          // Tango Accordion
543
544        // Guitar (24-31)
545        24 => Instrument::acoustic_guitar(),    // Acoustic Guitar (nylon)
546        25 => Instrument::acoustic_guitar(),    // Acoustic Guitar (steel)
547        26 => Instrument::electric_guitar_clean(), // Electric Guitar (jazz)
548        27 => Instrument::electric_guitar_clean(), // Electric Guitar (clean)
549        28 => Instrument::guitar_palm_muted(),  // Electric Guitar (muted)
550        29 => Instrument::electric_guitar_distorted(), // Overdriven Guitar
551        30 => Instrument::electric_guitar_distorted(), // Distortion Guitar
552        31 => Instrument::guitar_harmonics(),   // Guitar Harmonics
553
554        // Bass (32-39)
555        32 => Instrument::upright_bass(),       // Acoustic Bass
556        33 => Instrument::fingerstyle_bass(),   // Electric Bass (finger)
557        34 => Instrument::slap_bass(),          // Electric Bass (pick)
558        35 => Instrument::fretless_bass(),      // Fretless Bass
559        36 => Instrument::slap_bass(),          // Slap Bass 1
560        37 => Instrument::slap_bass(),          // Slap Bass 2
561        38 => Instrument::synth_bass(),         // Synth Bass 1
562        39 => Instrument::synth_bass(),         // Synth Bass 2
563
564        // Strings (40-47)
565        40 => Instrument::violin(),             // Violin
566        41 => Instrument::viola(),              // Viola
567        42 => Instrument::cello(),              // Cello
568        43 => Instrument::double_bass(),        // Contrabass
569        44 => Instrument::tremolo_strings(),    // Tremolo Strings
570        45 => Instrument::pizzicato_strings(),  // Pizzicato Strings
571        46 => Instrument::harp(),               // Orchestral Harp
572        47 => Instrument::timpani(),            // Timpani
573
574        // Ensemble (48-55)
575        48 => Instrument::strings(),            // String Ensemble 1
576        49 => Instrument::slow_strings(),       // String Ensemble 2
577        50 => Instrument::strings(),            // Synth Strings 1
578        51 => Instrument::strings(),            // Synth Strings 2
579        52 => Instrument::choir_aahs(),         // Choir Aahs
580        53 => Instrument::choir_oohs(),         // Voice Oohs
581        54 => Instrument::synth_voice(),        // Synth Voice
582        55 => Instrument::strings(),            // Orchestra Hit
583
584        // Brass (56-63)
585        56 => Instrument::solo_trumpet(),       // Trumpet
586        57 => Instrument::trombone(),           // Trombone
587        58 => Instrument::tuba(),               // Tuba
588        59 => Instrument::muted_trumpet(),      // Muted Trumpet
589        60 => Instrument::french_horn(),        // French Horn
590        61 => Instrument::brass_section(),      // Brass Section
591        62 => Instrument::prophet_brass(),      // Synth Brass 1
592        63 => Instrument::analog_brass(),       // Synth Brass 2
593
594        // Reed (64-71)
595        64 => Instrument::soprano_sax(),        // Soprano Sax
596        65 => Instrument::alto_sax(),           // Alto Sax
597        66 => Instrument::tenor_sax(),          // Tenor Sax
598        67 => Instrument::baritone_sax(),       // Baritone Sax
599        68 => Instrument::oboe(),               // Oboe
600        69 => Instrument::english_horn(),       // English Horn
601        70 => Instrument::bassoon(),            // Bassoon
602        71 => Instrument::clarinet(),           // Clarinet
603
604        // Pipe (72-79)
605        72 => Instrument::piccolo(),            // Piccolo
606        73 => Instrument::flute(),              // Flute
607        74 => Instrument::flute(),              // Recorder
608        75 => Instrument::pan_flute(),          // Pan Flute
609        76 => Instrument::didgeridoo(),         // Blown Bottle
610        77 => Instrument::shakuhachi(),         // Shakuhachi
611        78 => Instrument::shakuhachi(),         // Whistle
612        79 => Instrument::uilleann_pipes(),     // Ocarina
613
614        // Synth Lead (80-87)
615        80 => Instrument::square_lead(),        // Lead 1 (square)
616        81 => Instrument::saw_lead(),           // Lead 2 (sawtooth)
617        82 => Instrument::synth_voice(),        // Lead 3 (calliope)
618        83 => Instrument::chiptune(),           // Lead 4 (chiff)
619        84 => Instrument::synth_lead(),         // Lead 5 (charang)
620        85 => Instrument::synth_voice(),        // Lead 6 (voice)
621        86 => Instrument::supersaw(),           // Lead 7 (fifths)
622        87 => Instrument::saw_lead(),           // Lead 8 (bass + lead)
623
624        // Synth Pad (88-95)
625        88 => Instrument::warm_pad(),           // Pad 1 (new age)
626        89 => Instrument::ambient_pad(),        // Pad 2 (warm)
627        90 => Instrument::vocal_pad(),          // Pad 3 (polysynth)
628        91 => Instrument::choir_oohs(),         // Pad 4 (choir)
629        92 => Instrument::shimmer_pad(),        // Pad 5 (bowed)
630        93 => Instrument::ambient_pad(),        // Pad 6 (metallic)
631        94 => Instrument::warm_pad(),           // Pad 7 (halo)
632        95 => Instrument::juno_pad(),           // Pad 8 (sweep)
633
634        // Synth Effects (96-103)
635        96 => Instrument::cosmic_rays(),        // FX 1 (rain)
636        97 => Instrument::wind_chimes(),        // FX 2 (soundtrack)
637        98 => Instrument::glass_harmonica(),    // FX 3 (crystal)
638        99 => Instrument::ambient_pad(),        // FX 4 (atmosphere)
639        100 => Instrument::shimmer_pad(),       // FX 5 (brightness)
640        101 => Instrument::granular_pad(),      // FX 6 (goblins)
641        102 => Instrument::cosmic_rays(),       // FX 7 (echoes)
642        103 => Instrument::glitch(),            // FX 8 (sci-fi)
643
644        // Ethnic (104-111)
645        104 => Instrument::sitar(),             // Sitar
646        105 => Instrument::banjo(),             // Banjo
647        106 => Instrument::shamisen(),          // Shamisen
648        107 => Instrument::koto(),              // Koto
649        108 => Instrument::kalimba(),           // Kalimba
650        109 => Instrument::bagpipes(),          // Bag pipe
651        110 => Instrument::erhu(),              // Fiddle
652        111 => Instrument::duduk(),             // Shanai
653
654        // Percussive (112-119)
655        112 => Instrument::steel_drums(),       // Tinkle Bell
656        113 => Instrument::cowbell(),           // Agogo
657        114 => Instrument::steel_drums(),       // Steel Drums
658        115 => Instrument::taiko_drum(),        // Woodblock
659        116 => Instrument::taiko(),             // Taiko Drum
660        117 => Instrument::timpani(),           // Melodic Tom
661        118 => Instrument::djembe(),            // Synth Drum
662        119 => Instrument::metallic_perc(),     // Reverse Cymbal
663
664        // Sound Effects (120-127)
665        120 => Instrument::guitar_harmonics(),  // Guitar Fret Noise
666        121 => Instrument::glitch(),            // Breath Noise
667        122 => Instrument::wind_chimes(),       // Seashore
668        123 => Instrument::cosmic_rays(),       // Bird Tweet
669        124 => Instrument::glitch(),            // Telephone Ring
670        125 => Instrument::impact(),            // Helicopter
671        126 => Instrument::riser(),             // Applause
672        127 => Instrument::laser(),             // Gunshot
673
674        // Default fallback (should never reach here)
675        _ => Instrument::acoustic_piano(),
676    }
677}
678
679#[cfg(test)]
680mod tests {
681    use super::*;
682
683    #[test]
684    fn test_frequency_to_midi_note() {
685        assert_eq!(frequency_to_midi_note(440.0), 69); // A4
686        assert_eq!(frequency_to_midi_note(261.63), 60); // C4 (approximate)
687        assert_eq!(frequency_to_midi_note(523.25), 72); // C5 (approximate)
688
689        // Edge cases
690        assert_eq!(frequency_to_midi_note(0.0), 0);
691        assert_eq!(frequency_to_midi_note(-100.0), 0);
692        assert_eq!(frequency_to_midi_note(20000.0), 127); // Clamps to max
693    }
694
695    #[test]
696    fn test_seconds_to_ticks() {
697        // At 120 BPM, 1 beat = 0.5 seconds
698        // At 480 PPQ, 1 beat = 480 ticks
699        // So 0.5 seconds = 480 ticks
700        assert_eq!(seconds_to_ticks(0.5, 120.0, 480), 480);
701
702        // 1 second = 2 beats = 960 ticks
703        assert_eq!(seconds_to_ticks(1.0, 120.0, 480), 960);
704
705        // At 60 BPM, 1 beat = 1 second = 480 ticks
706        assert_eq!(seconds_to_ticks(1.0, 60.0, 480), 480);
707    }
708
709    #[test]
710    fn test_drum_type_to_midi_note() {
711        // Test a few standard mappings
712        assert_eq!(drum_type_to_midi_note(DrumType::Kick), 36);
713        assert_eq!(drum_type_to_midi_note(DrumType::Snare), 38);
714        assert_eq!(drum_type_to_midi_note(DrumType::HiHatClosed), 42);
715        assert_eq!(drum_type_to_midi_note(DrumType::HiHatOpen), 46);
716        assert_eq!(drum_type_to_midi_note(DrumType::Clap), 39);
717    }
718
719    #[test]
720    fn test_volume_to_velocity() {
721        assert_eq!(volume_to_velocity(0.0), 0);
722        assert_eq!(volume_to_velocity(1.0), 127);
723        assert_eq!(volume_to_velocity(0.5), 64); // Approximate
724        assert_eq!(volume_to_velocity(1.5), 127); // Clamps
725        assert_eq!(volume_to_velocity(-0.5), 0); // Clamps
726    }
727
728    #[test]
729    fn test_semitones_to_pitch_bend() {
730        // Center (no bend) should be 8192
731        assert_eq!(semitones_to_pitch_bend(0.0, 2.0), 8192);
732
733        // +2 semitones (max of standard range) should be 16383
734        assert_eq!(semitones_to_pitch_bend(2.0, 2.0), 16383);
735
736        // -2 semitones (min of standard range) should be 0
737        assert_eq!(semitones_to_pitch_bend(-2.0, 2.0), 0);
738
739        // +1 semitone (half of range) should be halfway between center and max
740        assert_eq!(semitones_to_pitch_bend(1.0, 2.0), 12288);
741
742        // -1 semitone should be halfway between center and min
743        assert_eq!(semitones_to_pitch_bend(-1.0, 2.0), 4096);
744
745        // Test clamping - values beyond range should clamp
746        assert_eq!(semitones_to_pitch_bend(10.0, 2.0), 16383); // Clamps to max
747        assert_eq!(semitones_to_pitch_bend(-10.0, 2.0), 0); // Clamps to min
748    }
749
750    #[test]
751    fn test_semitones_to_pitch_bend_different_range() {
752        // Test with 12 semitone range (full octave)
753        assert_eq!(semitones_to_pitch_bend(0.0, 12.0), 8192);
754        assert_eq!(semitones_to_pitch_bend(12.0, 12.0), 16383);
755        assert_eq!(semitones_to_pitch_bend(-12.0, 12.0), 0);
756        assert_eq!(semitones_to_pitch_bend(6.0, 12.0), 12288);
757    }
758
759    #[test]
760    fn test_pitch_bend_fractional_semitones() {
761        // Test fractional semitones (for microtonal bends)
762        let bend_quarter_tone = semitones_to_pitch_bend(0.5, 2.0);
763        // Should be between center (8192) and +1 semitone (12288)
764        assert!(bend_quarter_tone > 8192 && bend_quarter_tone < 12288);
765        assert_eq!(bend_quarter_tone, 10240); // Exactly halfway
766
767        let bend_eighth_tone = semitones_to_pitch_bend(0.25, 2.0);
768        // Should be between center and quarter tone
769        assert!(bend_eighth_tone > 8192 && bend_eighth_tone < bend_quarter_tone);
770    }
771
772    #[test]
773    fn test_mod_value_to_cc_unipolar() {
774        // Unipolar modulation (volume): 0.0 -> 0, 1.0 -> 127
775        assert_eq!(mod_value_to_cc(0.0, false), 0);
776        assert_eq!(mod_value_to_cc(1.0, false), 127);
777        assert_eq!(mod_value_to_cc(0.5, false), 64);
778
779        // Test clamping
780        assert_eq!(mod_value_to_cc(-0.5, false), 0);
781        assert_eq!(mod_value_to_cc(1.5, false), 127);
782    }
783
784    #[test]
785    fn test_mod_value_to_cc_bipolar() {
786        // Bipolar modulation (pitch, pan): -1.0 -> 0, 0.0 -> 64, 1.0 -> 127
787        assert_eq!(mod_value_to_cc(-1.0, true), 0);
788        assert_eq!(mod_value_to_cc(0.0, true), 64);
789        assert_eq!(mod_value_to_cc(1.0, true), 127);
790
791        // Test intermediate values
792        assert_eq!(mod_value_to_cc(0.5, true), 95); // Halfway between 64 and 127
793        assert_eq!(mod_value_to_cc(-0.5, true), 32); // Halfway between 0 and 64
794
795        // Test clamping
796        assert_eq!(mod_value_to_cc(-2.0, true), 0);
797        assert_eq!(mod_value_to_cc(2.0, true), 127);
798    }
799
800    #[test]
801    fn test_midi_note_to_frequency() {
802        // Test standard notes
803        assert_eq!(midi_note_to_frequency(69), 440.0); // A4
804        assert!((midi_note_to_frequency(60) - 261.63).abs() < 0.01); // C4
805        assert!((midi_note_to_frequency(72) - 523.25).abs() < 0.01); // C5
806
807        // Test octave relationship (doubling frequency)
808        let c4 = midi_note_to_frequency(60);
809        let c5 = midi_note_to_frequency(72);
810        assert!((c5 / c4 - 2.0).abs() < 0.001); // C5 should be double C4
811    }
812
813    #[test]
814    fn test_midi_note_frequency_roundtrip() {
815        // Test that converting back and forth works
816        for midi_note in 21..108 {
817            // Test range of a standard 88-key piano
818            let freq = midi_note_to_frequency(midi_note);
819            let converted_back = frequency_to_midi_note(freq);
820            assert_eq!(converted_back, midi_note);
821        }
822    }
823
824    #[test]
825    fn test_ticks_to_seconds() {
826        // At 120 BPM, 1 beat = 0.5 seconds
827        // At 480 PPQ, 1 beat = 480 ticks
828        // So 480 ticks = 0.5 seconds
829        assert_eq!(ticks_to_seconds(480, 120.0, 480), 0.5);
830
831        // 960 ticks = 1 second
832        assert_eq!(ticks_to_seconds(960, 120.0, 480), 1.0);
833
834        // At 60 BPM, 1 beat = 1 second = 480 ticks
835        assert_eq!(ticks_to_seconds(480, 60.0, 480), 1.0);
836    }
837
838    #[test]
839    fn test_ticks_seconds_roundtrip() {
840        // Test that converting back and forth works
841        let tempo = 120.0;
842        let ppq = 480;
843
844        for seconds in [0.25, 0.5, 1.0, 2.0, 4.0] {
845            let ticks = seconds_to_ticks(seconds, tempo, ppq);
846            let converted_back = ticks_to_seconds(ticks, tempo, ppq);
847            assert!((converted_back - seconds).abs() < 0.001);
848        }
849    }
850
851    #[test]
852    fn test_midi_note_to_drum_type() {
853        // Test standard drum mappings
854        assert_eq!(midi_note_to_drum_type(36), Some(DrumType::Kick));
855        assert_eq!(midi_note_to_drum_type(38), Some(DrumType::Snare));
856        assert_eq!(midi_note_to_drum_type(42), Some(DrumType::HiHatClosed));
857        assert_eq!(midi_note_to_drum_type(46), Some(DrumType::HiHatOpen));
858        assert_eq!(midi_note_to_drum_type(39), Some(DrumType::Clap));
859        assert_eq!(midi_note_to_drum_type(49), Some(DrumType::Crash));
860
861        // Test unsupported notes
862        assert_eq!(midi_note_to_drum_type(0), None);
863        assert_eq!(midi_note_to_drum_type(100), None);
864    }
865
866    #[test]
867    fn test_drum_type_midi_note_roundtrip() {
868        // Test that common drums can round-trip
869        let drums = [
870            DrumType::Kick,
871            DrumType::Snare,
872            DrumType::HiHatClosed,
873            DrumType::HiHatOpen,
874            DrumType::Clap,
875            DrumType::Crash,
876            DrumType::Ride,
877        ];
878
879        for drum in drums {
880            let midi_note = drum_type_to_midi_note(drum);
881            let converted_back = midi_note_to_drum_type(midi_note);
882            assert!(converted_back.is_some());
883        }
884    }
885
886    #[test]
887    fn test_tempo_map_single_tempo() {
888        // Test TempoMap with no tempo changes (single tempo throughout)
889        let tempo_map = TempoMap::new(120.0, 480);
890
891        // At 120 BPM: 1 beat = 0.5 seconds = 480 ticks
892        assert_eq!(tempo_map.seconds_to_ticks(0.0), 0);
893        assert_eq!(tempo_map.seconds_to_ticks(0.5), 480); // 1 beat
894        assert_eq!(tempo_map.seconds_to_ticks(1.0), 960); // 2 beats
895        assert_eq!(tempo_map.seconds_to_ticks(2.0), 1920); // 4 beats
896    }
897
898    #[test]
899    fn test_tempo_map_multiple_tempos() {
900        // Test TempoMap with tempo changes
901        let mut tempo_map = TempoMap::new(120.0, 480);
902
903        // Add tempo change at 2 seconds: switch to 60 BPM
904        tempo_map.add_change(2.0, 60.0);
905        tempo_map.finalize();
906
907        // First 2 seconds at 120 BPM:
908        // - 2 seconds = 4 beats = 1920 ticks
909        assert_eq!(tempo_map.seconds_to_ticks(0.0), 0);
910        assert_eq!(tempo_map.seconds_to_ticks(0.5), 480); // 1 beat at 120 BPM
911        assert_eq!(tempo_map.seconds_to_ticks(1.0), 960); // 2 beats at 120 BPM
912        assert_eq!(tempo_map.seconds_to_ticks(2.0), 1920); // 4 beats at 120 BPM
913
914        // After 2 seconds at 60 BPM:
915        // - Base: 1920 ticks (from first 2 seconds)
916        // - 1 second at 60 BPM = 1 beat = 480 ticks
917        // - Total at 3 seconds: 1920 + 480 = 2400 ticks
918        assert_eq!(tempo_map.seconds_to_ticks(3.0), 2400);
919
920        // 2 seconds at 60 BPM = 2 beats = 960 ticks
921        // Total at 4 seconds: 1920 + 960 = 2880 ticks
922        assert_eq!(tempo_map.seconds_to_ticks(4.0), 2880);
923    }
924
925    #[test]
926    fn test_tempo_map_complex_scenario() {
927        // Test with multiple tempo changes
928        let mut tempo_map = TempoMap::new(120.0, 480);
929
930        // Add multiple tempo changes
931        tempo_map.add_change(1.0, 90.0); // Switch to 90 BPM at 1 second
932        tempo_map.add_change(3.0, 180.0); // Switch to 180 BPM at 3 seconds
933        tempo_map.finalize();
934
935        // First 1 second at 120 BPM:
936        // - 1 second = 2 beats = 960 ticks
937        assert_eq!(tempo_map.seconds_to_ticks(1.0), 960);
938
939        // Next 2 seconds (1-3s) at 90 BPM:
940        // - 2 seconds at 90 BPM = 3 beats = 1440 ticks
941        // - Total at 3 seconds: 960 + 1440 = 2400 ticks
942        assert_eq!(tempo_map.seconds_to_ticks(3.0), 2400);
943
944        // Next 1 second (3-4s) at 180 BPM:
945        // - 1 second at 180 BPM = 3 beats = 1440 ticks
946        // - Total at 4 seconds: 2400 + 1440 = 3840 ticks
947        assert_eq!(tempo_map.seconds_to_ticks(4.0), 3840);
948    }
949
950    #[test]
951    fn test_tempo_map_tempo_speedup() {
952        // Test tempo doubling (should double tick rate)
953        let mut tempo_map = TempoMap::new(60.0, 480);
954
955        // At 60 BPM: 1 beat per second
956        assert_eq!(tempo_map.seconds_to_ticks(1.0), 480);
957
958        // Add tempo change to 120 BPM at 2 seconds
959        tempo_map.add_change(2.0, 120.0);
960        tempo_map.finalize();
961
962        // First 2 seconds at 60 BPM: 2 beats = 960 ticks
963        assert_eq!(tempo_map.seconds_to_ticks(2.0), 960);
964
965        // Next 1 second at 120 BPM: 2 beats = 960 ticks
966        // Total at 3 seconds: 960 + 960 = 1920 ticks
967        assert_eq!(tempo_map.seconds_to_ticks(3.0), 1920);
968    }
969
970    #[test]
971    fn test_tempo_map_edge_cases() {
972        let tempo_map = TempoMap::new(120.0, 480);
973
974        // Test zero and negative times
975        assert_eq!(tempo_map.seconds_to_ticks(0.0), 0);
976        assert_eq!(tempo_map.seconds_to_ticks(-1.0), 0);
977
978        // Test very small times
979        assert!(tempo_map.seconds_to_ticks(0.001) > 0);
980    }
981}