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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: sine sampler, adsr #3

Merged
merged 8 commits into from
Aug 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
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
6 changes: 5 additions & 1 deletion src/base/midi_message.rs
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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 {
Expand Down
7 changes: 4 additions & 3 deletions src/engine/cpal_audio_engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.");
Expand Down
13 changes: 10 additions & 3 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,19 @@ 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 mut sampler: Sampler<OscillatorSound, OscillatorVoice> = Sampler::new();

for _ in 0..64 {
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);

std::thread::park();
}
2 changes: 2 additions & 0 deletions src/processing.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
mod sampler;
mod sine;

pub use sampler::{Sampler, OscillatorSound, OscillatorVoice};
pub use sine::Sine;
161 changes: 161 additions & 0 deletions src/processing/sampler.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
mod linear_adsr;
mod oscillator_sound;
mod oscillator_voice;
mod sampler_sound;
mod sampler_voice;

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<Sound, Voice>
where
Sound: SamplerSound,
Voice: SamplerVoice<Sound>,
{
/// Number of output channels.
channel_count: u16,

/// Internal audio buffer (to mix stereo to mono).
internal_buffer: Box<[f32]>,

/// Sampler sounds.
sounds: Vec<Arc<Sound>>,

/// Sustain pedal state.
sustain_pedal_pressed: bool,

/// Sampler voices.
voices: Vec<Voice>,
}
impl<S: SamplerSound, V: SamplerVoice<S>> Sampler<S, V> {
/// Creates new sampler.
pub fn new() -> Self {
Sampler {
channel_count: 0,
internal_buffer: Box::new([]),
sounds: Vec::new(),
sustain_pedal_pressed: false,
voices: Vec::new(),
}
}

/// Adds a sound.
pub fn add_sound(&mut self, sound: S) {
self.sounds.push(Arc::new(sound));
}

/// Adds a voice.
pub fn add_voice(&mut self, voice: V) {
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;
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) {
self.voices.iter_mut()
.filter(|voice| voice.get_active_note() == Some(midi_note))
.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) {
// 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 {
// 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);
}
}
}
}
impl<S: SamplerSound, V: SamplerVoice<S>> AudioProcessor for Sampler<S, V> {
fn get_parameter(&self, id: ParameterId) -> Option<ParameterValue> {
todo!()
}

fn list_parameters(&self) -> &[Parameter] {
&[]
}

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(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) {
self.channel_count = output_channels;
}

fn set_parameter(&mut self, id: ParameterId, value: ParameterValue) {
todo!()
}
}
impl<S: SamplerSound, V: SamplerVoice<S>> MidiReceiver for Sampler<S, V> {
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),
_ => (),
}
}
}
92 changes: 92 additions & 0 deletions src/processing/sampler/linear_adsr.rs
Original file line number Diff line number Diff line change
@@ -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<AdsrStage>,
}
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,
}
21 changes: 21 additions & 0 deletions src/processing/sampler/oscillator_sound.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
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 {
fn applies_to_note(&self, _midi_note: u8) -> bool {
true
}
}
Loading