1#![allow(dead_code)]
2
3use std::io::Read;
4
5use crate::binary_reader::BinaryReader;
6use crate::error::SoundFontError;
7use crate::four_cc::FourCC;
8use crate::instrument::Instrument;
9use crate::preset::Preset;
10use crate::sample_header::SampleHeader;
11use crate::soundfont_info::SoundFontInfo;
12use crate::soundfont_parameters::SoundFontParameters;
13use crate::soundfont_sampledata::SoundFontSampleData;
14use crate::LoopMode;
15
16#[derive(Debug)]
18#[non_exhaustive]
19pub struct SoundFont {
20 pub(crate) info: SoundFontInfo,
21 pub(crate) bits_per_sample: i32,
22 pub(crate) wave_data: Vec<i16>,
23 pub(crate) sample_headers: Vec<SampleHeader>,
24 pub(crate) presets: Vec<Preset>,
25 pub(crate) instruments: Vec<Instrument>,
26}
27
28impl SoundFont {
29 pub fn new<R: Read>(reader: &mut R) -> Result<Self, SoundFontError> {
35 let chunk_id = BinaryReader::read_four_cc(reader)?;
36 if chunk_id != b"RIFF" {
37 return Err(SoundFontError::RiffChunkNotFound);
38 }
39
40 let _size = BinaryReader::read_i32(reader);
41
42 let form_type = BinaryReader::read_four_cc(reader)?;
43 if form_type != b"sfbk" {
44 return Err(SoundFontError::InvalidRiffChunkType {
45 expected: FourCC::from_bytes(*b"sfbk"),
46 actual: form_type,
47 });
48 }
49
50 let info = SoundFontInfo::new(reader)?;
51 let sample_data = SoundFontSampleData::new(reader)?;
52 let parameters = SoundFontParameters::new(reader)?;
53
54 let sound_font = Self {
55 info,
56 bits_per_sample: sample_data.bits_per_sample,
57 wave_data: sample_data.wave_data,
58 sample_headers: parameters.sample_headers,
59 presets: parameters.presets,
60 instruments: parameters.instruments,
61 };
62
63 sound_font.sanity_check()?;
64
65 Ok(sound_font)
66 }
67
68 fn sanity_check(&self) -> Result<(), SoundFontError> {
69 for instrument in &self.instruments {
73 for region in &instrument.regions {
74 let start = region.get_sample_start();
75 let end = region.get_sample_end();
76 let start_loop = region.get_sample_start_loop();
77 let end_loop = region.get_sample_end_loop();
78 let loop_mode = region.get_sample_modes();
79
80 if start < 0
81 || start_loop < 0
82 || end as usize >= self.wave_data.len()
83 || end_loop as usize >= self.wave_data.len()
84 || end <= start
85 || end_loop < start_loop
86 || (loop_mode != LoopMode::NoLoop && start_loop >= end_loop)
87 {
88 return Err(SoundFontError::SanityCheckFailed);
89 }
90 }
91 }
92
93 Ok(())
94 }
95
96 pub fn get_info(&self) -> &SoundFontInfo {
98 &self.info
99 }
100
101 pub fn get_bits_per_sample(&self) -> i32 {
103 self.bits_per_sample
104 }
105
106 pub fn get_wave_data(&self) -> &[i16] {
108 &self.wave_data[..]
109 }
110
111 pub fn get_sample_headers(&self) -> &[SampleHeader] {
113 &self.sample_headers[..]
114 }
115
116 pub fn get_presets(&self) -> &[Preset] {
118 &self.presets[..]
119 }
120
121 pub fn get_instruments(&self) -> &[Instrument] {
123 &self.instruments[..]
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130
131 use std::{fs::File, path::PathBuf};
132
133 fn samples_dir_path() -> PathBuf {
134 PathBuf::from(env!("CARGO_MANIFEST_DIR"))
135 .parent()
136 .unwrap()
137 .join("samples")
138 }
139
140 #[test]
141 fn test_load_reject_sf3() {
142 let path = samples_dir_path().join("dummy.sf3");
143 let mut file = File::open(&path).unwrap();
144 assert!(matches!(
145 SoundFont::new(&mut file),
146 Err(SoundFontError::UnsupportedSampleFormat)
147 ));
148 }
149
150 #[test]
152 fn test_load_empty_samples() {
153 let path = samples_dir_path().join("test_empty_samples.sf2");
154 let mut file = File::open(&path).unwrap();
155 assert!(matches!(
156 SoundFont::new(&mut file),
157 Err(SoundFontError::SampleDataNotFound)
158 ));
159 }
160}