1use super::chip::{ChipPlan, ChipVoice};
2use super::controls::{
3 self, ControlEvent, ControlId, ControlParameter, ControlReport, ControlState, ControlTarget,
4 MAX_SYNTH_CONTROLS, Resolved, Values,
5};
6use super::drum::{DrumPlan, DrumVoice};
7use super::prepared::{BeatClock, FrameScore, TempoMap};
8use super::score::{Instrument, Score};
9use super::synthwave::{WidePlan, WideVoice};
10use super::wave::{Aliasing, Duty, Oscillator, Waveform};
11use crate::math::cents_to_hz;
12use crate::{Audio, Error, Limits, Result};
13
14#[derive(Clone, Copy, Debug, PartialEq)]
15pub struct RenderOptions {
16 pub aliasing: Aliasing,
17 pub loops: Option<u32>,
20 pub gain: f32,
23}
24impl Default for RenderOptions {
25 fn default() -> Self {
26 Self {
27 aliasing: Aliasing::BandLimited,
28 loops: Some(0),
29 gain: 0.25,
30 }
31 }
32}
33
34impl Score {
35 pub fn render(&self, sample_rate: u32) -> Result<Audio> {
37 self.render_with(sample_rate, &RenderOptions::default())
38 }
39 pub fn render_with(&self, sample_rate: u32, options: &RenderOptions) -> Result<Audio> {
41 self.render_channels(sample_rate, options, false)
42 }
43 pub fn render_stereo(&self, sample_rate: u32) -> Result<Audio> {
46 self.render_stereo_with(sample_rate, &RenderOptions::default())
47 }
48 pub fn render_stereo_with(&self, sample_rate: u32, options: &RenderOptions) -> Result<Audio> {
49 self.render_channels(sample_rate, options, true)
50 }
51 fn render_channels(
52 &self,
53 sample_rate: u32,
54 options: &RenderOptions,
55 stereo: bool,
56 ) -> Result<Audio> {
57 let mut synth = if stereo {
58 Synth::new_stereo(self, sample_rate, options)?
59 } else {
60 Synth::new(self, sample_rate, options)?
61 };
62 let frames = synth
63 .max_frames()
64 .ok_or_else(|| Error::invalid("Cannot render an endless loop offline"))?;
65 let frames = usize::try_from(frames).map_err(|_| Error::limit("Song is too long"))?;
66 let channels = synth.channels();
67 Limits::default().check(frames, channels, sample_rate)?;
68 let mut samples = vec![0.0; frames * channels];
69 let written = synth.fill(&mut samples);
70 samples.truncate(written * channels);
71 if channels == 1 {
72 Audio::new(vec![samples], sample_rate)
73 } else {
74 Audio::new(
75 (0..channels)
76 .map(|c| samples.iter().skip(c).step_by(channels).copied().collect())
77 .collect(),
78 sample_rate,
79 )
80 }
81 }
82}
83
84#[derive(Clone, Debug)]
85struct Plan {
86 wide: Option<WidePlan>,
87 voices: usize,
90 chip: Option<ChipPlan>,
91 drum: Option<DrumPlan>,
92 waveform: Waveform,
93 attack: u64,
94 decay: u64,
95 sustain: f64,
96 release: u64,
97 volume: f64,
98 slide: f64,
99 vibrato: Option<(f64, u64, u64)>,
100 arpeggio: Option<(Vec<i8>, u64)>,
101 duty_sweep: Option<(Vec<Duty>, u64)>,
102}
103impl Plan {
104 fn control_base(&self, parameter: ControlParameter) -> Option<f64> {
105 use ControlParameter::*;
106 match parameter {
107 Expression => Some(1.0),
108 Pan => Some(
109 self.wide
110 .as_ref()
111 .map_or(0.0, |p| p.settings.pan as f64 / 100.0),
112 ),
113 _ => {
114 let s = self.wide.as_ref()?.settings;
115 Some(match parameter {
116 CutoffHz => s.filter?.cutoff_hz as f64,
117 ResonanceQ => s.filter?.resonance as f64 / 100.0,
118 PitchCents => 0.0,
119 PitchLfoDepth => s.pitch_lfo?.depth as f64,
120 CutoffLfoDepth => s.cutoff_lfo?.depth as f64,
121 AmplitudeLfoDepth => s.amplitude_lfo?.depth as f64,
122 PanLfoDepth => s.pan_lfo?.depth as f64,
123 _ => unreachable!(),
124 })
125 }
126 }
127 }
128 fn new(instrument: &Instrument, rate: u32, tempo: u16, ticks: u16) -> Self {
129 let samples = |ms: u32| (ms as u64 * rate as u64 + 500) / 1000;
130 let step = |ms: u32| samples(ms).max(1);
131 let e = instrument.envelope;
132 Self {
133 wide: instrument
136 .synthwave
137 .filter(|_| instrument.chip.is_none())
138 .map(|s| WidePlan::new(s, rate, tempo)),
139 voices: instrument.synthwave.map_or(0, |s| s.voices as usize),
140 chip: instrument
141 .chip
142 .as_ref()
143 .map(|chip| ChipPlan::new(chip, rate, tempo, ticks)),
144 drum: instrument.drum.map(|d| DrumPlan::new(d, rate)),
145 waveform: instrument.waveform,
146 attack: samples(e.attack_ms),
147 decay: samples(e.decay_ms),
148 sustain: e.sustain as f64 / 100.0,
149 release: samples(e.release_ms),
150 volume: instrument.volume as f64 / 15.0,
151 slide: instrument.slide as f64 / rate as f64,
152 vibrato: instrument
153 .vibrato
154 .map(|v| (v.depth_cents as f64, step(v.period_ms), samples(v.delay_ms))),
155 arpeggio: instrument
156 .arpeggio
157 .as_ref()
158 .map(|a| (a.offsets.clone(), step(a.step_ms))),
159 duty_sweep: instrument
160 .duty_sweep
161 .as_ref()
162 .map(|d| (d.duties.clone(), step(d.step_ms))),
163 }
164 }
165 fn modulated(&self) -> bool {
166 self.slide != 0.0 || self.vibrato.is_some() || self.arpeggio.is_some()
167 }
168 fn cents(&self, base: f64, elapsed: u64) -> f64 {
169 let mut cents = base + self.slide * elapsed as f64;
170 if let Some((depth, period, delay)) = self.vibrato {
171 if elapsed >= delay {
172 let phase = ((elapsed - delay) % period) as f64 / period as f64;
173 cents += depth * crate::math::sin_turns(phase);
174 }
175 }
176 if let Some((offsets, step)) = &self.arpeggio {
177 cents += offsets[(elapsed / step) as usize % offsets.len()] as f64 * 100.0;
178 }
179 cents
180 }
181 fn waveform(&self, elapsed: u64) -> Waveform {
182 match (&self.duty_sweep, self.waveform) {
183 (Some((duties, step)), Waveform::Pulse(_)) if elapsed >= *step => {
184 let index = (elapsed / step - 1).min(duties.len() as u64 - 1) as usize;
185 Waveform::Pulse(duties[index])
186 }
187 (_, waveform) => waveform,
188 }
189 }
190}
191
192#[derive(Clone, Copy, Debug)]
194struct Event {
195 note: usize,
196 on: u64,
197 off: u64,
198 cents: f64,
199 instrument: usize,
200 amplitude: f64,
201}
202
203#[derive(Clone, Copy, Debug, PartialEq)]
204enum Stage {
205 Idle,
206 Attack,
207 Decay,
208 Sustain,
209 Release,
210}
211
212#[derive(Clone, Debug)]
213struct Voice {
214 wide: WideVoice,
215 started: u64,
216 chip: ChipVoice,
217 drum: DrumVoice,
218 drumming: bool,
219 oscillator: Oscillator,
220 event: Option<Event>,
221 held: bool,
222 stage: Stage,
223 position: u64,
224 from: f64,
225 level: f64,
226 elapsed: u64,
227 frequency: f64,
228 beat_start: Option<u128>,
229}
230impl Voice {
231 fn new(aliasing: Aliasing) -> Self {
232 Self {
233 wide: WideVoice::new(),
234 started: 0,
235 chip: ChipVoice::new(aliasing),
236 drum: DrumVoice::new(aliasing),
237 drumming: false,
238 oscillator: Oscillator::new(aliasing),
239 event: None,
240 held: false,
241 stage: Stage::Idle,
242 position: 0,
243 from: 0.0,
244 level: 0.0,
245 elapsed: 0,
246 frequency: 0.0,
247 beat_start: None,
248 }
249 }
250 fn start(&mut self, event: Event, plans: &[Plan]) {
251 let active = self.active();
252 let legato = plans[event.instrument]
253 .wide
254 .as_ref()
255 .is_some_and(|p| p.settings.legato)
256 && active
257 && self.held
258 && self
259 .event
260 .is_some_and(|old| old.instrument == event.instrument && old.off == event.on);
261 let plan = &plans[event.instrument];
265 if plan.voices > 0 && (!active || plan.wide.is_none()) {
266 self.level = 0.0;
267 }
268 if let Some(plan) = &plans[event.instrument].wide {
269 let gliding = active
272 && self
273 .event
274 .is_some_and(|old| plans[old.instrument].wide.is_some());
275 self.wide.trigger(plan, event.cents, gliding, legato);
276 }
277 self.event = Some(event);
278 self.held = true;
279 if legato {
280 return;
281 }
282 self.stage = Stage::Attack;
283 self.position = 0;
284 self.from = self.level;
285 self.elapsed = 0;
286 self.beat_start = None;
287 self.frequency = cents_to_hz(event.cents);
288 if plans[event.instrument].chip.is_some() {
289 self.chip.reset();
290 }
291 self.drumming = plans[event.instrument].drum.is_some();
292 if let Some(drum) = &plans[event.instrument].drum {
293 self.level = 0.0;
294 self.drum
295 .trigger(drum, crate::math::exp2((event.cents + 900.0) / 1200.0));
296 }
297 }
298 fn release(&mut self, plans: &[Plan]) {
299 self.held = false;
300 self.wide.release();
301 if self.drumming {
302 return;
303 }
304 if self.stage != Stage::Idle {
305 let release = self.event.map_or(0, |e| plans[e.instrument].release);
306 self.stage = if release == 0 {
307 Stage::Idle
308 } else {
309 Stage::Release
310 };
311 self.position = 0;
312 self.from = self.level;
313 }
314 }
315 fn envelope(&mut self, plan: &Plan) -> f64 {
316 let level = loop {
317 let p = self.position as f64;
318 match self.stage {
319 Stage::Attack if self.position < plan.attack => {
320 break self.from + (1.0 - self.from) * p / plan.attack as f64;
321 }
322 Stage::Attack => (self.stage, self.position) = (Stage::Decay, 0),
323 Stage::Decay if self.position < plan.decay => {
324 break 1.0 - (1.0 - plan.sustain) * p / plan.decay as f64;
325 }
326 Stage::Decay => self.stage = Stage::Sustain,
327 Stage::Sustain => break plan.sustain,
328 Stage::Release if self.position < plan.release => {
329 break self.from * (1.0 - p / plan.release as f64);
330 }
331 Stage::Release | Stage::Idle => break 0.0,
332 }
333 };
334 self.position += 1;
335 if self.stage == Stage::Release && self.position >= plan.release {
336 self.stage = Stage::Idle;
337 self.level = 0.0;
338 } else {
339 self.level = level;
340 }
341 level
342 }
343 fn sample(
344 &mut self,
345 plans: &[Plan],
346 rate: u32,
347 musical: Option<BeatClock>,
348 controls: Option<Values>,
349 ) -> f64 {
350 let Some(event) = self.event else {
351 return 0.0;
352 };
353 let plan = &plans[event.instrument];
354 let expression = controls.and_then(|c| c.get(ControlParameter::Expression));
355 let express = |sample| expression.map_or(sample, |gain| sample * gain);
356 if let Some(drum) = &plan.drum {
357 return express(self.drum.sample(drum) * event.amplitude * plan.volume);
358 }
359 if self.stage == Stage::Idle {
360 return 0.0;
361 }
362 let level = self.envelope(plan);
363 let frequency = if plan.modulated() {
364 cents_to_hz(plan.cents(event.cents, self.elapsed))
365 } else {
366 self.frequency
367 };
368 let waveform = plan.waveform(self.elapsed);
369 if let Some(chip) = &plan.chip {
370 let relative = musical.map(|clock| BeatClock {
371 numerator: clock.numerator - *self.beat_start.get_or_insert(clock.numerator),
372 ..clock
373 });
374 let frequency = chip.frequency(frequency, self.elapsed, relative);
375 let (sample, finished) =
376 self.chip
377 .sample(chip, waveform, frequency, self.elapsed, relative);
378 let gain = chip.gain(
379 level * event.amplitude * plan.volume,
380 self.elapsed,
381 relative,
382 );
383 self.elapsed += 1;
384 if finished {
385 self.stage = Stage::Idle;
386 self.level = 0.0;
387 }
388 return express(sample * gain);
389 }
390 self.elapsed += 1;
391 express(
392 self.oscillator.next(waveform, frequency, rate) * level * event.amplitude * plan.volume,
393 )
394 }
395 fn active(&self) -> bool {
396 if self.drumming {
397 self.drum.active()
398 } else {
399 self.stage != Stage::Idle
400 }
401 }
402 fn sample_stereo(
403 &mut self,
404 plans: &[Plan],
405 rate: u32,
406 clock: u64,
407 musical: Option<BeatClock>,
408 controls: Option<Values>,
409 ) -> [f64; 2] {
410 let Some(event) = self.event else {
411 return [0.0; 2];
412 };
413 let plan = &plans[event.instrument];
414 let Some(wide) = &plan.wide else {
415 let sample = self.sample(plans, rate, musical, controls);
416 return controls
417 .and_then(|c| c.get(ControlParameter::Pan))
418 .map_or([sample; 2], |pan| {
419 [sample * (1.0 - pan.max(0.0)), sample * (1.0 + pan.min(0.0))]
420 });
421 };
422 if self.stage == Stage::Idle {
423 return [0.0; 2];
424 }
425 let gain = self.envelope(plan) * event.amplitude * plan.volume;
426 let offset = plan.cents(0.0, self.elapsed);
427 let mut result = self.wide.sample(
428 wide,
429 plan.waveform(self.elapsed),
430 offset,
431 clock,
432 musical,
433 controls,
434 );
435 if let Some(expression) = controls.and_then(|c| c.get(ControlParameter::Expression)) {
436 for value in &mut result {
437 *value *= expression;
438 }
439 }
440 self.elapsed += 1;
441 for value in &mut result {
442 *value *= gain;
443 }
444 result
445 }
446}
447
448#[derive(Clone, Debug)]
449struct Track {
450 events: Vec<Event>,
451 next: usize,
452 loop_next: usize,
454 loop_carry: Option<Event>,
455 voice: Voice,
456 poly: Vec<Voice>,
457 poly_carry: Vec<Event>,
458}
459impl Track {
460 fn trigger(&mut self, event: Event, plans: &[Plan], clock: u64) {
461 let limit = plans[event.instrument].voices;
462 if limit > 0 {
463 if self.voice.held {
464 self.voice.release(plans);
465 }
466 for voice in self.poly.iter_mut().skip(limit) {
467 voice.stage = Stage::Idle;
468 voice.held = false;
469 voice.level = 0.0;
470 }
471 let voices = &mut self.poly[..limit];
472 let slot = voices.iter().position(|v| !v.active()).unwrap_or_else(|| {
473 voices
474 .iter()
475 .enumerate()
476 .min_by_key(|(i, v)| (v.held, v.started, *i))
477 .unwrap()
478 .0
479 });
480 voices[slot].start(event, plans);
481 voices[slot].started = clock;
482 } else {
483 for voice in &mut self.poly {
484 if voice.held {
485 voice.release(plans);
486 }
487 }
488 self.voice.start(event, plans);
489 }
490 }
491 fn release(&mut self, plans: &[Plan]) {
492 if self.voice.held {
493 self.voice.release(plans);
494 }
495 for voice in &mut self.poly {
496 if voice.held {
497 voice.release(plans);
498 }
499 }
500 }
501 fn active(&self) -> bool {
502 self.voice.active() || self.poly.iter().any(Voice::active)
503 }
504}
505
506#[derive(Clone, Debug)]
519pub struct Synth {
520 rate: u32,
521 channels: usize,
522 aliasing: Aliasing,
523 gain: f64,
524 plans: Vec<Plan>,
525 tracks: Vec<Track>,
526 song_end: u64,
527 loop_start: u64,
528 loops: Option<u32>,
529 loops_left: Option<u32>,
530 position: u64,
531 ending: bool,
532 finished: bool,
533 produced: u64,
534 max_release: u64,
535 tempo_map: Option<TempoMap>,
536 controls: Option<ControlState>,
537}
538impl Synth {
539 pub fn new(score: &Score, sample_rate: u32, options: &RenderOptions) -> Result<Self> {
540 Self::with_channels(score, sample_rate, options, 1)
541 }
542 pub fn new_stereo(score: &Score, sample_rate: u32, options: &RenderOptions) -> Result<Self> {
545 Self::with_channels(score, sample_rate, options, 2)
546 }
547 fn with_channels(
548 score: &Score,
549 sample_rate: u32,
550 options: &RenderOptions,
551 channels: usize,
552 ) -> Result<Self> {
553 Limits::default().check(0, 1, sample_rate)?;
554 if !options.gain.is_finite() {
555 return Err(Error::invalid("Gain must be finite"));
556 }
557 let to_samples = |tick: u64| {
558 (tick as u128 * sample_rate as u128 * 60
559 / (score.tempo() as u128 * score.ticks_per_quarter() as u128)) as u64
560 };
561 let plans: Vec<Plan> = score
562 .instruments()
563 .iter()
564 .map(|(_, i)| Plan::new(i, sample_rate, score.tempo(), score.ticks_per_quarter()))
565 .collect();
566 let song_end = to_samples(score.length_ticks());
567 let loop_start = to_samples(score.loop_tick().unwrap_or(0));
568 if options.loops.is_none() && loop_start >= song_end {
569 return Err(Error::invalid(
570 "Cannot loop forever over an empty loop section",
571 ));
572 }
573 let tracks = score
574 .channels()
575 .iter()
576 .map(|channel| {
577 let events: Vec<Event> = channel
578 .notes()
579 .iter()
580 .enumerate()
581 .map(|(note, n)| {
582 let on = to_samples(n.start);
583 let end = to_samples(n.start + n.length);
584 Event {
585 note,
586 on,
587 off: on + (end - on) * n.gate as u64 / 8,
588 cents: (n.pitch.midi() as f64 - 69.0) * 100.0,
589 instrument: n.instrument,
590 amplitude: if n.velocity == 127 {
591 n.volume as f64 / 15.0
592 } else {
593 (n.volume as f64 / 15.0) * (n.velocity as f64 / 127.0)
594 },
595 }
596 })
597 .collect();
598 let loop_next = events.partition_point(|e| e.on < loop_start);
599 let loop_carry = loop_next
600 .checked_sub(1)
601 .map(|i| events[i])
602 .filter(|e| {
603 e.off > loop_start
604 && plans[e.instrument].drum.is_none()
605 && plans[e.instrument].voices == 0
606 })
607 .map(|e| Event {
608 on: loop_start,
609 ..e
610 });
611 let poly_count = events
612 .iter()
613 .map(|e| plans[e.instrument].voices)
614 .max()
615 .unwrap_or(0);
616 let poly_carry = events
617 .iter()
618 .filter(|e| {
619 e.on < loop_start && e.off > loop_start && plans[e.instrument].voices > 0
620 })
621 .map(|e| Event {
622 on: loop_start,
623 ..*e
624 })
625 .collect();
626 Track {
627 events,
628 next: 0,
629 loop_next,
630 loop_carry,
631 voice: Voice::new(options.aliasing),
632 poly: (0..poly_count)
633 .map(|_| Voice::new(options.aliasing))
634 .collect(),
635 poly_carry,
636 }
637 })
638 .collect();
639 Ok(Self {
640 rate: sample_rate,
641 channels,
642 aliasing: options.aliasing,
643 gain: options.gain as f64,
644 max_release: plans
645 .iter()
646 .map(|p| p.release.max(p.drum.as_ref().map_or(0, |d| d.frames)))
647 .max()
648 .unwrap_or(0),
649 plans,
650 tracks,
651 song_end,
652 loop_start,
653 loops: options.loops,
654 loops_left: options.loops,
655 position: 0,
656 ending: false,
657 finished: false,
658 produced: 0,
659 tempo_map: None,
660 controls: None,
661 })
662 }
663 pub fn from_frames(
683 score: &FrameScore,
684 map: &TempoMap,
685 options: &RenderOptions,
686 ) -> Result<Self> {
687 Self::from_frame_channels(score, map, options, 1)
688 }
689 pub fn from_frames_stereo(
693 score: &FrameScore,
694 map: &TempoMap,
695 options: &RenderOptions,
696 ) -> Result<Self> {
697 Self::from_frame_channels(score, map, options, 2)
698 }
699 fn from_frame_channels(
700 score: &FrameScore,
701 map: &TempoMap,
702 options: &RenderOptions,
703 channels: usize,
704 ) -> Result<Self> {
705 score.validate(map)?;
706 if options.loops != Some(0) || !options.gain.is_finite() {
707 return Err(Error::invalid(
708 "Prepared synth requires finite gain and no implicit loops",
709 ));
710 }
711 let rate = map.sample_rate();
712 let plans: Vec<_> = score
715 .instruments
716 .iter()
717 .map(|i| Plan::new(i, rate, 120, map.ticks_per_quarter()))
718 .collect();
719 let tracks = score
720 .tracks
721 .iter()
722 .map(|notes| {
723 let events: Vec<_> = notes
724 .iter()
725 .enumerate()
726 .map(|(note, n)| Event {
727 note,
728 on: n.on,
729 off: n.off,
730 cents: (n.pitch.midi() as f64 - 69.0) * 100.0,
731 instrument: n.instrument,
732 amplitude: if n.velocity == 127 {
733 n.volume as f64 / 15.0
734 } else {
735 (n.volume as f64 / 15.0) * (n.velocity as f64 / 127.0)
736 },
737 })
738 .collect();
739 let count = events
740 .iter()
741 .map(|e| plans[e.instrument].voices)
742 .max()
743 .unwrap_or(0);
744 Track {
745 events,
746 next: 0,
747 loop_next: 0,
748 loop_carry: None,
749 poly_carry: Vec::new(),
750 voice: Voice::new(options.aliasing),
751 poly: (0..count).map(|_| Voice::new(options.aliasing)).collect(),
752 }
753 })
754 .collect();
755 Ok(Self {
756 rate,
757 channels,
758 aliasing: options.aliasing,
759 gain: options.gain as f64,
760 max_release: plans
761 .iter()
762 .map(|p| p.release.max(p.drum.as_ref().map_or(0, |d| d.frames)))
763 .max()
764 .unwrap_or(0),
765 plans,
766 tracks,
767 song_end: score.end_frame,
768 loop_start: 0,
769 loops: Some(0),
770 loops_left: Some(0),
771 position: 0,
772 ending: false,
773 finished: false,
774 produced: 0,
775 tempo_map: Some(map.clone()),
776 controls: None,
777 })
778 }
779 pub fn prepare_controls(&mut self, events: &[ControlEvent], capacity: usize) -> Result<()> {
787 if self.produced != 0 || self.finished {
788 return Err(Error::invalid(
789 "Prepare synth controls before playback or after reset",
790 ));
791 }
792 if capacity > MAX_SYNTH_CONTROLS || events.len() > capacity {
793 return Err(Error::limit("Synth control capacity exceeded"));
794 }
795 if capacity == 0 {
796 self.controls = None;
797 return Ok(());
798 }
799 if self.loops != Some(0) {
800 return Err(Error::invalid("Synth controls require no implicit loops"));
801 }
802 let mut used = vec![false; self.tracks.len() * self.plans.len()];
803 for (t, track) in self.tracks.iter().enumerate() {
804 for event in &track.events {
805 used[t * self.plans.len() + event.instrument] = true;
806 }
807 }
808 let mut state =
809 ControlState::new(used, self.tracks.iter().map(|t| t.poly.len() + 1), capacity)?;
810 let entries: Vec<_> = events
811 .iter()
812 .copied()
813 .enumerate()
814 .map(|(id, event)| (ControlId(id as u32), event))
815 .collect();
816 state.queue = controls::compile(&entries, capacity, |event| {
817 self.resolve_control(&state, event)
818 })?;
819 self.controls = Some(state);
820 Ok(())
821 }
822 pub fn schedule_control(&mut self, event: ControlEvent) -> Result<ControlId> {
828 let state = self
829 .controls
830 .as_ref()
831 .ok_or_else(|| Error::limit("No synth control capacity prepared"))?;
832 if state.queue.len() >= state.capacity {
833 return Err(Error::limit("Synth control plan is full"));
834 }
835 if event.frame < self.produced || self.finished {
836 return Err(Error::invalid(
837 "Cannot schedule a past/finished synth control",
838 ));
839 }
840 let id = ControlId(state.queue.len() as u32);
841 let mut entries: Vec<_> = state.queue.iter().map(|c| (c.id, c.event)).collect();
842 let index = entries.partition_point(|(_, e)| e.frame <= event.frame);
843 entries.insert(index, (id, event));
844 let queue = controls::compile(&entries, state.capacity, |event| {
845 self.resolve_control(state, event)
846 })?;
847 self.controls.as_mut().unwrap().queue = queue;
848 Ok(id)
849 }
850 pub fn controls_len(&self) -> usize {
851 self.controls.as_ref().map_or(0, |s| s.queue.len())
852 }
853 pub fn controls_capacity(&self) -> usize {
854 self.controls.as_ref().map_or(0, |s| s.capacity)
855 }
856 pub fn poll_control_event(&mut self) -> Option<ControlReport> {
859 self.controls.as_mut()?.poll()
860 }
861 pub fn control_value(&self, target: ControlTarget, parameter: ControlParameter) -> Result<f64> {
865 let state = self
866 .controls
867 .as_ref()
868 .ok_or_else(|| Error::invalid("No synth controls prepared"))?;
869 let (resolved, base) = self.resolve_control_target(state, target, parameter)?;
870 if let Some((track, note)) = resolved.note {
871 let slot = std::iter::once(&self.tracks[track].voice)
872 .chain(&self.tracks[track].poly)
873 .position(|v| v.active() && v.event.is_some_and(|e| e.note == note))
874 .ok_or_else(|| Error::invalid("Synth control note is inactive"))?;
875 Ok(state
876 .values(resolved.group, track, slot, self.produced)
877 .get(parameter)
878 .unwrap_or(base))
879 } else {
880 Ok(state
881 .group_value(resolved.group, parameter, self.produced)
882 .unwrap_or(base))
883 }
884 }
885 fn resolve_control_target(
886 &self,
887 state: &ControlState,
888 target: ControlTarget,
889 parameter: ControlParameter,
890 ) -> Result<(Resolved, f64)> {
891 let (track, instrument, note) = match target {
892 ControlTarget::Instrument { track, instrument } => (track, instrument, None),
893 ControlTarget::Note { track, note } => {
894 let event = self
895 .tracks
896 .get(track)
897 .and_then(|t| t.events.get(note))
898 .ok_or_else(|| Error::invalid("Missing synth control note"))?;
899 (track, event.instrument, Some((track, note)))
900 }
901 };
902 if track >= self.tracks.len()
903 || instrument >= self.plans.len()
904 || !state.used[track * self.plans.len() + instrument]
905 {
906 return Err(Error::invalid("Missing synth control track/instrument"));
907 }
908 let base = self.plans[instrument]
909 .control_base(parameter)
910 .ok_or_else(|| Error::invalid("Control is unsupported by this instrument"))?;
911 Ok((
912 Resolved {
913 group: track * self.plans.len() + instrument,
914 note,
915 },
916 base,
917 ))
918 }
919 fn resolve_control(
920 &self,
921 state: &ControlState,
922 event: ControlEvent,
923 ) -> Result<(Resolved, f64)> {
924 let end = event
925 .frame
926 .checked_add(event.ramp_frames)
927 .ok_or_else(|| Error::limit("Synth ramp frame overflow"))?;
928 if event.frame >= self.song_end || end > self.song_end {
929 return Err(Error::invalid("Synth control exceeds the body"));
930 }
931 let resolved = self.resolve_control_target(state, event.target, event.parameter)?;
932 if let Some((track, note)) = resolved.0.note {
933 let n = self.tracks[track].events[note];
934 if event.frame < n.on || event.frame >= n.off {
935 return Err(Error::invalid(
936 "Note control must start inside its authored gate",
937 ));
938 }
939 }
940 Ok(resolved)
941 }
942 pub fn sample_rate(&self) -> u32 {
943 self.rate
944 }
945 pub fn channels(&self) -> usize {
947 self.channels
948 }
949 pub fn is_finished(&self) -> bool {
950 self.finished
951 }
952 pub fn frames_rendered(&self) -> u64 {
954 self.produced
955 }
956 pub fn max_frames(&self) -> Option<u64> {
958 let body = self.song_end.saturating_sub(self.loop_start);
959 self.loops.map(|n| {
960 (n as u64)
961 .saturating_mul(body)
962 .saturating_add(self.song_end + self.max_release + 1)
963 })
964 }
965 pub fn reset(&mut self) {
967 if let Some(controls) = &mut self.controls {
968 controls.reset();
969 }
970 for track in &mut self.tracks {
971 track.next = 0;
972 track.voice = Voice::new(self.aliasing);
973 for voice in &mut track.poly {
974 *voice = Voice::new(self.aliasing);
975 }
976 }
977 self.loops_left = self.loops;
978 self.position = 0;
979 self.ending = false;
980 self.finished = false;
981 self.produced = 0;
982 }
983 pub fn fill(&mut self, out: &mut [f32]) -> usize {
986 let frames = out.len() / self.channels;
987 for (written, slot) in out.chunks_exact_mut(self.channels).enumerate() {
988 if self.finished {
989 return written;
990 }
991 if !self.ending {
992 self.schedule();
993 }
994 if self.ending && self.tracks.iter().all(|t| !t.active()) {
995 self.finished = true;
996 return written;
997 }
998 let mut sum = 0.0;
999 let mut right = 0.0;
1000 let musical = self.tempo_map.as_ref().map(|map| map.clock(self.produced));
1001 if let Some(state) = &mut self.controls {
1002 for (t, track) in self.tracks.iter().enumerate() {
1003 for (slot, voice) in
1004 std::iter::once(&track.voice).chain(&track.poly).enumerate()
1005 {
1006 state.sync(
1007 t,
1008 slot,
1009 voice.event.filter(|_| voice.active()).map(|e| e.note),
1010 );
1011 }
1012 }
1013 state.advance(self.produced);
1014 }
1015 for (t, track) in self.tracks.iter_mut().enumerate() {
1016 let values = |slot: usize, instrument: usize| {
1017 self.controls.as_ref().and_then(|s| {
1018 s.values(t * self.plans.len() + instrument, t, slot, self.produced)
1019 .some()
1020 })
1021 };
1022 let control = track.voice.event.and_then(|e| values(0, e.instrument));
1023 if control.is_some() {
1024 let [l, r] = track.voice.sample_stereo(
1025 &self.plans,
1026 self.rate,
1027 self.produced,
1028 musical,
1029 control,
1030 );
1031 if self.channels == 1 {
1032 sum += (l + r) * 0.5;
1033 } else {
1034 sum += l;
1035 right += r;
1036 }
1037 } else {
1038 let legacy = track.voice.sample(&self.plans, self.rate, musical, None);
1039 sum += legacy;
1040 right += legacy;
1041 }
1042 for (slot, voice) in track.poly.iter_mut().enumerate() {
1043 let control = voice.event.and_then(|e| values(slot + 1, e.instrument));
1044 let [l, r] = voice.sample_stereo(
1045 &self.plans,
1046 self.rate,
1047 self.produced,
1048 musical,
1049 control,
1050 );
1051 if self.channels == 1 {
1052 sum += (l + r) * 0.5;
1053 } else {
1054 sum += l;
1055 right += r;
1056 }
1057 }
1058 }
1059 slot[0] = (sum * self.gain) as f32;
1060 if self.channels == 2 {
1061 slot[1] = (right * self.gain) as f32;
1062 }
1063 self.position += 1;
1064 self.produced += 1;
1065 }
1066 frames
1067 }
1068 fn schedule(&mut self) {
1069 if self.position >= self.song_end {
1070 for track in &mut self.tracks {
1071 track.release(&self.plans);
1072 }
1073 let can_loop = self.loop_start < self.song_end && self.loops_left != Some(0);
1074 if !can_loop {
1075 self.ending = true;
1076 return;
1077 }
1078 if let Some(n) = &mut self.loops_left {
1079 *n -= 1;
1080 }
1081 self.position = self.loop_start;
1082 for track in &mut self.tracks {
1083 track.next = track.loop_next;
1084 if let Some(event) = track.loop_carry {
1085 track.voice.start(event, &self.plans);
1086 }
1087 for i in 0..track.poly_carry.len() {
1089 let event = track.poly_carry[i];
1090 track.trigger(event, &self.plans, self.produced);
1091 }
1092 }
1093 }
1094 let position = self.position;
1095 for track in &mut self.tracks {
1096 while let Some(&event) = track.events.get(track.next) {
1097 if event.on > position {
1098 break;
1099 }
1100 track.trigger(event, &self.plans, self.produced);
1101 track.next += 1;
1102 }
1103 let voice = &mut track.voice;
1104 if voice.held && voice.event.is_some_and(|e| e.off <= position) {
1105 voice.release(&self.plans);
1106 }
1107 for voice in &mut track.poly {
1108 if voice.held && voice.event.is_some_and(|e| e.off <= position) {
1109 voice.release(&self.plans);
1110 }
1111 }
1112 }
1113 }
1114}
1115
1116#[cfg(test)]
1117mod tests {
1118 use super::*;
1119 #[test]
1120 fn scheduled_sweeps_preserve_oscillator_filter_and_envelope_state() {
1121 use super::super::ControlCurve;
1122 let score=Score::parse("tempo 120\ninstrument pad voices=1 wave=saw adsr=1000,0,100,40 filter=lowpass cutoff=1000 filter-adsr=1000,0,100,40 filter-env=1200\nchannel a=@pad c1").unwrap();
1123 let mut source = Synth::new_stereo(&score, 1000, &Default::default()).unwrap();
1124 let mut reference = source.clone();
1125 source.prepare_controls(&[], 4).unwrap();
1126 let mut a = [0.0; 800];
1127 let mut b = [0.0; 800];
1128 source.fill(&mut a);
1129 reference.fill(&mut b);
1130 assert_eq!(a, b);
1131 let before = format!("{:?}", source.tracks[0].poly[0]);
1132 for (parameter, value) in [
1133 (ControlParameter::CutoffHz, 6000.0),
1134 (ControlParameter::Expression, 0.2),
1135 ] {
1136 source
1137 .schedule_control(ControlEvent {
1138 frame: 400,
1139 target: ControlTarget::Instrument {
1140 track: 0,
1141 instrument: 0,
1142 },
1143 parameter,
1144 value,
1145 ramp_frames: 400,
1146 curve: ControlCurve::Linear,
1147 })
1148 .unwrap();
1149 }
1150 assert_eq!(
1151 format!("{:?}", source.tracks[0].poly[0]),
1152 before,
1153 "submission changes no DSP state"
1154 );
1155 let mut a = [0.0; 2];
1156 let mut b = [0.0; 2];
1157 source.fill(&mut a);
1158 reference.fill(&mut b);
1159 assert_eq!(a, b, "ramp start retains the current parameter values");
1160 assert_eq!(
1161 format!("{:?}", source.tracks[0].poly[0].wide),
1162 format!("{:?}", reference.tracks[0].poly[0].wide),
1163 "filter histories and oscillator phases retain their exact state"
1164 );
1165 source.fill(&mut [0.0; 798]);
1166 assert_eq!(source.tracks[0].poly[0].elapsed, 800);
1167 assert_eq!(source.tracks[0].poly[0].position, 800);
1168 assert_eq!(source.tracks[0].poly[0].stage, Stage::Attack);
1169 assert_eq!(
1170 source
1171 .control_value(
1172 ControlTarget::Instrument {
1173 track: 0,
1174 instrument: 0
1175 },
1176 ControlParameter::CutoffHz
1177 )
1178 .unwrap(),
1179 6000.0
1180 );
1181 }
1182 #[test]
1183 fn legato_keeps_envelopes_but_short_gates_and_gaps_retrigger() {
1184 for gate in [4, 8] {
1185 let score=Score::parse(&format!("tempo 600\ninstrument p voices=1 legato=on glide=50 adsr=200,0,100,20 filter=lowpass cutoff=1000 filter-adsr=200,0,100,20\nchannel a=@p q{gate} a4 b4 r4 c4")).unwrap();
1186 let mut synth = Synth::new(&score, 1000, &Default::default()).unwrap();
1187 synth.fill(&mut [0.0; 126]);
1188 let voice = &synth.tracks[0].poly[0];
1189 assert_eq!(voice.position, if gate == 8 { 126 } else { 26 });
1190 synth.fill(&mut [0.0; 175]);
1191 let voice = &synth.tracks[0].poly[0];
1192 assert_eq!(voice.position, 1);
1193 assert!(voice.held);
1194 }
1195 }
1196 #[test]
1197 fn stealing_reuses_oldest_release_and_its_new_note_off() {
1198 let score = Score::parse(
1199 "tempo 600\ninstrument p voices=2 adsr=0,0,100,500\nchannel a=@p a4 b4 c4 d4",
1200 )
1201 .unwrap();
1202 let mut synth = Synth::new(&score, 1000, &Default::default()).unwrap();
1203 synth.fill(&mut [0.0; 201]);
1204 let voices = &synth.tracks[0].poly;
1205 assert_eq!(voices.len(), 2);
1206 assert_eq!(voices[0].event.unwrap().cents, -900.0); assert!(voices[0].held);
1208 assert!(!voices[1].held);
1209 synth.fill(&mut [0.0; 50]);
1210 assert!(synth.tracks[0].poly[0].held);
1211 }
1212 #[test]
1213 fn reducing_and_restoring_a_voice_limit_restarts_retired_slots_from_silence() {
1214 let score=Score::parse("tempo 600\ninstrument wide voices=3 adsr=100,0,100,0\ninstrument narrow voices=1 adsr=100,0,100,0\nchannel a=@wide {c e g}4 @narrow c4 @wide {c e g}4").unwrap();
1215 let mut synth = Synth::new(&score, 1000, &Default::default()).unwrap();
1216 synth.fill(&mut [0.0; 201]);
1217 for v in &synth.tracks[0].poly[1..] {
1218 assert_eq!(v.from, 0.0);
1219 assert_eq!(v.level, 0.0);
1220 assert_eq!(v.position, 1);
1221 }
1222 }
1223}