From 65b57c8758ccbb639d9da19014681c59a6c3a9e5 Mon Sep 17 00:00:00 2001 From: Micha Hanselmann Date: Sun, 21 Aug 2022 14:12:14 +0200 Subject: [PATCH 1/8] compiles but somewhat broken --- src/main.rs | 11 +-- src/processing.rs | 2 + src/processing/sampler.rs | 89 ++++++++++++++++++++++ src/processing/sampler/oscillator_sound.rs | 17 +++++ src/processing/sampler/oscillator_voice.rs | 67 ++++++++++++++++ src/processing/sampler/sampler_sound.rs | 2 + src/processing/sampler/sampler_voice.rs | 19 +++++ 7 files changed, 202 insertions(+), 5 deletions(-) create mode 100644 src/processing/sampler.rs create mode 100644 src/processing/sampler/oscillator_sound.rs create mode 100644 src/processing/sampler/oscillator_voice.rs create mode 100644 src/processing/sampler/sampler_sound.rs create mode 100644 src/processing/sampler/sampler_voice.rs diff --git a/src/main.rs b/src/main.rs index e48b8b6..7d2ddb5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,12 +2,13 @@ mod base; mod engine; mod processing; -use processing::Sine; +use processing::{Sampler, OscillatorSound, OscillatorVoice}; fn main() { - let sine = Sine::new(); - let (processor, proxy) = engine::CpalProcessor::new(Box::new(sine)); - let _audio_engine = engine::CpalAudioEngine::new(processor); - let _midi_engine = engine::MidirMidiEngine::new(proxy); + // let sampler = Sampler::>::new(); + // let (processor, proxy) = engine::CpalProcessor::new(Box::new(sampler)); + // let _audio_engine = engine::CpalAudioEngine::new(processor); + // let _midi_engine = engine::MidirMidiEngine::new(proxy); + std::thread::park(); } diff --git a/src/processing.rs b/src/processing.rs index d099502..4ae6136 100644 --- a/src/processing.rs +++ b/src/processing.rs @@ -1,3 +1,5 @@ +mod sampler; mod sine; +pub use sampler::{Sampler, OscillatorSound, OscillatorVoice}; pub use sine::Sine; diff --git a/src/processing/sampler.rs b/src/processing/sampler.rs new file mode 100644 index 0000000..ddb5eb3 --- /dev/null +++ b/src/processing/sampler.rs @@ -0,0 +1,89 @@ +mod oscillator_sound; +mod oscillator_voice; +mod sampler_sound; +mod sampler_voice; + +use crate::base::{MidiMessage, AudioProcessor, Parameter, ParameterId, ParameterValue, MidiReceiver}; +pub use oscillator_sound::OscillatorSound; +pub use oscillator_voice::OscillatorVoice; +pub use sampler_sound::SamplerSound; +pub use sampler_voice::SamplerVoice; + +/// Sampler instrument processor. +pub struct Sampler +where + Sound: SamplerSound, + Voice: for<'a> SamplerVoice<'a, Sound>, +{ + /// Sampler sounds. + sounds: Vec, + + /// Sampler voices. + voices: Vec, +} +impl SamplerVoice<'a, S>> Sampler { + /// Creates new sampler. + pub fn new() -> Self { + Sampler { + sounds: Vec::new(), + voices: Vec::new(), + } + } + + /// Adds a sound. + pub fn add_sound(&mut self, sound: S) { + self.sounds.push(sound); + } + + /// Adds a voice. + pub fn add_voice(&mut self, voice: V) { + self.voices.push(voice); + } + + /// Note off (usually triggered by a midi message). + fn note_off(&mut self, midi_channel: u8, midi_note: u8, velocity: u8) { + + } + + /// Note on (usually triggered by a midi message). + fn note_on(&mut self, midi_channel: u8, midi_note: u8, velocity: u8) { + let voice = self.voices.first_mut().unwrap(); + let sound = self.sounds.first().unwrap(); + voice.start_note(midi_note, velocity as f32 / 127.0, sound); + } +} +impl SamplerVoice<'a, S>> AudioProcessor for Sampler { + fn get_parameter(&self, id: ParameterId) -> Option { + todo!() + } + + fn list_parameters(&self) -> &[Parameter] { + todo!() + } + + fn process(&mut self, buffer: &mut [f32]) { + // Render voices. + self.voices.iter_mut().for_each(|voice| voice.render(buffer)); + } + + fn reset(&mut self, sample_rate: f32, max_buffer_size: usize) { + // Reset voices. + self.voices.iter_mut().for_each(|voice| voice.reset(sample_rate, max_buffer_size)); + } + + fn set_channel_layout(&mut self, input_channels: u16, output_channels: u16) { + todo!() + } + + fn set_parameter(&mut self, id: ParameterId, value: ParameterValue) { + todo!() + } +} +impl SamplerVoice<'a, S>> MidiReceiver for Sampler { + fn handle_midi_message(&mut self, message: MidiMessage) { + match message { + MidiMessage::NoteOff(channel, note, velocity) => self.note_off(channel, note, velocity), + MidiMessage::NoteOn(channel, note, velocity) => self.note_on(channel, note, velocity), + } + } +} diff --git a/src/processing/sampler/oscillator_sound.rs b/src/processing/sampler/oscillator_sound.rs new file mode 100644 index 0000000..19df911 --- /dev/null +++ b/src/processing/sampler/oscillator_sound.rs @@ -0,0 +1,17 @@ +use super::SamplerSound; + +/// Oscillator sound for sampler. +pub struct OscillatorSound {} +impl OscillatorSound { + /// Creates new oscillator sound. + pub fn new() -> Self { + OscillatorSound {} + } + + /// Returns sample value depending on oscillator mode. + #[inline(always)] + pub fn get_value(&self, phase: f32) -> f32 { + f32::sin(phase) // Only sine waves for now. + } +} +impl SamplerSound for OscillatorSound {} diff --git a/src/processing/sampler/oscillator_voice.rs b/src/processing/sampler/oscillator_voice.rs new file mode 100644 index 0000000..54df641 --- /dev/null +++ b/src/processing/sampler/oscillator_voice.rs @@ -0,0 +1,67 @@ +use super::{SamplerVoice, OscillatorSound}; +use std::f32::consts::PI; + +/// Oscillator voice for sampler. +pub struct OscillatorVoice<'a> { + /// Sound that is currently playing. + active_sound: Option<&'a OscillatorSound>, + + /// Gain applied to sound. + gain: f32, + + /// Phase, used for internal processing. + phase: f32, + + /// Precalculated phase increment per sample, used for internal processing. + phase_increment: f32, + + /// Sample rate in Hz. + sample_rate: f32, +} +impl<'a> OscillatorVoice<'a> { + /// Creates new oscillator voice. + pub fn new(channel_count: u16) -> Self { + OscillatorVoice { + active_sound: None, + gain: 0.0, + phase: 0.0, + phase_increment: 0.0, + sample_rate: 44100.0, + } + } + + /// Calculates phase increment. + fn update_phase_increment(&mut self, frequency: f32) { + self.phase_increment = 2.0 * PI * frequency / self.sample_rate; + } +} +impl<'a> SamplerVoice<'a, OscillatorSound> for OscillatorVoice<'a> { + fn is_playing(&self) -> bool { + self.active_sound.is_some() + } + + fn render(&mut self, buffer: &mut [f32]) { + if let Some(sound) = self.active_sound { + for frame in buffer.chunks_mut(2) { // TODO: Support other channel configs. + frame.fill(sound.get_value(self.phase) * self.gain); + self.phase += self.phase_increment; + while self.phase >= 2.0 * PI { self.phase -= 2.0 * PI } + } + } + } + + fn reset(&mut self, sample_rate: f32, _max_buffer_size: usize) { + self.active_sound = None; + self.sample_rate = sample_rate; + // Other parameters will be reset on note start. + } + + fn start_note(&mut self, midi_note: u8, velocity: f32, sound: &'a OscillatorSound) { + let frequency = 440.0 * f32::powf(2.0, (midi_note as f32 - 69.0) / 12.0); + self.active_sound = Some(sound); + } + + fn stop_note(&mut self, velocity: f32, allow_tail: bool) { + todo!() + } +} diff --git a/src/processing/sampler/sampler_sound.rs b/src/processing/sampler/sampler_sound.rs new file mode 100644 index 0000000..14bb586 --- /dev/null +++ b/src/processing/sampler/sampler_sound.rs @@ -0,0 +1,2 @@ +/// Defines a sound that can be played by a sampler voice, e.g. an oscillator config or audio file data. +pub trait SamplerSound: Send {} diff --git a/src/processing/sampler/sampler_voice.rs b/src/processing/sampler/sampler_voice.rs new file mode 100644 index 0000000..9406f5e --- /dev/null +++ b/src/processing/sampler/sampler_voice.rs @@ -0,0 +1,19 @@ +use super::SamplerSound; + +/// Defines a voice that a sampler can use to play a sampler sound. +pub trait SamplerVoice<'a, Sound: SamplerSound>: Send { + /// Returns whether voice is currently in use. + fn is_playing(&self) -> bool; + + /// Renders audio samples from sound into buffer (additive). + fn render(&mut self, buffer: &mut [f32]); + + /// Resets internal parameters of the voice. + fn reset(&mut self, sample_rate: f32, max_buffer_size: usize); + + /// Plays a note on this voice. + fn start_note(&mut self, midi_note: u8, velocity: f32, sound: &'a Sound); + + /// Stops note. + fn stop_note(&mut self, velocity: f32, allow_tail: bool); +} From 326f65d396100c92ed975126216d0f7aa96bfb99 Mon Sep 17 00:00:00 2001 From: Micha Hanselmann Date: Sun, 21 Aug 2022 14:53:56 +0200 Subject: [PATCH 2/8] compiles with arc --- src/main.rs | 8 ++++---- src/processing/sampler.rs | 16 +++++++++------- src/processing/sampler/oscillator_voice.rs | 14 +++++++------- src/processing/sampler/sampler_sound.rs | 2 +- src/processing/sampler/sampler_voice.rs | 6 ++++-- 5 files changed, 25 insertions(+), 21 deletions(-) diff --git a/src/main.rs b/src/main.rs index 7d2ddb5..2783d91 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,10 +5,10 @@ mod processing; use processing::{Sampler, OscillatorSound, OscillatorVoice}; fn main() { - // let sampler = Sampler::>::new(); - // let (processor, proxy) = engine::CpalProcessor::new(Box::new(sampler)); - // let _audio_engine = engine::CpalAudioEngine::new(processor); - // let _midi_engine = engine::MidirMidiEngine::new(proxy); + let sampler: Sampler = Sampler::new(); + let (processor, proxy) = engine::CpalProcessor::new(Box::new(sampler)); + let _audio_engine = engine::CpalAudioEngine::new(processor); + let _midi_engine = engine::MidirMidiEngine::new(proxy); std::thread::park(); } diff --git a/src/processing/sampler.rs b/src/processing/sampler.rs index ddb5eb3..427df9f 100644 --- a/src/processing/sampler.rs +++ b/src/processing/sampler.rs @@ -3,6 +3,8 @@ mod oscillator_voice; mod sampler_sound; mod sampler_voice; +use std::{cell::RefCell, sync::Arc}; + use crate::base::{MidiMessage, AudioProcessor, Parameter, ParameterId, ParameterValue, MidiReceiver}; pub use oscillator_sound::OscillatorSound; pub use oscillator_voice::OscillatorVoice; @@ -13,15 +15,15 @@ pub use sampler_voice::SamplerVoice; pub struct Sampler where Sound: SamplerSound, - Voice: for<'a> SamplerVoice<'a, Sound>, + Voice: SamplerVoice, { /// Sampler sounds. - sounds: Vec, + sounds: Vec>, /// Sampler voices. voices: Vec, } -impl SamplerVoice<'a, S>> Sampler { +impl> Sampler { /// Creates new sampler. pub fn new() -> Self { Sampler { @@ -32,7 +34,7 @@ impl SamplerVoice<'a, S>> Sampler { /// Adds a sound. pub fn add_sound(&mut self, sound: S) { - self.sounds.push(sound); + self.sounds.push(Arc::new(sound)); } /// Adds a voice. @@ -49,10 +51,10 @@ impl SamplerVoice<'a, S>> Sampler { fn note_on(&mut self, midi_channel: u8, midi_note: u8, velocity: u8) { let voice = self.voices.first_mut().unwrap(); let sound = self.sounds.first().unwrap(); - voice.start_note(midi_note, velocity as f32 / 127.0, sound); + voice.start_note(midi_note, velocity as f32 / 127.0, sound.clone()); } } -impl SamplerVoice<'a, S>> AudioProcessor for Sampler { +impl> AudioProcessor for Sampler { fn get_parameter(&self, id: ParameterId) -> Option { todo!() } @@ -79,7 +81,7 @@ impl SamplerVoice<'a, S>> AudioProcessor for Sampler todo!() } } -impl SamplerVoice<'a, S>> MidiReceiver for Sampler { +impl> MidiReceiver for Sampler { fn handle_midi_message(&mut self, message: MidiMessage) { match message { MidiMessage::NoteOff(channel, note, velocity) => self.note_off(channel, note, velocity), diff --git a/src/processing/sampler/oscillator_voice.rs b/src/processing/sampler/oscillator_voice.rs index 54df641..15631b0 100644 --- a/src/processing/sampler/oscillator_voice.rs +++ b/src/processing/sampler/oscillator_voice.rs @@ -1,10 +1,10 @@ use super::{SamplerVoice, OscillatorSound}; -use std::f32::consts::PI; +use std::{f32::consts::PI, cell::{RefCell, Ref}, borrow::Borrow, sync::Arc}; /// Oscillator voice for sampler. -pub struct OscillatorVoice<'a> { +pub struct OscillatorVoice { /// Sound that is currently playing. - active_sound: Option<&'a OscillatorSound>, + active_sound: Option>, /// Gain applied to sound. gain: f32, @@ -18,7 +18,7 @@ pub struct OscillatorVoice<'a> { /// Sample rate in Hz. sample_rate: f32, } -impl<'a> OscillatorVoice<'a> { +impl OscillatorVoice { /// Creates new oscillator voice. pub fn new(channel_count: u16) -> Self { OscillatorVoice { @@ -35,13 +35,13 @@ impl<'a> OscillatorVoice<'a> { self.phase_increment = 2.0 * PI * frequency / self.sample_rate; } } -impl<'a> SamplerVoice<'a, OscillatorSound> for OscillatorVoice<'a> { +impl SamplerVoice for OscillatorVoice { fn is_playing(&self) -> bool { self.active_sound.is_some() } fn render(&mut self, buffer: &mut [f32]) { - if let Some(sound) = self.active_sound { + if let Some(sound) = &self.active_sound { for frame in buffer.chunks_mut(2) { // TODO: Support other channel configs. frame.fill(sound.get_value(self.phase) * self.gain); self.phase += self.phase_increment; @@ -56,7 +56,7 @@ impl<'a> SamplerVoice<'a, OscillatorSound> for OscillatorVoice<'a> { // Other parameters will be reset on note start. } - fn start_note(&mut self, midi_note: u8, velocity: f32, sound: &'a OscillatorSound) { + fn start_note(&mut self, midi_note: u8, velocity: f32, sound: Arc) { let frequency = 440.0 * f32::powf(2.0, (midi_note as f32 - 69.0) / 12.0); self.active_sound = Some(sound); } diff --git a/src/processing/sampler/sampler_sound.rs b/src/processing/sampler/sampler_sound.rs index 14bb586..87190fd 100644 --- a/src/processing/sampler/sampler_sound.rs +++ b/src/processing/sampler/sampler_sound.rs @@ -1,2 +1,2 @@ /// Defines a sound that can be played by a sampler voice, e.g. an oscillator config or audio file data. -pub trait SamplerSound: Send {} +pub trait SamplerSound: Send + Sync {} diff --git a/src/processing/sampler/sampler_voice.rs b/src/processing/sampler/sampler_voice.rs index 9406f5e..637e436 100644 --- a/src/processing/sampler/sampler_voice.rs +++ b/src/processing/sampler/sampler_voice.rs @@ -1,7 +1,9 @@ +use std::{cell::{RefCell, Ref}, sync::Arc}; + use super::SamplerSound; /// Defines a voice that a sampler can use to play a sampler sound. -pub trait SamplerVoice<'a, Sound: SamplerSound>: Send { +pub trait SamplerVoice: Send { /// Returns whether voice is currently in use. fn is_playing(&self) -> bool; @@ -12,7 +14,7 @@ pub trait SamplerVoice<'a, Sound: SamplerSound>: Send { fn reset(&mut self, sample_rate: f32, max_buffer_size: usize); /// Plays a note on this voice. - fn start_note(&mut self, midi_note: u8, velocity: f32, sound: &'a Sound); + fn start_note(&mut self, midi_note: u8, velocity: f32, sound: Arc); /// Stops note. fn stop_note(&mut self, velocity: f32, allow_tail: bool); From f0d4d00de417edbc77556daef2e7af5088eda21f Mon Sep 17 00:00:00 2001 From: Micha Hanselmann Date: Sun, 21 Aug 2022 15:50:29 +0200 Subject: [PATCH 3/8] some sound --- src/main.rs | 8 ++++++- src/processing/sampler.rs | 27 +++++++++++++++------- src/processing/sampler/oscillator_voice.rs | 19 ++++++++++----- src/processing/sampler/sampler_voice.rs | 3 +++ 4 files changed, 42 insertions(+), 15 deletions(-) diff --git a/src/main.rs b/src/main.rs index 2783d91..f7c6521 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,13 @@ mod processing; use processing::{Sampler, OscillatorSound, OscillatorVoice}; fn main() { - let sampler: Sampler = Sampler::new(); + let mut sampler: Sampler = Sampler::new(); + + for _ in 1..10 { + sampler.add_voice(OscillatorVoice::new()); + } + sampler.add_sound(OscillatorSound::new()); + let (processor, proxy) = engine::CpalProcessor::new(Box::new(sampler)); let _audio_engine = engine::CpalAudioEngine::new(processor); let _midi_engine = engine::MidirMidiEngine::new(proxy); diff --git a/src/processing/sampler.rs b/src/processing/sampler.rs index 427df9f..3fc13b6 100644 --- a/src/processing/sampler.rs +++ b/src/processing/sampler.rs @@ -42,16 +42,21 @@ impl> Sampler { self.voices.push(voice); } - /// Note off (usually triggered by a midi message). + /// Note off (usually triggered by a MIDI message). fn note_off(&mut self, midi_channel: u8, midi_note: u8, velocity: u8) { - + self.voices.iter_mut() + .filter(|voice| voice.get_active_note() == Some(midi_note)) + .for_each(|voice| voice.stop_note(velocity as f32 / 127.0, true)); } - /// Note on (usually triggered by a midi message). + /// Note on (usually triggered by a MIDI message). fn note_on(&mut self, midi_channel: u8, midi_note: u8, velocity: u8) { - let voice = self.voices.first_mut().unwrap(); - let sound = self.sounds.first().unwrap(); - voice.start_note(midi_note, velocity as f32 / 127.0, sound.clone()); + // Find free voice. + let voice = self.voices.iter_mut().find(|voice| !voice.is_playing()); + if let Some(voice) = voice { + let sound = self.sounds.first().unwrap().clone(); + voice.start_note(midi_note, velocity as f32 / 127.0, sound); + } } } impl> AudioProcessor for Sampler { @@ -60,12 +65,18 @@ impl> AudioProcessor for Sampler { } fn list_parameters(&self) -> &[Parameter] { - todo!() + const P: [Parameter; 0] = []; + &P } fn process(&mut self, buffer: &mut [f32]) { + buffer.fill(0.0); // Clean state. + // Render voices. self.voices.iter_mut().for_each(|voice| voice.render(buffer)); + + // Adjust volume. + buffer.iter_mut().for_each(|s| *s *= 0.2); } fn reset(&mut self, sample_rate: f32, max_buffer_size: usize) { @@ -74,7 +85,7 @@ impl> AudioProcessor for Sampler { } fn set_channel_layout(&mut self, input_channels: u16, output_channels: u16) { - todo!() + } fn set_parameter(&mut self, id: ParameterId, value: ParameterValue) { diff --git a/src/processing/sampler/oscillator_voice.rs b/src/processing/sampler/oscillator_voice.rs index 15631b0..c44a307 100644 --- a/src/processing/sampler/oscillator_voice.rs +++ b/src/processing/sampler/oscillator_voice.rs @@ -3,8 +3,8 @@ use std::{f32::consts::PI, cell::{RefCell, Ref}, borrow::Borrow, sync::Arc}; /// Oscillator voice for sampler. pub struct OscillatorVoice { - /// Sound that is currently playing. - active_sound: Option>, + /// Sound and MIDI note that is currently playing. + active_sound: Option<(Arc, u8)>, /// Gain applied to sound. gain: f32, @@ -20,7 +20,7 @@ pub struct OscillatorVoice { } impl OscillatorVoice { /// Creates new oscillator voice. - pub fn new(channel_count: u16) -> Self { + pub fn new() -> Self { OscillatorVoice { active_sound: None, gain: 0.0, @@ -36,6 +36,10 @@ impl OscillatorVoice { } } impl SamplerVoice for OscillatorVoice { + fn get_active_note(&self) -> Option { + if let Some((_, note)) = self.active_sound { Some(note) } else { None } + } + fn is_playing(&self) -> bool { self.active_sound.is_some() } @@ -43,7 +47,8 @@ impl SamplerVoice for OscillatorVoice { fn render(&mut self, buffer: &mut [f32]) { if let Some(sound) = &self.active_sound { for frame in buffer.chunks_mut(2) { // TODO: Support other channel configs. - frame.fill(sound.get_value(self.phase) * self.gain); + let sample = sound.0.get_value(self.phase) * self.gain; + frame.iter_mut().for_each(|s| *s += sample); self.phase += self.phase_increment; while self.phase >= 2.0 * PI { self.phase -= 2.0 * PI } } @@ -58,10 +63,12 @@ impl SamplerVoice for OscillatorVoice { fn start_note(&mut self, midi_note: u8, velocity: f32, sound: Arc) { let frequency = 440.0 * f32::powf(2.0, (midi_note as f32 - 69.0) / 12.0); - self.active_sound = Some(sound); + self.gain = velocity; + self.active_sound = Some((sound, midi_note)); + self.update_phase_increment(frequency); } fn stop_note(&mut self, velocity: f32, allow_tail: bool) { - todo!() + self.active_sound = None; } } diff --git a/src/processing/sampler/sampler_voice.rs b/src/processing/sampler/sampler_voice.rs index 637e436..54e40b0 100644 --- a/src/processing/sampler/sampler_voice.rs +++ b/src/processing/sampler/sampler_voice.rs @@ -4,6 +4,9 @@ use super::SamplerSound; /// Defines a voice that a sampler can use to play a sampler sound. pub trait SamplerVoice: Send { + /// Returns current MIDI note if playing, [None] otherwise. + fn get_active_note(&self) -> Option; + /// Returns whether voice is currently in use. fn is_playing(&self) -> bool; From 65e11af35a7c2d4f4b4580c6a9b4c94cd496e9be Mon Sep 17 00:00:00 2001 From: Micha Hanselmann Date: Sun, 21 Aug 2022 20:41:10 +0200 Subject: [PATCH 4/8] stereo mono mix --- src/engine/cpal_audio_engine.rs | 7 ++-- src/processing/sampler.rs | 41 ++++++++++++++++------ src/processing/sampler/oscillator_voice.rs | 4 +-- 3 files changed, 37 insertions(+), 15 deletions(-) diff --git a/src/engine/cpal_audio_engine.rs b/src/engine/cpal_audio_engine.rs index 06a8fea..62a0103 100644 --- a/src/engine/cpal_audio_engine.rs +++ b/src/engine/cpal_audio_engine.rs @@ -29,15 +29,16 @@ impl CpalAudioEngine { cpal::SupportedBufferSize::Range { min: _, max } => *max as usize, cpal::SupportedBufferSize::Unknown => 4096, }; - processor.set_channel_layout(0, config.channels()); - processor.reset(config.sample_rate().0 as f32, max_buffer_size); + let config: cpal::StreamConfig = config.into(); + processor.set_channel_layout(0, config.channels); + processor.reset(config.sample_rate.0 as f32, max_buffer_size); // Create output stream. let stream = { let audio_fn = move |buffer: &mut [f32], _: &cpal::OutputCallbackInfo| processor.process(buffer); let err_fn = move |err| eprintln!("An error occurred on the output audio stream: {}", err); device - .build_output_stream(&config.into(), audio_fn, err_fn) + .build_output_stream(&config, audio_fn, err_fn) .expect("Failed to create output stream.") }; stream.play().expect("Failed to start output stream."); diff --git a/src/processing/sampler.rs b/src/processing/sampler.rs index 3fc13b6..6b47408 100644 --- a/src/processing/sampler.rs +++ b/src/processing/sampler.rs @@ -17,6 +17,12 @@ where Sound: SamplerSound, Voice: SamplerVoice, { + /// Number of output channels. + channel_count: u16, + + /// Internal audio buffer (to mix stereo to mono). + internal_buffer: Box<[f32]>, + /// Sampler sounds. sounds: Vec>, @@ -27,6 +33,8 @@ impl> Sampler { /// Creates new sampler. pub fn new() -> Self { Sampler { + channel_count: 0, + internal_buffer: Box::new([]), sounds: Vec::new(), voices: Vec::new(), } @@ -65,27 +73,40 @@ impl> AudioProcessor for Sampler { } fn list_parameters(&self) -> &[Parameter] { - const P: [Parameter; 0] = []; - &P + &[] } - fn process(&mut self, buffer: &mut [f32]) { - buffer.fill(0.0); // Clean state. + fn process(&mut self, out_buffer: &mut [f32]) { + // Prepare internal buffer. + let frame_count = out_buffer.len() / self.channel_count as usize; + let internal_buffer = &mut self.internal_buffer[0..2*frame_count]; // Stereo. + internal_buffer.fill(0.0); // Render voices. - self.voices.iter_mut().for_each(|voice| voice.render(buffer)); - - // Adjust volume. - buffer.iter_mut().for_each(|s| *s *= 0.2); + self.voices.iter_mut().for_each(|voice| voice.render(internal_buffer)); + + // Mix internal buffer into output buffer. + if self.channel_count == 1 { + for (out, int) in out_buffer.iter_mut().zip(internal_buffer.chunks_mut(2)) { + *out = 0.5 * (int[0] + int[1]); // Mono output. + } + } else { + for (out, int) in out_buffer.chunks_mut(self.channel_count as usize).zip(internal_buffer.chunks_mut(2)) { + (out[0], out[1]) = (int[0], int[1]); // Stereo output, ignore other channels. + } + } } fn reset(&mut self, sample_rate: f32, max_buffer_size: usize) { + // Allocate internal resources. + self.internal_buffer = vec![0.0; 2 * max_buffer_size].into_boxed_slice(); + // Reset voices. self.voices.iter_mut().for_each(|voice| voice.reset(sample_rate, max_buffer_size)); } - fn set_channel_layout(&mut self, input_channels: u16, output_channels: u16) { - + fn set_channel_layout(&mut self, _input_channels: u16, output_channels: u16) { + self.channel_count = output_channels; } fn set_parameter(&mut self, id: ParameterId, value: ParameterValue) { diff --git a/src/processing/sampler/oscillator_voice.rs b/src/processing/sampler/oscillator_voice.rs index c44a307..df507a9 100644 --- a/src/processing/sampler/oscillator_voice.rs +++ b/src/processing/sampler/oscillator_voice.rs @@ -46,8 +46,8 @@ impl SamplerVoice for OscillatorVoice { fn render(&mut self, buffer: &mut [f32]) { if let Some(sound) = &self.active_sound { - for frame in buffer.chunks_mut(2) { // TODO: Support other channel configs. - let sample = sound.0.get_value(self.phase) * self.gain; + for frame in buffer.chunks_mut(2) { // Sampler expects stereo. + let sample = sound.0.get_value(self.phase) * self.gain * 0.1; // TODO frame.iter_mut().for_each(|s| *s += sample); self.phase += self.phase_increment; while self.phase >= 2.0 * PI { self.phase -= 2.0 * PI } From 45e4aff7b3ab5788abcef48b82d3b30ee7f37ed2 Mon Sep 17 00:00:00 2001 From: Micha Hanselmann Date: Sun, 21 Aug 2022 21:39:37 +0200 Subject: [PATCH 5/8] adsr --- src/main.rs | 2 +- src/processing/sampler.rs | 6 +- src/processing/sampler/linear_adsr.rs | 92 ++++++++++++++++++++++ src/processing/sampler/oscillator_voice.rs | 25 ++++-- src/processing/sine.rs | 2 +- 5 files changed, 118 insertions(+), 9 deletions(-) create mode 100644 src/processing/sampler/linear_adsr.rs diff --git a/src/main.rs b/src/main.rs index f7c6521..13ed1e8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,7 +7,7 @@ use processing::{Sampler, OscillatorSound, OscillatorVoice}; fn main() { let mut sampler: Sampler = Sampler::new(); - for _ in 1..10 { + for _ in 0..64 { sampler.add_voice(OscillatorVoice::new()); } sampler.add_sound(OscillatorSound::new()); diff --git a/src/processing/sampler.rs b/src/processing/sampler.rs index 6b47408..f1d3bc1 100644 --- a/src/processing/sampler.rs +++ b/src/processing/sampler.rs @@ -1,15 +1,16 @@ +mod linear_adsr; mod oscillator_sound; mod oscillator_voice; mod sampler_sound; mod sampler_voice; -use std::{cell::RefCell, sync::Arc}; - use crate::base::{MidiMessage, AudioProcessor, Parameter, ParameterId, ParameterValue, MidiReceiver}; +pub use linear_adsr::LinearAdsr; pub use oscillator_sound::OscillatorSound; pub use oscillator_voice::OscillatorVoice; pub use sampler_sound::SamplerSound; pub use sampler_voice::SamplerVoice; +use std::{sync::Arc}; /// Sampler instrument processor. pub struct Sampler @@ -117,6 +118,7 @@ impl> MidiReceiver for Sampler { fn handle_midi_message(&mut self, message: MidiMessage) { match message { MidiMessage::NoteOff(channel, note, velocity) => self.note_off(channel, note, velocity), + MidiMessage::NoteOn(channel, note, 0) => self.note_off(channel, note, 0), // MIDI running status. MidiMessage::NoteOn(channel, note, velocity) => self.note_on(channel, note, velocity), } } diff --git a/src/processing/sampler/linear_adsr.rs b/src/processing/sampler/linear_adsr.rs new file mode 100644 index 0000000..e90edd8 --- /dev/null +++ b/src/processing/sampler/linear_adsr.rs @@ -0,0 +1,92 @@ +/// Linear ADSR envelope. +pub struct LinearAdsr { + /// Attack in seconds. Valid range is 0.001s to 10.0s. + attack: f32, + attack_delta: f32, + + /// Current envelope gain. + envelope_gain: f32, + + /// Release in seconds. Valid range is 0.001s to 30.0s. + release: f32, + release_delta: f32, + + /// Sample rate in Hz. + sample_rate: f32, + + /// Current stage if active, otherwise [None]. + stage: Option, +} +impl LinearAdsr { + /// Create new linear ADSR. + pub fn new(attack: f32, release: f32) -> Self { + let mut adsr = LinearAdsr { + attack: 0.0, + attack_delta: 0.0, + envelope_gain: 0.0, + release: 0.0, + release_delta: 0.0, + sample_rate: 44100.0, + stage: None, + }; + adsr.set_parameters(attack, release); + adsr + } + + /// Returns whether ADSR is active. + pub fn is_active(&self) -> bool { + self.stage.is_some() + } + + /// Returns next sample and advances ADSR state. + pub fn next_sample(&mut self) -> f32 { + if let Some(stage) = &self.stage { + match stage { + AdsrStage::Attack => { + self.envelope_gain = (self.envelope_gain + self.attack_delta).min(1.0); + }, + AdsrStage::Release => { + self.envelope_gain -= self.release_delta; + if self.envelope_gain < 0.0 { self.envelope_gain = 0.0; self.stage = None; } + } + }; + } + self.envelope_gain + } + + /// Note off, triggers envelope release. + pub fn note_off(&mut self) { + self.stage = Some(AdsrStage::Release); + } + + /// Note on, triggers envelope attack. + pub fn note_on(&mut self) { + self.envelope_gain = 0.0; + self.stage = Some(AdsrStage::Attack); + } + + /// Set ADSR parameters. + pub fn set_parameters(&mut self, attack: f32, release: f32) { + // Clamp parameters. + self.attack = attack.clamp(0.001, 10.0); + self.release = release.clamp(0.001, 30.0); + + // Precalculate deltas. + self.attack_delta = 1.0 / (self.attack * self.sample_rate); + self.release_delta = 1.0 / (self.release * self.sample_rate); + } + + /// Resets internal parameters of the envelope. + pub fn reset(&mut self, sample_rate: f32) { + self.envelope_gain = 0.0; + self.sample_rate = sample_rate; + self.stage = None; + self.set_parameters(self.attack, self.release); + } +} + +/// ADSR stages. +enum AdsrStage { + Attack, + Release, +} diff --git a/src/processing/sampler/oscillator_voice.rs b/src/processing/sampler/oscillator_voice.rs index df507a9..2708dd8 100644 --- a/src/processing/sampler/oscillator_voice.rs +++ b/src/processing/sampler/oscillator_voice.rs @@ -1,4 +1,4 @@ -use super::{SamplerVoice, OscillatorSound}; +use super::{SamplerVoice, OscillatorSound, LinearAdsr}; use std::{f32::consts::PI, cell::{RefCell, Ref}, borrow::Borrow, sync::Arc}; /// Oscillator voice for sampler. @@ -6,6 +6,9 @@ pub struct OscillatorVoice { /// Sound and MIDI note that is currently playing. active_sound: Option<(Arc, u8)>, + /// ADSR envelope. + adsr: LinearAdsr, + /// Gain applied to sound. gain: f32, @@ -23,6 +26,7 @@ impl OscillatorVoice { pub fn new() -> Self { OscillatorVoice { active_sound: None, + adsr: LinearAdsr::new(0.1, 0.3), gain: 0.0, phase: 0.0, phase_increment: 0.0, @@ -47,28 +51,39 @@ impl SamplerVoice for OscillatorVoice { fn render(&mut self, buffer: &mut [f32]) { if let Some(sound) = &self.active_sound { for frame in buffer.chunks_mut(2) { // Sampler expects stereo. - let sample = sound.0.get_value(self.phase) * self.gain * 0.1; // TODO + let envelope_gain = self.adsr.next_sample(); + let sample = sound.0.get_value(self.phase) * self.gain * envelope_gain * 0.1; // TODO frame.iter_mut().for_each(|s| *s += sample); self.phase += self.phase_increment; while self.phase >= 2.0 * PI { self.phase -= 2.0 * PI } + + // Stop note after envelope finished release stage. + if !self.adsr.is_active() { self.stop_note(0.0, false); break; } } } } fn reset(&mut self, sample_rate: f32, _max_buffer_size: usize) { self.active_sound = None; + self.adsr.reset(sample_rate); self.sample_rate = sample_rate; // Other parameters will be reset on note start. } fn start_note(&mut self, midi_note: u8, velocity: f32, sound: Arc) { let frequency = 440.0 * f32::powf(2.0, (midi_note as f32 - 69.0) / 12.0); - self.gain = velocity; self.active_sound = Some((sound, midi_note)); + self.adsr.note_on(); + self.gain = velocity; + self.phase = 0.0; self.update_phase_increment(frequency); } - fn stop_note(&mut self, velocity: f32, allow_tail: bool) { - self.active_sound = None; + fn stop_note(&mut self, _velocity: f32, allow_tail: bool) { + if allow_tail { + self.adsr.note_off(); + } else { + self.active_sound = None; + } } } diff --git a/src/processing/sine.rs b/src/processing/sine.rs index a35aed7..2feccbf 100644 --- a/src/processing/sine.rs +++ b/src/processing/sine.rs @@ -7,7 +7,7 @@ pub struct Sine { /// Amplitude parameter. Valid range is 0.0 to 1.0, default 0.2. amplitude: f32, - /// Frequency parameter in Hz. Valid range is 20 Hz to (SampleRate/2), default 440 Hz. + /// Frequency parameter in Hz. Valid range is 20Hz to (SampleRate/2)Hz, default 440Hz. frequency: f32, /// Number of output channels. From 7b7f2b977771970e6b3fdacabab9542ef3fbd631 Mon Sep 17 00:00:00 2001 From: Micha Hanselmann Date: Mon, 22 Aug 2022 22:37:42 +0200 Subject: [PATCH 6/8] sustain pedal --- src/base/midi_message.rs | 6 +++- src/processing/sampler.rs | 33 ++++++++++++++++++++-- src/processing/sampler/oscillator_voice.rs | 14 ++++++++- src/processing/sampler/sampler_voice.rs | 6 ++++ 4 files changed, 54 insertions(+), 5 deletions(-) diff --git a/src/base/midi_message.rs b/src/base/midi_message.rs index 2981eb0..13580ef 100644 --- a/src/base/midi_message.rs +++ b/src/base/midi_message.rs @@ -1,7 +1,10 @@ /// MIDI message type. #[derive(Clone, Copy, Debug)] pub enum MidiMessage { - // Note off: channel, note number, velocity. + /// Control change: channel, control number, value. + ControlChange(u8, u8, u8), + + /// Note off: channel, note number, velocity. NoteOff(u8, u8, u8), /// Note on: channel, note number, velocity. @@ -14,6 +17,7 @@ impl MidiMessage { match status & 0xF0 { 0x80 => Some(MidiMessage::NoteOff(status & 0x0F, *data1, *data2)), 0x90 => Some(MidiMessage::NoteOn(status & 0x0F, *data1, *data2)), + 0xB0 => Some(MidiMessage::ControlChange(status & 0x0F, *data1, *data2)), _ => None, } } else { diff --git a/src/processing/sampler.rs b/src/processing/sampler.rs index f1d3bc1..a53de3a 100644 --- a/src/processing/sampler.rs +++ b/src/processing/sampler.rs @@ -27,6 +27,9 @@ where /// Sampler sounds. sounds: Vec>, + /// Sustain pedal state. + sustain_pedal_pressed: bool, + /// Sampler voices. voices: Vec, } @@ -37,6 +40,7 @@ impl> Sampler { channel_count: 0, internal_buffer: Box::new([]), sounds: Vec::new(), + sustain_pedal_pressed: false, voices: Vec::new(), } } @@ -51,20 +55,41 @@ impl> Sampler { self.voices.push(voice); } + /// Handles sustain pedal (usually triggered by a MIDI message). + fn sustain_pedal(&mut self, pressed: bool) { + self.sustain_pedal_pressed = pressed; + if !pressed { + self.voices.iter_mut() + .filter(|voice| voice.is_playing() && !voice.is_key_down()) + .for_each(|voice| voice.stop_note(0.0, true)); + } + } + /// Note off (usually triggered by a MIDI message). - fn note_off(&mut self, midi_channel: u8, midi_note: u8, velocity: u8) { + fn note_off(&mut self, _midi_channel: u8, midi_note: u8, velocity: u8) { self.voices.iter_mut() .filter(|voice| voice.get_active_note() == Some(midi_note)) - .for_each(|voice| voice.stop_note(velocity as f32 / 127.0, true)); + .for_each(|voice| { + voice.set_key_down(false); + if !self.sustain_pedal_pressed { + voice.stop_note(velocity as f32 / 127.0, true); + } + }); } /// Note on (usually triggered by a MIDI message). - fn note_on(&mut self, midi_channel: u8, midi_note: u8, velocity: u8) { + fn note_on(&mut self, _midi_channel: u8, midi_note: u8, velocity: u8) { + // If hitting a note that's still ringing, stop it first (sustain pedal). + self.voices.iter_mut() + .filter(|voice| voice.get_active_note().map_or(false, |note| note == midi_note)) + .for_each(|voice| voice.stop_note(0.0, true)); + // Find free voice. let voice = self.voices.iter_mut().find(|voice| !voice.is_playing()); if let Some(voice) = voice { let sound = self.sounds.first().unwrap().clone(); voice.start_note(midi_note, velocity as f32 / 127.0, sound); + voice.set_key_down(true); } } } @@ -117,9 +142,11 @@ impl> AudioProcessor for Sampler { impl> MidiReceiver for Sampler { fn handle_midi_message(&mut self, message: MidiMessage) { match message { + MidiMessage::ControlChange(_, 0x40, value) => self.sustain_pedal(value >= 64), MidiMessage::NoteOff(channel, note, velocity) => self.note_off(channel, note, velocity), MidiMessage::NoteOn(channel, note, 0) => self.note_off(channel, note, 0), // MIDI running status. MidiMessage::NoteOn(channel, note, velocity) => self.note_on(channel, note, velocity), + _ => (), } } } diff --git a/src/processing/sampler/oscillator_voice.rs b/src/processing/sampler/oscillator_voice.rs index 2708dd8..dba1561 100644 --- a/src/processing/sampler/oscillator_voice.rs +++ b/src/processing/sampler/oscillator_voice.rs @@ -12,6 +12,9 @@ pub struct OscillatorVoice { /// Gain applied to sound. gain: f32, + /// Key down state. + key_down: bool, + /// Phase, used for internal processing. phase: f32, @@ -26,8 +29,9 @@ impl OscillatorVoice { pub fn new() -> Self { OscillatorVoice { active_sound: None, - adsr: LinearAdsr::new(0.1, 0.3), + adsr: LinearAdsr::new(0.03, 0.1), gain: 0.0, + key_down: false, phase: 0.0, phase_increment: 0.0, sample_rate: 44100.0, @@ -44,6 +48,10 @@ impl SamplerVoice for OscillatorVoice { if let Some((_, note)) = self.active_sound { Some(note) } else { None } } + fn is_key_down(&self) -> bool { + self.key_down + } + fn is_playing(&self) -> bool { self.active_sound.is_some() } @@ -70,6 +78,10 @@ impl SamplerVoice for OscillatorVoice { // Other parameters will be reset on note start. } + fn set_key_down(&mut self, key_down: bool) { + self.key_down = key_down; + } + fn start_note(&mut self, midi_note: u8, velocity: f32, sound: Arc) { let frequency = 440.0 * f32::powf(2.0, (midi_note as f32 - 69.0) / 12.0); self.active_sound = Some((sound, midi_note)); diff --git a/src/processing/sampler/sampler_voice.rs b/src/processing/sampler/sampler_voice.rs index 54e40b0..991e6c9 100644 --- a/src/processing/sampler/sampler_voice.rs +++ b/src/processing/sampler/sampler_voice.rs @@ -7,6 +7,9 @@ pub trait SamplerVoice: Send { /// Returns current MIDI note if playing, [None] otherwise. fn get_active_note(&self) -> Option; + /// Returns key down state. + fn is_key_down(&self) -> bool; + /// Returns whether voice is currently in use. fn is_playing(&self) -> bool; @@ -16,6 +19,9 @@ pub trait SamplerVoice: Send { /// Resets internal parameters of the voice. fn reset(&mut self, sample_rate: f32, max_buffer_size: usize); + /// Sets key down state. + fn set_key_down(&mut self, key_down: bool); + /// Plays a note on this voice. fn start_note(&mut self, midi_note: u8, velocity: f32, sound: Arc); From cdce2378e986be365badc44b52c78a5c3b5a3cba Mon Sep 17 00:00:00 2001 From: Micha Hanselmann Date: Wed, 24 Aug 2022 20:44:25 +0200 Subject: [PATCH 7/8] all notes off --- src/processing/sampler.rs | 7 +++++++ src/processing/sampler/oscillator_voice.rs | 2 +- src/processing/sampler/sampler_voice.rs | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/processing/sampler.rs b/src/processing/sampler.rs index a53de3a..1d262e3 100644 --- a/src/processing/sampler.rs +++ b/src/processing/sampler.rs @@ -55,6 +55,12 @@ impl> Sampler { self.voices.push(voice); } + /// All notes off (usually triggered by a MIDI message). + fn all_notes_off(&mut self, allow_tail: bool) { + self.voices.iter_mut() + .for_each(|voice| voice.stop_note(0.0, allow_tail)); + } + /// Handles sustain pedal (usually triggered by a MIDI message). fn sustain_pedal(&mut self, pressed: bool) { self.sustain_pedal_pressed = pressed; @@ -143,6 +149,7 @@ impl> MidiReceiver for Sampler { fn handle_midi_message(&mut self, message: MidiMessage) { match message { MidiMessage::ControlChange(_, 0x40, value) => self.sustain_pedal(value >= 64), + MidiMessage::ControlChange(_, 0x7B, _) => self.all_notes_off(true), MidiMessage::NoteOff(channel, note, velocity) => self.note_off(channel, note, velocity), MidiMessage::NoteOn(channel, note, 0) => self.note_off(channel, note, 0), // MIDI running status. MidiMessage::NoteOn(channel, note, velocity) => self.note_on(channel, note, velocity), diff --git a/src/processing/sampler/oscillator_voice.rs b/src/processing/sampler/oscillator_voice.rs index dba1561..7fc7471 100644 --- a/src/processing/sampler/oscillator_voice.rs +++ b/src/processing/sampler/oscillator_voice.rs @@ -1,5 +1,5 @@ use super::{SamplerVoice, OscillatorSound, LinearAdsr}; -use std::{f32::consts::PI, cell::{RefCell, Ref}, borrow::Borrow, sync::Arc}; +use std::{f32::consts::PI, sync::Arc}; /// Oscillator voice for sampler. pub struct OscillatorVoice { diff --git a/src/processing/sampler/sampler_voice.rs b/src/processing/sampler/sampler_voice.rs index 991e6c9..dc43133 100644 --- a/src/processing/sampler/sampler_voice.rs +++ b/src/processing/sampler/sampler_voice.rs @@ -1,4 +1,4 @@ -use std::{cell::{RefCell, Ref}, sync::Arc}; +use std::sync::Arc; use super::SamplerSound; From 91fb73d64ed2be7361434441dfb0b4d8119736a6 Mon Sep 17 00:00:00 2001 From: Micha Hanselmann Date: Wed, 24 Aug 2022 20:55:57 +0200 Subject: [PATCH 8/8] applies --- src/processing/sampler.rs | 8 +++++--- src/processing/sampler/oscillator_sound.rs | 6 +++++- src/processing/sampler/sampler_sound.rs | 5 ++++- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/processing/sampler.rs b/src/processing/sampler.rs index 1d262e3..81160c6 100644 --- a/src/processing/sampler.rs +++ b/src/processing/sampler.rs @@ -93,9 +93,11 @@ impl> Sampler { // Find free voice. let voice = self.voices.iter_mut().find(|voice| !voice.is_playing()); if let Some(voice) = voice { - let sound = self.sounds.first().unwrap().clone(); - voice.start_note(midi_note, velocity as f32 / 127.0, sound); - voice.set_key_down(true); + // Find matching sound. + if let Some(sound) = self.sounds.iter().find(|sound| sound.applies_to_note(midi_note)) { + voice.start_note(midi_note, velocity as f32 / 127.0, sound.clone()); + voice.set_key_down(true); + } } } } diff --git a/src/processing/sampler/oscillator_sound.rs b/src/processing/sampler/oscillator_sound.rs index 19df911..2f61bdf 100644 --- a/src/processing/sampler/oscillator_sound.rs +++ b/src/processing/sampler/oscillator_sound.rs @@ -14,4 +14,8 @@ impl OscillatorSound { f32::sin(phase) // Only sine waves for now. } } -impl SamplerSound for OscillatorSound {} +impl SamplerSound for OscillatorSound { + fn applies_to_note(&self, _midi_note: u8) -> bool { + true + } +} diff --git a/src/processing/sampler/sampler_sound.rs b/src/processing/sampler/sampler_sound.rs index 87190fd..13c52f6 100644 --- a/src/processing/sampler/sampler_sound.rs +++ b/src/processing/sampler/sampler_sound.rs @@ -1,2 +1,5 @@ /// Defines a sound that can be played by a sampler voice, e.g. an oscillator config or audio file data. -pub trait SamplerSound: Send + Sync {} +pub trait SamplerSound: Send + Sync { + /// Returns whether sound applies to given midi note. + fn applies_to_note(&self, midi_note: u8) -> bool; +}