Skip to main content

game_toolkit_dosaudio/
engine.rs

1use crate::bus::{Command, TimedCommand};
2#[cfg(feature = "opl")]
3use crate::device::opl::OplVoice;
4#[cfg(feature = "psg")]
5use crate::device::psg::PsgVoice;
6#[cfg(feature = "sb")]
7use crate::device::sb::{DacState, SbControl};
8use crate::device::speaker::SpeakerVoice;
9#[cfg(feature = "mpu401")]
10use crate::midi::voice::MidiVoice;
11use crate::music::{MusicCapabilities, MusicRequest, MusicRuntime, Player, validate_play};
12use crate::{
13    Error, Frame, MusicEvent, MusicHandle, MusicTarget, PlayOptions, PreparedMusic, Result,
14    SoundConfig, Tick,
15};
16use rtrb::Consumer;
17
18/// Observable synthesis counters, independent of callback buffer size.
19#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
20pub struct EngineStats {
21    /// Total output frames produced.
22    pub frames: u64,
23    /// Port commands delivered after their intended output timestamp.
24    pub late_commands: u64,
25}
26
27/// Audio-thread synthesis, also used by offline rendering.
28///
29/// Construction and `play` allocate. `render` neither allocates nor locks. Output
30/// time starts at zero; the host must pace its machine clock against playback.
31/// Late commands take effect at the current output position and are counted.
32/// The configured fixed latency absorbs jitter but does not synchronize clocks.
33pub struct AudioEngine {
34    rate: u32,
35    latency: Tick,
36    native: bool,
37    commands: Consumer<TimedCommand>,
38    speaker: Option<SpeakerVoice>,
39    #[cfg(feature = "mpu401")]
40    midi: Option<MidiVoice>,
41    #[cfg(feature = "psg")]
42    psg: Option<PsgVoice>,
43    #[cfg(feature = "opl")]
44    opl: Option<OplVoice>,
45    #[cfg(feature = "sb")]
46    dac: Option<DacState>,
47    #[cfg(feature = "sb")]
48    dac_filter: [f64; 2],
49    player: Option<Player>,
50    runtime: Option<MusicRuntime>,
51    speaker_player: Option<Player>,
52    speaker_runtime: Option<MusicRuntime>,
53    capture: Option<crate::capture::stream::CaptureWriter>,
54    event_time: Tick,
55    stats: EngineStats,
56}
57
58impl AudioEngine {
59    pub(crate) fn new(config: &SoundConfig, commands: Consumer<TimedCommand>) -> Result<Self> {
60        #[cfg(feature = "mpu401")]
61        let midi = config
62            .midi
63            .as_ref()
64            .map(|backend| MidiVoice::new(backend, config.sample_rate))
65            .transpose()?;
66        Ok(Self {
67            rate: config.sample_rate,
68            latency: config.latency,
69            native: config.native,
70            commands,
71            #[cfg(feature = "mpu401")]
72            midi,
73            #[cfg(feature = "psg")]
74            psg: config.psg.then(PsgVoice::new),
75            speaker: config
76                .speaker
77                .then(|| SpeakerVoice::new(config.sample_rate)),
78            #[cfg(feature = "opl")]
79            opl: config
80                .opl
81                .map(|chip| OplVoice::new(chip, config.backend.clone())),
82            #[cfg(feature = "sb")]
83            dac: config
84                .sb
85                .map(|resources| SbControl::new(resources, config.sb_model, config.dma16).output()),
86            #[cfg(feature = "sb")]
87            dac_filter: [0.0; 2],
88            player: None,
89            runtime: None,
90            speaker_player: None,
91            speaker_runtime: None,
92            capture: None,
93            event_time: Tick::ZERO,
94            stats: EngineStats::default(),
95        })
96    }
97
98    /// Output rate in frames per second.
99    pub fn sample_rate(&self) -> u32 {
100        self.rate
101    }
102    /// Current output time, rounded down to a crystal tick.
103    pub fn now(&self) -> Tick {
104        Tick(
105            (u128::from(self.stats.frames) * u128::from(Tick::HZ_NUMERATOR) / self.denominator())
106                as u64,
107        )
108    }
109    /// Current synthesis counters.
110    pub fn stats(&self) -> EngineStats {
111        self.stats
112    }
113
114    /// Dropped messages reported by a bounded MIDI synthesis backend.
115    pub fn midi_dropped_messages(&self) -> u64 {
116        #[cfg(feature = "mpu401")]
117        if let Some(midi) = &self.midi {
118            return midi.core.dropped_messages();
119        }
120        0
121    }
122
123    /// Capture applied OPL writes from manual I/O, sequencers and live events.
124    /// Create outside rendering; stop is acknowledged at the next frame start.
125    /// The bounded writer stays owned by the engine after capture ends.
126    pub fn start_capture(&mut self, capacity: usize) -> Result<crate::MusicCapture> {
127        if self
128            .capture
129            .as_ref()
130            .is_some_and(|writer| !writer.is_finished())
131        {
132            return Err(Error::Config("synthesis capture is already active"));
133        }
134        #[cfg(feature = "opl")]
135        let chip = self.opl.as_ref().map(|voice| voice.chip);
136        #[cfg(not(feature = "opl"))]
137        let chip: Option<crate::OplChip> = None;
138        let chip = chip.ok_or(Error::Config("synthesis capture requires OPL hardware"))?;
139        let (capture, writer) = crate::capture::stream::stream(chip, self.now(), capacity)?;
140        self.capture = Some(writer);
141        Ok(capture)
142    }
143
144    /// Construct a unique runtime music sender before moving this engine to a stream.
145    /// Allocates two bounded queues. Retired resources are reclaimed by the sender.
146    pub fn music_controller(&mut self, capacity: usize) -> Result<crate::MusicController> {
147        if self.runtime.is_some() {
148            return Err(Error::Config("a music controller already exists"));
149        }
150        let capabilities = self.capabilities();
151        if capabilities.opl.is_none()
152            && !capabilities.midi
153            && !capabilities.psg
154            && !capabilities.speaker
155            && !capabilities.native
156        {
157            return Err(Error::Config("runtime music requires installed hardware"));
158        }
159        let (controller, runtime) =
160            crate::MusicController::new(capabilities, self.rate, self.now(), capacity)?;
161        self.runtime = Some(runtime);
162        Ok(controller)
163    }
164
165    /// Independently sequence a speaker melody or PWM stream alongside FM/MIDI music.
166    /// Construction/replacement allocates only on the caller's thread.
167    /// The melody starts at the engine's current output time, like `play`.
168    pub fn play_speaker(
169        &mut self,
170        music: impl Into<PreparedMusic>,
171        options: PlayOptions,
172    ) -> Result<crate::SpeakerHandle> {
173        self.play_speaker_at(music, options, self.now())
174    }
175
176    /// Start a speaker melody at a future absolute output timestamp, such as
177    /// machine time on a host that renders behind it. Allocates like `play_speaker`.
178    pub fn play_speaker_at(
179        &mut self,
180        music: impl Into<PreparedMusic>,
181        options: PlayOptions,
182        at: Tick,
183    ) -> Result<crate::SpeakerHandle> {
184        if at < self.now() {
185            return Err(Error::BackwardsTime);
186        }
187        let music = music.into();
188        if music.target != MusicTarget::Speaker {
189            return Err(Error::Config("speaker playback requires speaker events"));
190        }
191        validate_play(self.capabilities(), &music, options, at)?;
192        if let Some(old) = self.speaker_player.take() {
193            old.state
194                .finished
195                .store(true, std::sync::atomic::Ordering::Release);
196        }
197        let handle = MusicHandle::new(options.volume, self.rate);
198        self.speaker_player = Some(Player::new(music, options, &handle, at, self.now()));
199        Ok(crate::SpeakerHandle::new(handle))
200    }
201
202    /// Construct a bounded runtime sender dedicated to speaker melodies/PWM.
203    pub fn speaker_controller(&mut self, capacity: usize) -> Result<crate::MusicController> {
204        if self.speaker.is_none() || self.speaker_runtime.is_some() {
205            return Err(Error::Config(
206                "speaker is absent or its controller already exists",
207            ));
208        }
209        let capabilities = MusicCapabilities {
210            opl: None,
211            midi: false,
212            midi_device: None,
213            native: false,
214            psg: false,
215            speaker: true,
216        };
217        let (controller, runtime) =
218            crate::MusicController::new(capabilities, self.rate, self.now(), capacity)?;
219        self.speaker_runtime = Some(runtime);
220        Ok(controller)
221    }
222
223    pub(crate) fn speaker_end(&self) -> Result<Option<Tick>> {
224        let Some(player) = &self.speaker_player else {
225            return Ok(None);
226        };
227        if player.options.looped {
228            return Err(Error::Config(
229                "render_to_end requires finite speaker playback",
230            ));
231        }
232        player
233            .origin
234            .checked_add(player.sequence.duration)
235            .map(Some)
236            .ok_or(Error::TooLarge)
237    }
238
239    fn speaker_next(&self, at: Tick) -> Option<Tick> {
240        let sequence = self.speaker_player.as_ref().and_then(Player::next);
241        let runtime = self
242            .speaker_runtime
243            .as_ref()
244            .and_then(|runtime| runtime.next(at, self.speaker_player.is_some()));
245        sequence.into_iter().chain(runtime).min()
246    }
247
248    fn speaker_due(&mut self, at: Tick) -> bool {
249        if self.speaker_player.is_none() && self.speaker_runtime.is_none() {
250            return false;
251        }
252        std::mem::swap(&mut self.player, &mut self.speaker_player);
253        std::mem::swap(&mut self.runtime, &mut self.speaker_runtime);
254        let changed = self.runtime_due(at);
255        self.sequence_due(at);
256        std::mem::swap(&mut self.player, &mut self.speaker_player);
257        std::mem::swap(&mut self.runtime, &mut self.speaker_runtime);
258        changed
259    }
260
261    /// Install music before handing the engine to an audio stream.
262    ///
263    /// This method allocates the control handle and may drop an earlier sequence.
264    /// The sequence starts at the engine's current output time. OPL3 can play an
265    /// OPL2 sequence; dual OPL2 and OPL3 arrangements require matching hardware.
266    /// Retain the returned handle while playing: its last drop stops the music.
267    pub fn play(
268        &mut self,
269        sequence: impl Into<PreparedMusic>,
270        options: PlayOptions,
271    ) -> Result<MusicHandle> {
272        self.play_at(sequence, options, self.now())
273    }
274
275    /// Install music at a future absolute output timestamp. Allocates like `play`.
276    pub fn play_at(
277        &mut self,
278        sequence: impl Into<PreparedMusic>,
279        options: PlayOptions,
280        at: Tick,
281    ) -> Result<MusicHandle> {
282        if at < self.now() {
283            return Err(Error::BackwardsTime);
284        }
285        let sequence = sequence.into();
286        validate_play(self.capabilities(), &sequence, options, at)?;
287        self.event_time = self.now();
288        self.release_notes();
289        let handle = MusicHandle::new(options.volume, self.rate);
290        let now = self.now();
291        self.player = Some(Player::new(sequence, options, &handle, at, now));
292        Ok(handle)
293    }
294
295    pub(crate) fn music_end(&self) -> Result<Option<Tick>> {
296        let Some(player) = self.player.as_ref() else {
297            return Ok(None);
298        };
299        if player.options.looped {
300            return Err(Error::Config(
301                "offline render_to_end requires non-looping music",
302            ));
303        }
304        player
305            .origin
306            .checked_add(player.sequence.duration)
307            .map(Some)
308            .ok_or(Error::TooLarge)
309    }
310
311    /// Start a soundtrack and prepare a switchable handle before streaming.
312    pub fn play_soundtrack(
313        &mut self,
314        soundtrack: std::sync::Arc<crate::Soundtrack>,
315        preferred: crate::MusicDevice,
316        options: PlayOptions,
317    ) -> Result<MusicHandle> {
318        let (handle, player) = crate::music::soundtrack::player(
319            soundtrack,
320            preferred,
321            self.capabilities(),
322            self.rate,
323            options,
324            self.now(),
325            self.now(),
326        )?;
327        self.release_notes();
328        if let Some(old) = self.player.replace(player) {
329            old.state
330                .finished
331                .store(true, std::sync::atomic::Ordering::Release);
332        }
333        Ok(handle)
334    }
335
336    fn switch_music_device(&mut self) {
337        use std::sync::atomic::Ordering;
338        let Some(player) = &self.player else {
339            return;
340        };
341        let requested = player.state.switch_request.swap(u32::MAX, Ordering::AcqRel);
342        let Some(device) = crate::MusicDevice::from_code(requested) else {
343            return;
344        };
345        if player.ended || player.state.device.load(Ordering::Relaxed) == requested {
346            return;
347        }
348        let position = Tick(
349            self.event_time
350                .0
351                .saturating_sub(player.origin.0)
352                .min(player.sequence.duration.0),
353        );
354        self.release_notes();
355        let player = self.player.as_mut().expect("active soundtrack");
356        let arrangement = player
357            .soundtrack
358            .as_ref()
359            .expect("switchable handle")
360            .find(device)
361            .expect("validated route");
362        let checkpoint = arrangement
363            .checkpoints
364            .partition_point(|c| c.at <= position)
365            .saturating_sub(1);
366        player.sequence = arrangement.music.clone();
367        player.index = arrangement.checkpoints[checkpoint].index;
368        let len = arrangement.checkpoints[checkpoint].events.len();
369        #[cfg(feature = "opl")]
370        if matches!(
371            self.player.as_ref().unwrap().sequence.target,
372            MusicTarget::Opl(crate::OplChip::Opl2)
373        ) && self
374            .opl
375            .as_ref()
376            .is_some_and(|o| o.chip == crate::OplChip::Opl3)
377        {
378            self.write_opl(0x105, 0);
379        }
380
381        for index in 0..len {
382            let event = self
383                .player
384                .as_ref()
385                .unwrap()
386                .soundtrack
387                .as_ref()
388                .unwrap()
389                .find(device)
390                .unwrap()
391                .checkpoints[checkpoint]
392                .events[index];
393            self.music_event(event);
394        }
395        self.player
396            .as_ref()
397            .unwrap()
398            .state
399            .device
400            .store(requested, Ordering::Release);
401    }
402
403    fn native_sample(&self, start: u128, denominator: u128) -> Frame {
404        let Some(player) = &self.player else {
405            return [0.0; 2];
406        };
407        if player
408            .state
409            .stopped
410            .load(std::sync::atomic::Ordering::Relaxed)
411            || player.was_paused
412        {
413            return [0.0; 2];
414        }
415        let Some(pcm) = &player.sequence.pcm else {
416            return [0.0; 2];
417        };
418        let elapsed = start.saturating_sub(u128::from(player.origin.0) * denominator);
419        let frame = elapsed * u128::from(pcm.rate) * u128::from(Tick::HZ_DENOMINATOR)
420            / (denominator * u128::from(Tick::HZ_NUMERATOR));
421        pcm.frames.get(frame as usize).copied().unwrap_or([0.0; 2])
422    }
423    fn soundtrack_mutes(&self, target: MusicTarget) -> bool {
424        self.player.as_ref().is_some_and(|p| {
425            p.soundtrack.is_some()
426                && p.sequence.target != target
427                && !(matches!(target, MusicTarget::Opl(_))
428                    && matches!(p.sequence.target, MusicTarget::Opl(_)))
429        })
430    }
431
432    fn denominator(&self) -> u128 {
433        u128::from(self.rate) * u128::from(Tick::HZ_DENOMINATOR)
434    }
435
436    fn capabilities(&self) -> MusicCapabilities {
437        #[cfg(feature = "opl")]
438        let opl = self.opl.as_ref().map(|voice| voice.chip);
439        #[cfg(not(feature = "opl"))]
440        let opl = None;
441        MusicCapabilities {
442            opl,
443            midi: {
444                #[cfg(feature = "mpu401")]
445                {
446                    self.midi.is_some()
447                }
448                #[cfg(not(feature = "mpu401"))]
449                {
450                    false
451                }
452            },
453            midi_device: {
454                #[cfg(feature = "mpu401")]
455                {
456                    self.midi.as_ref().map(|m| m.device)
457                }
458                #[cfg(not(feature = "mpu401"))]
459                {
460                    None
461                }
462            },
463            psg: {
464                #[cfg(feature = "psg")]
465                {
466                    self.psg.is_some()
467                }
468                #[cfg(not(feature = "psg"))]
469                {
470                    false
471                }
472            },
473            speaker: self.speaker.is_some(),
474            native: self.native,
475        }
476    }
477
478    fn runtime_next(&self, now: Tick) -> Option<Tick> {
479        self.runtime
480            .as_ref()
481            .and_then(|runtime| runtime.next(now, self.player.is_some()))
482    }
483
484    fn runtime_due(&mut self, at: Tick) -> bool {
485        let mut changed = false;
486        while self.runtime_next(at).is_some_and(|next| next <= at) {
487            let request = self.runtime.as_mut().expect("scheduled runtime").pop();
488            if request.at.is_some_and(|stamp| stamp < at) {
489                self.stats.late_commands += 1;
490            }
491            match request.request {
492                MusicRequest::Event(event) => self.music_event(event),
493                MusicRequest::Replace(mut player) => {
494                    self.release_notes();
495                    if let Some(old) = self.player.take() {
496                        old.state
497                            .finished
498                            .store(true, std::sync::atomic::Ordering::Release);
499                        self.runtime
500                            .as_mut()
501                            .expect("scheduled runtime")
502                            .retire(old);
503                    }
504                    player.origin = at;
505                    player.last = at;
506                    if at.checked_add(player.sequence.duration).is_none() {
507                        player.ended = true;
508                        player
509                            .state
510                            .finished
511                            .store(true, std::sync::atomic::Ordering::Release);
512                    }
513                    player.prepare(at);
514                    self.player = Some(player);
515                    self.switch_music_device();
516                    self.sequence_due(at);
517                    changed = true;
518                }
519            }
520        }
521        changed
522    }
523
524    fn sequence_due(&mut self, at: Tick) {
525        while self
526            .player
527            .as_ref()
528            .and_then(Player::next)
529            .is_some_and(|next| next <= at)
530        {
531            let player = self.player.as_mut().expect("scheduled player");
532            let event = player.pop();
533            let ended = player.ended;
534            // Published for `SpeakerHandle::tone`; PWM levels have no pitch.
535            match event {
536                Some(MusicEvent::Speaker { divisor, enabled }) => {
537                    let divisor = match (enabled, divisor) {
538                        (false, _) => 0,
539                        (true, 0) => 65_536,
540                        (true, divisor) => u32::from(divisor),
541                    };
542                    player
543                        .state
544                        .tone
545                        .store(divisor, std::sync::atomic::Ordering::Relaxed);
546                }
547                Some(MusicEvent::SpeakerLevel(_)) => player
548                    .state
549                    .tone
550                    .store(0, std::sync::atomic::Ordering::Relaxed),
551                _ => {}
552            }
553            if let Some(event) = event {
554                self.music_event(event);
555            }
556            if ended {
557                self.release_notes();
558            }
559        }
560    }
561    fn command_time(&self, command: &TimedCommand) -> Tick {
562        command.at.checked_add(self.latency).unwrap_or(Tick::MAX)
563    }
564
565    fn apply(&mut self, command: Command) {
566        match command {
567            #[cfg(feature = "mpu401")]
568            Command::MidiByte(byte) => {
569                if let Some(midi) = &mut self.midi {
570                    midi.byte(byte);
571                }
572            }
573            #[cfg(feature = "psg")]
574            Command::Psg(byte) => {
575                if let Some(psg) = &mut self.psg {
576                    psg.write(byte);
577                }
578            }
579
580            #[cfg(feature = "sb")]
581            Command::Dac(state) => self.dac = Some(state),
582            Command::Speaker(mut state) => {
583                state.epoch = state.epoch.checked_add(self.latency).unwrap_or(Tick::MAX);
584                if let Some(speaker) = &mut self.speaker {
585                    speaker.state = state;
586                }
587            }
588            #[cfg(feature = "opl")]
589            Command::Opl { register, value } => {
590                self.write_opl(register, value);
591            }
592            #[cfg(feature = "opl")]
593            Command::OplPair { first, second } => {
594                self.write_opl(first.0, first.1);
595                self.write_opl(second.0, second.1);
596            }
597        }
598    }
599
600    fn music_event(&mut self, event: MusicEvent) {
601        match event {
602            MusicEvent::SpeakerLevel(high) => {
603                if let Some(speaker) = &mut self.speaker {
604                    speaker.state.gate = false;
605                    speaker.state.data = high;
606                    speaker.state.epoch = self.event_time;
607                }
608            }
609
610            MusicEvent::Speaker { divisor, enabled } => {
611                if let Some(speaker) = &mut self.speaker {
612                    speaker.state.divisor = if divisor == 0 {
613                        65_536
614                    } else {
615                        u32::from(divisor)
616                    };
617                    speaker.state.epoch = self.event_time;
618                    speaker.state.mode = 3;
619                    speaker.state.gate = enabled;
620                    speaker.state.data = enabled;
621                }
622            }
623            MusicEvent::Midi(message) => {
624                #[cfg(feature = "mpu401")]
625                if let Some(midi) = &mut self.midi {
626                    midi.message(message.bytes());
627                }
628                #[cfg(not(feature = "mpu401"))]
629                let _ = message;
630            }
631            MusicEvent::SysEx { offset, length } => {
632                #[cfg(feature = "mpu401")]
633                if let (Some(midi), Some(player)) = (&mut self.midi, &self.player) {
634                    midi.message(
635                        &player.sequence.sysex[offset as usize..offset as usize + length as usize],
636                    );
637                }
638                #[cfg(not(feature = "mpu401"))]
639                let _ = (offset, length);
640            }
641            MusicEvent::Psg(byte) => {
642                #[cfg(feature = "psg")]
643                if let Some(psg) = &mut self.psg {
644                    psg.write(byte);
645                }
646                #[cfg(not(feature = "psg"))]
647                let _ = byte;
648            }
649
650            MusicEvent::Opl { register, value } => {
651                #[cfg(feature = "opl")]
652                self.write_opl(register, value);
653                #[cfg(not(feature = "opl"))]
654                let _ = (register, value);
655            }
656        }
657    }
658
659    #[cfg(feature = "opl")]
660    fn write_opl(&mut self, register: u16, value: u8) {
661        if let Some(opl) = &mut self.opl {
662            if let Some(capture) = &mut self.capture {
663                capture.record(self.event_time, register, value);
664            }
665            opl.write(register, value);
666        }
667    }
668
669    fn release_notes(&mut self) {
670        if self
671            .player
672            .as_ref()
673            .is_some_and(|p| p.sequence.target == MusicTarget::Native)
674        {
675            return;
676        }
677
678        if self
679            .player
680            .as_ref()
681            .is_some_and(|player| player.sequence.target == MusicTarget::Midi)
682        {
683            #[cfg(feature = "mpu401")]
684            if let Some(midi) = &mut self.midi {
685                midi.release();
686            }
687            return;
688        }
689        if self.player.as_ref().is_some_and(|player| {
690            matches!(
691                player.sequence.target,
692                MusicTarget::Psg | MusicTarget::OplPsg(_)
693            )
694        }) {
695            #[cfg(feature = "psg")]
696            if let Some(psg) = &mut self.psg {
697                psg.mute();
698            }
699            if self
700                .player
701                .as_ref()
702                .is_some_and(|player| player.sequence.target == MusicTarget::Psg)
703            {
704                return;
705            }
706        }
707
708        if self
709            .player
710            .as_ref()
711            .is_some_and(|player| player.sequence.target == MusicTarget::Speaker)
712        {
713            if let Some(speaker) = &mut self.speaker {
714                speaker.state.data = false;
715            }
716        } else {
717            #[cfg(feature = "opl")]
718            if let Some(opl) = &mut self.opl {
719                let capture = &mut self.capture;
720                let at = self.event_time;
721                opl.release_notes(|register, value| {
722                    if let Some(capture) = capture {
723                        capture.record(at, register, value);
724                    }
725                });
726            }
727        }
728    }
729
730    /// Overwrite a buffer with the next stereo frames, without allocating.
731    ///
732    /// Each sample interval is split at port commands, music events and native
733    /// OPL boundaries. Speaker pulses, held DSP DAC values and native FM frames are integrated
734    /// over those intervals, including commands inside a single output sample.
735    /// Commands at the sample's end belong to the following sample.
736    pub fn render(&mut self, out: &mut [Frame]) {
737        let denominator = self.denominator();
738        for frame in out {
739            let start = u128::from(self.stats.frames) * u128::from(Tick::HZ_NUMERATOR);
740            let end = start + u128::from(Tick::HZ_NUMERATOR);
741            self.event_time = Tick((start / denominator) as u64);
742            if let Some(capture) = &mut self.capture {
743                capture.boundary(self.event_time);
744            }
745            if let Some(player) = &mut self.player
746                && player.prepare(Tick((start / denominator) as u64))
747            {
748                self.release_notes();
749            }
750            if let Some(player) = &mut self.speaker_player
751                && player.prepare(self.event_time)
752                && let Some(speaker) = &mut self.speaker
753            {
754                speaker.state.data = false;
755            }
756            self.switch_music_device();
757            let mut speaker_gain = self.speaker_player.as_mut().map_or(1.0, Player::gain);
758            let mut gain = self.player.as_mut().map_or(1.0, Player::gain);
759            let mut cursor = start;
760            let mut speaker_area = 0.0;
761            #[cfg(any(feature = "mpu401", feature = "psg"))]
762            let mut extra_area = [0.0f64; 2];
763            #[cfg(not(any(feature = "mpu401", feature = "psg")))]
764            let extra_area = [0.0f64; 2];
765            #[cfg(feature = "sb")]
766            let mut dac_area = [0.0; 2];
767            #[cfg(feature = "opl")]
768            let mut opl_area = [0.0f64; 2];
769            #[cfg(not(feature = "opl"))]
770            let opl_area = [0.0f64; 2];
771            while cursor < end {
772                self.event_time = Tick((cursor / denominator) as u64);
773                if self.runtime_due(Tick((cursor / denominator) as u64)) {
774                    gain = self.player.as_mut().map_or(1.0, Player::gain);
775                }
776                if self.speaker_due(self.event_time) {
777                    speaker_gain = self.speaker_player.as_mut().map_or(1.0, Player::gain);
778                }
779                while let Ok(command) = self.commands.peek() {
780                    let stamp = u128::from(self.command_time(command).0) * denominator;
781                    if stamp > cursor {
782                        break;
783                    }
784                    if stamp < cursor {
785                        self.stats.late_commands += 1;
786                    }
787                    let command = self.commands.pop().expect("peeked command");
788                    self.apply(command.command);
789                }
790                while self
791                    .player
792                    .as_ref()
793                    .and_then(Player::next)
794                    .is_some_and(|at| u128::from(at.0) * denominator <= cursor)
795                {
796                    let player = self.player.as_mut().expect("scheduled player");
797                    let event = player.pop();
798                    let ended = player.ended;
799                    if let Some(event) = event {
800                        self.music_event(event);
801                    }
802                    if ended {
803                        self.release_notes();
804                    }
805                }
806                #[cfg(feature = "opl")]
807                if let Some(opl) = &mut self.opl
808                    && u128::from(opl.next.0) * denominator <= cursor
809                {
810                    opl.step();
811                }
812                #[cfg(feature = "mpu401")]
813                if let Some(midi) = &mut self.midi
814                    && u128::from(midi.next.0) * denominator <= cursor
815                {
816                    midi.step();
817                }
818                #[cfg(feature = "psg")]
819                if let Some(psg) = &mut self.psg
820                    && u128::from(psg.next.0) * denominator <= cursor
821                {
822                    psg.step();
823                }
824                let mut next = end;
825                if let Some(at) = self.speaker_next(self.event_time) {
826                    next = next.min(u128::from(at.0) * denominator);
827                }
828                if let Some(at) = self.runtime_next(Tick((cursor / denominator) as u64)) {
829                    next = next.min(u128::from(at.0) * denominator);
830                }
831                if let Ok(command) = self.commands.peek() {
832                    next = next.min(u128::from(self.command_time(command).0) * denominator);
833                }
834                if let Some(at) = self.player.as_ref().and_then(Player::next) {
835                    next = next.min(u128::from(at.0) * denominator);
836                }
837                #[cfg(feature = "opl")]
838                if let Some(opl) = &self.opl {
839                    next = next.min(u128::from(opl.next.0) * denominator);
840                }
841                #[cfg(feature = "mpu401")]
842                if let Some(midi) = &self.midi {
843                    next = next.min(u128::from(midi.next.0) * denominator);
844                }
845                #[cfg(feature = "psg")]
846                if let Some(psg) = &self.psg {
847                    next = next.min(u128::from(psg.next.0) * denominator);
848                }
849                // A producer may publish an already-late command between peeks.
850                if next <= cursor {
851                    continue;
852                }
853                let from = cursor as f64 / denominator as f64;
854                let to = next as f64 / denominator as f64;
855                if let Some(speaker) = &self.speaker {
856                    speaker_area += speaker.state.integral(from, to);
857                }
858                #[cfg(feature = "sb")]
859                if let Some(dac) = self.dac {
860                    for (area, sample) in dac_area.iter_mut().zip(dac.output()) {
861                        *area += sample * (to - from);
862                    }
863                }
864                #[cfg(feature = "opl")]
865                if let Some(opl) = &self.opl {
866                    for (channel, (area, value)) in opl_area.iter_mut().zip(opl.held).enumerate() {
867                        #[cfg(feature = "sb")]
868                        let value = f64::from(value)
869                            * f64::from(self.dac.map_or(1.0, |dac| dac.fm_gain[channel]));
870                        #[cfg(not(feature = "sb"))]
871                        let value = {
872                            let _ = channel;
873                            f64::from(value)
874                        };
875                        let value = if self.soundtrack_mutes(MusicTarget::Opl(opl.chip)) {
876                            0.0
877                        } else {
878                            value
879                        };
880                        *area += value
881                            * (to - from)
882                            * if self.runtime.is_some()
883                                && self.player.as_ref().is_none_or(|p| {
884                                    matches!(
885                                        p.sequence.target,
886                                        MusicTarget::Opl(_) | MusicTarget::OplPsg(_)
887                                    )
888                                })
889                            {
890                                gain
891                            } else {
892                                1.0
893                            };
894                    }
895                }
896                #[cfg(feature = "mpu401")]
897                if let Some(midi) = &self.midi {
898                    let gain = if self
899                        .player
900                        .as_ref()
901                        .is_some_and(|p| p.sequence.target == MusicTarget::Midi)
902                    {
903                        gain
904                    } else {
905                        1.0
906                    };
907                    for (area, sample) in extra_area.iter_mut().zip(midi.held) {
908                        *area += f64::from(sample)
909                            * (to - from)
910                            * gain
911                            * if self.soundtrack_mutes(MusicTarget::Midi) {
912                                0.0
913                            } else {
914                                1.0
915                            };
916                    }
917                }
918                #[cfg(feature = "psg")]
919                if let Some(psg) = &self.psg {
920                    let gain = if self.player.as_ref().is_some_and(|p| {
921                        matches!(p.sequence.target, MusicTarget::Psg | MusicTarget::OplPsg(_))
922                    }) {
923                        gain
924                    } else {
925                        1.0
926                    };
927                    for (area, sample) in extra_area.iter_mut().zip(psg.held) {
928                        *area += f64::from(sample)
929                            * (to - from)
930                            * gain
931                            * if self.soundtrack_mutes(MusicTarget::Psg) {
932                                0.0
933                            } else {
934                                1.0
935                            };
936                    }
937                }
938                cursor = next;
939            }
940            let width = Tick::HZ_NUMERATOR as f64 / denominator as f64;
941            let speaker = self
942                .speaker
943                .as_mut()
944                .map_or(0.0, |speaker| speaker.filter(speaker_area / width));
945            let speaker_gain = if self.speaker_player.is_some() {
946                speaker_gain
947            } else if self
948                .player
949                .as_ref()
950                .is_some_and(|p| p.sequence.target == MusicTarget::Speaker)
951            {
952                gain
953            } else {
954                1.0
955            };
956            let speaker = speaker
957                * speaker_gain as f32
958                * if self.speaker_player.is_none() && self.soundtrack_mutes(MusicTarget::Speaker) {
959                    0.0
960                } else {
961                    1.0
962                };
963            let native = self
964                .native_sample(start, denominator)
965                .map(|s| s * gain as f32);
966            #[cfg(feature = "sb")]
967            let pcm = core::array::from_fn::<_, 2, _>(|channel| {
968                let mean = dac_area[channel] / width;
969                let hz = self.dac.map_or(0, |dac| dac.filter_hz);
970                if hz != 0 {
971                    let alpha =
972                        1.0 - (-std::f64::consts::TAU * f64::from(hz) / f64::from(self.rate)).exp();
973                    self.dac_filter[channel] += alpha * (mean - self.dac_filter[channel]);
974                    self.dac_filter[channel] as f32
975                } else {
976                    self.dac_filter[channel] = mean;
977                    mean as f32
978                }
979            });
980            #[cfg(not(feature = "sb"))]
981            let pcm = [0.0; 2];
982            let gain = if self.runtime.is_some()
983                || self.player.as_ref().is_some_and(|p| {
984                    !matches!(
985                        p.sequence.target,
986                        MusicTarget::Opl(_) | MusicTarget::OplPsg(_)
987                    )
988                }) {
989                1.0
990            } else {
991                gain
992            };
993            *frame = [
994                (speaker
995                    + pcm[0]
996                    + (extra_area[0] / width) as f32
997                    + native[0]
998                    + (opl_area[0] / width * gain) as f32)
999                    .clamp(-1.0, 1.0),
1000                (speaker
1001                    + pcm[1]
1002                    + (extra_area[1] / width) as f32
1003                    + native[1]
1004                    + (opl_area[1] / width * gain) as f32)
1005                    .clamp(-1.0, 1.0),
1006            ];
1007            self.stats.frames += 1;
1008            if let Some(runtime) = &self.runtime {
1009                runtime.publish_time(self.now());
1010            }
1011            if let Some(runtime) = &self.speaker_runtime {
1012                runtime.publish_time(self.now());
1013            }
1014        }
1015    }
1016}
1017
1018#[cfg(feature = "toolkit")]
1019impl game_toolkit_audio::StereoStream for AudioEngine {
1020    fn fill(&mut self, out: &mut [[f32; 2]]) {
1021        self.render(out);
1022    }
1023}