1use std::collections::BTreeMap;
5
6use super::SCRATCH_FRAMES;
7use super::ring::{Controller, Renderer, spsc};
8use super::source::AudioSource;
9use crate::dsl::{Node, SoundDoc};
10use crate::edit::{EditOp, apply_ops};
11use crate::patch::Patch;
12use crate::player::Player;
13
14const DECLICK_MS: f32 = 5.0;
17const CROSSFADE_MIN_MS: f32 = 8.0;
19
20#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
23pub struct PatchId(usize);
24
25#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
28pub struct InstanceHandle(pub(super) u64);
29
30#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
33pub struct ParamId {
34 patch: usize,
35 index: usize,
36}
37
38#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
40pub struct LayerId {
41 patch: usize,
42 index: usize,
43}
44
45#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
50pub struct Priority(pub u8);
51
52impl Priority {
53 pub const LOW: Priority = Priority(0);
55 pub const NORMAL: Priority = Priority(64);
57 pub const HIGH: Priority = Priority(128);
59 pub const CRITICAL: Priority = Priority(255);
61}
62
63impl Default for Priority {
64 fn default() -> Self {
65 Priority::NORMAL
66 }
67}
68
69#[derive(Clone, Copy, Debug)]
72pub struct Tween {
73 frames: u32,
74}
75
76impl Tween {
77 pub const IMMEDIATE: Tween = Tween { frames: 0 };
79
80 pub const fn frames(n: u32) -> Self {
82 Tween { frames: n }
83 }
84
85 pub fn ms(ms: f32, sample_rate: u32) -> Self {
87 let f = (ms / 1000.0 * sample_rate as f32).round();
88 Tween {
89 frames: if f > 0.0 { f as u32 } else { 0 },
90 }
91 }
92}
93
94impl Default for Tween {
95 fn default() -> Self {
96 Tween::IMMEDIATE
97 }
98}
99
100#[derive(Clone, Copy)]
102struct Ramp {
103 value: f32,
104 target: f32,
105 step: f32,
106 remaining: u32,
107}
108
109impl Ramp {
110 fn new(v: f32) -> Self {
111 Ramp {
112 value: v,
113 target: v,
114 step: 0.0,
115 remaining: 0,
116 }
117 }
118
119 fn set(&mut self, target: f32, tw: Tween) {
120 self.target = target;
121 if tw.frames == 0 {
122 self.value = target;
123 self.step = 0.0;
124 self.remaining = 0;
125 } else {
126 self.step = (target - self.value) / tw.frames as f32;
127 self.remaining = tw.frames;
128 }
129 }
130
131 fn tick(&mut self) -> f32 {
133 if self.remaining > 0 {
134 self.value += self.step;
135 self.remaining -= 1;
136 if self.remaining == 0 {
137 self.value = self.target;
138 }
139 }
140 self.value
141 }
142
143 fn at_target(&self) -> bool {
144 self.remaining == 0
145 }
146}
147
148pub(super) fn balance(pan: f32) -> (f32, f32) {
151 let l = if pan <= 0.0 { 1.0 } else { 1.0 - pan };
152 let r = if pan >= 0.0 { 1.0 } else { 1.0 + pan };
153 (l, r)
154}
155
156pub(super) struct Instance {
157 pub(super) id: u64,
158 patch: usize,
159 values: BTreeMap<String, f32>,
161 layer_gains: BTreeMap<usize, f32>,
163 player: Player,
164 fading_in: Option<(Player, Ramp)>,
167 gain: Ramp,
168 pan: Ramp,
169 pub(super) stopping: bool,
171 priority: Priority,
173}
174
175pub struct Engine {
200 sample_rate: u32,
201 patches: Vec<Patch>,
202 pub(super) instances: Vec<Instance>,
203 next_id: u64,
204 max_voices: Option<usize>,
206 buf_a: Vec<f32>,
207 buf_b: Vec<f32>,
208}
209
210impl Engine {
211 pub fn new(sample_rate: u32) -> Self {
213 Engine {
214 sample_rate,
215 patches: Vec::new(),
216 instances: Vec::new(),
217 next_id: 1,
218 max_voices: None,
219 buf_a: vec![0.0; SCRATCH_FRAMES * 2],
220 buf_b: vec![0.0; SCRATCH_FRAMES * 2],
221 }
222 }
223
224 pub fn set_max_voices(&mut self, max: usize) {
229 self.max_voices = Some(max.max(1));
230 }
231
232 pub fn max_voices(&self) -> Option<usize> {
234 self.max_voices
235 }
236
237 pub fn sample_rate(&self) -> u32 {
239 self.sample_rate
240 }
241
242 pub fn load(&mut self, doc: &SoundDoc) -> PatchId {
244 self.load_patch(&Patch {
245 doc: doc.clone(),
246 params: Vec::new(),
247 })
248 }
249
250 pub fn load_patch(&mut self, patch: &Patch) -> PatchId {
253 self.patches.push(patch.clone());
254 PatchId(self.patches.len() - 1)
255 }
256
257 pub fn param(&self, patch: PatchId, name: &str) -> Option<ParamId> {
260 self.patches
263 .get(patch.0)?
264 .params
265 .iter()
266 .position(|p| p.name == name)
267 .map(|index| ParamId {
268 patch: patch.0,
269 index,
270 })
271 }
272
273 pub fn layer(&self, patch: PatchId, name: &str) -> Option<LayerId> {
276 match &self.patches.get(patch.0)?.doc.root {
277 Node::Tracks { tracks, .. } => tracks
278 .iter()
279 .position(|t| t.id.as_deref() == Some(name))
280 .map(|index| LayerId {
281 patch: patch.0,
282 index,
283 }),
284 _ => None,
285 }
286 }
287
288 pub fn play(&mut self, patch: PatchId) -> InstanceHandle {
291 self.spawn(patch, false, Priority::NORMAL)
292 }
293
294 pub fn play_looping(&mut self, patch: PatchId) -> InstanceHandle {
297 self.spawn(patch, true, Priority::NORMAL)
298 }
299
300 pub fn play_prioritized(&mut self, patch: PatchId, priority: Priority) -> InstanceHandle {
305 self.spawn(patch, false, priority)
306 }
307
308 pub fn play_looping_prioritized(
311 &mut self,
312 patch: PatchId,
313 priority: Priority,
314 ) -> InstanceHandle {
315 self.spawn(patch, true, priority)
316 }
317
318 pub fn set_priority(&mut self, h: InstanceHandle, priority: Priority) {
320 if let Some(i) = self.instance_mut(h) {
321 i.priority = priority;
322 }
323 }
324
325 fn spawn(&mut self, patch: PatchId, looping: bool, priority: Priority) -> InstanceHandle {
326 if let Some(max) = self.max_voices
328 && !self.make_room(max, priority)
329 {
330 return InstanceHandle(0);
332 }
333 let Some(values) = self.patches.get(patch.0).map(Patch::defaults) else {
336 return InstanceHandle(0);
337 };
338 let doc = self.build_doc(patch.0, &values, &BTreeMap::new());
339 let player = self.new_player(doc, looping, 0);
340 let id = self.next_id;
341 self.next_id += 1;
342 self.instances.push(Instance {
343 id,
344 patch: patch.0,
345 values,
346 layer_gains: BTreeMap::new(),
347 player,
348 fading_in: None,
349 gain: Ramp::new(1.0),
350 pan: Ramp::new(0.0),
351 stopping: false,
352 priority,
353 });
354 InstanceHandle(id)
355 }
356
357 fn make_room(&mut self, max: usize, priority: Priority) -> bool {
363 let sounding = self.instances.iter().filter(|i| !i.stopping).count();
364 if sounding >= max {
365 let victim = self
367 .instances
368 .iter()
369 .filter(|i| !i.stopping)
370 .min_by(|a, b| a.priority.cmp(&b.priority).then(a.id.cmp(&b.id)))
371 .map(|i| (i.id, i.priority));
372 match victim {
373 Some((id, vp)) if vp <= priority => {
374 let fade = Tween::ms(DECLICK_MS, self.sample_rate);
375 if let Some(v) = self.instances.iter_mut().find(|i| i.id == id) {
376 v.gain.set(0.0, fade);
377 v.stopping = true;
378 }
379 }
380 _ => return false,
381 }
382 }
383 if self.instances.len() >= max * 2
385 && let Some(pos) = self
386 .instances
387 .iter()
388 .enumerate()
389 .min_by(|(_, a), (_, b)| a.priority.cmp(&b.priority).then(a.id.cmp(&b.id)))
390 .map(|(pos, _)| pos)
391 {
392 self.instances.remove(pos);
393 }
394 true
395 }
396
397 fn build_doc(
400 &self,
401 patch: usize,
402 values: &BTreeMap<String, f32>,
403 layer_gains: &BTreeMap<usize, f32>,
404 ) -> SoundDoc {
405 let p = &self.patches[patch];
406 let doc = p.instantiate(values).unwrap_or_else(|_| p.doc.clone());
407 if layer_gains.is_empty() {
408 return doc;
409 }
410 let ops: Vec<EditOp> = layer_gains
411 .iter()
412 .map(|(i, g)| EditOp::Set {
413 path: format!("root.tracks[{i}].gain"),
414 value: serde_json::json!(g),
415 })
416 .collect();
417 apply_ops(&doc, &ops).unwrap_or(doc)
418 }
419
420 fn new_player(&self, mut doc: SoundDoc, looping: bool, seek: usize) -> Player {
421 doc.sample_rate = self.sample_rate;
424 let mut player = Player::new(doc);
425 player.looping = looping;
426 player.seek(seek);
427 player.play();
428 player
429 }
430
431 fn instance_mut(&mut self, h: InstanceHandle) -> Option<&mut Instance> {
432 self.instances.iter_mut().find(|i| i.id == h.0)
433 }
434
435 fn find(&self, h: InstanceHandle) -> Option<usize> {
436 self.instances.iter().position(|i| i.id == h.0)
437 }
438
439 pub fn set_gain(&mut self, h: InstanceHandle, gain: f32, tw: Tween) {
441 if let Some(i) = self.instance_mut(h) {
442 i.gain.set(gain.max(0.0), tw);
443 }
444 }
445
446 pub fn set_pan(&mut self, h: InstanceHandle, pan: f32, tw: Tween) {
448 if let Some(i) = self.instance_mut(h) {
449 let pan = if pan.is_nan() {
452 0.0
453 } else {
454 pan.clamp(-1.0, 1.0)
455 };
456 i.pan.set(pan, tw);
457 }
458 }
459
460 pub fn set_param(&mut self, h: InstanceHandle, param: ParamId, value: f32, tw: Tween) {
463 let Some(idx) = self.find(h) else { return };
464 if self.instances[idx].patch != param.patch {
465 return;
466 }
467 let Some(name) = self
470 .patches
471 .get(param.patch)
472 .and_then(|p| p.params.get(param.index))
473 .map(|p| p.name.clone())
474 else {
475 return;
476 };
477 self.instances[idx].values.insert(name, value);
478 self.rerender(idx, tw);
479 }
480
481 pub fn set_layer_gain(&mut self, h: InstanceHandle, layer: LayerId, gain: f32, tw: Tween) {
483 let Some(idx) = self.find(h) else { return };
484 if self.instances[idx].patch != layer.patch {
485 return;
486 }
487 self.instances[idx].layer_gains.insert(layer.index, gain);
488 self.rerender(idx, tw);
489 }
490
491 fn rerender(&mut self, idx: usize, tw: Tween) {
494 let (patch, looping, pos) = {
495 let i = &self.instances[idx];
496 (i.patch, i.player.looping, i.player.position())
497 };
498 let values = self.instances[idx].values.clone();
499 let layer_gains = self.instances[idx].layer_gains.clone();
500 let doc = self.build_doc(patch, &values, &layer_gains);
501 let fresh = self.new_player(doc, looping, pos);
502
503 let fade = if tw.frames == 0 {
504 Tween::ms(CROSSFADE_MIN_MS, self.sample_rate)
505 } else {
506 tw
507 };
508 let inst = &mut self.instances[idx];
509 let outgoing = std::mem::replace(&mut inst.player, fresh);
511 let w0 = inst.fading_in.as_ref().map_or(0.0, |(_, m)| m.value);
517 let mut mix = Ramp::new(w0);
518 mix.set(1.0, fade);
519 inst.fading_in = Some((outgoing, mix));
520 }
521
522 pub fn stop(&mut self, h: InstanceHandle, fade: Tween) {
525 let min_fade = Tween::ms(DECLICK_MS, self.sample_rate);
526 if let Some(i) = self.instance_mut(h) {
527 let fade = if fade.frames == 0 { min_fade } else { fade };
528 i.gain.set(0.0, fade);
529 i.stopping = true;
530 }
531 }
532
533 pub fn active(&self) -> usize {
535 self.instances.len()
536 }
537
538 pub fn is_active(&self, h: InstanceHandle) -> bool {
540 self.instances.iter().any(|i| i.id == h.0)
541 }
542
543 #[cfg(test)]
545 pub(super) fn fade_weight(&self, idx: usize) -> Option<f32> {
546 self.instances
547 .get(idx)?
548 .fading_in
549 .as_ref()
550 .map(|(_, m)| m.value)
551 }
552}
553
554impl AudioSource for Engine {
555 fn fill(&mut self, out: &mut [f32]) -> usize {
556 let frames = out.len() / 2;
557 out.fill(0.0);
558 if frames == 0 {
559 return 0;
560 }
561 if self.buf_a.len() < out.len() {
562 self.buf_a.resize(out.len(), 0.0);
563 self.buf_b.resize(out.len(), 0.0);
564 }
565 let (a, b) = (&mut self.buf_a[..out.len()], &mut self.buf_b[..out.len()]);
566
567 for inst in self.instances.iter_mut() {
568 inst.player.fill(a);
571 if let Some((out_player, _)) = inst.fading_in.as_mut() {
572 out_player.fill(b);
573 }
574 for f in 0..frames {
575 let g = inst.gain.tick();
576 let (lg, rg) = balance(inst.pan.tick());
577 let (mut l, mut r) = (a[f * 2], a[f * 2 + 1]);
578 if let Some((_, mix)) = inst.fading_in.as_mut() {
579 let w = mix.tick();
580 l = l * w + b[f * 2] * (1.0 - w);
581 r = r * w + b[f * 2 + 1] * (1.0 - w);
582 }
583 out[f * 2] += l * g * lg;
584 out[f * 2 + 1] += r * g * rg;
585 }
586 if let Some((_, mix)) = &inst.fading_in
587 && mix.at_target()
588 {
589 inst.fading_in = None; }
591 }
592
593 self.instances.retain(|i| {
597 let outgoing_sounding = i.fading_in.as_ref().is_some_and(|(p, _)| p.playing);
598 (i.player.playing || outgoing_sounding) && !(i.stopping && i.gain.at_target())
599 });
600 frames
601 }
602}
603
604impl Engine {
605 pub fn split(self, ring_frames: usize) -> (Controller, Renderer) {
609 spsc(self, ring_frames)
610 }
611}