Skip to content

Commit

Permalink
Add documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
MyBlackMIDIScore committed Jul 31, 2024
1 parent 3385cd0 commit 0ca32c3
Show file tree
Hide file tree
Showing 31 changed files with 439 additions and 67 deletions.
14 changes: 11 additions & 3 deletions core/src/audio_pipe.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
use crate::AudioStreamParams;

/// An object to read audio samples from.
pub trait AudioPipe {
/// The stream parameters of the audio pipe
/// The audio stream parameters of the audio pipe.
fn stream_params(&self) -> &'_ AudioStreamParams;

/// Reads samples from the pipe
/// Reads samples from the pipe.
///
/// When using in a MIDI synthesizer, the amount of samples determines the
/// time of the current active MIDI events. For example if we send a note
/// on event and read 44100 samples (with a 44.1kHz sample rate), then the
/// note will be audible for 1 second. If after reading those samples we
/// send a note off event for the same key, then on the next read the key
/// will be released. If we don't, then the note will keep playing.
fn read_samples(&mut self, to: &mut [f32]) {
assert!(to.len() as u32 % self.stream_params().channels.count() as u32 == 0);
self.read_samples_unchecked(to);
}

/// Reads samples from the pipe without checking the channel count of the output
/// Reads samples from the pipe without checking the channel count of the output.
fn read_samples_unchecked(&mut self, to: &mut [f32]);
}

Expand Down
2 changes: 2 additions & 0 deletions core/src/audio_stream.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/// Number of audio channels.
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum ChannelCount {
Mono,
Expand Down Expand Up @@ -28,6 +29,7 @@ impl From<u16> for ChannelCount {
}
}

/// Parameters of the output audio.
#[derive(Debug, Clone, Copy)]
pub struct AudioStreamParams {
pub sample_rate: u32,
Expand Down
28 changes: 20 additions & 8 deletions core/src/buffered_renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,53 +14,57 @@ use crate::AudioStreamParams;

use super::AudioPipe;

/// Holds the statistics for an instance of BufferedRenderer.
#[derive(Debug, Clone)]
pub struct BufferedRendererStats {
/// The number of samples currently buffered.
/// Can be negative if the reader is waiting for more samples.
struct BufferedRendererStats {
samples: Arc<AtomicI64>,

/// The number of samples that were in the buffer after the last read.
last_samples_after_read: Arc<AtomicI64>,

/// The last number of samples last requested by the read command.
last_request_samples: Arc<AtomicI64>,

/// The last 100 render time percentages (0 to 1)
/// of how long the render thread spent rendering, from the max allowed time.
render_time: Arc<RwLock<VecDeque<f64>>>,

/// The number of samples to render each iteration
render_size: Arc<AtomicUsize>,
}

/// Reads the statistics of an instance of BufferedRenderer in a usable way.
pub struct BufferedRendererStatsReader {
stats: BufferedRendererStats,
}

impl BufferedRendererStatsReader {
/// The number of samples currently buffered.
/// Can be negative if the reader is waiting for more samples.
pub fn samples(&self) -> i64 {
self.stats.samples.load(Ordering::Relaxed)
}

/// The number of samples that were in the buffer after the last read.
pub fn last_samples_after_read(&self) -> i64 {
self.stats.last_samples_after_read.load(Ordering::Relaxed)
}

/// The last number of samples last requested by the read command.
pub fn last_request_samples(&self) -> i64 {
self.stats.last_request_samples.load(Ordering::Relaxed)
}

/// The number of samples to render each iteration.
pub fn render_size(&self) -> usize {
self.stats.render_size.load(Ordering::Relaxed)
}

/// The average render time percentages (0 to 1)
/// of how long the render thread spent rendering, from the max allowed time.
pub fn average_renderer_load(&self) -> f64 {
let queue = self.stats.render_time.read().unwrap();
let total = queue.len();
queue.iter().sum::<f64>() / total as f64
}

/// The last render time percentage (0 to 1)
/// of how long the render thread spent rendering, from the max allowed time.
pub fn last_renderer_load(&self) -> f64 {
let queue = self.stats.render_time.read().unwrap();
*queue.front().unwrap_or(&0.0)
Expand All @@ -69,6 +73,7 @@ impl BufferedRendererStatsReader {

/// The helper struct for deferred sample rendering.
/// Helps avoid stutter when the render time is exceding the max time allowed by the audio driver.
///
/// Instead, it renders in a separate thread with much smaller sample sizes, causing a minimal impact on latency
/// while allowing more time to render per sample.
///
Expand All @@ -84,13 +89,20 @@ pub struct BufferedRenderer {

/// Whether the render thread should be killed.
killed: Arc<RwLock<bool>>,

/// The thread handle to wait for at the end.
thread_handle: Option<JoinHandle<()>>,

stream_params: AudioStreamParams,
}

impl BufferedRenderer {
/// Creates a new instance of BufferedRenderer
///
/// - `render`: An object implementing the AudioPipe struct for BufferedRenderer to
/// read samples from
/// - `stream_params`: Parameters of the output audio
/// - `render_size`: The number of samples to render each iteration
pub fn new<F: 'static + AudioPipe + Send>(
mut render: F,
stream_params: AudioStreamParams,
Expand Down
22 changes: 19 additions & 3 deletions core/src/channel/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,55 +2,71 @@ use std::sync::Arc;

use crate::soundfont::SoundfontBase;

/// MIDI events for a single key in a channel.
#[derive(Debug, Clone)]
pub enum KeyNoteEvent {
/// Starts a new note voice with a velocity
On(u8),

/// Signals off to a note voice
Off,

/// Signals off to all note voices
AllOff,

/// Kills all note voices without decay
AllKilled,
}

/// Events to modify parameters of a channel.
#[derive(Debug, Clone)]
pub enum ChannelConfigEvent {
/// Sets the soundfonts for the channel
SetSoundfonts(Vec<Arc<dyn SoundfontBase>>),

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

/// MIDI events for a channel.
#[derive(Debug, Clone)]
pub enum ChannelAudioEvent {
/// Starts a new note vocice
/// Starts a new note voice
NoteOn { key: u8, vel: u8 },

/// Signals off to a note voice
NoteOff { key: u8 },

/// Signal off to all voices
AllNotesOff,

/// Kill all voices without decay
AllNotesKilled,

/// Resets all CC to their default values
ResetControl,

/// Control event for the channel
Control(ControlEvent),

/// Program change event
ProgramChange(u8),
}

/// Wrapper enum for various events for a channel.
#[derive(Debug, Clone)]
pub enum ChannelEvent {
/// Audio
/// Audio event
Audio(ChannelAudioEvent),

/// Config event for the channel
/// Configuration event for the channel
Config(ChannelConfigEvent),
}

/// MIDI control events for a channel.
#[derive(Debug, Clone)]
pub enum ControlEvent {
/// A raw control change event
Raw(u8, u8),

/// The pitch bend strength, in tones
Expand Down
33 changes: 27 additions & 6 deletions core/src/channel/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,7 @@ use crate::{

use soundfonts::FilterType;

use self::{
key::KeyData,
params::{VoiceChannelParams, VoiceChannelStatsReader},
};
use self::{key::KeyData, params::VoiceChannelParams};

use super::AudioPipe;

Expand All @@ -29,7 +26,9 @@ mod voice_spawner;
mod event;
pub use event::*;

pub struct ValueLerp {
pub use params::VoiceChannelStatsReader;

pub(crate) struct ValueLerp {
lerp_length: f32,
step: f32,
current: f32,
Expand Down Expand Up @@ -121,9 +120,18 @@ impl ControlEventData {
}
}

/// Options for initializing a new VoiceChannel.
#[derive(Debug, Clone, Copy)]
pub struct ChannelInitOptions {
/// If set to true, the voices killed due to the voice limit will fade out.
/// If set to false, they will be killed immediately, usually causing clicking.
///
/// Default: `false`
pub fade_out_killing: bool,

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

Expand All @@ -137,6 +145,9 @@ impl Default for ChannelInitOptions {
}
}

/// Represents a single MIDI channel within XSynth.
///
/// Keeps track and manages MIDI events and the active voices of a channel.
pub struct VoiceChannel {
key_voices: Vec<Key>,

Expand All @@ -152,11 +163,17 @@ pub struct VoiceChannel {
/// Processed control data, ready to feed to voices
voice_control_data: VoiceControlData,

// Effects
/// Effects
cutoff: MultiChannelBiQuad,
}

impl VoiceChannel {
/// Initializes a new voice channel.
///
/// - `options`: Channel configuration
/// - `stream_params`: Parameters of the output audio
/// - `threadpool`: The thread-pool that will be used to render the voices concurrently.
/// If None is used, the voices of the channel will be rendered successively.
pub fn new(
options: ChannelInitOptions,
stream_params: AudioStreamParams,
Expand Down Expand Up @@ -292,6 +309,7 @@ impl VoiceChannel {
}
}

/// Sends a ControlEvent to the channel.
pub fn process_control_event(&mut self, event: ControlEvent) {
match event {
ControlEvent::Raw(controller, value) => match controller {
Expand Down Expand Up @@ -490,10 +508,12 @@ impl VoiceChannel {
self.propagate_voice_controls();
}

/// Sends a ChannelEvent to the channel.
pub fn process_event(&mut self, event: ChannelEvent) {
self.push_events_iter(std::iter::once(event));
}

/// Sends multiple ChannelEvent items to the channel as an iterator.
pub fn push_events_iter<T: Iterator<Item = ChannelEvent>>(&mut self, iter: T) {
for e in iter {
match e {
Expand Down Expand Up @@ -541,6 +561,7 @@ impl VoiceChannel {
}
}

/// Returns a reader for the VoiceChannel statistics.
pub fn get_channel_stats(&self) -> VoiceChannelStatsReader {
let stats = self.params.stats.clone();
VoiceChannelStatsReader::new(stats)
Expand Down
5 changes: 4 additions & 1 deletion core/src/channel/params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ use crate::AudioStreamParams;

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

/// Holds the statistics for an instance of VoiceChannel.
#[derive(Debug, Clone)]
pub struct VoiceChannelStats {
pub(super) voice_counter: Arc<AtomicU64>,
}

/// Reads the statistics of an instance of VoiceChannel in a usable way.
pub struct VoiceChannelStatsReader {
stats: VoiceChannelStats,
}
Expand Down Expand Up @@ -63,10 +65,11 @@ impl VoiceChannelParams {
}

impl VoiceChannelStatsReader {
pub fn new(stats: VoiceChannelStats) -> Self {
pub(super) fn new(stats: VoiceChannelStats) -> Self {
Self { stats }
}

/// The active voice count of the VoiceChannel
pub fn voice_count(&self) -> u64 {
self.stats
.voice_counter
Expand Down
6 changes: 6 additions & 0 deletions core/src/channel_group/events.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
use crate::channel::{ChannelAudioEvent, ChannelConfigEvent};

/// Wrapper enum for various events to be sent to a MIDI synthesizer.
pub enum SynthEvent {
/// An audio event to be sent to the specified channel
Channel(u32, ChannelAudioEvent),

/// An audio event to be sent to all available channels
AllChannels(ChannelAudioEvent),

/// Configuration event for all channels
ChannelConfig(ChannelConfigEvent),
}
Loading

0 comments on commit 0ca32c3

Please sign in to comment.