1use std::io::Read;
2
3use crate::binary_reader::BinaryReader;
4use crate::error::SoundFontError;
5use crate::four_cc::FourCC;
6use crate::instrument::Instrument;
7use crate::preset::Preset;
8use crate::sample_header::SampleHeader;
9use crate::soundfont_info::SoundFontInfo;
10use crate::soundfont_parameters::SoundFontParameters;
11use crate::soundfont_sampledata::SoundFontSampleData;
12
13#[derive(Debug)]
15#[non_exhaustive]
16pub struct SoundFont {
17 pub(crate) info: SoundFontInfo,
18 pub(crate) bits_per_sample: i32,
19 pub(crate) wave_data: Vec<i16>,
20 pub(crate) sample_headers: Vec<SampleHeader>,
21 pub(crate) presets: Vec<Preset>,
22 pub(crate) instruments: Vec<Instrument>,
23}
24
25impl SoundFont {
26 pub fn new<R: Read>(reader: &mut R) -> Result<Self, SoundFontError> {
32 let chunk_id = BinaryReader::read_four_cc(reader)?;
33 if chunk_id != b"RIFF" {
34 return Err(SoundFontError::RiffChunkNotFound);
35 }
36
37 let _size = BinaryReader::read_i32(reader)?;
38
39 let form_type = BinaryReader::read_four_cc(reader)?;
40 if form_type != b"sfbk" {
41 return Err(SoundFontError::InvalidRiffChunkType {
42 expected: FourCC::from_bytes(*b"sfbk"),
43 actual: form_type,
44 });
45 }
46
47 let info = SoundFontInfo::new(reader)?;
48 let sample_bytes = SoundFontSampleData::read(reader)?;
49 let (parameters, sample_data) = SoundFontParameters::load(reader, sample_bytes)?;
50
51 let sound_font = Self {
52 info,
53 bits_per_sample: sample_data.bits_per_sample,
54 wave_data: sample_data.wave_data,
55 sample_headers: parameters.sample_headers,
56 presets: parameters.presets,
57 instruments: parameters.instruments,
58 };
59
60 sound_font.sanity_check()?;
61
62 Ok(sound_font)
63 }
64
65 fn sanity_check(&self) -> Result<(), SoundFontError> {
66 for instrument in &self.instruments {
70 for region in &instrument.regions {
71 let start = region.get_sample_start();
72 let end = region.get_sample_end();
73 let start_loop = region.get_sample_start_loop();
74 let end_loop = region.get_sample_end_loop();
75
76 if start < 0
85 || start_loop < 0
86 || end as usize >= self.wave_data.len()
87 || end_loop as usize >= self.wave_data.len()
88 || end <= start
89 {
90 return Err(SoundFontError::SanityCheckFailed);
91 }
92 }
93 }
94
95 Ok(())
96 }
97
98 pub fn get_info(&self) -> &SoundFontInfo {
100 &self.info
101 }
102
103 pub fn get_bits_per_sample(&self) -> i32 {
105 self.bits_per_sample
106 }
107
108 pub fn get_wave_data(&self) -> &[i16] {
110 &self.wave_data[..]
111 }
112
113 pub fn get_sample_headers(&self) -> &[SampleHeader] {
115 &self.sample_headers[..]
116 }
117
118 pub fn get_presets(&self) -> &[Preset] {
120 &self.presets[..]
121 }
122
123 pub fn get_instruments(&self) -> &[Instrument] {
125 &self.instruments[..]
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132
133 use std::{fs::File, path::PathBuf};
134
135 use crate::test_font::*;
136
137 fn samples_dir_path() -> PathBuf {
138 PathBuf::from(env!("CARGO_MANIFEST_DIR"))
139 .parent()
140 .unwrap()
141 .join("samples")
142 }
143
144 #[cfg(feature = "sf3")]
148 fn preset_region_values(region: &crate::PresetRegion) -> Vec<f64> {
149 vec![
150 region.get_modulation_lfo_to_pitch() as f64,
151 region.get_vibrato_lfo_to_pitch() as f64,
152 region.get_modulation_envelope_to_pitch() as f64,
153 region.get_initial_filter_cutoff_frequency() as f64,
154 region.get_initial_filter_q() as f64,
155 region.get_modulation_lfo_to_filter_cutoff_frequency() as f64,
156 region.get_modulation_envelope_to_filter_cutoff_frequency() as f64,
157 region.get_modulation_lfo_to_volume() as f64,
158 region.get_chorus_effects_send() as f64,
159 region.get_reverb_effects_send() as f64,
160 region.get_pan() as f64,
161 region.get_delay_modulation_lfo() as f64,
162 region.get_frequency_modulation_lfo() as f64,
163 region.get_delay_vibrato_lfo() as f64,
164 region.get_frequency_vibrato_lfo() as f64,
165 region.get_delay_modulation_envelope() as f64,
166 region.get_attack_modulation_envelope() as f64,
167 region.get_hold_modulation_envelope() as f64,
168 region.get_decay_modulation_envelope() as f64,
169 region.get_sustain_modulation_envelope() as f64,
170 region.get_release_modulation_envelope() as f64,
171 region.get_key_number_to_modulation_envelope_hold() as f64,
172 region.get_key_number_to_modulation_envelope_decay() as f64,
173 region.get_delay_volume_envelope() as f64,
174 region.get_attack_volume_envelope() as f64,
175 region.get_hold_volume_envelope() as f64,
176 region.get_decay_volume_envelope() as f64,
177 region.get_sustain_volume_envelope() as f64,
178 region.get_release_volume_envelope() as f64,
179 region.get_key_number_to_volume_envelope_hold() as f64,
180 region.get_key_number_to_volume_envelope_decay() as f64,
181 region.get_key_range_start() as f64,
182 region.get_key_range_end() as f64,
183 region.get_velocity_range_start() as f64,
184 region.get_velocity_range_end() as f64,
185 region.get_initial_attenuation() as f64,
186 region.get_coarse_tune() as f64,
187 region.get_fine_tune() as f64,
188 region.get_scale_tuning() as f64,
189 ]
190 }
191
192 #[cfg(not(feature = "sf3"))]
193 #[test]
194 fn test_load_reject_sf3() {
195 let path = samples_dir_path().join("dummy.sf3");
196 let mut file = File::open(&path).unwrap();
197 assert!(matches!(
198 SoundFont::new(&mut file),
199 Err(SoundFontError::UnsupportedSampleFormat)
200 ));
201 }
202
203 #[cfg(feature = "sf3")]
204 #[test]
205 fn test_load_sf3_dummy() {
206 let path = samples_dir_path().join("dummy.sf3");
207 let mut file = File::open(&path).unwrap();
208 let sound_font = SoundFont::new(&mut file).unwrap();
209 assert_eq!(sound_font.get_bits_per_sample(), 16);
210 assert!(!sound_font.get_wave_data().is_empty());
211 assert!(!sound_font.get_sample_headers().is_empty());
212 assert!(!sound_font.get_presets().is_empty());
213 }
214
215 #[test]
217 fn test_load_sf2_fluid() {
218 let path = samples_dir_path().join("FluidR3Mono_GM.sf2");
219 let mut file = File::open(&path).unwrap();
220 let sound_font = SoundFont::new(&mut file).unwrap();
221
222 assert_eq!(sound_font.get_bits_per_sample(), 16);
223 assert!(!sound_font.get_wave_data().is_empty());
224 assert!(!sound_font.get_sample_headers().is_empty());
225 assert!(!sound_font.get_presets().is_empty());
226 assert!(!sound_font.get_instruments().is_empty());
227
228 let wave_len = sound_font.get_wave_data().len();
230 for sample in sound_font.get_sample_headers() {
231 assert!(sample.get_start() >= 0);
232 assert!(sample.get_start() < sample.get_end());
233 assert!(sample.get_end() as usize <= wave_len);
234 assert!(sample.get_end_loop() as usize <= wave_len);
235 }
236
237 use std::sync::Arc;
239
240 use crate::{Synthesizer, SynthesizerSettings};
241
242 let sound_font = Arc::new(sound_font);
243 let settings = SynthesizerSettings::new(44100);
244 let mut synthesizer = Synthesizer::new(&sound_font, &settings).unwrap();
245 synthesizer.note_on(0, 60, 100); let mut left = vec![0_f32; 44100];
248 let mut right = vec![0_f32; 44100];
249 synthesizer.render(&mut left, &mut right);
250
251 let peak = left
252 .iter()
253 .fold(0_f32, |peak, sample| peak.max(sample.abs()));
254 assert!(
255 peak > 0.01,
256 "the rendered note was effectively silent (peak {peak})"
257 );
258 }
259
260 #[cfg(feature = "sf3")]
261 #[test]
262 fn test_load_sf3_fluid() {
263 let path = samples_dir_path().join("FluidR3Mono_GM.sf3");
264 let mut file = File::open(&path).unwrap();
265 let sound_font = SoundFont::new(&mut file).unwrap();
266
267 assert_eq!(sound_font.get_bits_per_sample(), 16);
268 assert!(!sound_font.get_wave_data().is_empty());
269 assert!(!sound_font.get_sample_headers().is_empty());
270 assert!(!sound_font.get_presets().is_empty());
271 assert!(!sound_font.get_instruments().is_empty());
272
273 let wave_len = sound_font.get_wave_data().len();
275 for sample in sound_font.get_sample_headers() {
276 assert!(sample.get_start() >= 0);
277 assert!(sample.get_start() < sample.get_end());
278 assert!(sample.get_end() as usize <= wave_len);
279 assert!(sample.get_end_loop() as usize <= wave_len);
280 }
281
282 use std::sync::Arc;
285
286 use crate::{Synthesizer, SynthesizerSettings};
287
288 let sound_font = Arc::new(sound_font);
289 let settings = SynthesizerSettings::new(44100);
290 let mut synthesizer = Synthesizer::new(&sound_font, &settings).unwrap();
291 synthesizer.note_on(0, 60, 100); let mut left = vec![0_f32; 44100];
294 let mut right = vec![0_f32; 44100];
295 synthesizer.render(&mut left, &mut right);
296
297 let peak = left
298 .iter()
299 .fold(0_f32, |peak, sample| peak.max(sample.abs()));
300 assert!(
301 peak > 0.01,
302 "the rendered note was effectively silent (peak {peak})"
303 );
304 }
305
306 #[cfg(feature = "sf3")]
318 #[test]
319 fn test_sf3_matches_sf2() {
320 use std::collections::HashMap;
321
322 const TOLERANCE: i32 = 2048;
326
327 fn load(file_name: &str) -> SoundFont {
328 let path = samples_dir_path().join(file_name);
329 let mut file = File::open(&path).unwrap();
330 SoundFont::new(&mut file).unwrap()
331 }
332
333 fn grouped(sf: &SoundFont) -> HashMap<&str, Vec<&crate::SampleHeader>> {
334 let mut groups: HashMap<&str, Vec<&SampleHeader>> = HashMap::new();
335 for header in sf.get_sample_headers() {
336 groups.entry(header.get_name()).or_default().push(header);
337 }
338 groups
339 }
340
341 fn by_name_i(sf: &SoundFont) -> HashMap<&str, &crate::Instrument> {
342 sf.get_instruments()
343 .iter()
344 .map(|i| (i.get_name(), i))
345 .collect()
346 }
347
348 fn find_preset(sf: &SoundFont, bank: i32, patch: i32) -> Option<&crate::Preset> {
349 sf.get_presets()
350 .iter()
351 .find(|p| p.get_bank_number() == bank && p.get_patch_number() == patch)
352 }
353
354 fn rel_loop(header: &crate::SampleHeader, field: fn(&crate::SampleHeader) -> i32) -> i32 {
355 field(header) - header.get_start()
356 }
357
358 let sf2 = load("FluidR3Mono_GM.sf2");
359 let sf3 = load("FluidR3Mono_GM.sf3");
360
361 let headers_2 = sf2.get_sample_headers();
363 let headers_3 = sf3.get_sample_headers();
364 assert_eq!(
365 headers_2.len(),
366 headers_3.len(),
367 "the two builds must contain the same number of samples"
368 );
369
370 let grouped_2 = grouped(&sf2);
371 let grouped_3 = grouped(&sf3);
372 assert_eq!(
373 grouped_2.len(),
374 grouped_3.len(),
375 "the distinct sample names differ"
376 );
377
378 for (name, occurrences_2) in &grouped_2 {
379 let occurrences_3 = grouped_3
380 .get(name)
381 .unwrap_or_else(|| panic!("sample '{name}' is missing in the sf3 build"));
382 assert_eq!(
383 occurrences_2.len(),
384 occurrences_3.len(),
385 "the number of samples named '{name}' differs"
386 );
387
388 let mut occurrences_2 = occurrences_2.clone();
391 let mut occurrences_3 = occurrences_3.clone();
392 let key = |header: &&crate::SampleHeader| {
393 (
394 header.get_start_loop() - header.get_start(),
395 header.get_end_loop() - header.get_start(),
396 )
397 };
398 occurrences_2.sort_by_key(key);
399 occurrences_3.sort_by_key(key);
400
401 for (a, b) in occurrences_2.iter().zip(&occurrences_3) {
402 assert_eq!(a.get_sample_rate(), b.get_sample_rate(), "sample '{name}'");
403 assert_eq!(
404 a.get_original_pitch(),
405 b.get_original_pitch(),
406 "sample '{name}'"
407 );
408 assert_eq!(
409 a.get_pitch_correction(),
410 b.get_pitch_correction(),
411 "sample '{name}'"
412 );
413 assert!(
414 (rel_loop(a, crate::SampleHeader::get_start_loop)
415 - rel_loop(b, crate::SampleHeader::get_start_loop))
416 .abs()
417 <= TOLERANCE,
418 "sample '{name}' loop start differs by more than {TOLERANCE} frames"
419 );
420 assert!(
421 (rel_loop(a, crate::SampleHeader::get_end_loop)
422 - rel_loop(b, crate::SampleHeader::get_end_loop))
423 .abs()
424 <= TOLERANCE,
425 "sample '{name}' loop end differs by more than {TOLERANCE} frames"
426 );
427 assert!(
428 ((a.get_end() - a.get_start()) - (b.get_end() - b.get_start())).abs()
429 <= TOLERANCE,
430 "sample '{name}' length differs by more than {TOLERANCE} frames"
431 );
432 assert!(
433 b.get_sample_type() & 0x10 != 0,
434 "sf3 sample '{name}' must be flagged as compressed"
435 );
436 }
437 }
438
439 let pool_diff = (sf3.get_wave_data().len() as i64 - sf2.get_wave_data().len() as i64).abs();
442 assert!(
443 pool_diff <= headers_3.len() as i64 * TOLERANCE as i64,
444 "decoded PCM pool lengths differ by {pool_diff} frames"
445 );
446
447 let instruments_2 = sf2.get_instruments();
449 let instruments_3 = sf3.get_instruments();
450 assert_eq!(instruments_2.len(), instruments_3.len());
451
452 let mut names_2: Vec<&str> = instruments_2.iter().map(|i| i.get_name()).collect();
453 let mut names_3: Vec<&str> = instruments_3.iter().map(|i| i.get_name()).collect();
454 names_2.sort_unstable();
455 names_3.sort_unstable();
456 assert_eq!(names_2, names_3, "instrument name sets differ");
457
458 let by_name_i2 = by_name_i(&sf2);
459 let by_name_i3 = by_name_i(&sf3);
460 assert_eq!(by_name_i2.len(), by_name_i3.len());
461 assert_eq!(
462 by_name_i2.len(),
463 instruments_2.len(),
464 "duplicate instrument names"
465 );
466
467 for (name, a) in &by_name_i2 {
468 let b = by_name_i3
469 .get(name)
470 .unwrap_or_else(|| panic!("instrument '{name}' is missing in the sf3 build"));
471 assert_eq!(
472 a.get_regions().len(),
473 b.get_regions().len(),
474 "instrument '{name}'"
475 );
476 }
477
478 let presets_2 = sf2.get_presets();
480 let presets_3 = sf3.get_presets();
481 assert_eq!(presets_2.len(), presets_3.len());
482
483 let mut count_2 = 0;
484 for preset in presets_2 {
485 let bank = preset.get_bank_number();
486 let patch = preset.get_patch_number();
487 let counterpart = find_preset(&sf3, bank, patch).unwrap_or_else(|| {
488 panic!("preset (bank {bank}, patch {patch}) is missing in the sf3 build")
489 });
490 assert_eq!(preset.get_name(), counterpart.get_name());
491 assert_eq!(preset.get_regions().len(), counterpart.get_regions().len());
492 for (ra, rb) in preset.get_regions().iter().zip(counterpart.get_regions()) {
493 assert_eq!(preset_region_values(ra), preset_region_values(rb));
494 }
495 count_2 += 1;
496 }
497 assert_eq!(count_2, presets_2.len());
498 }
499
500 #[test]
502 fn test_load_empty_samples() {
503 let path = samples_dir_path().join("test_empty_samples.sf2");
504 let mut file = File::open(&path).unwrap();
505 assert!(matches!(
506 SoundFont::new(&mut file),
507 Err(SoundFontError::SampleDataNotFound)
508 ));
509 }
510
511 #[test]
513 fn test_load_rejects_duplicate_sample_data_chunks() {
514 let bytes = build_sf2(&sample_spec(), 2);
515 let mut reader = std::io::Cursor::new(bytes);
516 assert!(matches!(
517 SoundFont::new(&mut reader),
518 Err(SoundFontError::InvalidSampleData)
519 ));
520 }
521
522 #[test]
525 fn test_load_does_not_trust_the_declared_sample_size() {
526 let mut bytes = build_sf2(&sample_spec(), 1);
527
528 let position = bytes
531 .windows(4)
532 .position(|window| window == b"smpl")
533 .unwrap();
534 bytes[position + 4..position + 8].copy_from_slice(&u32::MAX.to_le_bytes());
535
536 let mut reader = std::io::Cursor::new(bytes);
537 assert!(matches!(
538 SoundFont::new(&mut reader),
539 Err(SoundFontError::IoError(ref error))
540 if error.kind() == std::io::ErrorKind::UnexpectedEof
541 ));
542 }
543
544 #[test]
548 fn test_load_accepts_an_invalid_loop_range() {
549 let mut spec = sample_spec();
550 spec.start_loop = 96;
551 spec.end_loop = 32;
552
553 let bytes = build_sf2(&spec, 1);
554 let mut reader = std::io::Cursor::new(bytes);
555 let sound_font = SoundFont::new(&mut reader).unwrap();
556
557 let region = &sound_font.get_instruments()[0].get_regions()[0];
558 assert_eq!(region.get_sample_start_loop(), 96);
559 assert_eq!(region.get_sample_end_loop(), 32);
560 assert_eq!(region.get_sample_modes(), crate::LoopMode::Continuous);
561 }
562
563 #[test]
566 fn test_invalid_loop_range_plays_once() {
567 let mut looping = sample_spec();
568 looping.end_loop = 96;
569 let mut reversed = sample_spec();
570 reversed.start_loop = 96;
571 reversed.end_loop = 32;
572
573 let looping_peak = render_tail_peak(build_sf2(&looping, 1));
574 let reversed_peak = render_tail_peak(build_sf2(&reversed, 1));
575
576 assert!(
577 looping_peak > 0.01,
578 "the looping region stopped sounding (tail peak {looping_peak})"
579 );
580 assert!(
581 reversed_peak < 1e-6,
582 "the reversed loop region kept sounding (tail peak {reversed_peak})"
583 );
584 }
585
586 #[test]
590 fn test_load_skips_padding_after_an_odd_sized_chunk() {
591 let builder = FontBuilder {
592 info_chunks: vec![
593 chunk(b"ifil", &[2, 0, 0, 0]),
594 padded_chunk(b"INAM", b"Odd"),
595 padded_chunk(b"ICMT", b"Odd comment"),
596 ],
597 pmod_bytes: Some(vec![0_u8; 6]),
598 ..FontBuilder::default()
599 };
600
601 let mut reader = std::io::Cursor::new(builder.build());
602 let sound_font = SoundFont::new(&mut reader).unwrap();
603
604 let info = sound_font.get_info();
605 assert_eq!(info.get_bank_name(), "Odd");
606 assert_eq!(info.get_comments(), "Odd comment");
607 }
608
609 #[test]
613 fn test_load_reads_past_the_version_numbers() {
614 let builder = FontBuilder {
615 info_chunks: vec![
616 chunk(b"ifil", &[2, 0, 1, 0, 0x41, 0x41, 0x41, 0x41]),
617 chunk(b"INAM", b"Bank"),
618 ],
619 ..FontBuilder::default()
620 };
621
622 let mut reader = std::io::Cursor::new(builder.build());
623 let sound_font = SoundFont::new(&mut reader).unwrap();
624
625 let info = sound_font.get_info();
626 assert_eq!(info.get_version().get_major(), 2);
627 assert_eq!(info.get_version().get_minor(), 1);
628 assert_eq!(info.get_bank_name(), "Bank");
629 }
630
631 #[test]
634 fn test_load_rejects_an_instrument_zone_past_the_zone_list() {
635 let builder = FontBuilder {
636 instrument_zone_end: 10,
637 ..FontBuilder::default()
638 };
639
640 let mut reader = std::io::Cursor::new(builder.build());
641 assert!(matches!(
642 SoundFont::new(&mut reader),
643 Err(SoundFontError::InvalidInstrument(0))
644 ));
645 }
646
647 #[test]
649 fn test_load_rejects_a_preset_zone_past_the_zone_list() {
650 let builder = FontBuilder {
651 preset_zone_end: 10,
652 ..FontBuilder::default()
653 };
654
655 let mut reader = std::io::Cursor::new(builder.build());
656 assert!(matches!(
657 SoundFont::new(&mut reader),
658 Err(SoundFontError::InvalidPreset(0))
659 ));
660 }
661
662 fn render_tail_peak(sound_font: Vec<u8>) -> f32 {
665 use std::sync::Arc;
666
667 use crate::{Synthesizer, SynthesizerSettings};
668
669 let mut reader = std::io::Cursor::new(sound_font);
670 let sound_font = Arc::new(SoundFont::new(&mut reader).unwrap());
671
672 let mut settings = SynthesizerSettings::new(44100);
673 settings.enable_reverb_and_chorus = false;
674 let mut synthesizer = Synthesizer::new(&sound_font, &settings).unwrap();
675 synthesizer.note_on(0, 60, 100);
676
677 const FRAMES: usize = 4096;
680 let mut left = vec![0_f32; FRAMES];
681 let mut right = vec![0_f32; FRAMES];
682 synthesizer.render(&mut left, &mut right);
683
684 left[FRAMES - 1024..]
685 .iter()
686 .fold(0_f32, |peak, sample| peak.max(sample.abs()))
687 }
688}