summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpommicket <pommicket@gmail.com>2022-09-28 14:29:14 -0400
committerpommicket <pommicket@gmail.com>2022-09-28 14:29:14 -0400
commit318533fd0b4b416719c7f4f76268e0415c11a674 (patch)
treeccafc2199ca76c1ca70de7b495038b4ce20cf8a5
parent2a8d4a848b36589140bc81abfd4afcc484d637df (diff)
reading soundfonts
-rw-r--r--src/main.rs36
-rw-r--r--src/midi_input.rs46
-rw-r--r--src/soundfont.rs223
3 files changed, 288 insertions, 17 deletions
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<DeviceInfo>,
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<DeviceOpenError> 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<DeviceListError> 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<DeviceList, String> {
+ pub fn list(&self) -> Result<DeviceList, DeviceListError> {
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<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;
+ }
+}