Skip to content

Commit

Permalink
Make requested changes
Browse files Browse the repository at this point in the history
  • Loading branch information
MyBlackMIDIScore committed Aug 8, 2024
1 parent 1e267ac commit 84ab795
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 81 deletions.
76 changes: 76 additions & 0 deletions core/src/channel_group/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
use crate::{channel::ChannelInitOptions, AudioStreamParams};

/// Use multithreading for all actions inside the synthesizer (more info at `ParallelismOptions`)
/// with automatically determined thread counts.
pub const AUTO_MULTITHREADING: ParallelismOptions = ParallelismOptions {
channel: ThreadCount::Auto,
key: ThreadCount::Auto,
};

/// Defines the multithreading options for each task that supports it.
#[derive(Clone)]
pub enum ThreadCount {
/// No multithreading. Run everything on the same thread.
None,

/// Run with multithreading, with an automatically determined thread count.
/// Please read
/// [this](https://docs.rs/rayon-core/1.5.0/rayon_core/struct.ThreadPoolBuilder.html#method.num_threads)
/// for more information about the thread count selection.
Auto,

/// Run with multithreading, with the specified thread count.
Manual(usize),
}

/// Options regarding which parts of the ChannelGroup should be multithreaded.
///
/// Responsibilities of a channel: processing input events for the channel,
/// dispatching per-key rendering of audio, applying filters to the final channel's audio
///
/// Responsibilities of a key: Rendering per-voice audio for all the voices stored in a
/// key for a channel. This is generally the most compute intensive part of the synth.
///
/// Best practices:
/// - As there are often 16 channels in MIDI, per-key multithreading can balance out the
/// load more evenly between CPU cores.
/// - However, per-key multithreading adds some overhead, so if the synth is invoked to
/// render very small sample counts each time (e.g. sub 1 millisecond), not using per-key
/// multithreading becomes more efficient.
#[derive(Clone)]
pub struct ParallelismOptions {
/// Render the MIDI channels parallel in a threadpool with the specified
/// thread count.
pub channel: ThreadCount,

/// Render the individisual keys of each channel parallel in a threadpool
/// with the specified thread count.
pub key: ThreadCount,
}

/// Options for initializing a new ChannelGroup.
#[derive(Clone)]
pub struct ChannelGroupConfig {
/// Channel initialization options (same for all channels).
/// See the `ChannelInitOptions` documentation for more information.
pub channel_init_options: ChannelInitOptions,

/// Amount of VoiceChannel objects to be created
/// (Number of MIDI channels)
/// The MIDI 1 spec uses 16 channels.
pub channel_count: u32,

/// A vector which specifies which of the created channels (indexes) will be used for drums.
///
/// For example in a conventional 16 MIDI channel setup where channel 10 is used for
/// drums, the vector would be set as vec!\[9\] (counting from 0).
pub drums_channels: Vec<u32>,

/// Parameters of the output audio.
/// See the `AudioStreamParams` documentation for more information.
pub audio_params: AudioStreamParams,

/// Options about the `ChannelGroup` instance's parallelism. See the `ParallelismOptions`
/// documentation for more information.
pub parallelism: ParallelismOptions,
}
83 changes: 19 additions & 64 deletions core/src/channel_group/mod.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
use std::sync::Arc;

use crate::{
channel::{ChannelAudioEvent, ChannelEvent, ChannelInitOptions, VoiceChannel},
channel::{ChannelAudioEvent, ChannelEvent, VoiceChannel},
helpers::sum_simd,
AudioPipe, AudioStreamParams,
};

mod config;
pub use config::*;
mod events;
pub use events::*;
use rayon::prelude::*;
Expand All @@ -24,59 +26,6 @@ pub struct ChannelGroup {
audio_params: AudioStreamParams,
}

/// Options regarding which parts of the ChannelGroup should be multithreaded.
///
/// Responsibilities of a channel: processing input events for the channel, dispatching per-key rendering of audio, applying filters to the final channel's audio
/// Responsibilities of a key: Rendering per-voice audio for all the voices stored in that key for this channel. This is generally the most compute intensive part of the synth.
///
/// Best practices:
/// - As there are often 16 channels in MIDI, per-key multithreading can balance out the load more evenly between CPU cores.
/// - However, per-key multithreading adds some overhead, so if the synth is invoked to render very small sample counts each time (e.g. sub 1 millisecond), not using per-key multithreading becomes more efficient.
///
/// The following apply for all the values:
/// - A value of `None` means no multithreading.
/// - If the value is set to `Some(0)` then the number of threads will be
/// determined automatically by `rayon`. Please read
/// [this](https://docs.rs/rayon-core/1.11.0/rayon_core/struct.ThreadPoolBuilder.html#method.num_threads)
/// for more information.
#[derive(Clone)]
pub struct ParallelismOptions {
/// Render the MIDI channels parallel in a threadpool with the specified
/// thread count.
pub channel: Option<usize>,

/// Render the individisual keys of each channel parallel in a threadpool
/// with the specified thread count.
pub key: Option<usize>,
}

/// Options for initializing a new ChannelGroup.
#[derive(Clone)]
pub struct ChannelGroupConfig {
/// Channel initialization options (same for all channels).
/// See the `ChannelInitOptions` documentation for more information.
pub channel_init_options: ChannelInitOptions,

/// Amount of VoiceChannel objects to be created
/// (Number of MIDI channels)
/// The MIDI 1 spec uses 16 channels.
pub channel_count: u32,

/// A vector which specifies which of the created channels (indexes) will be used for drums.
///
/// For example in a conventional 16 MIDI channel setup where channel 10 is used for
/// drums, the vector would be set as vec!\[9\] (counting from 0).
pub drums_channels: Vec<u32>,

/// Parameters of the output audio.
/// See the `AudioStreamParams` documentation for more information.
pub audio_params: AudioStreamParams,

/// Options about the `ChannelGroup` instance's parallelism. See the `ParallelismOptions`
/// documentation for more information.
pub parallelism: ParallelismOptions,
}

impl ChannelGroup {
/// Creates a new ChannelGroup with the given configuration.
/// See the `ChannelGroupConfig` documentation for the available options.
Expand All @@ -86,22 +35,28 @@ impl ChannelGroup {
let mut sample_cache_vecs = Vec::new();

// Thread pool for individual channels to split between keys
let channel_pool = config.parallelism.key.map(|threads| {
Arc::new(
let channel_pool = match config.parallelism.channel {
ThreadCount::None => None,
ThreadCount::Auto => Some(Arc::new(rayon::ThreadPoolBuilder::new().build().unwrap())),
ThreadCount::Manual(threads) => Some(Arc::new(
rayon::ThreadPoolBuilder::new()
.num_threads(threads)
.build()
.unwrap(),
)
});
)),
};

// Thread pool for splitting channels between threads
let group_pool = config.parallelism.channel.map(|threads| {
rayon::ThreadPoolBuilder::new()
.num_threads(threads)
.build()
.unwrap()
});
let group_pool = match config.parallelism.key {
ThreadCount::None => None,
ThreadCount::Auto => Some(rayon::ThreadPoolBuilder::new().build().unwrap()),
ThreadCount::Manual(threads) => Some(
rayon::ThreadPoolBuilder::new()
.num_threads(threads)
.build()
.unwrap(),
),
};

for i in 0..config.channel_count {
let mut init = config.channel_init_options;
Expand Down
19 changes: 7 additions & 12 deletions realtime/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use std::ops::RangeInclusive;
pub use xsynth_core::channel::ChannelInitOptions;
pub use xsynth_core::{channel::ChannelInitOptions, channel_group::ThreadCount};

/// Options for initializing a new RealtimeSynth.
pub struct XSynthRealtimeConfig {
Expand All @@ -25,17 +25,12 @@ pub struct XSynthRealtimeConfig {
/// Default: `[9]`
pub drums_channels: Vec<u32>,

/// Controls the use a threadpool to render individual keys' voices.
/// When a value is set, the specified number of threads will be used
/// for that operation, while `None` means no per-key concurrency.
/// If the value is set to `Some(0)` then the number of threads will
/// be determined automatically by `rayon`.
/// Controls the multithreading used for rendering per-voice audio for all
/// the voices stored in a key for a channel. See the `ThreadCount` documentation
/// for the available options.
///
/// Regardless, each MIDI channel uses its own thread. This setting
/// adds more fine-grained threading per key rather than per channel.
///
/// Default: `None`
pub threadpool: Option<usize>,
/// Default: `ThreadCount::None`
pub multithreading: ThreadCount,

/// A range of velocities that will not be played.
///
Expand All @@ -50,7 +45,7 @@ impl Default for XSynthRealtimeConfig {
render_window_ms: 10.0,
channel_count: 16,
drums_channels: vec![9],
threadpool: None,
multithreading: ThreadCount::None,
ignore_range: 0..=0,
}
}
Expand Down
12 changes: 7 additions & 5 deletions realtime/src/realtime_synth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use xsynth_core::{
};

use crate::{
config::XSynthRealtimeConfig, util::ReadWriteAtomicU64, RealtimeEventSender, SynthEvent,
util::ReadWriteAtomicU64, RealtimeEventSender, SynthEvent, ThreadCount, XSynthRealtimeConfig,
};

/// Holds the statistics for an instance of RealtimeSynth.
Expand Down Expand Up @@ -137,14 +137,16 @@ impl RealtimeSynth {
let sample_rate = stream_config.sample_rate().0;
let stream_params = AudioStreamParams::new(sample_rate, stream_config.channels().into());

let pool = config.threadpool.map(|threads| {
Arc::new(
let pool = match config.multithreading {
ThreadCount::None => None,
ThreadCount::Auto => Some(Arc::new(rayon::ThreadPoolBuilder::new().build().unwrap())),
ThreadCount::Manual(threads) => Some(Arc::new(
rayon::ThreadPoolBuilder::new()
.num_threads(threads)
.build()
.unwrap(),
)
});
)),
};

let (output_sender, output_receiver) = bounded::<Vec<f32>>(config.channel_count as usize);

Expand Down

0 comments on commit 84ab795

Please sign in to comment.