1use crate::{Error, Result, Tick};
2
3#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
5#[non_exhaustive]
6pub enum SbModel {
7 Sb1,
9 #[default]
11 Sb2,
12 SbPro,
14 SbPro2,
16 Sb16,
18}
19
20impl SbModel {
21 pub const fn dsp_version(self) -> (u8, u8) {
23 match self {
24 Self::Sb1 => (1, 5),
25 Self::Sb2 => (2, 1),
26 Self::SbPro => (3, 0),
27 Self::SbPro2 => (3, 2),
28 Self::Sb16 => (4, 5),
29 }
30 }
31 pub const fn opl_chip(self) -> OplChip {
33 match self {
34 Self::Sb1 | Self::Sb2 => OplChip::Opl2,
35 Self::SbPro => OplChip::DualOpl2,
36 Self::SbPro2 | Self::Sb16 => OplChip::Opl3,
37 }
38 }
39 pub(crate) const fn has_mixer(self) -> bool {
40 matches!(self, Self::SbPro | Self::SbPro2 | Self::Sb16)
41 }
42}
43
44#[derive(Clone, Copy, Debug, Eq, PartialEq)]
50pub struct SoundBlasterConfig {
51 pub base: u16,
53 pub irq: u8,
55 pub dma: u8,
57}
58
59impl Default for SoundBlasterConfig {
60 fn default() -> Self {
61 Self {
62 base: 0x220,
63 irq: 5,
64 dma: 1,
65 }
66 }
67}
68
69#[derive(Clone, Copy, Debug, Eq, PartialEq)]
71pub enum OplChip {
72 Opl2,
74 DualOpl2,
76 Opl3,
78}
79
80#[derive(Clone, Default)]
82pub enum OplBackend {
83 #[default]
85 Oplon,
86 #[cfg(feature = "nuked")]
88 Nuked,
89 #[cfg(feature = "opl")]
94 Custom(std::sync::Arc<dyn Fn() -> Box<dyn crate::OplCore> + Send + Sync>),
95}
96
97impl core::fmt::Debug for OplBackend {
98 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
99 f.write_str(match self {
100 Self::Oplon => "Oplon",
101 #[cfg(feature = "nuked")]
102 Self::Nuked => "Nuked",
103 #[cfg(feature = "opl")]
104 Self::Custom(_) => "Custom",
105 })
106 }
107}
108
109#[derive(Clone, Debug)]
111pub struct SoundConfig {
112 pub(crate) opl: Option<OplChip>,
113 pub(crate) sb: Option<SoundBlasterConfig>,
114 pub(crate) sb_model: SbModel,
115 pub(crate) dma16: u8,
116 pub(crate) backend: OplBackend,
117 pub(crate) speaker: bool,
118 pub(crate) native: bool,
119 pub(crate) psg: bool,
120 pub(crate) midi: Option<crate::MidiBackend>,
121 pub(crate) midi_port: u16,
122 pub(crate) sample_rate: u32,
123 pub(crate) queue_capacity: usize,
124 pub(crate) latency: Tick,
125}
126
127impl Default for SoundConfig {
128 fn default() -> Self {
129 Self::adlib()
130 }
131}
132
133impl SoundConfig {
134 pub fn none() -> Self {
136 Self {
137 opl: None,
138 sb: None,
139 sb_model: SbModel::Sb2,
140 dma16: 5,
141 backend: OplBackend::Oplon,
142 speaker: false,
143 native: false,
144 psg: false,
145 midi: None,
146 midi_port: 0x330,
147 sample_rate: 48_000,
148 queue_capacity: 16_384,
149 latency: Tick::from_micros(10_000),
150 }
151 }
152
153 pub fn adlib() -> Self {
155 Self {
156 opl: Some(OplChip::Opl2),
157 ..Self::none()
158 }
159 }
160
161 pub fn sb2() -> Self {
163 Self::none().sound_blaster(SoundBlasterConfig::default())
164 }
165
166 pub fn sb1() -> Self {
168 Self::none().sb_model(SbModel::Sb1)
169 }
170 pub fn sb_pro() -> Self {
172 Self::none().sb_model(SbModel::SbPro)
173 }
174 pub fn sb_pro2() -> Self {
176 Self::none().sb_model(SbModel::SbPro2)
177 }
178 pub fn sb16() -> Self {
180 Self::none().sb_model(SbModel::Sb16)
181 }
182
183 #[must_use]
185 pub fn sb_model(mut self, model: SbModel) -> Self {
186 self.sb_model = model;
187 self.sb.get_or_insert_with(SoundBlasterConfig::default);
188 self.opl = Some(model.opl_chip());
189 self
190 }
191
192 #[must_use]
194 pub fn dma16(mut self, channel: u8) -> Self {
195 self.dma16 = channel;
196 self
197 }
198
199 pub fn from_blaster(value: &str) -> Result<Self> {
205 let mut fields = [None; 5];
206 for field in value.split_ascii_whitespace() {
207 if !field.is_ascii() {
208 return Err(Error::Config("BLASTER fields must be ASCII"));
209 }
210 let (key, number) = field.split_at(1);
211 let index = match key.to_ascii_uppercase().as_str() {
212 "A" => 0,
213 "I" => 1,
214 "D" => 2,
215 "T" => 3,
216 "H" => 4,
217 _ => return Err(Error::Config("unsupported BLASTER field")),
218 };
219 if fields[index].is_some() {
220 return Err(Error::Config("duplicate BLASTER field"));
221 }
222 let number = if index == 0 {
223 number
224 .strip_prefix("0x")
225 .or_else(|| number.strip_prefix("0X"))
226 .unwrap_or(number)
227 } else {
228 number
229 };
230 fields[index] = Some(
231 u16::from_str_radix(number, if index == 0 { 16 } else { 10 })
232 .map_err(|_| Error::Config("invalid BLASTER field value"))?,
233 );
234 }
235 let [Some(base), Some(irq), Some(dma), Some(card), high] = fields else {
236 return Err(Error::Config("BLASTER requires A, I, D and T fields"));
237 };
238 let model = match card {
239 1 => SbModel::Sb1,
240 2 => SbModel::SbPro,
241 3 => SbModel::Sb2,
242 4 => SbModel::SbPro2,
243 6 => SbModel::Sb16,
244 _ => return Err(Error::Config("unsupported BLASTER card type")),
245 };
246 if high.is_some() && model != SbModel::Sb16 {
247 return Err(Error::Config("BLASTER H requires an SB16"));
248 }
249 let resources = SoundBlasterConfig {
250 base,
251 irq: u8::try_from(irq).map_err(|_| Error::Config("invalid BLASTER IRQ"))?,
252 dma: u8::try_from(dma).map_err(|_| Error::Config("invalid BLASTER DMA"))?,
253 };
254 let config = Self::none().sb_model(model).sound_blaster(resources).dma16(
255 u8::try_from(high.unwrap_or(5))
256 .map_err(|_| Error::Config("invalid BLASTER high DMA"))?,
257 );
258 config.validate()?;
259 Ok(config)
260 }
261
262 #[must_use]
264 pub fn sound_blaster(mut self, resources: SoundBlasterConfig) -> Self {
265 self.sb = Some(resources);
266 self.opl = Some(self.sb_model.opl_chip());
267 self
268 }
269
270 pub fn opl3() -> Self {
272 Self {
273 opl: Some(OplChip::Opl3),
274 ..Self::none()
275 }
276 }
277
278 pub fn dual_opl2() -> Self {
280 Self {
281 opl: Some(OplChip::DualOpl2),
282 ..Self::none()
283 }
284 }
285
286 #[must_use]
288 pub fn speaker(mut self, enabled: bool) -> Self {
289 self.speaker = enabled;
290 self
291 }
292
293 #[cfg(feature = "synthie")]
295 #[must_use]
296 pub fn native(mut self, enabled: bool) -> Self {
297 self.native = enabled;
298 self
299 }
300
301 #[must_use]
303 pub fn mpu401(mut self, backend: crate::MidiBackend) -> Self {
304 self.midi = Some(backend);
305 self
306 }
307 #[cfg(feature = "gm")]
309 pub fn gm(font: std::sync::Arc<crate::SoundFont>) -> Self {
310 Self::none().mpu401(crate::MidiBackend::Gm(font))
311 }
312 #[cfg(feature = "mt32")]
314 pub fn mt32(roms: std::sync::Arc<crate::Mt32Roms>) -> Self {
315 Self::none().mpu401(crate::MidiBackend::Mt32(roms))
316 }
317
318 #[must_use]
320 pub fn midi_port(mut self, base: u16) -> Self {
321 self.midi_port = base;
322 self
323 }
324 #[must_use]
326 pub fn psg(mut self, enabled: bool) -> Self {
327 self.psg = enabled;
328 self
329 }
330
331 #[must_use]
333 pub fn opl_backend(mut self, backend: OplBackend) -> Self {
334 self.backend = backend;
335 self
336 }
337
338 #[must_use]
340 pub fn sample_rate(mut self, sample_rate: u32) -> Self {
341 self.sample_rate = sample_rate;
342 self
343 }
344
345 #[must_use]
347 pub fn queue_capacity(mut self, capacity: usize) -> Self {
348 self.queue_capacity = capacity;
349 self
350 }
351
352 #[must_use]
357 pub fn latency(mut self, latency: Tick) -> Self {
358 self.latency = latency;
359 self
360 }
361
362 pub(crate) fn validate(&self) -> Result<()> {
363 if self.midi.is_some() {
364 if !cfg!(feature = "mpu401") {
365 return Err(Error::Config("MPU-401 requires the mpu401 feature"));
366 }
367 if !(0x300..=0x360).contains(&self.midi_port) || self.midi_port & 0xf != 0 {
368 return Err(Error::Config("invalid MPU-401 base"));
369 }
370 }
371 if self.psg && !cfg!(feature = "psg") {
372 return Err(Error::Config("PSG requires the psg feature"));
373 }
374 if let Some(sb) = self.sb {
375 if !cfg!(feature = "sb") {
376 return Err(Error::Config("Sound Blaster requires the sb feature"));
377 }
378 if !(0x200..=0x280).contains(&sb.base) || sb.base & 0x0f != 0 {
379 return Err(Error::Config("invalid Sound Blaster base address"));
380 }
381 if !matches!(sb.irq, 2 | 3 | 5 | 7 | 10) || !matches!(sb.dma, 0 | 1 | 3) {
382 return Err(Error::Config("invalid Sound Blaster IRQ or DMA channel"));
383 }
384 if sb.irq == 10 && self.sb_model != SbModel::Sb16 {
385 return Err(Error::Config("IRQ 10 requires an SB16"));
386 }
387 if !(5..=7).contains(&self.dma16)
388 && !(self.sb_model == SbModel::Sb16 && self.dma16 == sb.dma)
389 {
390 return Err(Error::Config("word DMA channel must be 5, 6 or 7"));
391 }
392 }
393 if !(8_000..=192_000).contains(&self.sample_rate) {
394 return Err(Error::Config("sample rate must be 8,000..=192,000 Hz"));
395 }
396 if self.queue_capacity == 0 || self.queue_capacity > 1_048_576 {
397 return Err(Error::Config("queue capacity must be 1..=1,048,576"));
398 }
399 if self.opl.is_some() && !cfg!(feature = "opl") {
400 return Err(Error::Config("OPL hardware requires the opl feature"));
401 }
402 Ok(())
403 }
404}