From 0d1d11d766faf9ef0aefd8a4d0cf2dfa1ed32dd6 Mon Sep 17 00:00:00 2001 From: MBMS <31241793+MyBlackMIDIScore@users.noreply.github.com> Date: Thu, 10 Oct 2024 11:17:58 +0300 Subject: [PATCH] Add ability to modify buffer window and ignore range in realtime synth (#105) --- clib/src/lib.rs | 10 +++++++ clib/src/realtime.rs | 44 ++++++++++++++++++++++--------- core/src/effects/limiter.rs | 2 +- kdmapi/src/lib.rs | 3 ++- realtime/examples/midi.rs | 2 +- realtime/src/event_senders.rs | 28 +++++++++++--------- realtime/src/realtime_synth.rs | 32 +++++++++++++++++++--- soundfonts/src/sfz/grammar/mod.rs | 4 +-- 8 files changed, 91 insertions(+), 34 deletions(-) diff --git a/clib/src/lib.rs b/clib/src/lib.rs index 118d6f1..974eba8 100644 --- a/clib/src/lib.rs +++ b/clib/src/lib.rs @@ -1,5 +1,6 @@ #![allow(clippy::missing_safety_doc)] #![allow(clippy::result_unit_err)] +#![allow(clippy::too_long_first_doc_paragraph)] pub mod consts; pub mod group; @@ -45,3 +46,12 @@ pub extern "C" fn XSynth_GenDefault_StreamParams() -> XSynth_StreamParams { audio_channels: XSYNTH_AUDIO_CHANNELS_STEREO, } } + +/// A helper struct to specify a range of bytes. +/// - start: The start of the range +/// - end: The end of the range +#[repr(C)] +pub struct XSynth_ByteRange { + pub start: u8, + pub end: u8, +} diff --git a/clib/src/realtime.rs b/clib/src/realtime.rs index bf01a31..9c45c8a 100644 --- a/clib/src/realtime.rs +++ b/clib/src/realtime.rs @@ -1,4 +1,4 @@ -use crate::{handles::*, utils::*, XSynth_StreamParams}; +use crate::{handles::*, utils::*, XSynth_ByteRange, XSynth_StreamParams}; use xsynth_core::{ channel::{ChannelConfigEvent, ChannelEvent, ChannelInitOptions}, channel_group::SynthEvent, @@ -17,14 +17,14 @@ use xsynth_realtime::{RealtimeSynth, XSynthRealtimeConfig}; /// usually causing clicking but improving performance. /// - render_window_ms: The length of the buffer reader in ms /// - ignore_range: A range of velocities that will not be played -/// LOBYTE = start (0-127), HIBYTE = end (start-127) +/// (see XSynth_ByteRange) #[repr(C)] pub struct XSynth_RealtimeConfig { pub channels: u32, pub multithreading: i32, pub fade_out_killing: bool, pub render_window_ms: f64, - pub ignore_range: u16, + pub ignore_range: XSynth_ByteRange, } /// Generates the default values for the XSynth_RealtimeConfig struct @@ -41,7 +41,7 @@ pub extern "C" fn XSynth_GenDefault_RealtimeConfig() -> XSynth_RealtimeConfig { multithreading: -1, fade_out_killing: false, render_window_ms: 10.0, - ignore_range: 0, + ignore_range: XSynth_ByteRange { start: 0, end: 0 }, } } @@ -72,18 +72,12 @@ pub extern "C" fn XSynth_Realtime_Create(config: XSynth_RealtimeConfig) -> XSynt fade_out_killing: config.fade_out_killing, }; - let ignore_range = { - let low = (config.ignore_range & 255) as u8; - let high = (config.ignore_range >> 8) as u8; - low..=high - }; - let options = XSynthRealtimeConfig { channel_init_options, render_window_ms: config.render_window_ms, format: convert_synth_format(config.channels), multithreading: convert_threadcount(config.multithreading), - ignore_range, + ignore_range: config.ignore_range.start..=config.ignore_range.end, }; let new = RealtimeSynth::open_with_default_output(options); @@ -169,6 +163,32 @@ pub extern "C" fn XSynth_Realtime_SendConfigEventAll( } } +/// Sets the length of the buffer reader to the desired value in ms. +/// +/// --Parameters-- +/// - handle: The handle of the realtime synthesizer instance +/// - render_window_ms: The length of the buffer reader in ms +#[no_mangle] +pub extern "C" fn XSynth_Realtime_SetBuffer(handle: XSynth_RealtimeSynth, render_window_ms: f64) { + handle.as_ref().set_buffer(render_window_ms); +} + +/// Sets the range of velocities that will be ignored. +/// +/// --Parameters-- +/// - handle: The handle of the realtime synthesizer instance +/// - ignore_range: The range. LOBYTE = start (0-127), HIBYTE = end (start-127) +#[no_mangle] +pub extern "C" fn XSynth_Realtime_SetIgnoreRange( + handle: XSynth_RealtimeSynth, + ignore_range: XSynth_ByteRange, +) { + handle + .as_mut() + .get_sender_mut() + .set_ignore_range(ignore_range.start..=ignore_range.end); +} + /// Sets a list of soundfonts to be used in the specified realtime synth /// instance. To load a new soundfont, see the XSynth_Soundfont_LoadNew /// function. @@ -248,7 +268,7 @@ pub extern "C" fn XSynth_Realtime_GetStats(handle: XSynth_RealtimeSynth) -> XSyn /// - handle: The handle of the realtime synthesizer instance #[no_mangle] pub extern "C" fn XSynth_Realtime_Reset(handle: XSynth_RealtimeSynth) { - handle.as_ref().get_senders().reset_synth(); + handle.as_mut().get_sender_mut().reset_synth(); } /// Drops the specified realtime synth instance. diff --git a/core/src/effects/limiter.rs b/core/src/effects/limiter.rs index 3659511..7dbf0b0 100644 --- a/core/src/effects/limiter.rs +++ b/core/src/effects/limiter.rs @@ -76,7 +76,7 @@ impl VolumeLimiter { &'a mut self, samples: T, ) -> VolumeLimiterIter<'a, 'b, T> { - impl<'a, 'b, T: 'b + Iterator> Iterator for VolumeLimiterIter<'a, 'b, T> { + impl<'b, T: 'b + Iterator> Iterator for VolumeLimiterIter<'_, 'b, T> { type Item = f32; fn next(&mut self) -> Option { diff --git a/kdmapi/src/lib.rs b/kdmapi/src/lib.rs index 7535644..53de9e4 100644 --- a/kdmapi/src/lib.rs +++ b/kdmapi/src/lib.rs @@ -1,4 +1,5 @@ #![allow(non_snake_case)] +#![allow(static_mut_refs)] use hotwatch::{Event, EventKind, Hotwatch}; use std::{ @@ -59,7 +60,7 @@ pub extern "C" fn InitializeKDMAPIStream() -> i32 { let sflist = Config::::new().load().unwrap(); let realtime_synth = RealtimeSynth::open_with_default_output(config.get_synth_config()); - let mut sender = realtime_synth.get_senders(); + let mut sender = realtime_synth.get_sender_ref().clone(); let params = realtime_synth.stream_params(); sender.send_event(SynthEvent::AllChannels(ChannelEvent::Config( diff --git a/realtime/examples/midi.rs b/realtime/examples/midi.rs index 483e993..263c600 100644 --- a/realtime/examples/midi.rs +++ b/realtime/examples/midi.rs @@ -39,7 +39,7 @@ fn main() { }; let synth = RealtimeSynth::open_with_all_defaults(); - let mut sender = synth.get_senders(); + let mut sender = synth.get_sender_ref().clone(); let params = synth.stream_params(); diff --git a/realtime/src/event_senders.rs b/realtime/src/event_senders.rs index 4fd6df3..c5c8579 100644 --- a/realtime/src/event_senders.rs +++ b/realtime/src/event_senders.rs @@ -148,10 +148,11 @@ impl EventSender { return; } - let in_ignore_range = self.ignore_range.contains(vel); - let nps = self.nps.calculate_nps(); - if should_send_for_vel_and_nps(*vel, nps, self.max_nps.read()) && !in_ignore_range { + + if should_send_for_vel_and_nps(*vel, nps, self.max_nps.read()) + && !self.ignore_range.contains(vel) + { self.sender.send(ChannelEvent::Audio(event)).ok(); self.nps.add_note(); } else { @@ -179,16 +180,9 @@ impl EventSender { self.sender.send(ChannelEvent::Config(event)).ok(); } - // pub fn send(&mut self, event: ChannelEvent) { - // match event { - // ChannelEvent::Audio(event) => { - // self.send_audio(event); - // } - // ChannelEvent::Config(event) => { - // self.send_config(event); - // } - // } - // } + pub fn set_ignore_range(&mut self, ignore_range: RangeInclusive) { + self.ignore_range = ignore_range; + } } impl Clone for EventSender { @@ -333,4 +327,12 @@ impl RealtimeEventSender { ChannelAudioEvent::ResetControl, ))); } + + /// Changes the range of velocities that will be ignored for the + /// specific sender instance. + pub fn set_ignore_range(&mut self, ignore_range: RangeInclusive) { + for sender in self.senders.iter_mut() { + sender.set_ignore_range(ignore_range.clone()); + } + } } diff --git a/realtime/src/realtime_synth.rs b/realtime/src/realtime_synth.rs index 06a01f0..4073243 100644 --- a/realtime/src/realtime_synth.rs +++ b/realtime/src/realtime_synth.rs @@ -227,7 +227,7 @@ impl RealtimeSynth { let buffered = Arc::new(Mutex::new(BufferedRenderer::new( render, stream_params, - (sample_rate as f64 * config.render_window_ms / 1000.0) as usize, + calculate_render_size(sample_rate, config.render_window_ms), ))); fn build_stream( @@ -289,13 +289,25 @@ impl RealtimeSynth { data.event_senders.send_event(event); } - /// Returns the event sender of the realtime synthesizer. + /// Returns a reference to the event sender of the realtime synthesizer. + /// This can be used to clone the sender so it can be passed in threads. /// /// See the `RealtimeEventSender` documentation for more information /// on how to use. - pub fn get_senders(&self) -> RealtimeEventSender { + pub fn get_sender_ref(&self) -> &RealtimeEventSender { let data = self.data.as_ref().unwrap(); - data.event_senders.clone() + &data.event_senders + } + + /// Returns a mutable reference the event sender of the realtime synthesizer. + /// This can be used to modify its parameters (eg. ignore range). + /// Please note that each clone will store its own distinct parameters. + /// + /// See the `RealtimeEventSender` documentation for more information + /// on how to use. + pub fn get_sender_mut(&mut self) -> &mut RealtimeEventSender { + let data = self.data.as_mut().unwrap(); + &mut data.event_senders } /// Returns the statistics reader of the realtime synthesizer. @@ -325,6 +337,14 @@ impl RealtimeSynth { let data = self.data.as_mut().unwrap(); data.stream.play() } + + /// Changes the length of the buffer reader. + pub fn set_buffer(&self, render_window_ms: f64) { + let data = self.data.as_ref().unwrap(); + let sample_rate = self.stream_params.sample_rate; + let size = calculate_render_size(sample_rate, render_window_ms); + data.buffered_renderer.lock().unwrap().set_render_size(size); + } } impl Drop for RealtimeSynth { @@ -359,3 +379,7 @@ impl ConvertSample for u16 { ((s * u16::MAX as f32) as i32 + i16::MIN as i32) as u16 } } + +fn calculate_render_size(sample_rate: u32, buffer_ms: f64) -> usize { + (sample_rate as f64 * buffer_ms / 1000.0) as usize +} diff --git a/soundfonts/src/sfz/grammar/mod.rs b/soundfonts/src/sfz/grammar/mod.rs index fdbbe99..0035f00 100644 --- a/soundfonts/src/sfz/grammar/mod.rs +++ b/soundfonts/src/sfz/grammar/mod.rs @@ -59,13 +59,13 @@ bnf! { impl<'a> OpcodeValue<'a> { pub fn as_string(&self) -> Cow<'a, str> { if self.rest.is_empty() { - return Cow::Borrowed(self.first.value.text.text); + Cow::Borrowed(self.first.value.text.text) } else { let mut result = String::from(self.first.value.text.text); for part in self.rest.iter() { result.push_str(part.value.text.text); } - return Cow::Owned(result); + Cow::Owned(result) } } }