1use super::{
51 Action, BusId, Command, Fade, Mixer, MixerEvent, MixerHandle, Play, Returned, Sound, Voice,
52};
53use crate::{Error, ErrorCode, FadeCurve, Limits, Result};
54use std::sync::Arc;
55use std::sync::atomic::AtomicU64;
56
57pub const MAX_LAYERS: usize = 16;
59
60#[derive(Clone, Copy, Debug, Eq, PartialEq)]
63pub struct TimeSignature {
64 pub numerator: u8,
65 pub denominator: u8,
66}
67
68impl Default for TimeSignature {
69 fn default() -> Self {
70 Self {
71 numerator: 4,
72 denominator: 4,
73 }
74 }
75}
76
77#[derive(Clone, Copy, Debug)]
81pub struct Transport {
82 rate: u32,
83 milli_bpm: u32,
84 signature: TimeSignature,
85 origin: u64,
86}
87
88impl Transport {
89 pub fn new(sample_rate: u32, bpm: f64, signature: TimeSignature, origin: u64) -> Result<Self> {
92 Limits::default().check(0, 1, sample_rate)?;
93 let milli_bpm = crate::timing::milli_bpm(bpm)?;
94 if !(1..=32).contains(&signature.numerator)
95 || !signature.denominator.is_power_of_two()
96 || signature.denominator > 32
97 {
98 return Err(Error::invalid(
99 "Music needs 1..=32 beats and a power-of-two beat unit up to 32",
100 ));
101 }
102 Ok(Self {
103 rate: sample_rate,
104 milli_bpm,
105 signature,
106 origin,
107 })
108 }
109
110 pub fn sample_rate(&self) -> u32 {
111 self.rate
112 }
113 pub fn bpm(&self) -> f64 {
114 self.milli_bpm as f64 / 1000.0
115 }
116 pub fn signature(&self) -> TimeSignature {
117 self.signature
118 }
119 pub fn origin(&self) -> u64 {
120 self.origin
121 }
122
123 pub fn boundary_after(&self, frame: u64, quantize: Quantize) -> Result<u64> {
127 self.boundary(frame, quantize)
128 .ok_or_else(|| Error::limit("Music boundary exceeds the output clock"))
129 }
130
131 fn boundary(&self, frame: u64, quantize: Quantize) -> Option<u64> {
133 if quantize == Quantize::Immediate {
134 return Some(frame);
135 }
136 if frame < self.origin {
137 return Some(self.origin);
138 }
139 let beats = if quantize == Quantize::Bar {
140 self.signature.numerator as u128
141 } else {
142 1
143 };
144 let numerator = self.rate as u128 * 240_000 * beats;
145 let denominator = self.milli_bpm as u128 * self.signature.denominator as u128;
146 let elapsed = (frame - self.origin) as u128;
147 let mut index = elapsed * denominator / numerator + 1;
148 loop {
149 let offset = crate::timing::rounded_ratio(index * numerator, denominator)?;
150 let boundary = self.origin.checked_add(offset)?;
151 if boundary > frame {
152 return Some(boundary);
153 }
154 index += 1;
155 }
156 }
157}
158
159#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
162pub enum Quantize {
163 #[default]
164 Immediate,
165 Beat,
166 Bar,
167}
168
169#[derive(Clone, Copy, Debug, Default)]
172pub struct Timing {
173 pub not_before: u64,
174 pub quantize: Quantize,
175}
176
177impl Timing {
178 pub const NOW: Self = Self {
179 not_before: 0,
180 quantize: Quantize::Immediate,
181 };
182 pub const BEAT: Self = Self {
183 not_before: 0,
184 quantize: Quantize::Beat,
185 };
186 pub const BAR: Self = Self {
187 not_before: 0,
188 quantize: Quantize::Bar,
189 };
190}
191
192pub struct Layer {
194 pub sound: Sound,
195 pub gain: f32,
197 pub intensity: [f32; 2],
200}
201
202impl Layer {
203 pub fn new(sound: impl Into<Sound>) -> Self {
204 Self {
205 sound: sound.into(),
206 gain: 1.0,
207 intensity: [0.0, 0.0],
208 }
209 }
210}
211
212pub struct Track {
216 pub layers: Vec<Layer>,
217 pub looping: super::Loop,
218}
219
220impl Track {
221 pub fn new(sound: impl Into<Sound>) -> Self {
222 Self {
223 layers: vec![Layer::new(sound)],
224 looping: super::Loop::Off,
225 }
226 }
227}
228
229#[derive(Clone, Copy, Debug, Eq, PartialEq)]
231pub struct MusicId(u64);
232
233pub struct Music {
237 owner: u64,
238 clock: Arc<AtomicU64>,
239 transport: Transport,
240 bus: BusId,
241}
242
243impl Music {
244 pub fn new(handle: &mut MixerHandle, transport: Transport, bus: BusId) -> Result<Self> {
245 if transport.rate != handle.rate || bus.0 > handle.buses.len() {
246 return Err(Error::invalid(
247 "Music transport rate and bus must match its mixer",
248 ));
249 }
250 let owner = handle.next_voice;
251 handle.next_voice += 1;
252 Ok(Self {
253 owner,
254 clock: handle.clock.clone(),
255 transport,
256 bus,
257 })
258 }
259
260 pub fn transport(&self) -> Transport {
261 self.transport
262 }
263
264 pub fn transition(
268 &self,
269 handle: &mut MixerHandle,
270 track: Track,
271 intensity: f32,
272 timing: Timing,
273 fade: Fade,
274 ) -> Result<MusicId> {
275 self.check(handle)?;
276 validate_intensity(intensity)?;
277 if track.layers.is_empty() || track.layers.len() > MAX_LAYERS {
278 return Err(Error::invalid("A music track needs 1..=16 layers"));
279 }
280 let (len, curve) = handle.frames(fade)?;
281 let mut rate = None;
282 let mut length = None;
283 let mut voices = Vec::with_capacity(track.layers.len());
284 for layer in track.layers {
285 let [low, high] = layer.intensity;
286 validate_intensity(low)?;
287 validate_intensity(high)?;
288 if low > high || !layer.gain.is_finite() || layer.gain < 0.0 {
289 return Err(Error::invalid(
290 "A layer needs ordered intensity bounds and finite nonnegative gain",
291 ));
292 }
293 let (source_rate, frames) = shape(&layer.sound);
294 if rate.is_some_and(|rate| rate != source_rate)
295 || frames.zip(length).is_some_and(|(a, b)| a != b)
296 {
297 return Err(Error::invalid(
298 "Music layers must share a source rate and known length",
299 ));
300 }
301 rate = Some(source_rate);
302 if frames.is_some() {
303 length = frames;
304 }
305 let state = LayerState {
306 owner: self.owner,
307 active: true,
308 gain: layer.gain as f64,
309 intensity: layer.intensity,
310 };
311 let mut voice = handle.prepare_voice(
312 layer.sound,
313 &Play {
314 bus: self.bus,
315 priority: 255,
316 gain: state.gain_at(intensity) as f32,
317 fade_in: fade,
318 looping: track.looping,
319 ..Play::default()
320 },
321 )?;
322 voice.music = Some(state);
323 voices.push(Some(voice));
324 }
325 self.submit(
326 handle,
327 timing,
328 len,
329 curve,
330 Operation::Transition,
331 intensity,
332 voices,
333 )
334 }
335
336 pub fn set_intensity(
338 &self,
339 handle: &mut MixerHandle,
340 intensity: f32,
341 timing: Timing,
342 fade: Fade,
343 ) -> Result<MusicId> {
344 self.check(handle)?;
345 validate_intensity(intensity)?;
346 let (len, curve) = handle.frames(fade)?;
347 self.submit(
348 handle,
349 timing,
350 len,
351 curve,
352 Operation::Intensity,
353 intensity,
354 Vec::new(),
355 )
356 }
357
358 pub fn cue(
360 &self,
361 handle: &mut MixerHandle,
362 sound: impl Into<Sound>,
363 gain: f32,
364 timing: Timing,
365 ) -> Result<MusicId> {
366 self.check(handle)?;
367 let voice = handle.prepare_voice(
368 sound.into(),
369 &Play {
370 bus: self.bus,
371 gain,
372 priority: 255,
373 ..Play::default()
374 },
375 )?;
376 self.submit(
377 handle,
378 timing,
379 0,
380 FadeCurve::Linear,
381 Operation::Cue,
382 0.0,
383 vec![Some(voice)],
384 )
385 }
386
387 pub fn stop(&self, handle: &mut MixerHandle, timing: Timing, fade: Fade) -> Result<MusicId> {
390 self.check(handle)?;
391 let (len, curve) = handle.frames(fade)?;
392 self.submit(handle, timing, len, curve, Operation::Stop, 0.0, Vec::new())
393 }
394
395 fn check(&self, handle: &MixerHandle) -> Result<()> {
396 if Arc::ptr_eq(&self.clock, &handle.clock) {
397 Ok(())
398 } else {
399 Err(Error::invalid("Music controller belongs to another mixer"))
400 }
401 }
402
403 #[allow(clippy::too_many_arguments)]
404 fn submit(
405 &self,
406 handle: &mut MixerHandle,
407 timing: Timing,
408 len: u64,
409 curve: FadeCurve,
410 operation: Operation,
411 intensity: f32,
412 voices: Vec<Option<Box<Voice>>>,
413 ) -> Result<MusicId> {
414 self.transport
416 .boundary_after(timing.not_before, timing.quantize)?
417 .checked_add(len)
418 .ok_or_else(|| Error::limit("Music fade exceeds the output clock"))?;
419 let id = MusicId(handle.next_voice);
420 handle.next_voice += 1;
421 let request = Box::new(Request {
422 id,
423 owner: self.owner,
424 transport: self.transport,
425 timing,
426 len,
427 curve,
428 operation,
429 intensity,
430 voices,
431 });
432 handle.send(None, Action::Music(request))?;
433 Ok(id)
434 }
435}
436
437fn validate_intensity(intensity: f32) -> Result<()> {
438 if (0.0..=1.0).contains(&intensity) {
439 Ok(())
440 } else {
441 Err(Error::invalid("Intensity must be finite and 0..=1"))
442 }
443}
444
445fn shape(sound: &Sound) -> (u32, Option<u64>) {
446 match sound {
447 Sound::Memory(audio) => (audio.sample_rate(), Some(audio.frames() as u64)),
448 Sound::Stream(stream) => (stream.sample_rate(), stream.frames()),
449 }
450}
451
452#[derive(Clone, Copy)]
453pub(super) struct LayerState {
454 owner: u64,
455 active: bool,
456 gain: f64,
457 intensity: [f32; 2],
458}
459
460impl LayerState {
461 fn gain_at(self, value: f32) -> f64 {
462 let [low, high] = self.intensity;
463 let amount = if value >= high {
464 1.0
465 } else if value <= low {
466 0.0
467 } else {
468 (value as f64 - low as f64) / (high as f64 - low as f64)
469 };
470 self.gain * amount
471 }
472}
473
474#[derive(Clone, Copy, PartialEq)]
475enum Operation {
476 Transition,
477 Cue,
478 Intensity,
479 Stop,
480}
481
482pub(super) struct Request {
483 id: MusicId,
484 owner: u64,
485 transport: Transport,
486 timing: Timing,
487 len: u64,
488 curve: FadeCurve,
489 operation: Operation,
490 intensity: f32,
491 voices: Vec<Option<Box<Voice>>>,
492}
493
494impl Mixer {
495 pub(super) fn quantize_music(&mut self, mut command: Command) -> Option<Command> {
496 if let Action::Music(request) = &command.action {
497 let reference = self.frame.max(request.timing.not_before);
498 let resolved = request
499 .transport
500 .boundary(reference, request.timing.quantize);
501 match resolved.and_then(|frame| frame.checked_add(request.len).map(|_| frame)) {
502 Some(frame) => command.frame = Some(frame),
503 None => {
504 if let Action::Music(request) = command.action {
505 let id = request.id;
506 self.return_music(
507 request,
508 MixerEvent::MusicRejected(id, ErrorCode::LimitExceeded),
509 );
510 }
511 return None;
512 }
513 }
514 }
515 Some(command)
516 }
517
518 pub(super) fn apply_music(&mut self, frame: u64, mut request: Box<Request>) {
519 self.retire_done();
520 let replacing = matches!(request.operation, Operation::Transition | Operation::Stop);
521 let targets = |layer: LayerState| {
522 layer.owner == request.owner && (layer.active || request.operation == Operation::Stop)
523 };
524 let incoming = request.voices.len();
525 let occupied = self.voices.iter().flatten().count();
526 let removed = if replacing {
527 self.voices
528 .iter()
529 .flatten()
530 .filter(|v| {
531 v.music.is_some_and(targets)
532 && (request.len == 0
533 || v.paused()
534 || self.buses[v.bus].paused
535 || self.buses[0].paused)
536 })
537 .count()
538 } else {
539 0
540 };
541 if incoming > 0 && occupied - removed + incoming > self.voice_limit {
542 let id = request.id;
543 return self.return_music(
544 request,
545 MixerEvent::MusicRejected(id, ErrorCode::LimitExceeded),
546 );
547 }
548 if replacing {
549 for slot in 0..self.voices.len() {
550 if self.voices[slot]
551 .as_ref()
552 .is_some_and(|v| v.music.is_some_and(targets))
553 {
554 self.voices[slot]
555 .as_mut()
556 .unwrap()
557 .music
558 .as_mut()
559 .unwrap()
560 .active = false;
561 self.stop_slot(slot, frame, request.len, request.curve);
562 }
563 }
564 self.retire_done();
565 }
566 if request.operation == Operation::Intensity {
567 for voice in self.voices.iter_mut().flatten() {
568 if let Some(layer) = voice.music.filter(|m| m.owner == request.owner && m.active) {
569 voice.gain.retarget(
570 voice.time(frame),
571 layer.gain_at(request.intensity),
572 request.len,
573 request.curve,
574 );
575 }
576 }
577 }
578 for voice in &mut request.voices {
579 if let Some(voice) = voice.take() {
580 self.start(frame, voice);
581 }
582 }
583 let id = request.id;
584 self.return_music(request, MixerEvent::MusicApplied(id, frame));
585 }
586
587 fn return_music(&mut self, request: Box<Request>, event: MixerEvent) {
588 if self.retired.len() < self.retired.capacity() {
589 self.retired.push(Returned {
590 event: Some(event),
591 voice: None,
592 music: Some(request),
593 });
594 }
595 }
596}
597
598#[cfg(test)]
599mod tests {
600 use super::*;
601
602 #[test]
603 fn a_boundary_that_overflows_only_after_receipt_rejects_the_operation() {
604 let (mut mixer, mut handle) = Mixer::new(super::super::MixerConfig::default()).unwrap();
605 let transport = Transport::new(48_000, 120.0, TimeSignature::default(), 0).unwrap();
606 let music = Music::new(&mut handle, transport, BusId::MASTER).unwrap();
607 let request = music.stop(&mut handle, Timing::BEAT, Fade::NONE).unwrap();
608 mixer.frame = u64::MAX;
609 mixer.render(&mut []);
612 assert_eq!(
613 handle.poll_event(),
614 Some(MixerEvent::MusicRejected(request, ErrorCode::LimitExceeded))
615 );
616 assert!(mixer.pending.is_empty());
617 }
618}