From 93b0d691522e48974bbdbc30c93d051d223f2f53 Mon Sep 17 00:00:00 2001 From: pommicket Date: Fri, 30 Sep 2022 23:52:18 -0400 Subject: playing some notes --- src/main.rs | 55 ++++-- src/soundfont.rs | 530 +++++++++++++++++++++++++++++++++++++++---------------- 2 files changed, 424 insertions(+), 161 deletions(-) diff --git a/src/main.rs b/src/main.rs index 47bc913..4f6089e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,9 @@ +// @TODO: sort presets alphabetically extern crate cpal; use std::io::Write; -//use cpal::traits::{HostTrait, DeviceTrait, StreamTrait}; +use cpal::traits::{HostTrait, DeviceTrait, StreamTrait}; mod midi_input; mod soundfont; @@ -69,33 +70,64 @@ fn playmidi_main() -> Result<(), String> { } fn main() { - let _sf = match soundfont::SoundFont::open("/etc/alternatives/default-GM.sf2") { + let mut sf = match soundfont::SoundFont::open("/etc/alternatives/default-GM.sf2") { Err(x) => { eprintln!("Error: {}", String::from(x)); return; - }, + } Ok(s) => s, }; - - + + for i in 0..sf.preset_count() { + println!("{}. {}", i, sf.preset_name(i).unwrap()); + } + // 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"); - let mut supported_configs_range = device.supported_output_configs() + let supported_configs = device.supported_output_configs() .expect("error while querying configs"); - let config = supported_configs_range.next() - .expect("no supported config?!") + let mut chosen_config = None; + for config in supported_configs { + if config.channels() != 2 || config.sample_format() != cpal::SampleFormat::I16 { + continue; + } + chosen_config = Some(config); + } + let chosen_config = match chosen_config { + None => { + eprintln!("Couldn't configure audio device to have 2 16-bit channels."); + return; + }, + Some(x) => x, + }; + let supp_config: cpal::SupportedStreamConfig = chosen_config .with_max_sample_rate() .into(); + if supp_config.channels() != 2 { + } + let config = supp_config.into(); + let mut time = 0.0; + let mut key = 60; let stream = device.build_output_stream( &config, move |data: &mut [i16], _: &cpal::OutputCallbackInfo| { - for sample in data.iter_mut() { - *sample = 0; + for x in data.iter_mut() { + *x = 0; + } + let sample_rate = config.sample_rate.0 as f64; + match sf.add_samples_interlaced(126, 1.0, key, 60, time, data, sample_rate) { + Ok(false) => {println!("stop")}, + Err(e) => eprintln!("{}", e), + _ => {}, + } + time += (data.len() as f64) / (2.0 * sample_rate); + if time >= 1.0 { + time = 0.0; + key += 1; } }, move |err| { @@ -107,5 +139,4 @@ fn main() { loop { std::thread::sleep(std::time::Duration::from_millis(100)); } - */ } diff --git a/src/soundfont.rs b/src/soundfont.rs index 79cd312..9841a47 100644 --- a/src/soundfont.rs +++ b/src/soundfont.rs @@ -1,21 +1,33 @@ +/* +IMPORTANT SOUNDFONT TERMINOLOGY: + a PRESET is a source you can play from, e.g. "Piano", "Harpsichord", "Choir" + an INSTRUMENT is an internal group of samples which presets refer to + (the idea here is that you could, e.g., have presets "Piano 1" and "Piano 2" which use the + same underlying "Piano" instrument, but different settings like pitch correction, etc.) + an OBJECT is a preset or instrument + a ZONE is a specially designated interval of keys and velocities. + the zone controls various settings about how a sample should be played. + both presets and instruments have zones. + a preset zone refers to an instrument + an object zone refers to a sample + a SAMPLE is a block of audio data with some properties of how it should be played +*/ + #![allow(unused)] // @TODO: delete me +use std::collections::HashMap; use std::fs::File; use std::io::{Read, Seek}; -use std::collections::HashMap; #[derive(Clone, Debug)] enum ZoneReference { None, - SampleID(u16), // for instrument zones - Instrument(u16) // for preset zones + SampleID(u16), // for instrument zones + Instrument(u16), // for preset zones } impl ZoneReference { fn is_none(&self) -> bool { - match &self { - ZoneReference::None => true, - _ => false, - } + matches!(self, ZoneReference::None) } } @@ -23,17 +35,16 @@ impl ZoneReference { struct Zone { key_range: (u8, u8), vel_range: (u8, u8), - inst: u32, - start_offset: i64, - end_offset: i64, - startloop_offset: i64, - endloop_offset: i64, + start_offset: i32, + end_offset: i32, + startloop_offset: i32, + endloop_offset: i32, pan: i16, // -1000 = full pan left, 1000 = full pan right is_global: bool, force_key: i8, // -1 for no forced key, otherwise input MIDI key is replaced with this force_vel: i8, // -1 for no forced velocity initial_attenuation: u16, // in centibels - tune: i32, // in cents + tune: i32, // in cents reference: ZoneReference, loops: bool, scale_tuning: u16, // 100 = normal tuning, 50 = each MIDI key is a *quarter* tone, etc. @@ -48,7 +59,7 @@ trait SFObject { #[derive(Default)] struct Instrument { name: String, - zones: Vec + zones: Vec, } impl SFObject for Instrument { @@ -60,7 +71,7 @@ impl SFObject for Instrument { #[derive(Default)] pub struct Preset { name: String, - zones: Vec + zones: Vec, } impl SFObject for Preset { @@ -69,29 +80,28 @@ impl SFObject for Preset { } } - -#[derive(Debug)] +#[derive(Debug, Clone)] struct Sample { - offset: u32, + start: u32, len: u32, startloop: u32, endloop: u32, sample_rate: u32, - pitch: u8, + root_key: u8, pitch_correction: i8, - data: Vec, } const FILE_CACHE_CHUNK_SIZE: usize = 4096; pub struct SoundFont { - file: Option, + file: File, + sdta_offset: u64, presets: Vec, instruments: Vec, samples: Vec, // maps (offset, len) to sample data file_cache: HashMap<(u32, u32), Vec>, - pub name: String + pub name: String, } pub enum OpenError { @@ -100,8 +110,9 @@ pub enum OpenError { BadSoundFont(String), } -pub enum PresetError { +pub enum SampleError { BadPreset, + NoSamples, IO(std::io::Error), } @@ -122,25 +133,44 @@ impl From for String { } } -impl From<&PresetError> for String { - fn from(err: &PresetError) -> String { - use PresetError::*; +impl From for OpenError { + fn from(err: std::io::Error) -> OpenError { + OpenError::IO(err) + } +} + +impl std::fmt::Debug for OpenError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + write!(f, "OpenError({})", String::from(self)) + } +} + +impl From<&SampleError> for String { + fn from(err: &SampleError) -> String { + use SampleError::*; match err { IO(e) => format!("IO error: {}", e), - BadPreset => format!("bad preset index"), + BadPreset => "bad preset index".to_string(), + NoSamples => "no samples".to_string(), } } } -impl From for String { - fn from(err: PresetError) -> String { +impl From for String { + fn from(err: SampleError) -> String { String::from(&err) } } -impl From for OpenError { - fn from(err: std::io::Error) -> OpenError { - OpenError::IO(err) +impl std::fmt::Display for SampleError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + write!(f, "{}", String::from(self)) + } +} + +impl std::fmt::Debug for SampleError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + write!(f, "SampleError({})", String::from(self)) } } @@ -171,7 +201,7 @@ 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") + None => panic!("bad fourcc"), } } @@ -210,21 +240,18 @@ fn bad_sound_font(s: &str) -> OpenError { fn read_utf8_fixed_len(file: &mut File, len: usize, what: &str) -> Result { let mut name_vec = vec![0; len]; file.read_exact(&mut name_vec); - while !name_vec.is_empty() && name_vec[name_vec.len()-1] == 0 { + while !name_vec.is_empty() && name_vec[name_vec.len() - 1] == 0 { name_vec.pop(); } - String::from_utf8(name_vec).map_err( - |_| OpenError::BadSoundFont(format!("invalid UTF-8 in {}", what)) - ) + String::from_utf8(name_vec) + .map_err(|_| OpenError::BadSoundFont(format!("invalid UTF-8 in {}", what))) } - impl Zone { - fn new(inst: u32) -> Self { + fn new() -> Self { Self { key_range: (0, 127), vel_range: (0, 127), - inst, start_offset: 0, end_offset: 0, startloop_offset: 0, @@ -241,6 +268,59 @@ impl Zone { force_root_key: -1, } } + + fn contains(&self, key: u8, vel: u8) -> bool { + if self.is_global { + return false; + } + + key >= self.key_range.0 + && key <= self.key_range.1 + && vel >= self.vel_range.0 + && vel <= self.vel_range.1 + } + + // the zone for a note is generated by adding + fn add(zone1: &Self, zone2: &Self) -> Self { + fn add_forced(a: i8, b: i8) -> i8 { + // the standard isn't really clear about this + // but whatever probably doesn't matter + if a == -1 { + b + } else if b == -1 { + a + } else { + a + b + } + } + + let mut reference = ZoneReference::None; + if let ZoneReference::SampleID(id) = zone1.reference { + reference = ZoneReference::SampleID(id); + } + if let ZoneReference::SampleID(id) = zone2.reference { + reference = ZoneReference::SampleID(id); + } + + Self { + key_range: (0,0), // not relevant + vel_range: (0,0), // not relevant + is_global: false, // not relevant + start_offset: zone1.start_offset + zone2.start_offset, + end_offset: zone1.end_offset + zone2.end_offset, + startloop_offset: zone1.startloop_offset + zone2.startloop_offset, + endloop_offset: zone1.endloop_offset + zone2.endloop_offset, + pan: zone1.pan + zone2.pan, + force_key: add_forced(zone1.force_key, zone2.force_key), + force_vel: add_forced(zone1.force_vel, zone2.force_vel), + initial_attenuation: zone1.initial_attenuation + zone2.initial_attenuation, + tune: zone1.tune + zone2.tune, + reference, + loops: zone1.loops ^ zone2.loops, + force_root_key: add_forced(zone1.force_root_key, zone2.force_root_key), + scale_tuning: zone1.scale_tuning, // it doesn't really make sense to add scale tunings + } + } } fn read_gen_zone(file: &mut File, zone: &mut Zone, gen_count: u16) { @@ -248,8 +328,11 @@ fn read_gen_zone(file: &mut File, zone: &mut Zone, gen_count: u16) { let gen_type = read_u16(file); let amount_u16 = read_u16(file); let amount_i16 = amount_u16 as i16; - let amount_range = ((amount_u16 >> 8) as u8, amount_u16 as u8); - + let mut amount_range = ((amount_u16 >> 8) as u8, amount_u16 as u8); + // "LS byte indicates the highest and the MS byte the lowest valid key." (soundfont ยง 8.1.2) + // but "TimGM6mb.sf2" seems to disagree. maybe something something endianness. + amount_range = (u8::min(amount_range.0, amount_range.1), u8::max(amount_range.0, amount_range.1)); + mod gen { // generators // these aren't all of the ones soundfont defines, @@ -277,16 +360,16 @@ fn read_gen_zone(file: &mut File, zone: &mut Zone, gen_count: u16) { pub const OVERRIDING_ROOT_KEY: u16 = 58; } use gen::*; - + match gen_type { - START_ADDRS_OFFSET => zone.start_offset += amount_i16 as i64, - START_ADDRS_COARSE_OFFSET => zone.start_offset += (amount_i16 as i64) * 32768, - END_ADDRS_OFFSET => zone.end_offset += amount_i16 as i64, - END_ADDRS_COARSE_OFFSET => zone.end_offset += (amount_i16 as i64) * 32768, - STARTLOOP_ADDRS_OFFSET => zone.startloop_offset += amount_i16 as i64, - STARTLOOP_ADDRS_COARSE_OFFSET => zone.startloop_offset += (amount_i16 as i64) * 32768, - ENDLOOP_ADDRS_OFFSET => zone.endloop_offset += amount_i16 as i64, - ENDLOOP_ADDRS_COARSE_OFFSET => zone.endloop_offset += (amount_i16 as i64) * 32768, + START_ADDRS_OFFSET => zone.start_offset += amount_i16 as i32, + START_ADDRS_COARSE_OFFSET => zone.start_offset += (amount_i16 as i32) * 32768, + END_ADDRS_OFFSET => zone.end_offset += amount_i16 as i32, + END_ADDRS_COARSE_OFFSET => zone.end_offset += (amount_i16 as i32) * 32768, + STARTLOOP_ADDRS_OFFSET => zone.startloop_offset += amount_i16 as i32, + STARTLOOP_ADDRS_COARSE_OFFSET => zone.startloop_offset += (amount_i16 as i32) * 32768, + ENDLOOP_ADDRS_OFFSET => zone.endloop_offset += amount_i16 as i32, + ENDLOOP_ADDRS_COARSE_OFFSET => zone.endloop_offset += (amount_i16 as i32) * 32768, PAN => zone.pan = amount_i16.clamp(-1000, 1000), KEY_RANGE => zone.key_range = amount_range, VEL_RANGE => zone.vel_range = amount_range, @@ -301,47 +384,75 @@ fn read_gen_zone(file: &mut File, zone: &mut Zone, gen_count: u16) { SCALE_TUNING => zone.scale_tuning = amount_u16, OVERRIDING_ROOT_KEY => zone.force_root_key = amount_i16.clamp(-1, 127) as i8, //other => println!("OTHER: {}",other), - _ => {}, + _ => {} } - } } // reads the ibag or pbag chunk of a soundfont -fn read_bag_chunk(file: &mut File, bag_indices: Vec) -> (Vec<(u32, u16)>, Vec<(u32, u16)>) { +struct GenIndex { + obj: u16, + gen: u16, +} + +struct ModIndex { + obj: u16, + r#mod: u16, +} + +// read pbag or ibag +fn read_bag_chunk(file: &mut File, bag_indices: Vec) -> (Vec, Vec) { let mut gen_indices = vec![]; let mut mod_indices = vec![]; - for inst_ndx in 0..bag_indices.len() - 1 { - let start_ndx = bag_indices[inst_ndx]; - let end_ndx = bag_indices[inst_ndx+1]; + for obj_ndx in 0..bag_indices.len() - 1 { + let start_ndx = bag_indices[obj_ndx]; + let end_ndx = bag_indices[obj_ndx + 1]; for i in start_ndx..end_ndx { let gen_ndx = read_u16(file); let mod_ndx = read_u16(file); - gen_indices.push((inst_ndx as u32, gen_ndx)); - mod_indices.push((inst_ndx as u32, mod_ndx)); + gen_indices.push(GenIndex { + obj: obj_ndx as u16, + gen: gen_ndx, + }); + mod_indices.push(ModIndex { + obj: obj_ndx as u16, + r#mod: mod_ndx, + }); } } - + { // terminal zone - let item_ndx = bag_indices.len() as u32; + let obj_ndx = bag_indices.len(); let gen_ndx = read_u16(file); let mod_ndx = read_u16(file); - gen_indices.push((item_ndx, gen_ndx)); - mod_indices.push((item_ndx, mod_ndx)); + gen_indices.push(GenIndex { + obj: obj_ndx as u16, + gen: gen_ndx, + }); + mod_indices.push(ModIndex { + obj: obj_ndx as u16, + r#mod: mod_ndx, + }); } - + (gen_indices, mod_indices) } // read pgen or igen chunk -fn read_gen_zones(file: &mut File, items: &mut Vec, gen_indices: Vec<(u32, u16)>, mod_indices: Vec<(u32, u16)>) { - let mut prev_inst_ndx = u32::MAX; +fn read_gen_zones( + file: &mut File, + items: &mut [Item], + gen_indices: Vec, + mod_indices: Vec, +) { + let mut prev_inst_ndx = u16::MAX; let mut global_zone: Option = None; - for zone_ndx in 0..gen_indices.len()-1 { - let (inst_ndx, start_ndx) = gen_indices[zone_ndx]; - let (_, end_ndx) = gen_indices[zone_ndx+1]; - let mut zone = Zone::new(inst_ndx); + for zone_ndx in 0..gen_indices.len() - 1 { + let inst_ndx = gen_indices[zone_ndx].obj; + let start_gen = gen_indices[zone_ndx].gen; + let end_gen = gen_indices[zone_ndx + 1].gen; + let mut zone = Zone::new(); if inst_ndx == prev_inst_ndx { if let Some(z) = &global_zone { zone = z.clone(); @@ -350,9 +461,9 @@ fn read_gen_zones(file: &mut File, items: &mut Vec, gen_in global_zone = None; } prev_inst_ndx = inst_ndx; - - read_gen_zone(file, &mut zone, end_ndx - start_ndx); - + + read_gen_zone(file, &mut zone, end_gen - start_gen); + if zone.reference.is_none() { // this is a global zone zone.is_global = true; @@ -366,7 +477,6 @@ fn read_gen_zones(file: &mut File, items: &mut Vec, gen_in impl SoundFont { /// Open a soundfont. /// Note: SoundFont keeps a handle to the file. - /// When you've loaded all the presets you need, you can call `close_file()` to close it. pub fn open(filename: &str) -> Result { const RIFF: FourCC = fourcc("RIFF"); const SFBK: FourCC = fourcc("sfbk"); @@ -383,7 +493,7 @@ impl SoundFont { const PHDR: FourCC = fourcc("phdr"); const PGEN: FourCC = fourcc("pgen"); const PBAG: FourCC = fourcc("pbag"); - + let mut file = File::open(filename)?; let riff = read_fourcc(&mut file)?; @@ -434,43 +544,42 @@ impl SoundFont { 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_offset = file.stream_position()?; let sdta_end = sdta_offset + 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)?; + let pdta = read_fourcc(&mut file)?; if list != LIST || pdta != PDTA { return Err(bad_sound_font("no pdta chunk")); } - + struct Chunk { offset: u64, - size: u32 + size: u32, } impl Chunk { fn new() -> Self { Chunk { offset: 0, size: 0 } } fn end(&self) -> u64 { - return self.offset + self.size as u64; + self.offset + self.size as u64 } } let mut inst = Chunk::new(); @@ -481,18 +590,18 @@ impl SoundFont { let mut phdr = Chunk::new(); let mut pbag = Chunk::new(); let mut pgen = Chunk::new(); - + // 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; - + let chunk = Chunk { offset: file.stream_position()?, size: chunk_size, }; - + match chunk_type { INST => inst = chunk, IBAG => ibag = chunk, @@ -502,12 +611,12 @@ impl SoundFont { PHDR => phdr = chunk, PBAG => pbag = chunk, PGEN => pgen = chunk, - _ => {}, + _ => {} } - + file.seek(std::io::SeekFrom::Start(chunk_end)); } - + if inst.offset == 0 { return Err(bad_sound_font("no inst chunk.")); } @@ -526,11 +635,10 @@ impl SoundFont { if phdr.offset == 0 { return Err(bad_sound_font("no phdr chunk.")); } - - + let mut instruments: Vec = vec![]; let mut instrument_bag_indices: Vec = vec![]; - + // --- read inst chunk --- { file.seek(std::io::SeekFrom::Start(inst.offset)); @@ -539,44 +647,55 @@ impl SoundFont { if name.is_empty() { return Err(bad_sound_font("instrument with no name.")); } - + let bag_ndx = read_u16(&mut file); - + let is_eoi = name == "EOI"; - - instruments.push(Instrument { name, ..Default::default() }); + + instruments.push(Instrument { + name, + ..Default::default() + }); instrument_bag_indices.push(bag_ndx); - + if is_eoi { // terminal instrument. break; } - + if file.stream_position()? >= inst.end() { return Err(bad_sound_font("No terminal instrument.")); } } } - // --- read ibag chunk --- file.seek(std::io::SeekFrom::Start(ibag.offset)); // these are vecs of (instrument idx, gen idx) // and (instrument idx, mod idx) - let (instrument_gen_indices, instrument_mod_indices) = read_bag_chunk(&mut file, instrument_bag_indices); - + let (instrument_gen_indices, instrument_mod_indices) = + read_bag_chunk(&mut file, instrument_bag_indices); + // --- read igen chunk --- // annoyingly, the igen chunk appears after the imod chunk, even though you need it first // to figure out which modifiers are global. file.seek(std::io::SeekFrom::Start(igen.offset)); - read_gen_zones(&mut file, &mut instruments, instrument_gen_indices, instrument_mod_indices); - + read_gen_zones( + &mut file, + &mut instruments, + instrument_gen_indices, + instrument_mod_indices, + ); + // --- read phdr chunk --- let mut presets = vec![]; let mut preset_bag_indices = vec![]; file.seek(std::io::SeekFrom::Start(phdr.offset)); if phdr.size < 38 * 2 || phdr.size % 38 != 0 { - return Err(OpenError::BadSoundFont(format!("Bad PHDR size: {}", phdr.size))); + return Err(OpenError::BadSoundFont(format!( + "Bad PHDR size: {}", + phdr.size + ))); } for i in 0..phdr.size / 38 { let name = read_utf8_fixed_len(&mut file, 20, "preset name")?; @@ -586,23 +705,29 @@ impl SoundFont { let library = read_u32(&mut file); let genre = read_u32(&mut file); let morphology = read_u32(&mut file); - presets.push(Preset { name, ..Default::default() }); + presets.push(Preset { + name, + ..Default::default() + }); preset_bag_indices.push(bag_ndx); - } - - + // --- read pbag chunk --- file.seek(std::io::SeekFrom::Start(pbag.offset)); // these are vecs of (preset idx, gen idx) // and (preset idx, mod idx) - let (preset_gen_indices, preset_mod_indices) = read_bag_chunk(&mut file, preset_bag_indices); - + let (preset_gen_indices, preset_mod_indices) = + read_bag_chunk(&mut file, preset_bag_indices); + // --- read pgen chunk --- file.seek(std::io::SeekFrom::Start(pgen.offset)); - read_gen_zones(&mut file, &mut presets, preset_gen_indices, preset_mod_indices); - - + read_gen_zones( + &mut file, + &mut presets, + preset_gen_indices, + preset_mod_indices, + ); + // --- read shdr chunk --- file.seek(std::io::SeekFrom::Start(shdr.offset)); let samples_count = shdr.size / 46; @@ -610,7 +735,9 @@ impl SoundFont { for i in 0..shdr.size / 46 { // a sample let sample_name = read_utf8_fixed_len(&mut file, 20, "sample name")?; - if sample_name == "EOS" { break; } + if sample_name == "EOS" { + break; + } let start = read_u32(&mut file); let end = read_u32(&mut file); let startloop = read_u32(&mut file); @@ -629,31 +756,26 @@ impl SoundFont { let pitch_correction = read_i8(&mut file); let _sample_link = read_u16(&mut file); let _sample_type = read_u16(&mut file); - - // file offset - let offset = sdta_offset as u32 + 2 * start; - + + let sample = Sample { - offset, + start, len: end - start, startloop: startloop - start, endloop: endloop - start, sample_rate, - pitch: original_pitch, + root_key: original_pitch, pitch_correction, - data: vec![] }; samples.push(sample); - } - - + instruments.pop(); // remove EOI presets.pop(); // remove EOP - - + Ok(SoundFont { - file: Some(file), + file, + sdta_offset, name: name_unwrapped, instruments, samples, @@ -661,11 +783,139 @@ impl SoundFont { file_cache: HashMap::new(), }) } - + + pub fn clear_cache(&mut self) { + self.file_cache.clear(); + } + + fn get_samples(&mut self, start: u32, len: u32) -> &[i16] { + if self.file_cache.get(&(start, len)).is_none() { + self.file.seek(std::io::SeekFrom::Start(self.sdta_offset + 2 * start as u64)); + let mut data8 = vec![0u8; 2 * (len as usize)]; + self.file.read_exact(&mut data8); + + let mut data16 = vec![0i16; len as usize]; + for i in 0..len as usize { + data16[i] = i16::from_le_bytes([data8[2 * i], data8[2 * i + 1]]); + } + + self.file_cache.insert((start, len), data16); + } + self.file_cache.get(&(start, len)).unwrap() + } + + fn get_zones(&self, preset: &Preset, key: u8, vel: u8) -> Vec { + let mut zones = vec![]; + for pzone in preset.zones.iter() { + if pzone.contains(key, vel) { + if let ZoneReference::Instrument(i) = pzone.reference { + let inst = &self.instruments[i as usize]; + for izone in inst.zones.iter() { + if izone.contains(key, vel) { + zones.push(Zone::add(pzone, izone)); + } + } + } + } + } + zones + } + + /// adds sample data to `samples` which is an i16 slice containing samples LRLRLRLR... + /// (`samples` should have even length) + /// volume in (0,1) = volume of max velocity note. + /// returns Ok(true) if the note should still be held + pub fn add_samples_interlaced( + &mut self, + preset_idx: usize, + volume: f32, + // falloff: f32, @TODO + key: u8, + vel: u8, + hold_time: f64, + samples: &mut [i16], + sample_rate: f64, + ) -> Result { + if preset_idx >= self.presets.len() { + return Err(SampleError::BadPreset); + } + + let zones = self + .get_zones(&self.presets[preset_idx], key, vel); + if zones.len() == 0 { + return Err(SampleError::NoSamples); + } + let mut held = false; + + for zone in zones.iter() { + let sample = match zone.reference { + ZoneReference::SampleID(id) => self.samples[id as usize].clone(), + _ => return Err(SampleError::NoSamples), + }; + + let mut tune = zone.tune as i32; + let root_key = if zone.force_root_key != -1 { + zone.force_root_key as u8 + } else { + sample.root_key + }; + let keynum = if zone.force_key != -1 { + zone.force_key as u8 + } else { + key + }; + tune += (keynum as i32 - root_key as i32) * zone.scale_tuning as i32; + tune += sample.pitch_correction as i32; + let freq_modulation = f64::powf(1.0005777895065548 /* 2 ^ (1/1200) */, tune as f64); + + let data = self.get_samples(sample.start, sample.len); + + let mut this_held = true; + + let pan = zone.pan as f32 * 0.001 + 0.5; + + let velnum = if zone.force_vel != -1 { + zone.force_vel as u8 + } else { + vel + }; + let mut amplitude = volume * (velnum as f32) * (1.0 / 127.0); + /* + i've taken initial attenuation out because i dont want it (better to just control the volume). + if you want it back in: + //0.9885530946569389 = 0.1^0.005 + // initial attenuation is in cB, so why 0.005 instead of 0.1? + // i have no clue. if you do a -10dB gain on audacity, + // it decreases all the samples by a factor of sqrt(10), not 10. + // some bullshit audio thing or something. + amplitude *= f32::powf(0.9885530946569389, zone.initial_attenuation as f32); + */ + + let mut t = hold_time; + for i in 0..samples.len() / 2 { + let mut s = (t * freq_modulation * sample.sample_rate as f64) as usize; + if zone.loops { + // @TODO alter s + } + if s >= data.len() { + this_held = false; + break; + } + let sample = data[s] as f32; + + samples[2*i] += (amplitude * sample * (1.0 - pan)) as i16; + samples[2*i+1] += (amplitude * sample * pan) as i16; + t += 1.0 / sample_rate; + } + held |= this_held; + } + Ok(held) + } + pub fn preset_count(&self) -> usize { self.presets.len() } - + pub fn preset_name(&self, idx: usize) -> Option<&str> { if idx >= self.presets.len() { None @@ -673,22 +923,4 @@ impl SoundFont { Some(&self.presets[idx].name) } } - - pub fn load_preset(&mut self, preset_idx: usize) -> Result { - if preset_idx >= self.presets.len() { - return Err(PresetError::BadPreset); - } - let preset = &self.presets[preset_idx]; - // @TODO - } - - pub fn close_file(&mut self) { - self.file = None; - } -} - -impl Preset { - /// adds own sample data to `samples`. - pub fn add_samples(&self, key: u8, vel: u8, hold_time: f64, samples: &mut [i16]) { - } } -- cgit v1.2.3