1#![doc = include_str!("../docs_boilerplate.md")]
2#![doc = include_str!("../README.md")]
3
4use crate::splice::{TimedStateChange, splice_numeric_buffer_states};
5
6use self::state::{State, UpdateScratch};
7use conformal_component::{
8 ProcessingEnvironment,
9 audio::{BufferMut, channels_mut},
10 events::{self as component_events, NoteID},
11 parameters::{
12 NumericBufferState, PiecewiseLinearCurvePoint, left_numeric_buffer, right_numeric_buffer,
13 },
14 synth::{self, valid_range_for_per_note_expression},
15};
16
17pub use conformal_component::events::NoteData;
18
19mod splice;
20mod state;
21
22#[derive(Clone, Debug, PartialEq)]
24pub enum EventData {
25 NoteOn {
27 data: NoteData,
29 },
30 NoteOff {
32 data: NoteData,
34 },
35}
36
37#[derive(Clone, Debug, PartialEq)]
39pub struct Event {
40 pub sample_offset: usize,
42 pub data: EventData,
44}
45
46impl TryFrom<component_events::Data> for EventData {
47 type Error = ();
48 fn try_from(value: component_events::Data) -> Result<Self, Self::Error> {
49 #[allow(unreachable_patterns)]
50 match value {
51 component_events::Data::NoteOn { data } => Ok(EventData::NoteOn { data }),
52 component_events::Data::NoteOff { data } => Ok(EventData::NoteOff { data }),
53 _ => Err(()),
54 }
55 }
56}
57
58impl TryFrom<component_events::Event> for Event {
59 type Error = ();
60 fn try_from(value: component_events::Event) -> Result<Self, Self::Error> {
61 Ok(Event {
62 sample_offset: value.sample_offset,
63 data: value.data.try_into()?,
64 })
65 }
66}
67
68fn add_in_place(x: &[f32], y: &mut [f32]) {
69 for (x, y) in x.iter().zip(y.iter_mut()) {
70 *y += *x;
71 }
72}
73
74fn mul_constant_in_place(x: f32, y: &mut [f32]) {
75 for y in y.iter_mut() {
76 *y *= x;
77 }
78}
79
80pub trait VoiceProcessContext {
88 fn events(&self) -> impl Iterator<Item = Event> + Clone;
90
91 fn parameters(&self) -> &impl synth::SynthParamBufferStates;
93
94 fn per_note_expression(
100 &self,
101 expression: synth::NumericPerNoteExpression,
102 ) -> NumericBufferState<impl Iterator<Item = PiecewiseLinearCurvePoint> + Clone>;
103}
104
105pub trait Voice {
107 type SharedData<'a>: Clone;
110
111 fn new(voice_index: usize, max_samples_per_process_call: usize, sampling_rate: f32) -> Self;
113
114 fn handle_event(&mut self, event: &EventData);
118
119 fn process(
124 &mut self,
125 context: &impl VoiceProcessContext,
126 shared_data: &Self::SharedData<'_>,
127 output: &mut [f32],
128 );
129
130 #[must_use]
136 fn quiescent(&self) -> bool;
137
138 fn skip_samples(&mut self, _num_samples: usize) {}
144
145 fn reset(&mut self);
147}
148
149#[derive(Clone)]
153struct NoteChangesInfo {
154 effective_initial_note_id: Option<NoteID>,
157
158 has_any_change: bool,
161
162 initial_state_was_off: bool,
165}
166
167struct ProcessContextImpl<'a, E, P> {
168 initial_note_id: Option<NoteID>,
169 events_fn: E,
170 parameters: &'a P,
171 buffer_size: usize,
172 note_changes_info: &'a NoteChangesInfo,
173}
174
175#[derive(Clone, Debug, PartialEq)]
176struct TimedNoteChange {
177 note_id: NoteID,
178 sample_offset: usize,
179}
180
181fn note_changes_iter(
187 initial_note_id: Option<NoteID>,
188 events: impl Iterator<Item = Event> + Clone,
189) -> impl Iterator<Item = TimedNoteChange> + Clone {
190 let mut last_note_id = initial_note_id;
191 events.filter_map(move |e| match e.data {
192 EventData::NoteOn { data } => {
193 if Some(data.id) == last_note_id {
194 None
195 } else {
196 last_note_id = Some(data.id);
197 Some(TimedNoteChange {
198 note_id: data.id,
199 sample_offset: e.sample_offset,
200 })
201 }
202 }
203 EventData::NoteOff { .. } => None,
204 })
205}
206
207fn keep_last_per_sample(
208 iter: impl Iterator<Item = Event> + Clone,
209) -> impl Iterator<Item = Event> + Clone {
210 let mut iter = iter.peekable();
211 std::iter::from_fn(move || {
212 loop {
213 let current = iter.next()?;
214 if iter
215 .peek()
216 .is_some_and(|next| next.sample_offset == current.sample_offset)
217 {
218 continue;
219 }
220 return Some(current);
221 }
222 })
223}
224
225impl<I: Iterator<Item = Event> + Clone, E: Fn() -> I, P: synth::SynthParamBufferStates>
226 ProcessContextImpl<'_, E, P>
227{
228 fn get_note_changes(&self) -> impl Iterator<Item = TimedNoteChange> + Clone {
229 note_changes_iter(
233 self.initial_note_id,
234 keep_last_per_sample(
235 self.events()
236 .filter(|e| matches!(e.data, EventData::NoteOn { .. })),
237 ),
238 )
239 }
240}
241
242impl<I: Iterator<Item = Event> + Clone, E: Fn() -> I, P: synth::SynthParamBufferStates>
243 VoiceProcessContext for ProcessContextImpl<'_, E, P>
244{
245 fn events(&self) -> impl Iterator<Item = Event> + Clone {
246 (self.events_fn)()
247 }
248
249 fn parameters(&self) -> &impl synth::SynthParamBufferStates {
250 self.parameters
251 }
252
253 fn per_note_expression(
254 &self,
255 expression: synth::NumericPerNoteExpression,
256 ) -> NumericBufferState<impl Iterator<Item = PiecewiseLinearCurvePoint> + Clone> {
257 let note_change_to_state_change =
266 move |TimedNoteChange {
267 note_id,
268 sample_offset,
269 }| TimedStateChange {
270 sample_offset,
271 state: self
272 .parameters
273 .get_numeric_expression_for_note(expression, note_id),
274 };
275 match (
276 self.note_changes_info.effective_initial_note_id,
277 self.note_changes_info.has_any_change,
278 ) {
279 (Some(initial_note_id), false) => left_numeric_buffer(
282 self.parameters
283 .get_numeric_expression_for_note(expression, initial_note_id),
284 ),
285 (None, false) => NumericBufferState::Constant(Default::default()),
288 (Some(initial_note_id), true) => {
290 let prefix_len = usize::from(self.note_changes_info.initial_state_was_off);
293 let note_changes = self.get_note_changes().skip(prefix_len);
294 right_numeric_buffer(splice_numeric_buffer_states(
295 self.parameters
296 .get_numeric_expression_for_note(expression, initial_note_id),
297 note_changes.map(note_change_to_state_change),
298 self.buffer_size,
299 valid_range_for_per_note_expression(expression),
300 ))
301 }
302 (None, true) => unreachable!(),
304 }
305 }
306}
307
308pub struct Poly<V, const MAX_VOICES: usize = 32> {
316 voices: Vec<V>,
317 state: State<MAX_VOICES>,
318 update_scratch: UpdateScratch<MAX_VOICES>,
319 voice_scratch_buffer: Vec<f32>,
320}
321
322impl<V: std::fmt::Debug, const MAX_VOICES: usize> std::fmt::Debug for Poly<V, MAX_VOICES> {
323 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
324 f.debug_struct("Poly")
325 .field("voices", &self.voices)
326 .field("state", &self.state)
327 .finish_non_exhaustive()
328 }
329}
330
331impl<V: Voice, const MAX_VOICES: usize> Poly<V, MAX_VOICES> {
332 #[must_use]
334 pub fn new(environment: &ProcessingEnvironment) -> Self {
335 let voices = (0..MAX_VOICES)
336 .map(|voice_index| {
337 V::new(
338 voice_index,
339 environment.max_samples_per_process_call,
340 environment.sampling_rate,
341 )
342 })
343 .collect();
344 let state = State::new();
345
346 Self {
347 voices,
348 state,
349 update_scratch: Default::default(),
350 voice_scratch_buffer: vec![0f32; environment.max_samples_per_process_call],
351 }
352 }
353
354 pub fn handle_events(&mut self, context: &impl synth::HandleEventsContext) {
358 let poly_events = context.events().filter_map(|data| {
359 EventData::try_from(data).ok().map(|data| Event {
360 sample_offset: 0,
361 data,
362 })
363 });
364
365 for (v, ev) in self.state.clone().dispatch_events(poly_events.clone()) {
366 self.voices[v].handle_event(&ev.data);
367 }
368
369 self.state.update(poly_events, &mut self.update_scratch);
370 }
371
372 pub fn process(
377 &mut self,
378 context: &impl synth::ProcessContext,
379 shared_data: &V::SharedData<'_>,
380 output: &mut impl BufferMut,
381 ) {
382 let params = context.parameters();
383 let poly_events = context
384 .events()
385 .into_iter()
386 .filter_map(|e| Event::try_from(e).ok());
387 self.process_inner(poly_events, params, shared_data, output);
388 }
389
390 fn process_inner(
391 &mut self,
392 events: impl Iterator<Item = Event> + Clone,
393 params: &impl synth::SynthParamBufferStates,
394 shared_data: &V::SharedData<'_>,
395 output: &mut impl BufferMut,
396 ) {
397 let buffer_size: usize = output.num_frames();
398 #[allow(clippy::cast_precision_loss)]
399 let voice_scale = 1f32 / self.voices.len() as f32;
400 let mut voices_with_events = [false; MAX_VOICES];
401 let mut note_changes_infos: [NoteChangesInfo; MAX_VOICES] = std::array::from_fn(|i| {
402 let initial_note_id = self.state.note_id_for_voice(i);
403 NoteChangesInfo {
404 effective_initial_note_id: initial_note_id,
405 has_any_change: false,
406 initial_state_was_off: initial_note_id.is_none(),
407 }
408 });
409 let mut note_changes_infos_updated_sample_offset = [0; MAX_VOICES];
410
411 for (voice_index, event) in self.state.clone().dispatch_events(events.clone()) {
414 voices_with_events[voice_index] = true;
415 match event.data {
416 EventData::NoteOn { data } => {
417 if note_changes_infos[voice_index]
418 .effective_initial_note_id
419 .is_none()
420 || note_changes_infos_updated_sample_offset[voice_index]
421 == event.sample_offset
422 {
423 note_changes_infos[voice_index].effective_initial_note_id = Some(data.id);
424 note_changes_infos_updated_sample_offset[voice_index] = event.sample_offset;
425 } else {
426 note_changes_infos[voice_index].has_any_change = true;
427 }
428 }
429 EventData::NoteOff { .. } => {}
430 }
431 }
432 let mut cleared = false;
433 for (index, voice) in self.voices.iter_mut().enumerate() {
434 if !voices_with_events[index] && voice.quiescent() {
435 voice.skip_samples(buffer_size);
436 self.state.clear_prev_note_id_for_voice(index);
438 continue;
439 }
440 let events_fn = || {
441 self.state
442 .clone()
443 .dispatch_events(events.clone())
444 .filter_map(|(i, event)| if i == index { Some(event) } else { None })
445 };
446 voice.process(
447 &ProcessContextImpl {
448 initial_note_id: self.state.note_id_for_voice(index),
449 events_fn,
450 parameters: params,
451 buffer_size: output.num_frames(),
452 note_changes_info: ¬e_changes_infos[index],
453 },
454 shared_data,
455 &mut self.voice_scratch_buffer[0..output.num_frames()],
456 );
457 mul_constant_in_place(voice_scale, &mut self.voice_scratch_buffer);
458 if cleared {
459 for channel_mut in channels_mut(output) {
460 add_in_place(&self.voice_scratch_buffer[0..buffer_size], channel_mut);
461 }
462 } else {
463 for channel_mut in channels_mut(output) {
464 channel_mut.copy_from_slice(&self.voice_scratch_buffer[0..buffer_size]);
465 }
466 cleared = true;
467 }
468 }
469 if !cleared {
470 for channel_mut in channels_mut(output) {
471 channel_mut.fill(0f32);
472 }
473 }
474 self.state.update(events, &mut self.update_scratch);
475 }
476
477 pub fn reset(&mut self) {
481 for voice in &mut self.voices {
482 voice.reset();
483 }
484 self.state.reset();
485 }
486}