From 84ab795ad63769a186e8cf4079e5dd7e9bea8754 Mon Sep 17 00:00:00 2001 From: MyBlackMIDIScore Date: Thu, 8 Aug 2024 14:28:46 +0300 Subject: [PATCH] Make requested changes --- core/src/channel_group/config.rs | 76 +++++++++++++++++++++++++++++ core/src/channel_group/mod.rs | 83 ++++++++------------------------ realtime/src/config.rs | 19 +++----- realtime/src/realtime_synth.rs | 12 +++-- 4 files changed, 109 insertions(+), 81 deletions(-) create mode 100644 core/src/channel_group/config.rs diff --git a/core/src/channel_group/config.rs b/core/src/channel_group/config.rs new file mode 100644 index 0000000..6c26e12 --- /dev/null +++ b/core/src/channel_group/config.rs @@ -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, + + /// 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, +} diff --git a/core/src/channel_group/mod.rs b/core/src/channel_group/mod.rs index c01654e..2eb5bac 100644 --- a/core/src/channel_group/mod.rs +++ b/core/src/channel_group/mod.rs @@ -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::*; @@ -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, - - /// Render the individisual keys of each channel parallel in a threadpool - /// with the specified thread count. - pub key: Option, -} - -/// 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, - - /// 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. @@ -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; diff --git a/realtime/src/config.rs b/realtime/src/config.rs index 6c40992..12c2b03 100644 --- a/realtime/src/config.rs +++ b/realtime/src/config.rs @@ -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 { @@ -25,17 +25,12 @@ pub struct XSynthRealtimeConfig { /// Default: `[9]` pub drums_channels: Vec, - /// 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, + /// Default: `ThreadCount::None` + pub multithreading: ThreadCount, /// A range of velocities that will not be played. /// @@ -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, } } diff --git a/realtime/src/realtime_synth.rs b/realtime/src/realtime_synth.rs index df3d240..a659e71 100644 --- a/realtime/src/realtime_synth.rs +++ b/realtime/src/realtime_synth.rs @@ -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. @@ -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::>(config.channel_count as usize);