Skip to content

Commit

Permalink
SF2 Support (#76)
Browse files Browse the repository at this point in the history
- Added support for SF2 soundfonts
- Replaced the sinc resampler with rubato
- Fixed some instrument related bugs
- Changed the instrument replacing rules to the conventional ones
- Changed the text and names in the examples to imply SF2 support
- Changed the voice popping algorithm so that if the ignored ID has more voices than the limit it allows those voices to exceed the limit instead of freezing the entire synth
  • Loading branch information
MyBlackMIDIScore authored Jul 30, 2024
1 parent d4b6bc4 commit 3385cd0
Show file tree
Hide file tree
Showing 26 changed files with 902 additions and 143 deletions.
94 changes: 92 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
78 changes: 48 additions & 30 deletions core/src/channel/channel_sf.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::{iter, ops::Deref, sync::Arc};

use crate::{
helpers::are_arc_vecs_equal,
soundfont::SoundfontBase,
voice::{Voice, VoiceControlData},
};
Expand All @@ -10,8 +11,8 @@ use super::voice_spawner::VoiceSpawnerMatrix;
pub struct ChannelSoundfont {
soundfonts: Vec<Arc<dyn SoundfontBase>>,
matrix: VoiceSpawnerMatrix,
curr_bank: Option<u8>,
curr_preset: Option<u8>,
curr_bank: u8,
curr_preset: u8,
}

impl Deref for ChannelSoundfont {
Expand All @@ -28,68 +29,85 @@ impl ChannelSoundfont {
ChannelSoundfont {
soundfonts: Vec::new(),
matrix: VoiceSpawnerMatrix::new(),
curr_bank: None,
curr_preset: None,
curr_bank: 0,
curr_preset: 0,
}
}

pub fn set_soundfonts(&mut self, soundfonts: Vec<Arc<dyn SoundfontBase>>) {
self.soundfonts = soundfonts;
self.curr_bank = None;
self.curr_preset = None;
self.rebuild_matrix(0, 0);
if !are_arc_vecs_equal(&self.soundfonts, &soundfonts) {
self.soundfonts = soundfonts;
self.rebuild_matrix();
}
}

pub fn change_program(&mut self, bank: u8, preset: u8) {
self.rebuild_matrix(bank, preset);
if self.curr_bank != bank || self.curr_preset != preset {
self.curr_bank = bank;
self.curr_preset = preset;
self.rebuild_matrix();
}
}

fn rebuild_matrix(&mut self, bank: u8, preset: u8) {
if self.curr_bank == Some(bank) && self.curr_preset == Some(preset) {
return;
}
fn rebuild_matrix(&mut self) {
// If a preset/instr. is missing from all banks it will be muted,
// if a preset/instr. has regions in bank 0, all missing banks will be replaced by 0,
// if a preset/instr. has regions in any bank other than 0, all missing banks will be muted.
// For drum patches the same applies with bank and preset switched.

let bank = self.curr_bank;
let preset = self.curr_preset;

for k in 0..128u8 {
for v in 0..128u8 {
// The fallback piano finder in case no other instrument is found
let find_piano_attack = || {
self.soundfonts
.iter()
.map(|sf| sf.get_attack_voice_spawners_at(0, 0, k, v))
.find(|vec| !vec.is_empty())
let find_replacement_attack = || {
if bank == 128 {
self.soundfonts
.iter()
.map(|sf| sf.get_attack_voice_spawners_at(bank, 0, k, v))
.find(|vec| !vec.is_empty())
} else {
self.soundfonts
.iter()
.map(|sf| sf.get_attack_voice_spawners_at(0, preset, k, v))
.find(|vec| !vec.is_empty())
}
};

let attack_spawners = self
.soundfonts
.iter()
.map(|sf| sf.get_attack_voice_spawners_at(bank, preset, k, v))
.chain(iter::once_with(find_piano_attack).flatten())
.chain(iter::once_with(find_replacement_attack).flatten())
.find(|vec| !vec.is_empty())
.unwrap_or_default();

// The fallback piano finder in case no other instrument is found
let find_piano_release = || {
self.soundfonts
.iter()
.map(|sf| sf.get_release_voice_spawners_at(0, 0, k, v))
.find(|vec| !vec.is_empty())
let find_replacement_release = || {
if bank == 128 {
self.soundfonts
.iter()
.map(|sf| sf.get_release_voice_spawners_at(bank, 0, k, v))
.find(|vec| !vec.is_empty())
} else {
self.soundfonts
.iter()
.map(|sf| sf.get_release_voice_spawners_at(0, preset, k, v))
.find(|vec| !vec.is_empty())
}
};

let release_spawners = self
.soundfonts
.iter()
.map(|sf| sf.get_release_voice_spawners_at(bank, preset, k, v))
.chain(iter::once_with(find_piano_release).flatten())
.chain(iter::once_with(find_replacement_release).flatten())
.find(|vec| !vec.is_empty())
.unwrap_or_default();

self.matrix.set_spawners_attack(k, v, attack_spawners);
self.matrix.set_spawners_release(k, v, release_spawners);
}
}

self.curr_bank = Some(bank);
self.curr_preset = Some(preset);
}

pub fn spawn_voices_attack<'a>(
Expand Down
18 changes: 9 additions & 9 deletions core/src/channel/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ struct ControlEventData {
}

impl ControlEventData {
pub fn new_defaults(sample_rate: u32) -> Self {
pub fn new_defaults(sample_rate: u32, drums_only: bool) -> Self {
ControlEventData {
selected_lsb: -1,
selected_msb: -1,
Expand All @@ -116,7 +116,7 @@ impl ControlEventData {
resonance: None,
expression: ValueLerp::new(1.0, sample_rate),
preset: 0,
bank: 0,
bank: if drums_only { 128 } else { 0 },
}
}
}
Expand Down Expand Up @@ -173,11 +173,6 @@ impl VoiceChannel {
let params = VoiceChannelParams::new(stream_params);
let shared_voice_counter = params.stats.voice_counter.clone();

let mut control_event_data = ControlEventData::new_defaults(stream_params.sample_rate);
if options.drums_only {
control_event_data.bank = 128;
}

VoiceChannel {
params,
key_voices: fill_key_array(|i| Key::new(i, shared_voice_counter.clone(), options)),
Expand All @@ -187,7 +182,10 @@ impl VoiceChannel {
stream_params,
options,

control_event_data,
control_event_data: ControlEventData::new_defaults(
stream_params.sample_rate,
options.drums_only,
),
voice_control_data: VoiceControlData::new_defaults(),

cutoff: MultiChannelBiQuad::new(
Expand Down Expand Up @@ -549,8 +547,10 @@ impl VoiceChannel {
}

fn reset_control(&mut self) {
self.control_event_data = ControlEventData::new_defaults(self.stream_params.sample_rate);
self.control_event_data =
ControlEventData::new_defaults(self.stream_params.sample_rate, self.options.drums_only);
self.voice_control_data = VoiceControlData::new_defaults();
self.process_event(ChannelEvent::Audio(ChannelAudioEvent::ProgramChange(0)));
self.propagate_voice_controls();

self.control_event_data.cutoff = None;
Expand Down
7 changes: 6 additions & 1 deletion core/src/channel/voice_buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,13 +137,18 @@ impl VoiceBuffer {
voices: impl Iterator<Item = Box<dyn Voice>>,
max_voices: Option<usize>,
) {
let mut len = 0;

let id = self.get_id();
for voice in voices {
self.buffer.push_back(GroupVoice { id, voice });
len += 1;
}

if let Some(max_voices) = max_voices {
if self.options.fade_out_killing {
if len > max_voices {
self.pop_quietest_voice_group(id);
} else if self.options.fade_out_killing {
while self.get_active_count() > max_voices {
self.pop_quietest_voice_group(id);
}
Expand Down
Loading

0 comments on commit 3385cd0

Please sign in to comment.