Skip to content

Commit

Permalink
Add ability to modify buffer window and ignore range in realtime synth (
Browse files Browse the repository at this point in the history
  • Loading branch information
MyBlackMIDIScore authored Oct 10, 2024
1 parent 076b930 commit 0d1d11d
Show file tree
Hide file tree
Showing 8 changed files with 91 additions and 34 deletions.
10 changes: 10 additions & 0 deletions clib/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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,
}
44 changes: 32 additions & 12 deletions clib/src/realtime.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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
Expand All @@ -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 },
}
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion core/src/effects/limiter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ impl VolumeLimiter {
&'a mut self,
samples: T,
) -> VolumeLimiterIter<'a, 'b, T> {
impl<'a, 'b, T: 'b + Iterator<Item = f32>> Iterator for VolumeLimiterIter<'a, 'b, T> {
impl<'b, T: 'b + Iterator<Item = f32>> Iterator for VolumeLimiterIter<'_, 'b, T> {
type Item = f32;

fn next(&mut self) -> Option<Self::Item> {
Expand Down
3 changes: 2 additions & 1 deletion kdmapi/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#![allow(non_snake_case)]
#![allow(static_mut_refs)]

use hotwatch::{Event, EventKind, Hotwatch};
use std::{
Expand Down Expand Up @@ -59,7 +60,7 @@ pub extern "C" fn InitializeKDMAPIStream() -> i32 {
let sflist = Config::<SFList>::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(
Expand Down
2 changes: 1 addition & 1 deletion realtime/examples/midi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
28 changes: 15 additions & 13 deletions realtime/src/event_senders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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<u8>) {
self.ignore_range = ignore_range;
}
}

impl Clone for EventSender {
Expand Down Expand Up @@ -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<u8>) {
for sender in self.senders.iter_mut() {
sender.set_ignore_range(ignore_range.clone());
}
}
}
32 changes: 28 additions & 4 deletions realtime/src/realtime_synth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T: SizedSample + ConvertSample>(
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
4 changes: 2 additions & 2 deletions soundfonts/src/sfz/grammar/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}
Expand Down

0 comments on commit 0d1d11d

Please sign in to comment.