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}