1#[cfg(feature = "mpu401")]
2use crate::device::mpu401::MpuControl;
3#[cfg(feature = "opl")]
4use crate::device::opl::OplControl;
5#[cfg(feature = "sb")]
6use crate::device::sb::{DacState, SbControl};
7use crate::device::speaker::{SpeakerControl, SpeakerState};
8#[cfg(feature = "sb")]
9use crate::dma::playback::{MemoryPlan, MemoryPlayback};
10#[cfg(feature = "sb")]
11use crate::sfx::{PcmPlan, PcmPlayback};
12use crate::{
13 AudioEngine, Capture, Error, IoDevice, PortRange, PortWrite, Result, SoundConfig, Tick,
14};
15use rtrb::Producer;
16
17#[derive(Clone, Copy, Debug)]
18pub(crate) enum Command {
19 Speaker(SpeakerState),
20 #[cfg(feature = "mpu401")]
21 MidiByte(u8),
22 #[cfg(feature = "psg")]
23 Psg(u8),
24 #[cfg(feature = "sb")]
25 Dac(DacState),
26 #[cfg(feature = "opl")]
27 Opl {
28 register: u16,
29 value: u8,
30 },
31 #[cfg(feature = "opl")]
32 OplPair {
33 first: (u16, u8),
34 second: (u16, u8),
35 },
36}
37
38#[derive(Clone, Copy, Debug)]
39pub(crate) struct TimedCommand {
40 pub at: Tick,
41 pub command: Command,
42}
43
44#[cfg(feature = "sb")]
45#[derive(Clone, Copy)]
46enum AutomaticPlan {
47 Buffer(PcmPlan),
48 Memory(MemoryPlan),
49}
50#[cfg(feature = "sb")]
51impl AutomaticPlan {
52 fn sb(self) -> SbControl {
53 match self {
54 Self::Buffer(p) => p.sb,
55 Self::Memory(p) => p.sb,
56 }
57 }
58 fn commands(self) -> usize {
59 match self {
60 Self::Buffer(p) => p.commands,
61 Self::Memory(p) => p.commands,
62 }
63 }
64}
65
66pub struct DosAudio {
75 now: Tick,
76 ports: Vec<PortRange>,
77 speaker: Option<SpeakerControl>,
78 #[cfg(feature = "mpu401")]
79 mpu: Option<MpuControl>,
80 #[cfg(feature = "psg")]
81 psg: bool,
82 #[cfg(feature = "opl")]
83 opl: Option<OplControl>,
84 #[cfg(feature = "sb")]
85 sb: Option<SbControl>,
86 #[cfg(feature = "sb")]
87 pcm: Option<PcmPlayback>,
88 #[cfg(feature = "sb")]
89 memory_dma: Option<MemoryPlayback>,
90 #[cfg(feature = "sb")]
91 queue_capacity: usize,
92 commands: Producer<TimedCommand>,
93 capture: Option<Capture>,
94 latency: Tick,
95}
96
97impl DosAudio {
98 pub fn split(config: SoundConfig) -> Result<(Self, AudioEngine)> {
100 config.validate()?;
101 let (producer, consumer) = rtrb::RingBuffer::new(config.queue_capacity);
102 let mut ports = Vec::new();
103 if config.midi.is_some() {
104 ports.push(PortRange::new(config.midi_port, config.midi_port + 1));
105 }
106 if config.psg {
107 ports.push(PortRange::single(0xc0));
108 }
109 if config.speaker {
110 ports.extend([PortRange::new(0x40, 0x43), PortRange::single(0x61)]);
111 }
112 if let Some(chip) = config.opl {
113 ports.push(PortRange::new(
114 0x388,
115 if chip == crate::OplChip::Opl2 {
116 0x389
117 } else {
118 0x38b
119 },
120 ));
121 }
122 if let Some(sb) = config.sb {
123 if config.sb_model.has_mixer() {
124 ports.push(PortRange::new(sb.base, sb.base + 5));
125 }
126 ports.extend([
127 PortRange::single(sb.base + 6),
128 PortRange::new(sb.base + 8, sb.base + 0x0a),
129 PortRange::single(sb.base + 0x0c),
130 PortRange::single(sb.base + 0x0e),
131 ]);
132 if config.sb_model == crate::SbModel::Sb16 {
133 ports.push(PortRange::single(sb.base + 0x0f));
134 }
135 }
136 let control = Self {
137 now: Tick::ZERO,
138 ports,
139 speaker: config.speaker.then(SpeakerControl::default),
140 #[cfg(feature = "mpu401")]
141 mpu: config
142 .midi
143 .as_ref()
144 .map(|_| MpuControl::new(config.midi_port)),
145 #[cfg(feature = "psg")]
146 psg: config.psg,
147 #[cfg(feature = "opl")]
148 opl: config.opl.map(OplControl::new),
149 #[cfg(feature = "sb")]
150 sb: config
151 .sb
152 .map(|resources| SbControl::new(resources, config.sb_model, config.dma16)),
153 #[cfg(feature = "sb")]
154 pcm: None,
155 #[cfg(feature = "sb")]
156 memory_dma: None,
157 #[cfg(feature = "sb")]
158 queue_capacity: config.queue_capacity,
159 commands: producer,
160 capture: None,
161 latency: config.latency,
162 };
163 Ok((control, AudioEngine::new(&config, consumer)?))
164 }
165
166 pub fn now(&self) -> Tick {
168 self.now
169 }
170
171 pub fn speaker_tone(&self) -> Option<f64> {
177 let state = self.speaker.as_ref()?.state;
178 (state.gate && state.data && state.mode != 0)
179 .then(|| crate::device::speaker::pit_hz(state.divisor))
180 }
181
182 pub fn pit(&self) -> Option<&game_toolkit_dosbus::Pit> {
189 self.speaker.as_ref().map(|speaker| &speaker.pit)
190 }
191
192 pub fn advance(&mut self, duration: Tick) {
194 self.advance_to(self.now + duration);
195 }
196
197 pub fn advance_to(&mut self, at: Tick) {
200 self.try_advance_to(at).expect("sound time advance failed");
201 }
202
203 pub fn try_advance_to(&mut self, at: Tick) -> Result<()> {
208 self.check_time(at)?;
209 #[cfg(feature = "sb")]
210 if let Some(plan) = self.plan_pcm(at)? {
211 self.reserve(plan.commands())?;
212 self.commit_pcm(at, plan);
213 }
214 self.set_time(at);
215 Ok(())
216 }
217
218 fn check_time(&self, at: Tick) -> Result<()> {
219 if at < self.now {
220 return Err(Error::BackwardsTime);
221 }
222 at.checked_add(self.latency).ok_or(Error::TooLarge)?;
223 Ok(())
224 }
225
226 fn reserve(&mut self, count: usize) -> Result<()> {
227 if self.commands.slots() < count {
228 return Err(Error::QueueFull);
229 }
230 Ok(())
231 }
232
233 fn set_time(&mut self, at: Tick) {
234 #[cfg(feature = "opl")]
235 if let Some(opl) = &mut self.opl {
236 opl.sync(at);
237 }
238 self.now = at;
239 }
240
241 pub fn wait_us(&mut self, micros: u64) {
243 self.advance(Tick::from_micros(micros));
244 }
245
246 pub fn outb(&mut self, port: u16, value: u8) {
248 self.try_outb_at(self.now, port, value)
249 .expect("sound port write failed");
250 }
251
252 pub fn inb(&mut self, port: u16) -> u8 {
254 self.try_inb_at(self.now, port)
255 .expect("sound port read failed")
256 }
257
258 pub fn try_outb_at(&mut self, at: Tick, port: u16, value: u8) -> Result<()> {
260 self.check_time(at)?;
261 #[cfg(feature = "sb")]
262 let plan = self.plan_pcm(at)?;
263 let mut command = None;
264 #[cfg(feature = "mpu401")]
265 let mut next_mpu = self.mpu;
266 #[cfg(feature = "mpu401")]
267 if let Some(mpu) = &mut next_mpu
268 && (mpu.base..=mpu.base + 1).contains(&port)
269 && let Some(byte) = mpu.write(port, value)
270 {
271 command = Some(Command::MidiByte(byte));
272 }
273 #[cfg(feature = "psg")]
274 if self.psg && port == 0xc0 {
275 command = Some(Command::Psg(value));
276 }
277 let mut next_speaker = self.speaker.clone();
279 if matches!(port, 0x40..=0x43 | 0x61)
280 && let Some(speaker) = &mut next_speaker
281 && let Some(state) = speaker.write(at, port, value)
282 {
283 command = Some(Command::Speaker(state));
284 }
285 #[cfg(feature = "opl")]
286 let mut next_opl = self.opl;
287 #[cfg(feature = "opl")]
288 if let Some(opl) = &mut next_opl {
289 let writes = self
290 .opl_ports(port)
291 .map(|port| port.and_then(|port| opl.write(at, port, value)));
292 match writes {
293 [Some(first), Some(second)] => command = Some(Command::OplPair { first, second }),
294 [Some((register, value)), None] | [None, Some((register, value))] => {
295 command = Some(Command::Opl { register, value })
296 }
297 _ => {}
298 }
299 }
300 #[cfg(feature = "sb")]
301 let mut next_sb = plan.map_or(self.sb, |plan| Some(plan.sb()));
302 #[cfg(feature = "sb")]
303 let mut cancel_pcm = false;
304 #[cfg(feature = "sb")]
305 if let Some(sb) = &mut next_sb
306 && (sb.resources.base..=sb.resources.base + 0x0f).contains(&port)
307 && let Some(state) = sb.write(at, port, value)
308 {
309 command = Some(Command::Dac(state));
310 }
311 #[cfg(feature = "sb")]
312 if self.pcm.is_some()
313 && let Some(sb) = &mut next_sb
314 && sb.dma_channel().is_none()
315 {
316 command = Some(Command::Dac(sb.stop_pcm()));
317 cancel_pcm = true;
318 }
319 let needed = usize::from(command.is_some());
320 #[cfg(feature = "sb")]
321 let needed = needed + plan.map_or(0, AutomaticPlan::commands);
322 self.reserve(needed)?;
323 #[cfg(feature = "sb")]
324 if let Some(plan) = plan {
325 self.commit_pcm(at, plan);
326 }
327 #[cfg(feature = "sb")]
328 if matches!(plan, Some(AutomaticPlan::Memory(_))) {
329 next_sb = self.sb;
332 if let Some(sb) = &mut next_sb
333 && (sb.resources.base..=sb.resources.base + 0x0f).contains(&port)
334 && let Some(state) = sb.write(at, port, value)
335 {
336 command = Some(Command::Dac(state));
337 }
338 }
339 if let Some(command) = command {
340 self.push(at, command).expect("reserved port command");
341 }
342 self.speaker = next_speaker;
343 #[cfg(feature = "mpu401")]
344 {
345 self.mpu = next_mpu;
346 }
347 #[cfg(feature = "opl")]
348 {
349 self.opl = next_opl;
350 }
351 #[cfg(feature = "sb")]
352 {
353 self.sb = next_sb;
354 if let Some(memory) = &mut self.memory_dma {
355 memory.controller.outb(port, value);
356 }
357 if let Some(sb) = &self.sb
358 && (cancel_pcm || matches!(port.checked_sub(sb.resources.base), Some(6 | 0x0c)))
359 {
360 self.pcm = None; }
362 }
363 self.set_time(at);
364 if let Some(capture) = &mut self.capture {
365 capture.writes.push(PortWrite { at, port, value });
366 }
367 Ok(())
368 }
369
370 #[cfg(feature = "sb")]
381 pub fn play_pcm(
382 &mut self,
383 samples: impl Into<std::sync::Arc<[u8]>>,
384 sample_rate: u32,
385 ) -> Result<crate::SfxHandle> {
386 if !(4_000..=23_000).contains(&sample_rate) {
387 return Err(Error::Config("PCM sample rate must be 4,000..=23,000 Hz"));
388 }
389 let mut sb = self
390 .sb
391 .ok_or(Error::Config("Sound Blaster is not installed"))?;
392 let samples = samples.into();
393 if samples.is_empty() || samples.len() > 65_536 {
394 return Err(Error::Config("PCM buffer must contain 1..=65,536 bytes"));
395 }
396 let period = ((1_000_000 + sample_rate / 2) / sample_rate).clamp(44, 250) as u16;
397 let (state, end) = sb.begin_pcm(self.now, samples.len() as u32, (256 - period) as u8)?;
398 end.checked_add(self.latency).ok_or(Error::TooLarge)?;
399 self.reserve(1)?;
400 let handle = crate::SfxHandle::new(period);
401 self.push(self.now, Command::Dac(state))
402 .expect("reserved PCM start");
403 self.pcm = Some(PcmPlayback::new(samples, &handle, end));
404 self.sb = Some(sb);
405 Ok(handle)
406 }
407
408 #[cfg(feature = "sb")]
409 fn plan_pcm(&self, at: Tick) -> Result<Option<AutomaticPlan>> {
410 if let Some(pcm) = &self.pcm {
411 return Ok(Some(AutomaticPlan::Buffer(pcm.plan(
412 self.sb.expect("buffer owns DSP"),
413 self.now,
414 at,
415 ))));
416 }
417 if let (Some(memory), Some(sb)) = (&self.memory_dma, self.sb) {
418 return memory
419 .plan(sb, self.now, at)
420 .map(|p| Some(AutomaticPlan::Memory(p)));
421 }
422 Ok(None)
423 }
424
425 #[cfg(feature = "sb")]
426 fn commit_pcm(&mut self, at: Tick, plan: AutomaticPlan) {
427 let commands = &mut self.commands;
428 let mut emit = |at, state| {
429 commands
430 .push(TimedCommand {
431 at,
432 command: Command::Dac(state),
433 })
434 .expect("reserved DMA command");
435 };
436 if let AutomaticPlan::Memory(_) = plan {
437 self.memory_dma
438 .as_mut()
439 .expect("prepared memory DMA")
440 .commit(self.sb.as_mut().expect("installed DSP"), self.now, at, emit)
441 .expect("preflighted memory DMA");
442 return;
443 }
444 let AutomaticPlan::Buffer(plan) = plan else {
445 unreachable!()
446 };
447 let finished = self.pcm.as_mut().expect("prepared buffer").commit(
448 plan,
449 self.sb.expect("buffer owns DSP"),
450 self.now,
451 at,
452 &mut emit,
453 );
454 self.sb = Some(plan.sb);
455 if finished {
456 self.pcm = None;
457 }
458 }
459
460 #[cfg(feature = "sb")]
461 pub(crate) fn next_pcm_tick(&self) -> Option<Tick> {
462 if let Some(pcm) = &self.pcm {
463 return Some(pcm.next(self.sb.as_ref().expect("buffer owns DSP"), self.now));
464 }
465 self.memory_dma
466 .as_ref()
467 .and_then(|memory| self.sb.as_ref().and_then(|sb| memory.next(sb, self.now)))
468 }
469
470 #[cfg(feature = "sb")]
474 pub fn attach_dma(&mut self, memory: std::sync::Arc<crate::ConventionalMemory>) -> Result<()> {
475 #[cfg(feature = "psg")]
476 if self.psg {
477 return Err(Error::Config(
478 "Tandy PSG overlaps the internal AT DMA controller",
479 ));
480 }
481 if self.memory_dma.is_some() {
482 return Err(Error::Config("DMA memory is already attached"));
483 }
484 if self.sb.is_none() {
485 return Err(Error::Config("Sound Blaster is not installed"));
486 }
487 let dma = MemoryPlayback::new(memory, self.queue_capacity);
488 self.ports.extend_from_slice(dma.controller.ports());
489 self.memory_dma = Some(dma);
490 Ok(())
491 }
492
493 #[cfg(feature = "sb")]
496 pub fn poll_dma_completion(&mut self) -> Option<crate::DmaCompletion> {
497 self.memory_dma.as_mut().and_then(MemoryPlayback::poll)
498 }
499
500 #[cfg(feature = "sb")]
502 pub fn next_dma_block_tick(&self) -> Option<Tick> {
503 self.sb.as_ref().and_then(SbControl::next_block_tick)
504 }
505
506 #[cfg(feature = "sb")]
507 pub(crate) fn pcm_end(&self) -> Option<Tick> {
508 self.pcm.as_ref().map(|pcm| pcm.end(self.now))
509 }
510
511 fn push(&mut self, at: Tick, command: Command) -> Result<()> {
512 self.commands
513 .push(TimedCommand { at, command })
514 .map_err(|_| Error::QueueFull)
515 }
516
517 #[cfg(feature = "opl")]
518 fn opl_ports(&self, port: u16) -> [Option<u16>; 2] {
519 #[cfg(feature = "sb")]
520 let card = self.sb.as_ref().map(|sb| (sb.resources.base, sb.model));
521 #[cfg(not(feature = "sb"))]
522 let card = None;
523 map_opl_ports(port, card)
524 }
525
526 #[cfg(feature = "sb")]
532 pub fn next_dma_tick(&self) -> Option<Tick> {
533 if self.pcm.is_some() {
534 return None;
535 }
536 self.sb.as_ref().and_then(SbControl::next_dma_tick)
537 }
538
539 #[cfg(feature = "sb")]
547 pub fn try_dma_write_at(
548 &mut self,
549 at: Tick,
550 channel: crate::DmaChannel,
551 value: u8,
552 ) -> Result<()> {
553 self.check_time(at)?;
554 if self.pcm.is_some() || self.memory_dma.is_some() {
555 return Err(Error::Config("PCM buffer owns Sound Blaster DMA"));
556 }
557 let mut sb = self
558 .sb
559 .ok_or(Error::Config("Sound Blaster is not installed"))?;
560 let state = sb.dma_write(at, channel, value)?;
561 self.push(at, Command::Dac(state))?;
562 self.sb = Some(sb);
563 self.set_time(at);
564 Ok(())
565 }
566
567 #[cfg(feature = "sb")]
570 pub fn try_dma_write16_at(
571 &mut self,
572 at: Tick,
573 channel: crate::DmaChannel,
574 value: u16,
575 ) -> Result<()> {
576 self.check_time(at)?;
577 if self.pcm.is_some() || self.memory_dma.is_some() {
578 return Err(Error::Config("PCM buffer owns Sound Blaster DMA"));
579 }
580 let mut sb = self
581 .sb
582 .ok_or(Error::Config("Sound Blaster is not installed"))?;
583 let state = sb.dma_write16(at, channel, value)?;
584 self.push(at, Command::Dac(state))?;
585 self.sb = Some(sb);
586 self.set_time(at);
587 Ok(())
588 }
589
590 pub fn try_inb_at(&mut self, at: Tick, port: u16) -> Result<u8> {
592 self.try_advance_to(at)?;
593 #[cfg(feature = "mpu401")]
594 if let Some(mpu) = &mut self.mpu
595 && (mpu.base..=mpu.base + 1).contains(&port)
596 {
597 return Ok(mpu.read(port));
598 }
599 #[cfg(feature = "sb")]
600 if let Some(memory) = &mut self.memory_dma
601 && memory
602 .controller
603 .ports()
604 .iter()
605 .any(|range| range.contains(port))
606 {
607 return Ok(memory.controller.inb(port));
608 }
609 if matches!(port, 0x40..=0x42 | 0x61)
610 && let Some(speaker) = &mut self.speaker
611 {
612 return Ok(speaker.read(at, port));
613 }
614 #[cfg(feature = "opl")]
615 if let Some(opl_port) = self.opl_ports(port)[0]
616 && let Some(opl) = &mut self.opl
617 {
618 return Ok(opl.read(at, opl_port));
619 }
620 #[cfg(feature = "sb")]
621 if let Some(sb) = &mut self.sb
622 && (sb.resources.base..=sb.resources.base + 0x0f).contains(&port)
623 {
624 return Ok(sb.read(port));
625 }
626 Ok(0xff)
627 }
628
629 pub fn sound(&mut self, hz: f64) -> Result<()> {
634 if !hz.is_finite() || hz <= 0.0 {
635 return Err(Error::Config("invalid speaker frequency"));
636 }
637 if self.speaker.is_none() {
638 return Err(Error::Config("PC speaker is not installed"));
639 }
640 let needed = 3;
642 #[cfg(feature = "sb")]
644 let needed = needed + usize::from(self.pcm.is_some());
645 self.reserve(needed)?;
646 let divisor = (Tick::HZ_NUMERATOR as f64 / (Tick::HZ_DENOMINATOR as f64 * 12.0 * hz))
647 .round()
648 .clamp(1.0, 65_536.0) as u32 as u16;
649 self.try_outb_at(self.now, 0x43, 0xb6)?;
650 self.try_outb_at(self.now, 0x42, divisor as u8)?;
651 self.try_outb_at(self.now, 0x42, (divisor >> 8) as u8)?;
652 let bits = self.inb(0x61);
653 self.try_outb_at(self.now, 0x61, bits | 3)
654 }
655
656 pub fn nosound(&mut self) -> Result<()> {
658 let bits = self.inb(0x61);
659 self.try_outb_at(self.now, 0x61, bits & !3)
660 }
661
662 pub fn bus_device(self) -> impl IoDevice {
664 self
665 }
666 #[cfg(feature = "psg")]
668 pub fn psg(&mut self) -> Result<crate::Psg<'_>> {
669 if !self.psg {
670 return Err(Error::Config("PSG is not installed"));
671 }
672 Ok(crate::Psg::new(self))
673 }
674 #[cfg(feature = "psg")]
675 pub(crate) fn psg_write(&mut self, bytes: &[u8]) -> Result<()> {
676 if !self.psg {
677 return Err(Error::Config("PSG is not installed"));
678 }
679 let needed = bytes.len();
680 #[cfg(feature = "sb")]
681 let needed = needed
682 + self.plan_pcm(self.now)?.map_or(0, AutomaticPlan::commands)
683 + usize::from(self.pcm.is_some());
684 self.reserve(needed)?;
685 for &byte in bytes {
686 self.try_outb_at(self.now, 0xc0, byte)?;
687 }
688 Ok(())
689 }
690
691 #[cfg(feature = "opl")]
693 pub fn opl(&mut self) -> Result<crate::Opl<'_>> {
694 let chip = self
695 .opl
696 .as_ref()
697 .ok_or(Error::Config("OPL hardware is not installed"))?
698 .chip;
699 Ok(crate::Opl::new(self, chip))
700 }
701
702 #[cfg(feature = "opl")]
703 pub(crate) fn opl_register(&self, register: u16) -> Result<u8> {
704 let opl = self
705 .opl
706 .as_ref()
707 .ok_or(Error::Config("OPL hardware is not installed"))?;
708 if register > 0x1ff || opl.chip == crate::OplChip::Opl2 && register > 0xff {
709 return Err(Error::Config("OPL register is not installed"));
710 }
711 Ok(opl.register(register))
712 }
713
714 #[cfg(feature = "opl")]
715 pub(crate) fn opl_patch_levels(&self, channel: u8) -> Result<[u8; 2]> {
716 let opl = self
717 .opl
718 .as_ref()
719 .ok_or(Error::Config("OPL hardware is not installed"))?;
720 if channel
721 >= if opl.chip == crate::OplChip::Opl2 {
722 9
723 } else {
724 18
725 }
726 {
727 return Err(Error::Config("invalid OPL channel"));
728 }
729 Ok(opl.patch_levels(usize::from(channel)))
730 }
731
732 #[cfg(feature = "opl")]
733 pub(crate) fn opl_set_patch_levels(&mut self, channel: u8, levels: [u8; 2]) -> Result<()> {
734 self.opl_patch_levels(channel)?;
735 self.opl
736 .as_mut()
737 .expect("validated OPL")
738 .set_patch_levels(usize::from(channel), levels);
739 Ok(())
740 }
741
742 #[cfg(feature = "opl")]
743 pub(crate) fn opl_write(&mut self, writes: &[(u16, u8)]) -> Result<()> {
744 for &(register, _) in writes {
745 self.opl_register(register)?;
746 }
747 let needed = writes.len();
748 #[cfg(feature = "sb")]
749 let needed = needed
750 + self.plan_pcm(self.now)?.map_or(0, AutomaticPlan::commands)
751 + usize::from(self.pcm.is_some());
752 self.reserve(needed)?;
753 for &(register, value) in writes {
754 let port = 0x388 + (register >> 8) * 2;
755 #[cfg(feature = "sb")]
756 let port = if let Some(sb) = &self.sb
757 && sb.model == crate::SbModel::SbPro
758 {
759 sb.resources.base + (register >> 8) * 2
760 } else {
761 port
762 };
763 self.try_outb_at(self.now, port, register as u8)?;
764 self.try_outb_at(self.now, port + 1, value)?;
765 }
766 Ok(())
767 }
768
769 pub fn start_capture(&mut self) -> Result<()> {
771 if self.capture.is_some() {
772 return Err(Error::Config("a capture is already active"));
773 }
774 #[cfg(feature = "opl")]
775 let (chip, address) = self
776 .opl
777 .as_ref()
778 .map_or((None, [0; 2]), |opl| (Some(opl.chip), opl.addresses()));
779 #[cfg(not(feature = "opl"))]
780 let (chip, address) = (None, [0; 2]);
781 self.capture = Some(Capture {
782 start: self.now,
783 end: self.now,
784 chip,
785 address,
786 card: self.opl_card(),
787 writes: Vec::new(),
788 });
789 Ok(())
790 }
791
792 fn opl_card(&self) -> Option<(u16, crate::SbModel)> {
793 #[cfg(feature = "sb")]
794 if let Some(sb) = &self.sb {
795 return Some((sb.resources.base, sb.model));
796 }
797 None
798 }
799
800 pub fn finish_capture(&mut self) -> Option<Capture> {
802 self.capture.take().map(|mut capture| {
803 capture.end = self.now;
804 capture
805 })
806 }
807}
808
809pub(crate) fn map_opl_ports(port: u16, card: Option<(u16, crate::SbModel)>) -> [Option<u16>; 2] {
810 if let Some((base, model)) = card {
811 let common = (base + 8..=base + 9).contains(&port);
812 if model == crate::SbModel::SbPro && (common || (0x388..=0x389).contains(&port)) {
813 let data = port & 1;
814 return [Some(0x388 + data), Some(0x38a + data)];
815 }
816 if model.has_mixer() && (base..=base + 3).contains(&port) {
817 return [Some(0x388 + port - base), None];
818 }
819 if common {
820 return [Some(0x388 + port - base - 8), None];
821 }
822 }
823 [(0x388..=0x38b).contains(&port).then_some(port), None]
824}
825
826impl IoDevice for DosAudio {
827 fn ports(&self) -> &[PortRange] {
828 &self.ports
829 }
830 fn advance_to(&mut self, at: Tick) {
831 DosAudio::advance_to(self, at);
832 }
833 fn outb(&mut self, at: Tick, port: u16, value: u8) {
834 self.try_outb_at(at, port, value)
835 .expect("sound port write failed");
836 }
837 fn inb(&mut self, at: Tick, port: u16) -> u8 {
838 self.try_inb_at(at, port).expect("sound port read failed")
839 }
840
841 fn irq_request(&self, _at: Tick) -> Option<crate::IrqRequest> {
842 #[cfg(feature = "sb")]
843 if let Some(sb) = &self.sb {
844 return Some(sb.irq_request());
845 }
846 None
847 }
848
849 fn dma_request(&self, _at: Tick) -> Option<crate::DmaRequest> {
850 #[cfg(feature = "sb")]
851 if self.pcm.is_some() || self.memory_dma.is_some() {
852 return None;
853 }
854 #[cfg(feature = "sb")]
855 if let Some(sb) = &self.sb {
856 return sb.dma_request(_at);
857 }
858 None
859 }
860}