1use crate::{DosAudio, Error, OplChip, Result};
4
5mod bank;
6mod driver;
7pub use bank::{OplBank, OplBankEntry, OplBankKey, OplInstrument};
8pub use driver::OplMidiDriver;
9
10#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
12pub struct OplOperator {
13 pub characteristics: u8,
15 pub level: u8,
17 pub attack_decay: u8,
19 pub sustain_release: u8,
21 pub waveform: u8,
23}
24
25#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
27pub struct OplPatch {
28 pub modulator: OplOperator,
30 pub carrier: OplOperator,
32 pub feedback: u8,
34}
35
36impl OplPatch {
37 pub fn from_sbi(bytes: &[u8]) -> Result<Self> {
40 if bytes.len() < 52 || &bytes[..4] != b"SBI\x1a" {
41 return Err(Error::Format("invalid or truncated SBI instrument"));
42 }
43 let data = &bytes[36..52];
44 let operator = |offset: usize| OplOperator {
45 characteristics: data[offset],
46 level: data[offset + 2],
47 attack_decay: data[offset + 4],
48 sustain_release: data[offset + 6],
49 waveform: data[offset + 8],
50 };
51 Ok(Self {
52 modulator: operator(0),
53 carrier: operator(1),
54 feedback: data[10],
55 })
56 }
57}
58
59#[derive(Clone, Copy, Debug, Eq, PartialEq)]
61pub struct Note(u8);
62
63impl Note {
64 pub const C4: Self = Self(60);
66 pub const A4: Self = Self(69);
68
69 pub fn from_midi(note: u8) -> Result<Self> {
71 if note > 127 {
72 return Err(Error::Config("MIDI note must be in 0 through 127"));
73 }
74 Ok(Self(note))
75 }
76
77 pub fn frequency(self) -> f64 {
79 440.0 * 2.0f64.powf((f64::from(self.0) - 69.0) / 12.0)
80 }
81
82 fn tuning(self) -> Result<(u16, u8)> {
83 frequency_tuning(self.frequency())
84 }
85}
86
87#[derive(Clone, Copy, Debug, Eq, PartialEq)]
89pub enum Drum {
90 BassDrum,
92 Snare,
94 Tom,
96 Cymbal,
98 HiHat,
100}
101
102impl Drum {
103 fn mask(self) -> u8 {
104 match self {
105 Self::BassDrum => 0x10,
106 Self::Snare => 0x08,
107 Self::Tom => 0x04,
108 Self::Cymbal => 0x02,
109 Self::HiHat => 0x01,
110 }
111 }
112}
113
114pub struct Opl<'a> {
116 ports: &'a mut DosAudio,
117 chip: OplChip,
118}
119
120impl<'a> Opl<'a> {
121 pub(crate) fn new(ports: &'a mut DosAudio, chip: OplChip) -> Self {
122 Self { ports, chip }
123 }
124
125 fn channel(&self, channel: u8) -> Result<(u16, u16)> {
126 channel_address(self.chip, channel)
127 }
128
129 fn write(&mut self, writes: &[(u16, u8)]) -> Result<()> {
130 let mut batch = [(0, 0); 13];
131 let offset = usize::from(self.chip == OplChip::Opl3);
132 if offset != 0 {
133 batch[0] = (0x105, 1);
134 }
135 batch[offset..offset + writes.len()].copy_from_slice(writes);
136 self.ports.opl_write(&batch[..offset + writes.len()])
137 }
138
139 pub fn set_patch(&mut self, channel: u8, patch: &OplPatch) -> Result<()> {
142 let (channel_register, modulator) = self.channel(channel)?;
143 let mut routing = 0;
144 if self.chip == OplChip::Opl3 {
145 routing = self.ports.opl_register(0xc0 | channel_register)? & 0xf0;
146 if routing == 0 {
147 routing = 0x30;
148 }
149 }
150 let a = patch.modulator;
151 let b = patch.carrier;
152 let waveform_register = 0x01 | (modulator & 0x100);
153 let waveform_enable = self.ports.opl_register(waveform_register)? | 0x20;
154 self.write(&patch_writes(
155 channel_register,
156 modulator,
157 patch,
158 routing,
159 waveform_enable,
160 ))?;
161 self.ports.opl_set_patch_levels(channel, [a.level, b.level])
162 }
163
164 pub fn note_on(&mut self, channel: u8, note: Note, velocity: u8) -> Result<()> {
168 let (channel_register, modulator) = self.channel(channel)?;
169 if velocity > 127 {
170 return Err(Error::Config("MIDI velocity must be in 0 through 127"));
171 }
172 if velocity == 0 {
173 return self.note_off(channel);
174 }
175 let (number, block) = note.tuning()?;
176 let levels = self.ports.opl_patch_levels(channel)?;
177 let attenuation = ((127 - u16::from(velocity)) * 63 + 63) / 127;
178 let attenuate =
179 |level: u8| (level & 0xc0) | ((u16::from(level & 0x3f) + attenuation).min(63) as u8);
180 let additive = self.ports.opl_register(0xc0 | channel_register)? & 1 != 0;
181 let old_key = self.ports.opl_register(0xb0 | channel_register)?;
182 self.write(&[
183 (
184 0x40 | modulator,
185 if additive {
186 attenuate(levels[0])
187 } else {
188 levels[0]
189 },
190 ),
191 (0x40 | (modulator + 3), attenuate(levels[1])),
192 (0xa0 | channel_register, number as u8),
193 (
194 0xb0 | channel_register,
195 (old_key & 0xc0) | 0x20 | (block << 2) | (number >> 8) as u8,
196 ),
197 ])
198 }
199
200 pub fn note_off(&mut self, channel: u8) -> Result<()> {
202 let (channel_register, _) = self.channel(channel)?;
203 let register = 0xb0 | channel_register;
204 self.write(&[(register, self.ports.opl_register(register)? & !0x20)])
205 }
206
207 pub fn rhythm(&mut self, enabled: bool) -> Result<()> {
210 let value = self.ports.opl_register(0xbd)?;
211 self.write(&[(0xbd, if enabled { value | 0x20 } else { value & !0x3f })])
212 }
213
214 pub fn hit(&mut self, drum: Drum) -> Result<()> {
216 let value = self.ports.opl_register(0xbd)?;
217 self.write(&[
218 (0xbd, value & !drum.mask()),
219 (0xbd, value | 0x20 | drum.mask()),
220 ])
221 }
222
223 pub fn release(&mut self, drum: Drum) -> Result<()> {
225 let value = self.ports.opl_register(0xbd)?;
226 self.write(&[(0xbd, value & !drum.mask())])
227 }
228}
229
230fn channel_address(chip: OplChip, channel: u8) -> Result<(u16, u16)> {
231 let channels = if chip == OplChip::Opl2 { 9 } else { 18 };
232 if channel >= channels {
233 return Err(Error::Config("OPL channel is outside the installed chip"));
234 }
235 let bank = u16::from(channel / 9) << 8;
236 let local = usize::from(channel % 9);
237 let operator = [0, 1, 2, 8, 9, 10, 16, 17, 18][local];
238 Ok((bank | u16::from(channel % 9), bank | operator))
239}
240
241fn frequency_tuning(hz: f64) -> Result<(u16, u8)> {
242 let native_rate = 315_000_000.0 / (22.0 * 288.0);
243 let mut number = hz * 1_048_576.0 / native_rate;
244 for block in 0..=7 {
245 let rounded = number.round();
246 if rounded <= 1023.0 {
247 return Ok((rounded as u16, block));
248 }
249 number *= 0.5;
250 }
251 Err(Error::Config("note exceeds the OPL frequency range"))
252}
253
254fn patch_writes(
255 channel: u16,
256 modulator: u16,
257 patch: &OplPatch,
258 routing: u8,
259 waveform_enable: u8,
260) -> [(u16, u8); 12] {
261 let carrier = modulator + 3;
262 let a = patch.modulator;
263 let b = patch.carrier;
264 [
265 (0x01 | (modulator & 0x100), waveform_enable),
266 (0x20 | modulator, a.characteristics),
267 (0x20 | carrier, b.characteristics),
268 (0x40 | modulator, a.level),
269 (0x40 | carrier, b.level),
270 (0x60 | modulator, a.attack_decay),
271 (0x60 | carrier, b.attack_decay),
272 (0x80 | modulator, a.sustain_release),
273 (0x80 | carrier, b.sustain_release),
274 (0xe0 | modulator, a.waveform),
275 (0xe0 | carrier, b.waveform),
276 (0xc0 | channel, routing | (patch.feedback & 0x0f)),
277 ]
278}