summaryrefslogtreecommitdiff
path: root/src/soundfont.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/soundfont.rs')
-rw-r--r--src/soundfont.rs223
1 files changed, 223 insertions, 0 deletions
diff --git a/src/soundfont.rs b/src/soundfont.rs
new file mode 100644
index 0000000..acd96a6
--- /dev/null
+++ b/src/soundfont.rs
@@ -0,0 +1,223 @@
+#![allow(unused)] // @TODO: delete me
+use std::fs::File;
+use std::io::{Read, Seek};
+
+pub struct SoundFont {
+ file: Option<File>,
+ pub name: String
+}
+
+pub enum Error {
+ IO(std::io::Error),
+ NotASoundFont,
+ BadSoundFont(String),
+}
+
+impl From<&Error> for String {
+ fn from(err: &Error) -> String {
+ use Error::*;
+ match err {
+ IO(e) => format!("IO error: {}", e),
+ NotASoundFont => "not a sound font".to_string(),
+ BadSoundFont(s) => format!("bad sound font file: {}", s),
+ }
+ }
+}
+
+impl From<Error> for String {
+ fn from(err: Error) -> String {
+ String::from(&err)
+ }
+}
+
+impl From<std::io::Error> for Error {
+ fn from(err: std::io::Error) -> Error {
+ Error::IO(err)
+ }
+}
+
+#[derive(PartialEq, Eq)]
+struct FourCC(u8, u8, u8, u8);
+
+impl std::fmt::Debug for FourCC {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
+ let bytes = [self.0, self.1, self.2, self.3];
+ let string = String::from_utf8(Vec::from(bytes)).unwrap();
+ write!(f, "FourCC({})", string)
+ }
+}
+
+impl FourCC {
+ const fn new(a: u8, b: u8, c: u8, d: u8) -> Option<Self> {
+ if a == 0 || b == 0 || c == 0 || d == 0 {
+ return None;
+ }
+ if a >= 128 || b >= 128 || c >= 128 || d >= 128 {
+ return None;
+ }
+ Some(FourCC(a, b, c, d))
+ }
+}
+
+const fn fourcc(s: &str) -> FourCC {
+ let bytes = s.as_bytes();
+ match FourCC::new(bytes[0], bytes[1], bytes[2], bytes[3]) {
+ Some(x) => x,
+ None => panic!("bad fourcc")
+ }
+}
+
+fn read_fourcc(f: &mut File) -> Result<FourCC, Error> {
+ let mut bytes = [0; 4];
+ f.read_exact(&mut bytes);
+ FourCC::new(bytes[0], bytes[1], bytes[2], bytes[3]).ok_or(Error::NotASoundFont)
+}
+
+fn read_u32(f: &mut File) -> u32 {
+ let mut bytes = [0; 4];
+ f.read_exact(&mut bytes);
+ u32::from_le_bytes(bytes)
+}
+
+fn read_u16(f: &mut File) -> u16 {
+ let mut bytes = [0; 2];
+ f.read_exact(&mut bytes);
+ u16::from_le_bytes(bytes)
+}
+
+fn bad_sound_font(s: &str) -> Error {
+ Error::BadSoundFont(s.to_string())
+}
+
+impl SoundFont {
+ /// Open a soundfont.
+ /// Note: SoundFont keeps a handle to the file.
+ /// when you have loaded all the instruments you need,
+ /// you can call `close_file()` if you want to close it.
+ pub fn open(filename: &str) -> Result<Self, Error> {
+ const RIFF: FourCC = fourcc("RIFF");
+ const SFBK: FourCC = fourcc("sfbk");
+ const LIST: FourCC = fourcc("LIST");
+ const INFO: FourCC = fourcc("INFO");
+ const INAM: FourCC = fourcc("INAM");
+ const SDTA: FourCC = fourcc("sdta");
+ const PDTA: FourCC = fourcc("pdta");
+ const INST: FourCC = fourcc("inst");
+
+ let mut file = File::open(filename)?;
+
+ let riff = read_fourcc(&mut file)?;
+ if riff != RIFF {
+ // definitely not a soundfont
+ return Err(Error::NotASoundFont);
+ }
+
+ let _sfbk_size = read_u32(&mut file);
+
+ let sfbk = read_fourcc(&mut file)?;
+ if sfbk != SFBK {
+ // could be a WAV file, for example.
+ return Err(Error::NotASoundFont);
+ }
+
+ // at this point, the file *should* be a soundfont.
+
+ let list = read_fourcc(&mut file)?;
+ let info_size = read_u32(&mut file);
+ let info_end = file.stream_position()? + info_size as u64;
+ let info = read_fourcc(&mut file)?;
+ if list != LIST || info != INFO {
+ return Err(bad_sound_font("no INFO chunk"));
+ }
+
+ let mut name = None;
+
+ // read INFO data
+ while file.stream_position()? < info_end {
+ let chunk_type = read_fourcc(&mut file)?;
+ let chunk_size = read_u32(&mut file);
+ let chunk_end = file.stream_position()? + chunk_size as u64;
+
+ if chunk_type == INAM {
+ if chunk_size < 256 {
+ let mut data = vec![0; chunk_size as usize];
+ file.read(&mut data);
+ data.pop(); // null terminator
+ if let Ok(n) = String::from_utf8(data) {
+ name = Some(n);
+ }
+ }
+ if name.is_none() {
+ return Err(bad_sound_font("bad INAM"));
+ }
+ }
+
+ file.seek(std::io::SeekFrom::Start(chunk_end));
+ }
+
+ let name_unwrapped = match name {
+ None => return Err(bad_sound_font("no INAM")),
+ Some(n) => n,
+ };
+
+ let list = read_fourcc(&mut file)?;
+ let sdta_size = read_u32(&mut file);
+ let sdta_end = file.stream_position()? + sdta_size as u64;
+ let sdta = read_fourcc(&mut file)?;
+
+
+ if list != LIST || sdta != SDTA {
+ return Err(bad_sound_font("no sdta chunk"));
+ }
+
+ file.seek(std::io::SeekFrom::Start(sdta_end));
+
+ let list = read_fourcc(&mut file)?;
+ let pdta_size = read_u32(&mut file);
+ let pdta_end = file.stream_position()? + pdta_size as u64;
+ let pdta = read_fourcc(&mut file)?;
+ if list != LIST || pdta != PDTA {
+ return Err(bad_sound_font("no pdta chunk"));
+ }
+
+
+ // read pdta data
+ while file.stream_position()? < pdta_end {
+ let chunk_type = read_fourcc(&mut file)?;
+ let chunk_size = read_u32(&mut file);
+ let chunk_end = file.stream_position()? + chunk_size as u64;
+
+ if chunk_type == INST {
+ while file.stream_position()? < chunk_end {
+ let mut name_buf = [0; 20];
+ file.read_exact(&mut name_buf);
+ let mut name_vec = Vec::from(name_buf);
+ while !name_vec.is_empty() && name_vec[name_vec.len()-1] == 0 {
+ name_vec.pop();
+ }
+ let name = match String::from_utf8(name_vec) {
+ Ok(x) => x,
+ Err(_) => return Err(bad_sound_font("invalid UTF-8 in inst name")),
+ };
+ if name.is_empty() {
+ return Err(bad_sound_font("instrument with no name."));
+ }
+
+ let bag_idx = read_u16(&mut file);
+ println!("{:30} ---- {}",name, bag_idx);
+ }
+ }
+
+ file.seek(std::io::SeekFrom::Start(chunk_end));
+ }
+
+ Ok(SoundFont {
+ file: Some(file),
+ name: name_unwrapped,
+ })
+ }
+
+ pub fn close_file(&mut self) {
+ self.file = None;
+ }
+}