diff --git a/Cargo.lock b/Cargo.lock index 126416b..c416fb1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -816,44 +816,20 @@ dependencies = [ [[package]] name = "midi-toolkit-rs" version = "0.1.0" +source = "git+https://github.com/arduano/midi-toolkit-rs?rev=26747a3#26747a32fb0350c6fa625275b35d4301dc25572b" dependencies = [ "crossbeam-channel", "gen-iter", - "midi-toolkit-rs-derive 0.1.0", + "midi-toolkit-rs-derive", "num-traits", "rayon", "thiserror", ] -[[package]] -name = "midi-toolkit-rs" -version = "0.1.0" -source = "git+https://github.com/arduano/midi-toolkit-rs?rev=a54f198#a54f19890e47c90a246001b9f8b2519f3604eb58" -dependencies = [ - "crossbeam-channel", - "gen-iter", - "midi-toolkit-rs-derive 0.1.0 (git+https://github.com/arduano/midi-toolkit-rs?rev=a54f198)", - "num-traits", - "rayon", - "thiserror", -] - -[[package]] -name = "midi-toolkit-rs-derive" -version = "0.1.0" -dependencies = [ - "convert_case", - "num-traits", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "midi-toolkit-rs-derive" version = "0.1.0" -source = "git+https://github.com/arduano/midi-toolkit-rs?rev=a54f198#a54f19890e47c90a246001b9f8b2519f3604eb58" +source = "git+https://github.com/arduano/midi-toolkit-rs?rev=26747a3#26747a32fb0350c6fa625275b35d4301dc25572b" dependencies = [ "convert_case", "num-traits", @@ -2030,7 +2006,7 @@ dependencies = [ "criterion 0.4.0", "crossbeam-channel", "lazy_static", - "midi-toolkit-rs 0.1.0 (git+https://github.com/arduano/midi-toolkit-rs?rev=a54f198)", + "midi-toolkit-rs", "proc-macro2", "rand", "rayon", @@ -2053,7 +2029,7 @@ dependencies = [ "crossbeam-channel", "gen-iter", "lazy_static", - "midi-toolkit-rs 0.1.0", + "midi-toolkit-rs", "rayon", "spin_sleep", "to_vec", @@ -2068,7 +2044,7 @@ dependencies = [ "atomic_float", "crossbeam-channel", "hound", - "midi-toolkit-rs 0.1.0 (git+https://github.com/arduano/midi-toolkit-rs?rev=a54f198)", + "midi-toolkit-rs", "rayon", "spin_sleep", "thiserror", diff --git a/core/Cargo.toml b/core/Cargo.toml index fdf481e..228735c 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -21,7 +21,7 @@ simdeez = "2.0.0-dev3" proc-macro2 = "1.0.63" [dev-dependencies] -midi-toolkit-rs = { git = "https://github.com/arduano/midi-toolkit-rs", rev = "a54f198" } +midi-toolkit-rs = { git = "https://github.com/arduano/midi-toolkit-rs", rev = "26747a3" } rand = "0.8.5" criterion = "0.4.0" diff --git a/core/src/soundfont/mod.rs b/core/src/soundfont/mod.rs index 1bba20f..3df5daf 100644 --- a/core/src/soundfont/mod.rs +++ b/core/src/soundfont/mod.rs @@ -26,7 +26,7 @@ use crate::{ AudioStreamParams, ChannelCount, }; -use soundfonts::{FilterType, LoopMode}; +use soundfonts::{convert_sample_index, FilterType, LoopMode}; pub mod audio; mod voice_spawners; @@ -182,10 +182,6 @@ pub enum LoadSfError { Unsupported, } -fn convert_sample_index(idx: u32, old_sample_rate: u32, new_sample_rate: u32) -> u32 { - (new_sample_rate as f32 * idx as f32 / old_sample_rate as f32).round() as u32 -} - impl SampleSoundfont { pub fn new( path: impl Into, @@ -412,29 +408,15 @@ impl SampleSoundfont { let pan = ((region.pan as f32 / 500.0) + 1.0) / 2.0; - let sample_rate = region.sample_rate; - let loop_params = LoopParams { mode: if region.loop_start == region.loop_end { LoopMode::NoLoop } else { region.loop_mode }, - offset: convert_sample_index( - region.offset, - sample_rate, - stream_params.sample_rate, - ), - start: convert_sample_index( - region.loop_start, - sample_rate, - stream_params.sample_rate, - ), - end: convert_sample_index( - region.loop_end, - sample_rate, - stream_params.sample_rate, - ), + offset: region.offset, + start: region.loop_start, + end: region.loop_end, }; let mut region_samples = region.sample.clone(); @@ -444,7 +426,6 @@ impl SampleSoundfont { region_samples = Arc::new([region_samples[0].clone(), region_samples[0].clone()]); } - // FIXME: Stereo linked samples let spawner_params = Arc::new(SampleVoiceSpawnerParams { pan, diff --git a/realtime/Cargo.toml b/realtime/Cargo.toml index 18b0397..00676a1 100644 --- a/realtime/Cargo.toml +++ b/realtime/Cargo.toml @@ -18,7 +18,7 @@ wav = "1.0.0" core = { path = "../core", package = "xsynth-core" } [dev-dependencies] -midi-toolkit-rs = { git = "https://github.com/arduano/midi-toolkit-rs.git", rev = "a54f198" } +midi-toolkit-rs = { git = "https://github.com/arduano/midi-toolkit-rs.git", rev = "26747a3" } gen-iter = { git = "https://github.com/arduano/gen-iter.git", rev = "64e28bc" } [build-dependencies] diff --git a/render/Cargo.toml b/render/Cargo.toml index 5f4f35b..7204ebe 100644 --- a/render/Cargo.toml +++ b/render/Cargo.toml @@ -10,7 +10,7 @@ core = { path = "../core", package = "xsynth-core" } crossbeam-channel = "0.5.1" hound = "3.5.0" rayon = "1.5.3" -midi-toolkit-rs = { git = "https://github.com/arduano/midi-toolkit-rs", rev = "a54f198" } +midi-toolkit-rs = { git = "https://github.com/arduano/midi-toolkit-rs", rev = "26747a3" } spin_sleep = "1.0.0" atomic_float = "0.1.0" thiserror = "1.0.31" diff --git a/soundfonts/src/lib.rs b/soundfonts/src/lib.rs index 8c85946..f934a38 100644 --- a/soundfonts/src/lib.rs +++ b/soundfonts/src/lib.rs @@ -19,3 +19,7 @@ pub enum LoopMode { LoopContinuous, LoopSustain, } + +pub fn convert_sample_index(idx: u32, old_sample_rate: u32, new_sample_rate: u32) -> u32 { + (new_sample_rate as f32 * idx as f32 / old_sample_rate as f32).round() as u32 +} diff --git a/soundfonts/src/resample.rs b/soundfonts/src/resample.rs index 33ec6c6..c268bfe 100644 --- a/soundfonts/src/resample.rs +++ b/soundfonts/src/resample.rs @@ -9,27 +9,27 @@ pub fn resample_vecs( new_sample_rate: f32, ) -> Arc<[Arc<[f32]>]> { vecs.into_iter() - .map(|samples| { - let params = SincInterpolationParameters { - sinc_len: 32, - f_cutoff: 0.95, - interpolation: SincInterpolationType::Linear, - oversampling_factor: 128, - window: WindowFunction::BlackmanHarris2, - }; - - let len = samples.len(); - let mut resampler = SincFixedIn::::new( - new_sample_rate as f64 / sample_rate as f64, - 2.0, - params, - len, - 1, - ) - .unwrap(); - resampler.process(&[samples], None).unwrap()[0] - .clone() - .into() - }) + .map(|samples| resample_vec(samples, sample_rate, new_sample_rate)) .collect() } + +pub fn resample_vec(vec: Vec, sample_rate: f32, new_sample_rate: f32) -> Arc<[f32]> { + let params = SincInterpolationParameters { + sinc_len: 32, + f_cutoff: 0.95, + interpolation: SincInterpolationType::Linear, + oversampling_factor: 128, + window: WindowFunction::BlackmanHarris2, + }; + + let len = vec.len(); + let mut resampler = SincFixedIn::::new( + new_sample_rate as f64 / sample_rate as f64, + 2.0, + params, + len, + 1, + ) + .unwrap(); + resampler.process(&[vec], None).unwrap()[0].clone().into() +} diff --git a/soundfonts/src/sf2/mod.rs b/soundfonts/src/sf2/mod.rs index 95fc4f3..dc0a27c 100644 --- a/soundfonts/src/sf2/mod.rs +++ b/soundfonts/src/sf2/mod.rs @@ -40,6 +40,7 @@ struct Sf2Zone { pub root_override: Option, } +#[derive(Clone)] pub struct Sf2Region { pub sample: Arc<[Arc<[f32]>]>, pub sample_rate: u32, @@ -95,5 +96,6 @@ pub fn load_soundfont( sample_data, instruments, presets, + sample_rate, )) } diff --git a/soundfonts/src/sf2/preset.rs b/soundfonts/src/sf2/preset.rs index 8f53c5e..d833368 100644 --- a/soundfonts/src/sf2/preset.rs +++ b/soundfonts/src/sf2/preset.rs @@ -1,7 +1,7 @@ use super::{instrument::Sf2Instrument, sample::Sf2Sample, Sf2Preset, Sf2Region, Sf2Zone}; -use crate::{sfz::AmpegEnvelopeParams, LoopMode}; +use crate::{convert_sample_index, sfz::AmpegEnvelopeParams, LoopMode}; use soundfont::{data::hydra::generator::GeneratorType, Preset}; -use std::ops::RangeInclusive; +use std::{ops::RangeInclusive, sync::Arc}; #[derive(Clone, Debug)] pub struct Sf2ParsedPreset { @@ -23,15 +23,6 @@ impl Sf2ParsedPreset { for gen in &zone.gen_list { match gen.ty { - GeneratorType::StartAddrsOffset => { - region.offset = gen.amount.as_i16().copied() - } - GeneratorType::StartloopAddrsOffset => { - region.loop_start_offset = gen.amount.as_i16().copied() - } - GeneratorType::EndloopAddrsOffset => { - region.loop_end_offset = gen.amount.as_i16().copied() - } GeneratorType::InitialFilterFc => { region.cutoff = gen.amount.as_i16().copied() } @@ -81,16 +72,6 @@ impl Sf2ParsedPreset { } GeneratorType::FineTune => region.fine_tune = gen.amount.as_i16().copied(), GeneratorType::Instrument => region.index = gen.amount.as_u16().copied(), - GeneratorType::SampleModes => { - region.loop_mode = gen.amount.as_i16().map(|v| match v { - 1 => LoopMode::LoopContinuous, - 3 => LoopMode::LoopSustain, - _ => LoopMode::NoLoop, - }) - } - GeneratorType::OverridingRootKey => { - region.root_override = gen.amount.as_i16().copied() - } _ => {} } } @@ -115,6 +96,7 @@ impl Sf2ParsedPreset { sample_data: Vec, instruments: Vec, presets: Vec, + sample_rate: u32, ) -> Vec { let mut out: Vec = Vec::new(); @@ -125,6 +107,8 @@ impl Sf2ParsedPreset { regions: Vec::new(), }; + let mut regions = Vec::new(); + for zone in preset.zones { if let Some(instrument_idx) = zone.index { let instrument = &instruments[instrument_idx as usize]; @@ -134,7 +118,7 @@ impl Sf2ParsedPreset { let sample = &sample_data[sample_idx as usize]; let new_region = Sf2Region { - sample: sample.data.clone(), + sample: Arc::new([]), sample_rate: sample.sample_rate, velrange: combine_ranges( zone.velrange.clone().unwrap_or(0..=127), @@ -156,13 +140,23 @@ impl Sf2ParsedPreset { loop_mode: zone .loop_mode .unwrap_or(subzone.loop_mode.unwrap_or(LoopMode::NoLoop)), - loop_start: (sample.loop_start as i32 - + subzone.loop_start_offset.unwrap_or(0) as i32) - as u32, - loop_end: (sample.loop_end as i32 - + subzone.loop_end_offset.unwrap_or(0) as i32) - as u32, - offset: subzone.offset.unwrap_or(0) as u32, + loop_start: { + let v = (sample.loop_start as i32 + + subzone.loop_start_offset.unwrap_or(0) as i32) + as u32; + convert_sample_index(v, sample.sample_rate, sample_rate) + }, + loop_end: { + let v = (sample.loop_end as i32 + + subzone.loop_end_offset.unwrap_or(0) as i32) + as u32; + convert_sample_index(v, sample.sample_rate, sample_rate) + }, + offset: convert_sample_index( + subzone.offset.unwrap_or(0) as u32, + sample.sample_rate, + sample_rate, + ), cutoff: subzone.cutoff.map(|v| { 2f32.powf(v as f32 / 1200.0) * 8.176 @@ -194,12 +188,69 @@ impl Sf2ParsedPreset { }, }; - new_preset.regions.push(new_region); + regions.push((new_region, sample.clone())); } } } } + // Second pass -> Stereo sample linking + // This is kinda hacky, there has to be a better way to do this + // I hate this code + let mut ignored_idx = Vec::new(); + for (i, region) in regions.clone().into_iter().enumerate() { + if ignored_idx.contains(&i) { + continue; + } + if region.1.link_type.abs() == 1 { + if region.0.pan.abs() == 500 { + match regions + .clone() + .into_iter() + .position(|v: (Sf2Region, Sf2Sample)| { + let v1 = v.0.clone(); + let v2 = region.0.clone(); + v.1.link_type == -region.1.link_type + && v1.pan == -v2.pan + && v1.root_key == v2.root_key + && v1.keyrange == v2.keyrange + && v1.velrange == v2.velrange + }) { + Some(reg) => { + let sample_match = regions[reg].1.clone(); + let mut new_region = region.0.clone(); + match region.1.link_type { + -1 => { + new_region.sample = Arc::new([ + region.1.data.clone(), + sample_match.data.clone(), + ]) + } + 1 => { + new_region.sample = Arc::new([ + sample_match.data.clone(), + region.1.data.clone(), + ]) + } + _ => {} + } + new_region.pan = 0; + new_preset.regions.push(new_region); + ignored_idx.push(reg); + } + None => { + let mut new_region = region.0.clone(); + new_region.sample = Arc::new([region.1.data.clone()]); + new_preset.regions.push(new_region); + } + } + } + } else { + let mut new_region = region.0.clone(); + new_region.sample = Arc::new([region.1.data.clone()]); + new_preset.regions.push(new_region); + } + } out.push(new_preset); } diff --git a/soundfonts/src/sf2/sample.rs b/soundfonts/src/sf2/sample.rs index 1299270..518f161 100644 --- a/soundfonts/src/sf2/sample.rs +++ b/soundfonts/src/sf2/sample.rs @@ -1,11 +1,15 @@ use super::Sf2ParseError; -use crate::resample::resample_vecs; -use soundfont::data::{hydra::sample::SampleHeader, sample_data::SampleData}; +use crate::resample::resample_vec; +use soundfont::data::{ + hydra::sample::{SampleHeader, SampleLink}, + sample_data::SampleData, +}; use std::{fs::File, sync::Arc}; #[derive(Clone, Debug)] pub struct Sf2Sample { - pub data: Arc<[Arc<[f32]>]>, + pub data: Arc<[f32]>, + pub link_type: i8, pub loop_start: u32, pub loop_end: u32, pub sample_rate: u32, @@ -67,9 +71,14 @@ impl Sf2Sample { let new = Sf2Sample { data: if h.sample_rate != sample_rate || !sample.is_empty() { - resample_vecs(vec![sample], h.sample_rate as f32, sample_rate as f32) + resample_vec(sample, h.sample_rate as f32, sample_rate as f32) } else { - Arc::new([sample.into()]) + sample.into() + }, + link_type: match h.sample_type { + SampleLink::LeftSample => -1, + SampleLink::RightSample => 1, + _ => 0, }, loop_start: h.loop_start - start, loop_end: h.loop_end - start,