From 318533fd0b4b416719c7f4f76268e0415c11a674 Mon Sep 17 00:00:00 2001 From: pommicket Date: Wed, 28 Sep 2022 14:29:14 -0400 Subject: reading soundfonts --- src/main.rs | 36 +++++++-- src/midi_input.rs | 46 ++++++++--- src/soundfont.rs | 223 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 288 insertions(+), 17 deletions(-) create mode 100644 src/soundfont.rs diff --git a/src/main.rs b/src/main.rs index b6e39f2..157533f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,11 +5,13 @@ use std::io::Write; //use cpal::traits::{HostTrait, DeviceTrait, StreamTrait}; mod midi_input; +mod soundfont; -fn main() { - let mut device_mgr = midi_input::DeviceManager::new().expect("Couldn't create device manager"); +#[allow(unused)] +fn playmidi_main() -> Result<(), String> { + let mut device_mgr = midi_input::DeviceManager::new()?; device_mgr.set_quiet(true); - let devices = device_mgr.list().expect("couldn't list MIDI devices"); + let devices = device_mgr.list()?; for (index, device) in (&devices).into_iter().enumerate() { print!("{:3} | ", index + 1); let mut first = true; @@ -26,7 +28,7 @@ fn main() { if std::io::stdout().flush().is_err() { //who cares } - + let device_id; { let mut buf = String::new(); @@ -42,14 +44,14 @@ fn main() { device_id = &devices[idx - 1].id; } _ => { - eprintln!("Bad device ID: {}", s); - return; + return Err(format!("Bad device ID: {}", s)); } } } } - let mut device = device_mgr.open(device_id) - .expect("error opening MIDI device"); + let mut device = device_mgr + .open(device_id) + .expect("error opening MIDI device"); while device.is_connected() { let maybe_event = device.read_event(); @@ -63,6 +65,24 @@ fn main() { device.clear_error(); } } + Ok(()) +} + +fn main() { + let sf = match soundfont::SoundFont::open("/etc/alternatives/default-GM.sf2") { + Err(x) => { + eprintln!("Error: {}", String::from(x)); + return; + }, + Ok(s) => s, + }; + + println!("{}",sf.name); + + // let result = playmidi_main(); + // if let Err(s) = result { + // eprintln!("Error: {}", s); + // } /* let host = cpal::default_host(); let device = host.default_output_device().expect("no output device available"); diff --git a/src/midi_input.rs b/src/midi_input.rs index e8671ec..27b2c1c 100644 --- a/src/midi_input.rs +++ b/src/midi_input.rs @@ -5,11 +5,7 @@ use std::ffi::{c_char, c_int, c_void, CStr, CString}; /// Basic usage: /// ``` /// let mut manager = DeviceManager::new().unwrap(); -/// let mut devices = manager.list(); -/// while devices.len() == 0 { -/// std::thread::sleep(std::time::Duration::from_millis(100)); -/// devices = manager.list(); -/// } +/// let mut devices = manager.list().unwrap(); /// let mut i = devices.default; /// for device in devices { /// println!("{}",device.name); @@ -67,18 +63,28 @@ pub struct Device { #[derive(Debug)] pub enum DeviceOpenError { /// the device was not found. - /// this can hapen if it was disconnected between calling `list_devices` - /// and `open_device`. + /// this can hapen if it was disconnected between calling `list` + /// and `open`. NotFound(String), /// other error Other(String), } +/// A list of available MIDI input devices. pub struct DeviceList { pub devices: Vec, pub default: usize, } +/// Error produced by `DeviceManager::list` +#[derive(Debug, PartialEq, Eq)] +pub enum DeviceListError { + /// There are no MIDI input devices available + NoDevices, + /// Other error + Other(String), +} + /// A MIDI event. /// All notes and velocities are from 0 to 127. /// A note of 60 +/- n indicates n semitones above/below middle C. @@ -186,6 +192,22 @@ impl From for String { } } +impl From<&DeviceListError> for String { + fn from(e: &DeviceListError) -> String { + use DeviceListError::*; + match e { + NoDevices => "no devices found".to_string(), + Other(s) => s.clone(), + } + } +} + +impl From for String { + fn from(e: DeviceListError) -> String { + String::from(&e) + } +} + // technically there should be varargs here but oh well pub unsafe extern "C" fn snd_lib_error_handler_quiet( _file: *const c_char, @@ -239,7 +261,7 @@ impl DeviceManager { } /// Returns a `DeviceList` containing descriptions for all MIDI input devices. - pub fn list(&self) -> Result { + pub fn list(&self) -> Result { let mut hints: *mut *mut c_void = 0 as _; let err: c_int; unsafe { @@ -250,7 +272,10 @@ impl DeviceManager { // in theory hints should never be null if err != 0. // but you can never be sure. if err != 0 || hints == 0 as _ { - return Err(format!("failed to get device hints (error code {})", err)); + return Err(DeviceListError::Other(format!( + "failed to get device hints (error code {})", + err + ))); } let mut idx: usize = 0; @@ -307,6 +332,9 @@ impl DeviceManager { snd_device_name_free_hint(hints); } + if devices.is_empty() { + return Err(DeviceListError::NoDevices); + } Ok(DeviceList { devices, default: default.unwrap_or(0), 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, + 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 for String { + fn from(err: Error) -> String { + String::from(&err) + } +} + +impl From 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 { + 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 { + 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 { + 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; + } +} -- cgit v1.2.3