1
0
Fork 0

Add a basic aliasing sawtooth wave to PolyModSynth

This commit is contained in:
Robbert van der Helm 2022-07-06 16:44:52 +02:00
parent 4ad4f8f76d
commit efc32f3944

View file

@ -110,12 +110,13 @@ impl Plugin for PolyModSynth {
// num_remaining_samples, next_event_idx - block_start_idx)`. Because blocks also need to be // num_remaining_samples, next_event_idx - block_start_idx)`. Because blocks also need to be
// split on note events, it's easier to work with raw audio here and to do the splitting by // split on note events, it's easier to work with raw audio here and to do the splitting by
// hand. // hand.
let num_samples = buffer.len();
let output = buffer.as_slice(); let output = buffer.as_slice();
let mut next_event = context.next_event(); let mut next_event = context.next_event();
let mut block_start: usize = 0; let mut block_start: usize = 0;
let mut block_end: usize = MAX_BLOCK_SIZE.min(output.len()); let mut block_end: usize = MAX_BLOCK_SIZE.min(num_samples);
while block_start < output.len() { while block_start < num_samples {
// First of all, handle all note events that happen at the start of the block, and cut // First of all, handle all note events that happen at the start of the block, and cut
// the block short if another event happens before the end of it // the block short if another event happens before the end of it
'events: loop { 'events: loop {
@ -132,18 +133,12 @@ impl Plugin for PolyModSynth {
note, note,
velocity, velocity,
} => { } => {
let voice = self.start_voice( let initial_phase: f32 = self.prng.gen();
context, let voice =
timing, self.start_voice(context, timing, voice_id, channel, note);
voice_id.unwrap_or_else(|| {
compute_fallback_voice_id(note, channel)
}),
channel,
note,
);
// TODO: Add and set the other fields // TODO: Add and set the other fields
voice.phase = self.prng.gen(); voice.phase = initial_phase;
voice.phase_delta = voice.phase_delta =
util::midi_note_to_freq(note) / context.transport().sample_rate; util::midi_note_to_freq(note) / context.transport().sample_rate;
voice.velocity_sqrt = velocity.sqrt(); voice.velocity_sqrt = velocity.sqrt();
@ -153,21 +148,19 @@ impl Plugin for PolyModSynth {
voice_id, voice_id,
channel, channel,
note, note,
velocity, velocity: _,
} => todo!("Have note-off events fade out the notes"), } => {
// TODO: This should not immediately terminate the voice. For
// obvious reasons.
self.terminate_voice(context, timing, voice_id, channel, note);
}
NoteEvent::Choke { NoteEvent::Choke {
timing, timing,
voice_id, voice_id,
channel, channel,
note, note,
} => { } => {
self.terminate_voice( self.terminate_voice(context, timing, voice_id, channel, note);
context,
timing,
voice_id.unwrap_or_else(|| {
compute_fallback_voice_id(note, channel)
}),
);
} }
// TODO: Handle poly modulation // TODO: Handle poly modulation
NoteEvent::PolyModulation { NoteEvent::PolyModulation {
@ -190,9 +183,9 @@ impl Plugin for PolyModSynth {
// short so the next block starts at the event // short so the next block starts at the event
Some(event) if (event.timing() as usize) < block_end => { Some(event) if (event.timing() as usize) < block_end => {
block_end = event.timing() as usize; block_end = event.timing() as usize;
break; break 'events;
} }
_ => break, _ => break 'events,
} }
} }
@ -200,9 +193,30 @@ impl Plugin for PolyModSynth {
output[0][block_start..block_end].fill(0.0); output[0][block_start..block_end].fill(0.0);
output[1][block_start..block_end].fill(0.0); output[1][block_start..block_end].fill(0.0);
// TODO: Poly modulation
// TODO: Amp envelope
// TODO: Some form of band limiting
// TODO: Filter
for voice in self.voices.iter_mut().filter_map(|v| v.as_mut()) {
for sample_idx in block_start..block_end {
// TODO: This should of course take the envelope and probably a poly mod param into account
// TODO: And as mentioned above, basic PolyBLEP or something
let gain = voice.velocity_sqrt;
let sample = (voice.phase * 2.0 - 1.0) * gain;
voice.phase += voice.phase_delta;
if voice.phase >= 1.0 {
voice.phase -= 1.0;
}
output[0][sample_idx] += sample;
output[1][sample_idx] += sample;
}
}
// And then just keep processing blocks until we've run out of buffer to fill // And then just keep processing blocks until we've run out of buffer to fill
block_start = block_end; block_start = block_end;
block_end = (block_start + MAX_BLOCK_SIZE).min(output.len()); block_end = (block_start + MAX_BLOCK_SIZE).min(num_samples);
} }
ProcessStatus::Normal ProcessStatus::Normal
@ -224,12 +238,12 @@ impl PolyModSynth {
&mut self, &mut self,
context: &mut impl ProcessContext, context: &mut impl ProcessContext,
sample_offset: u32, sample_offset: u32,
voice_id: i32, voice_id: Option<i32>,
channel: u8, channel: u8,
note: u8, note: u8,
) -> &mut Voice { ) -> &mut Voice {
let new_voice = Voice { let new_voice = Voice {
voice_id, voice_id: voice_id.unwrap_or_else(|| compute_fallback_voice_id(note, channel)),
internal_voice_id: self.next_internal_voice_id, internal_voice_id: self.next_internal_voice_id,
channel, channel,
note, note,
@ -282,23 +296,28 @@ impl PolyModSynth {
&mut self, &mut self,
context: &mut impl ProcessContext, context: &mut impl ProcessContext,
sample_offset: u32, sample_offset: u32,
voice_id: i32, voice_id: Option<i32>,
channel: u8,
note: u8,
) -> bool { ) -> bool {
for voice in self.voices.iter_mut() { for voice in self.voices.iter_mut() {
match voice { match voice {
Some(Voice { Some(Voice {
voice_id: found_voice_id, voice_id: candidate_voice_id,
channel, channel: candidate_channel,
note, note: candidate_note,
.. ..
}) if *found_voice_id == voice_id => { }) if voice_id == Some(*candidate_voice_id)
|| (channel == *candidate_channel && note == *candidate_note) =>
{
// This event is very important, as it allows the host to manage its own modulation // This event is very important, as it allows the host to manage its own modulation
// voices // voices
context.send_event(NoteEvent::VoiceTerminated { context.send_event(NoteEvent::VoiceTerminated {
timing: sample_offset, timing: sample_offset,
voice_id: Some(voice_id), // Notice how we always send the terminated voice ID here
channel: *channel, voice_id: Some(*candidate_voice_id),
note: *note, channel,
note,
}); });
*voice = None; *voice = None;