Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change channel/synth configuration methods #93

Merged
merged 3 commits into from
Aug 21, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 13 additions & 10 deletions core/src/channel/channel_sf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,16 @@ use crate::{

use super::voice_spawner::VoiceSpawnerMatrix;

#[derive(Default, PartialEq, Eq, Clone, Copy, Debug)]
pub struct ProgramDescriptor {
pub bank: u8,
pub preset: u8,
}

pub struct ChannelSoundfont {
soundfonts: Vec<Arc<dyn SoundfontBase>>,
matrix: VoiceSpawnerMatrix,
curr_bank: u8,
curr_preset: u8,
curr_program: ProgramDescriptor,
}

impl Deref for ChannelSoundfont {
Expand All @@ -29,8 +34,7 @@ impl ChannelSoundfont {
ChannelSoundfont {
soundfonts: Vec::new(),
matrix: VoiceSpawnerMatrix::new(),
curr_bank: 0,
curr_preset: 0,
curr_program: Default::default(),
}
}

Expand All @@ -41,10 +45,9 @@ impl ChannelSoundfont {
}
}

pub fn change_program(&mut self, bank: u8, preset: u8) {
if self.curr_bank != bank || self.curr_preset != preset {
self.curr_bank = bank;
self.curr_preset = preset;
pub fn change_program(&mut self, program: ProgramDescriptor) {
if self.curr_program != program {
self.curr_program = program;
self.rebuild_matrix();
}
}
Expand All @@ -55,8 +58,8 @@ impl ChannelSoundfont {
// 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;
let bank = self.curr_program.bank;
let preset = self.curr_program.preset;

for k in 0..128u8 {
for v in 0..128u8 {
Expand Down
4 changes: 4 additions & 0 deletions core/src/channel/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ pub enum ChannelConfigEvent {

/// Sets the layer count for the soundfont
SetLayerCount(Option<usize>),

/// Controls whether the channel will be standard or percussion.
/// Setting to `true` will make the channel only use percussion patches.
SetPercussionMode(bool),
}

/// MIDI events for a channel.
Expand Down
32 changes: 6 additions & 26 deletions core/src/channel/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,10 @@ struct ControlEventData {
cutoff: Option<f32>,
resonance: Option<f32>,
expression: ValueLerp,
preset: u8,
bank: u8,
}

impl ControlEventData {
pub fn new_defaults(sample_rate: u32, drums_only: bool) -> Self {
pub fn new_defaults(sample_rate: u32) -> Self {
ControlEventData {
selected_lsb: -1,
selected_msb: -1,
Expand All @@ -114,8 +112,6 @@ impl ControlEventData {
cutoff: None,
resonance: None,
expression: ValueLerp::new(1.0, sample_rate),
preset: 0,
bank: if drums_only { 128 } else { 0 },
}
}
}
Expand All @@ -130,19 +126,13 @@ pub struct ChannelInitOptions {
///
/// Default: `false`
pub fade_out_killing: bool,

/// If set to true, the channel will only use drum patches.
///
/// Default: `false`
pub drums_only: bool,
}

#[allow(clippy::derivable_impls)]
impl Default for ChannelInitOptions {
fn default() -> Self {
Self {
fade_out_killing: false,
drums_only: false,
}
}
}
Expand Down Expand Up @@ -173,7 +163,6 @@ pub struct VoiceChannel {
threadpool: Option<Arc<rayon::ThreadPool>>,

stream_params: AudioStreamParams,
options: ChannelInitOptions,

/// The helper struct for keeping track of MIDI control event data
control_event_data: ControlEventData,
Expand Down Expand Up @@ -216,12 +205,8 @@ impl VoiceChannel {
threadpool,

stream_params,
options,

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

cutoff: MultiChannelBiQuad::new(
Expand Down Expand Up @@ -273,9 +258,7 @@ impl VoiceChannel {
}

fn push_key_events_and_render(&mut self, out: &mut [f32]) {
self.params
.channel_sf
.change_program(self.control_event_data.bank, self.control_event_data.preset);
self.params.load_program();

out.fill(0.0);
match self.threadpool.as_ref() {
Expand Down Expand Up @@ -332,9 +315,7 @@ impl VoiceChannel {
ControlEvent::Raw(controller, value) => match controller {
0x00 => {
// Bank select
if !self.options.drums_only {
self.control_event_data.bank = value;
}
self.params.set_bank(value);
}
0x64 => {
self.control_event_data.selected_lsb = value as i8;
Expand Down Expand Up @@ -563,7 +544,7 @@ impl VoiceChannel {
self.process_control_event(control);
}
ChannelAudioEvent::ProgramChange(preset) => {
self.control_event_data.preset = preset;
self.params.set_preset(preset);
}
},
ChannelEvent::Config(config) => self.params.process_config_event(config),
Expand All @@ -579,8 +560,7 @@ impl VoiceChannel {
}

fn reset_control(&mut self) {
self.control_event_data =
ControlEventData::new_defaults(self.stream_params.sample_rate, self.options.drums_only);
self.control_event_data = ControlEventData::new_defaults(self.stream_params.sample_rate);
self.voice_control_data = VoiceControlData::new_defaults();
self.process_event(ChannelEvent::Audio(ChannelAudioEvent::ProgramChange(0)));
self.propagate_voice_controls();
Expand Down
29 changes: 28 additions & 1 deletion core/src/channel/params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ use std::sync::{atomic::AtomicU64, Arc};

use crate::AudioStreamParams;

use super::{channel_sf::ChannelSoundfont, ChannelConfigEvent};
use super::{
channel_sf::{ChannelSoundfont, ProgramDescriptor},
ChannelConfigEvent,
};

/// Holds the statistics for an instance of VoiceChannel.
#[derive(Debug, Clone)]
Expand All @@ -24,6 +27,7 @@ pub struct VoiceChannelParams {
pub stats: VoiceChannelStats,
pub layers: Option<usize>,
pub channel_sf: ChannelSoundfont,
pub program: ProgramDescriptor,
pub constant: VoiceChannelConst,
}

Expand All @@ -48,6 +52,7 @@ impl VoiceChannelParams {
stats: VoiceChannelStats::new(),
layers: Some(4),
channel_sf,
program: Default::default(),
constant: VoiceChannelConst { stream_params },
}
}
Expand All @@ -60,8 +65,30 @@ impl VoiceChannelParams {
ChannelConfigEvent::SetLayerCount(count) => {
self.layers = count;
}
ChannelConfigEvent::SetPercussionMode(set) => {
if set {
self.program.bank = 128;
} else {
self.program.bank = 0;
}
self.channel_sf.change_program(self.program);
}
}
}

pub fn set_bank(&mut self, bank: u8) {
if self.program.bank != 128 {
self.program.bank = bank.min(127);
}
}

pub fn set_preset(&mut self, preset: u8) {
self.program.preset = preset.min(127);
}

pub fn load_program(&mut self) {
self.channel_sf.change_program(self.program);
}
}

impl VoiceChannelStatsReader {
Expand Down
12 changes: 3 additions & 9 deletions core/src/channel_group/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,17 +69,11 @@ pub struct ChannelGroupConfig {
/// 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.
/// Amount of VoiceChannel objects to be created (Number of MIDI channels).
/// The MIDI 1 spec uses 16 channels. If the channel count is 16 or
/// greater, then MIDI channel 10 will be set as the percussion channel.
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,
Expand Down
16 changes: 6 additions & 10 deletions core/src/channel_group/events.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
use crate::channel::{ChannelAudioEvent, ChannelConfigEvent};
use crate::channel::ChannelEvent;

/// Wrapper enum for various events to be sent to a MIDI synthesizer.
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum SynthEvent {
/// An audio event to be sent to the specified channel.
/// See `ChannelAudioEvent` documentation for more information.
Channel(u32, ChannelAudioEvent),
/// A channel event to be sent to the specified channel.
/// See `ChannelEvent` documentation for more information.
Channel(u32, ChannelEvent),

/// An audio event to be sent to all available channels.
/// A channel event to be sent to all available channels.
/// See `ChannelAudioEvent` documentation for more information.
AllChannels(ChannelAudioEvent),

/// Configuration event for all channels.
/// See `ChannelConfigEvent` documentation for more information.
ChannelConfig(ChannelConfigEvent),
AllChannels(ChannelEvent),
}
56 changes: 32 additions & 24 deletions core/src/channel_group/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::sync::Arc;

use crate::{
channel::{ChannelAudioEvent, ChannelEvent, VoiceChannel},
channel::{ChannelAudioEvent, ChannelConfigEvent, ChannelEvent, VoiceChannel},
helpers::{prepapre_cache_vec, sum_simd},
AudioPipe, AudioStreamParams,
};
Expand Down Expand Up @@ -59,19 +59,22 @@ impl ChannelGroup {
),
};

for i in 0..config.channel_count {
let mut init = config.channel_init_options;
init.drums_only = config.drums_channels.clone().into_iter().any(|c| c == i);

for _ in 0..config.channel_count {
channels.push(VoiceChannel::new(
init,
config.channel_init_options,
config.audio_params,
channel_pool.clone(),
));
channel_events_cache.push(Vec::new());
sample_cache_vecs.push(Vec::new());
}

if config.channel_count >= 16 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just for later, but should we make this optional in the future? Just a config flag or something if it's not too messy

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean we could, although the user always will have the option to send a config event to channel 10 to make it standard if they wish.

One idea would also be to manage the channel count with a type or something

enum SynthFormat {
    // Will create 16 channels, with 10 being drums
    Midi,

    // Probably other formats here? Idk what else there is

    // Will create the amount of channels without setting any to drums
    // so the user can do it themselves
    Custom { channels: u32 }.
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that works

channels[9].push_events_iter(std::iter::once(ChannelEvent::Config(
ChannelConfigEvent::SetPercussionMode(true),
)))
}

Self {
thread_pool: group_pool,
cached_event_count: 0,
Expand All @@ -86,27 +89,32 @@ impl ChannelGroup {
/// See the `SynthEvent` documentation for more information.
pub fn send_event(&mut self, event: SynthEvent) {
match event {
SynthEvent::Channel(channel, event) => {
self.channel_events_cache[channel as usize].push(event);
self.cached_event_count += 1;
if self.cached_event_count > MAX_EVENT_CACHE_SIZE {
self.flush_events();
}
}
SynthEvent::AllChannels(event) => {
for channel in self.channel_events_cache.iter_mut() {
channel.push(event);
SynthEvent::Channel(channel, event) => match event {
ChannelEvent::Audio(e) => {
self.channel_events_cache[channel as usize].push(e);
self.cached_event_count += 1;
if self.cached_event_count > MAX_EVENT_CACHE_SIZE {
self.flush_events();
}
}
self.cached_event_count += self.channel_events_cache.len() as u32;
if self.cached_event_count > MAX_EVENT_CACHE_SIZE {
self.flush_events();
ChannelEvent::Config(_) => self.channels[channel as usize].process_event(event),
},
SynthEvent::AllChannels(event) => match event {
ChannelEvent::Audio(e) => {
for channel in self.channel_events_cache.iter_mut() {
channel.push(e);
}
self.cached_event_count += self.channel_events_cache.len() as u32;
if self.cached_event_count > MAX_EVENT_CACHE_SIZE {
self.flush_events();
}
}
}
SynthEvent::ChannelConfig(config) => {
for channel in self.channels.iter_mut() {
channel.process_event(ChannelEvent::Config(config.clone()));
ChannelEvent::Config(_) => {
for channel in self.channels.iter_mut() {
channel.process_event(event.clone());
}
}
}
},
}
}

Expand Down
6 changes: 3 additions & 3 deletions realtime/examples/bench.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use std::time::{Duration, Instant};
use xsynth_core::channel::ChannelAudioEvent;
use xsynth_core::channel::{ChannelAudioEvent, ChannelEvent};

use xsynth_realtime::{RealtimeSynth, SynthEvent};

Expand All @@ -12,13 +12,13 @@ fn main() {
for _ in 0..100 {
synth.send_event(SynthEvent::Channel(
0,
ChannelAudioEvent::NoteOn { key: 0, vel: 5 },
ChannelEvent::Audio(ChannelAudioEvent::NoteOn { key: 0, vel: 5 }),
));
}
for _ in 0..100 {
synth.send_event(SynthEvent::Channel(
0,
ChannelAudioEvent::NoteOff { key: 0 },
ChannelEvent::Audio(ChannelAudioEvent::NoteOff { key: 0 }),
));
}
}
Expand Down
Loading
Loading