opl3_rs/lib.rs
1// #opl3-rs
2// A simple wrapper around the OPL3 chip library.
3// Bindings generated by Daniel Balsom.
4//
5// Nuked OPL3 Copyright (C) 2013-2020 Nuke.YKT
6#![warn(missing_docs)]
7#![doc = include_str!("./docs.md")]
8
9/*
10* Nuked OPL3 is free software: you can redistribute it and/or modify
11* it under the terms of the GNU Lesser General Public License as
12* published by the Free Software Foundation, either version 2.1
13* of the License, or (at your option) any later version.
14*
15* Nuked OPL3 is distributed in the hope that it will be useful,
16* but WITHOUT ANY WARRANTY; without even the implied warranty of
17* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18* GNU Lesser General Public License for more details.
19*
20* You should have received a copy of the GNU Lesser General Public License
21* along with Nuked OPL3. If not, see <https://www.gnu.org/licenses/>.
22
23* Nuked OPL3 emulator.
24* Thanks:
25* MAME Development Team(Jarek Burczynski, Tatsuyuki Satoh):
26* Feedback and Rhythm part calculation information.
27* forums.submarine.org.uk(carbon14, opl3):
28* Tremolo and phase generator calculation information.
29* OPLx decapsulated(Matthew Gambrell, Olli Niemitalo):
30* OPL2 ROMs.
31* siliconpr0n.org(John McMaster, digshadow):
32* YMF262 and VRC VII decaps and die shots.
33*/
34
35use thiserror::Error;
36
37mod bindings;
38
39unsafe impl Send for Opl3Chip {}
40
41// OPL3 register addresses for registers not handled by Nuked-OPL3 directly.
42const OPL_TIMER_1_REGISTER: u8 = 0x02;
43const OPL_TIMER_2_REGISTER: u8 = 0x03;
44const OPL_TIMER_CONTROL_REGISTER: u8 = 0x04;
45
46const OPL_IRQ_FLAG: u8 = 0b1000_0000;
47const OPL_TIMER_1_MASK: u8 = 0b0100_0000;
48const OPL_TIMER_2_MASK: u8 = 0b0010_0000;
49const OPL_TIMER_1_START: u8 = 0b0000_0001;
50const OPL_TIMER_2_START: u8 = 0b0000_0010;
51
52const OPL_TICK_RATE: f64 = 80.0; // Perform a timer tick every 80us.
53const OPL_TIMER_1_RATE: u32 = 80; // Timer 1 tick rate is every 80us.
54const OPL_TIMER_2_RATE: u32 = 320; // Timer 2 tick rate is every 320us.
55
56#[derive(Error, Debug)]
57/// The `OplError` enum represents errors that can occur when using the `opl3-rs` library.
58pub enum OplError {
59 #[error("Buffer slice provided was too small")]
60 /// The buffer slice provided was too small to contain the generated samples.
61 BufferUndersized,
62 #[error("Buffer slices must be equal in length")]
63 /// The buffer slices provided to generate_4ch_stream were not equal in length.
64 BufferMismatch,
65 #[error("Register number out of range")]
66 /// The specified register number is out of range.
67 RegisterOutOfRange,
68 #[error("Failed to lock mutex")]
69 /// Failed to lock the mutex for the OPL3 device.
70 MutexLockFailed,
71}
72
73#[derive(Debug)]
74/// The `Opl3RegisterFile` enum represents the two register files available on the OPL3 chip.
75/// If in OPL2 mode, only the primary register file is available.
76pub enum OplRegisterFile {
77 /// Select the OPL2 register file.
78 Primary,
79 /// Select the extended OPL3 register file.
80 Secondary,
81}
82
83/// The `Opl3DeviceStats` struct contains statistics about the OPL3 device.
84/// It can be retrieved via the `get_stats` function on `Opl3Device`.
85#[derive(Copy, Clone, Default)]
86pub struct Opl3DeviceStats {
87 /// The number of writes to the OPL3 data register since reset.
88 pub data_writes: usize,
89 /// The number of writes to the OPL3 address register since reset.
90 pub addr_writes: usize,
91 /// The number of reads from the OPL3 status register since reset.
92 pub status_reads: usize,
93 /// The number of samples generated since reset. A stereo pair (left and right) is considered
94 /// one sample.
95 pub samples_generated: usize,
96}
97
98/// The `Opl3Device` maintains two internal timers.
99#[derive(Default, Debug)]
100struct OplTimer {
101 enabled: bool,
102 masked: bool,
103 rate: u32,
104 preset: u8,
105 counter: u8,
106 usec_accumulator: f64,
107 elapsed: bool,
108}
109
110impl OplTimer {
111 fn new(rate: u32) -> Self {
112 OplTimer {
113 enabled: false,
114 masked: false,
115 rate,
116 preset: 0,
117 counter: 0,
118 usec_accumulator: 0.0,
119 elapsed: false,
120 }
121 }
122
123 fn mask(&mut self, masked: bool) {
124 self.masked = masked;
125 }
126
127 fn is_elapsed(&self) -> bool {
128 if self.masked {
129 false
130 } else {
131 self.elapsed
132 }
133 }
134
135 fn enable(&mut self, state: bool) {
136 self.enabled = state;
137 }
138
139 #[allow(dead_code)]
140 fn reset(&mut self) {
141 self.counter = self.preset;
142 self.elapsed = false;
143 }
144
145 fn reset_elapsed(&mut self) {
146 self.elapsed = false;
147 }
148
149 fn tick(&mut self, usec: f64) {
150 self.usec_accumulator += usec;
151 while self.usec_accumulator >= self.rate as f64 {
152 self.usec_accumulator -= self.rate as f64;
153 self.count();
154 }
155 }
156
157 #[inline]
158 fn count(&mut self) {
159 if self.enabled {
160 if self.counter == 255 {
161 self.elapsed = true;
162 self.counter = self.preset;
163 } else {
164 self.counter += 1;
165 }
166 }
167 }
168}
169
170/// The `Opl3Device` struct provides convenience functions for fully implementing an OPL3 device on
171/// top of Nuked-OPL3.
172/// By keeping a copy of all registers written, we can implement a read_register function.
173pub struct Opl3Device {
174 addr_reg: [u8; 2],
175 sample_rate: u32,
176 registers: [[u8; 256]; 2],
177 timers: [OplTimer; 2],
178 stats: Opl3DeviceStats,
179 inner_chip: Opl3Chip,
180 samples_fpart: f64,
181 usec_accumulator: f64,
182}
183
184impl Opl3Device {
185 /// Create a new OPL3 device instance.
186 /// `Opl3Device` is a convenience wrapper around the Nuked-OPL3's direct wrapper, `Opl3Chip`.
187 /// It provides the rest of an OPL3 implementation on top of the chip, including register
188 /// tracking and a read_register function.
189 pub fn new(sample_rate: u32) -> Self {
190 Opl3Device {
191 addr_reg: [0, 0],
192 sample_rate,
193 registers: [[0; 256], [0; 256]],
194 timers: [
195 OplTimer::new(OPL_TIMER_1_RATE),
196 OplTimer::new(OPL_TIMER_2_RATE),
197 ],
198 stats: Opl3DeviceStats::default(),
199 inner_chip: Opl3Chip::new(sample_rate),
200 samples_fpart: 0.0,
201 usec_accumulator: 0.0,
202 }
203 }
204
205 /// Retrieve the statistics for the OPL3 device in the form of an `Opl3DeviceStats` struct.
206 ///
207 /// # Returns
208 /// An `Opl3DeviceStats` struct containing the statistics for the OPL3 device.
209 pub fn stats(&self) -> Opl3DeviceStats {
210 self.stats
211 }
212
213 /// Update the `Opl3Device` instance. This function should be called periodically to update the
214 /// state of the OPL3 timers.
215 /// # Arguments
216 ///
217 /// * `usec` - The number of microseconds that have passed since the last call to `run`.
218 ///
219 /// # Returns
220 /// The number of samples that correspond to the specified microseconds that elapsed.
221 /// The Opl3Device maintains a fractional accumulator, so you can use this returned value to
222 /// determine how many samples to generate.
223 pub fn run(&mut self, usec: f64) -> usize {
224 self.usec_accumulator += usec;
225 while self.usec_accumulator >= OPL_TICK_RATE {
226 self.usec_accumulator -= OPL_TICK_RATE;
227 self.timers[0].tick(OPL_TICK_RATE);
228 self.timers[1].tick(OPL_TICK_RATE);
229 }
230
231 let samples_f = (usec / 1_000_000.0 * self.sample_rate as f64) + self.samples_fpart;
232
233 let samples = samples_f as usize;
234 self.samples_fpart = samples_f - samples_f.floor();
235
236 samples
237 }
238
239 /// Read a byte from the OPL3 device's Status register.
240 /// The Nuked-OPL3 library does not natively provide emulation of the OPL3 status register.
241 /// The status register contains bits that indicate the status of the OPL3's timers. To properly
242 /// emulate this timer state, it is necessary to call run() on the OPL3 device periodically.
243 pub fn read_status(&mut self) -> u8 {
244 self.stats.status_reads = self.stats.status_reads.saturating_add(1);
245
246 let mut status_reg = 0;
247
248 status_reg |= if self.timers[0].is_elapsed() {
249 OPL_TIMER_1_MASK
250 } else {
251 0
252 };
253
254 status_reg |= if self.timers[1].is_elapsed() {
255 OPL_TIMER_2_MASK
256 } else {
257 0
258 };
259
260 status_reg |= if self.timers[0].is_elapsed() || self.timers[1].is_elapsed() {
261 OPL_IRQ_FLAG
262 } else {
263 0
264 };
265
266 status_reg
267 }
268
269 /// Write a byte to the OPL3 device's Address register.
270 /// This function, along with write_data, is likely the primary interface for an emulator
271 /// implementing an OPL device.
272 ///
273 /// # Arguments
274 ///
275 /// * `addr` - The register address to write to the OPL3 device, in the range 0..=255.
276 /// * `file` - The register file to write to. OPL3 devices have two register files, the Primary
277 /// and Secondary files. OPL2 devices only have the Primary register file.
278 pub fn write_address(&mut self, addr: u8, file: OplRegisterFile) -> Result<(), OplError> {
279 match file {
280 OplRegisterFile::Primary => self.addr_reg[0] = addr,
281 OplRegisterFile::Secondary => self.addr_reg[1] = addr,
282 }
283 Ok(())
284 }
285
286 /// Write a byte to the OPL3 device's Data register.
287 /// This function, along with write_address, is likely the primary interface function for an
288 /// emulator implementing an OPL device.
289 ///
290 /// The actual internal register to be written should be set by writing to the OPL3 address
291 /// register via `write_address` before calling `write_data`.
292 ///
293 /// # Arguments
294 ///
295 /// * `data` - The byte of data to write to the OPL3 device.
296 /// * `buffered` - Whether to write the data in buffered mode. In buffered mode, Nuked-OPL3
297 /// will store the write in a buffer and execute it after any necessary delay.
298 /// This is useful for controlling the library manually, but if you are
299 /// implementing an emulator the software controlling the OPL3 module will
300 /// likely write registers with appropriate timings.
301 /// * `file` - The register file to write to. OPL3 devices have two register files, the Primary
302 /// and Secondary files. OPL2 devices only have the Primary register file.
303 pub fn write_data(
304 &mut self,
305 data: u8,
306 file: OplRegisterFile,
307 buffered: bool,
308 ) -> Result<(), OplError> {
309 let addr = match file {
310 OplRegisterFile::Primary => self.addr_reg[0],
311 OplRegisterFile::Secondary => self.addr_reg[1],
312 };
313 self.write_register(addr, data, file, buffered);
314 Ok(())
315 }
316
317 /// Return the value of the given chip register from internal state.
318 /// The OPL3 registers are not natively readable. `Opl3Device` keeps a copy of all registers
319 /// written so that they can be queried. This internal state will become desynchronized if
320 /// registers are written directly to the OPL3 chip.
321 ///
322 /// # Arguments
323 ///
324 /// * `reg` - The internal register index to read.
325 /// * `file` - The register file to write to. OPL3 devices have two register files, the Primary
326 /// and Secondary files. OPL2 devices only have the Primary register file
327 ///
328 /// # Returns
329 ///
330 /// The u8 value of the requested register.
331 pub fn read_register(&self, reg: u8, file: OplRegisterFile) -> u8 {
332 match file {
333 OplRegisterFile::Primary => self.registers[0][reg as usize],
334 OplRegisterFile::Secondary => self.registers[1][reg as usize],
335 }
336 }
337
338 /// Write to the specified register directly. This will update the internal state of the
339 /// Opl3Device so that the register value can later be read.
340 ///
341 /// # Arguments
342 ///
343 /// * `reg` - The internal register index to write.
344 /// * `value` - The value to write to the register.
345 /// * `buffered` - Whether to write the data in buffered mode. In buffered mode, Nuked-OPL3
346 /// will store the write in a buffer and execute it after any necessary delay.
347 /// This is useful for controlling the library manually, but if you are
348 /// implementing an emulator the software controlling the OPL3 module will
349 /// likely write registers with appropriate timings.
350 /// * `file` - The register file to write to. OPL3 devices have two register files, the Primary
351 /// and Secondary files. OPL2 devices only have the Primary register file
352 pub fn write_register(&mut self, reg: u8, value: u8, file: OplRegisterFile, buffered: bool) {
353 let reg16 = match file {
354 OplRegisterFile::Primary => {
355 self.registers[0][reg as usize] = value;
356 reg as u16
357 }
358 OplRegisterFile::Secondary => {
359 self.registers[1][reg as usize] = value;
360 reg as u16 | 0x100
361 }
362 };
363
364 // We need to intercept certain register addresses that Nuked-OPL3 doesn't emulate, namely
365 // the timer registers.
366 if let OplRegisterFile::Primary = file {
367 match reg {
368 OPL_TIMER_1_REGISTER => {
369 self.timers[0].counter = value;
370 self.timers[0].preset = value;
371 }
372 OPL_TIMER_2_REGISTER => {
373 self.timers[1].counter = value;
374 self.timers[1].preset = value;
375 }
376 OPL_TIMER_CONTROL_REGISTER => {
377 if (value & OPL_IRQ_FLAG) != 0 {
378 // Reset the timer and IRQ flags in the status register.
379 // All other bits are ignored when this bit is set.
380 self.timers[0].reset_elapsed();
381 self.timers[1].reset_elapsed();
382 } else {
383 // Mask & enable the timers based on the timer start bits.
384 self.timers[0].mask((value & OPL_TIMER_1_MASK) != 0);
385 self.timers[1].mask((value & OPL_TIMER_2_MASK) != 0);
386 self.timers[0].enable((value & OPL_TIMER_1_START) != 0);
387 self.timers[1].enable((value & OPL_TIMER_2_START) != 0);
388 }
389 }
390 _ => {}
391 }
392 }
393
394 self.stats.data_writes = self.stats.data_writes.saturating_add(1);
395 if buffered {
396 self.inner_chip.write_register_buffered(reg16, value);
397 } else {
398 self.inner_chip.write_register(reg16, value);
399 }
400 }
401
402 /// Reset the Opl3Device.
403 /// Reset the state of the OPL3 device, including the internal registers and the internal
404 /// Nuked-OPL3 instance.
405 ///
406 /// # Arguments
407 ///
408 /// * `sample_rate` - An option that either contains the new sample rate to reinitialize with
409 /// or None to keep the current sample rate.
410 ///
411 /// # Returns
412 ///
413 /// A Result containing either `()` on success or an `OplError` on failure.
414 pub fn reset(&mut self, sample_rate: Option<u32>) -> Result<(), OplError> {
415 let new_sample_rate = sample_rate.unwrap_or(self.sample_rate);
416 self.inner_chip.reset(new_sample_rate);
417 for file in 0..2 {
418 for reg in 0..256 {
419 self.registers[file][reg] = 0;
420 }
421 }
422 self.stats = Opl3DeviceStats::default();
423 Ok(())
424 }
425
426 /// Generate a 2 channel audio sample in interleaved i16 format.
427 ///
428 /// # Arguments
429 ///
430 /// * `sample` - A mutable reference to a two-element slice that will receive the audio sample.
431 /// The first element will contain the left channel sample, and the second element
432 /// will contain the right channel sample.
433 ///
434 /// # Returns
435 ///
436 /// A Result containing either `()` on success or an `OplError` on failure.
437 pub fn generate(&mut self, sample: &mut [i16]) -> Result<(), OplError> {
438 self.inner_chip.generate(sample)
439 }
440
441 /// Generate a stream of 2 channel, interleaved audio samples in i16 format.
442 ///
443 /// # Arguments
444 ///
445 /// * `buffer` - A mutable reference to a buffer slice that will be filled with stereo, i
446 /// interleaved audio samples.
447 ///
448 /// # Returns
449 ///
450 /// A Result containing either `()` on success or an `OplError` on failure.
451 pub fn generate_samples(&mut self, buffer: &mut [i16]) -> Result<(), OplError> {
452 self.inner_chip.generate_stream(buffer)
453 }
454}
455
456/// The `Opl3Chip` struct provides a safe interface for interacting with the Nuked-OPL3 library.
457pub struct Opl3Chip {
458 chip: *mut bindings::Opl3Chip,
459}
460
461impl Drop for Opl3Chip {
462 /// Drop the Opl3Chip instance by deallocating the memory used by the Nuked-OPL3 instance.
463 fn drop(&mut self) {
464 unsafe {
465 let layout = std::alloc::Layout::new::<bindings::Opl3Chip>();
466 std::alloc::dealloc(self.chip as *mut u8, layout);
467 }
468 }
469}
470
471impl Opl3Chip {
472 /// Creates a new OPL3 chip instance. The chip is initialized with the given sample rate.
473 /// The internal chip device is Pinned to ensure that it is not moved in memory. The Nuked-OPL3
474 /// instance contains many self-referencing pointers, which would be invalidated if moved.
475 ///
476 /// # Arguments
477 ///
478 /// * `sample_rate` - The sample rate to initialize the OPL3 chip with.
479 ///
480 /// # Returns
481 ///
482 /// The new Opl3Chip instance.
483 ///
484 /// # Example
485 ///
486 /// ```
487 /// use opl3_rs::Opl3Chip;
488 ///
489 /// let mut chip = Opl3Chip::new(44100);
490 /// ```
491 pub fn new(sample_rate: u32) -> Self {
492 unsafe {
493 let layout = std::alloc::Layout::new::<bindings::Opl3Chip>();
494 let chip = std::alloc::alloc(layout) as *mut bindings::Opl3Chip;
495 bindings::Opl3Reset(chip, sample_rate);
496 Opl3Chip { chip }
497 }
498 }
499
500 /// Reinitialize the OPL3 chip instance.
501 ///
502 /// # Arguments
503 ///
504 /// * `sample_rate` - The sample rate to initialize the OPL3 chip with.
505 /// I have not tested the effects of reinitializing the chip with a different
506 /// sample rate than the one initially used.
507 ///
508 /// # Example
509 ///
510 /// ```
511 /// use opl3_rs::Opl3Chip;
512 ///
513 /// let mut chip = Opl3Chip::new(44100);
514 /// chip.reset(44100);
515 /// ```
516 pub fn reset(&mut self, sample_rate: u32) {
517 unsafe {
518 bindings::Opl3Reset(&mut *self.chip, sample_rate);
519 }
520 }
521
522 /// Generate an audio sample.
523 ///
524 /// Internally, this calls Opl3Generate4Ch and returns samples for the first 2 channels.
525 ///
526 /// # Arguments
527 ///
528 /// * `sample` - A mutable slice of 2 elements that will receive the sample.
529 ///
530 /// # Returns
531 ///
532 /// A Result containing either `()` on success or an `OplError` on failure.
533 ///
534 /// # Example
535 ///
536 /// ```
537 /// use opl3_rs::Opl3Chip;
538 ///
539 /// let mut chip = Opl3Chip::new(44100);
540 /// let mut buffer = [0i16; 2];
541 /// _ = chip.generate(&mut buffer);
542 /// ```
543 pub fn generate(&mut self, sample: &mut [i16]) -> Result<(), OplError> {
544 if sample.len() < 2 {
545 return Err(OplError::BufferUndersized);
546 }
547 unsafe {
548 bindings::Opl3Generate(&mut *self.chip, sample.as_mut_ptr());
549 }
550 Ok(())
551 }
552
553 /// Generate a resampled audio sample.
554 ///
555 /// # Arguments
556 ///
557 /// * `sample` - A mutable slice of 2 elements that will receive the sample.
558 ///
559 /// # Returns
560 ///
561 /// A Result containing either `()` on success or an `OplError` on failure.
562 ///
563 /// # Example
564 ///
565 /// ```
566 /// use opl3_rs::Opl3Chip;
567 ///
568 /// let mut chip = Opl3Chip::new(44100);
569 /// let mut buffer = [0i16; 2];
570 /// _ = chip.generate_resampled(&mut buffer);
571 /// ```
572 pub fn generate_resampled(&mut self, sample: &mut [i16]) -> Result<(), OplError> {
573 if sample.len() < 2 {
574 return Err(OplError::BufferUndersized);
575 }
576 unsafe {
577 bindings::Opl3GenerateResampled(&mut *self.chip, sample.as_mut_ptr());
578 }
579 Ok(())
580 }
581
582 /// Writes a value to an OPL register.
583 ///
584 /// # Arguments
585 ///
586 /// * `reg` - The register to write to.
587 /// * `value` - The value to write to the register.
588 ///
589 /// # Example
590 ///
591 /// ```
592 /// use opl3_rs::Opl3Chip;
593 ///
594 /// let mut chip = Opl3Chip::new(44100);
595 /// chip.write_register(0x20, 0x01);
596 /// ```
597 pub fn write_register(&mut self, reg: u16, value: u8) {
598 unsafe {
599 bindings::Opl3WriteReg(&mut *self.chip, reg, value);
600 }
601 }
602
603 /// Write a value to an OPL register, in buffered mode.
604 ///
605 /// The OPL3 normally requires a delay between register writes. This function
606 /// will queue the write operation and execute it after any necessary delay.
607 ///
608 /// # Arguments
609 ///
610 /// * `reg` - The register to write to.
611 /// * `value` - The value to write to the register.
612 ///
613 /// # Example
614 ///
615 /// ```
616 /// use opl3_rs::Opl3Chip;
617 ///
618 /// let mut chip = Opl3Chip::new(44100);
619 /// chip.write_register_buffered(0x20, 0x01);
620 /// ```
621 pub fn write_register_buffered(&mut self, reg: u16, value: u8) {
622 unsafe {
623 bindings::Opl3WriteRegBuffered(&mut *self.chip, reg, value);
624 }
625 }
626
627 /// Generates a stream of resampled audio samples.
628 ///
629 /// The number of samples generated is determined by the size of the buffer provided.
630 ///
631 /// # Arguments
632 ///
633 /// * `buffer` - A mutable reference to a slice of i16 that will be filled with resampled audio
634 /// samples.
635 ///
636 /// # Returns
637 ///
638 /// A Result containing either `()` on success or an `OplError` on failure.
639 ///
640 /// # Example
641 ///
642 /// ```
643 /// use opl3_rs::Opl3Chip;
644 ///
645 /// let mut chip = Opl3Chip::new(44100);
646 /// let mut buffer = [0i16; 1024 * 2];
647 /// _ = chip.generate_stream(&mut buffer);
648 /// ```
649 pub fn generate_stream(&mut self, buffer: &mut [i16]) -> Result<(), OplError> {
650 if buffer.len() < 2 {
651 return Err(OplError::BufferUndersized);
652 }
653 unsafe {
654 bindings::Opl3GenerateStream(
655 &mut *self.chip,
656 buffer.as_mut_ptr(),
657 buffer.len() as u32 / 2,
658 );
659 }
660 Ok(())
661 }
662
663 /// Generate a 4 channel audio sample.
664 ///
665 /// # Arguments
666 ///
667 /// * `sample` - A mutable, 4-element slice of i16 that will receive the sample.
668 ///
669 /// # Returns
670 ///
671 /// A Result containing either `()` on success or an `OplError` on failure.
672 ///
673 /// # Example
674 ///
675 /// ```
676 /// use opl3_rs::Opl3Chip;
677 ///
678 /// let mut chip = Opl3Chip::new(44100);
679 /// let mut buffer = [0i16; 4];
680 /// _ = chip.generate_4ch(&mut buffer);
681 /// ```
682 pub fn generate_4ch(&mut self, sample: &mut [i16]) -> Result<(), OplError> {
683 if sample.len() < 4 {
684 return Err(OplError::BufferUndersized);
685 }
686 unsafe {
687 bindings::Opl3Generate4Ch(&mut *self.chip, sample.as_mut_ptr());
688 }
689 Ok(())
690 }
691
692 /// Generate a 4-channel resampled audio sample.
693 ///
694 /// # Arguments
695 ///
696 /// * `sample` - A mutable, 4-element slice of i16 that will receive the sample.
697 ///
698 /// # Returns
699 ///
700 /// A Result containing either `()` on success or an `OplError` on failure.
701 ///
702 /// # Example
703 ///
704 /// ```
705 /// use opl3_rs::Opl3Chip;
706 ///
707 /// let mut chip = Opl3Chip::new(44100);
708 /// let mut buffer = [0i16; 4];
709 /// _ = chip.generate_4ch_resampled(&mut buffer);
710 /// ```
711 pub fn generate_4ch_resampled(&mut self, sample: &mut [i16]) -> Result<(), OplError> {
712 if sample.len() < 4 {
713 return Err(OplError::BufferUndersized);
714 }
715 unsafe {
716 bindings::Opl3Generate4ChResampled(&mut *self.chip, sample.as_mut_ptr());
717 }
718 Ok(())
719 }
720
721 /// Generates a stream of 4-channel audio samples, resampled to the configured sample rate.
722 /// The OPL3 was capable of 4-channel output, although this feature was not widely used. Most
723 /// cards simply didn't provide 4-channel outputs, although there now exist modern reproduction
724 /// cards that do.
725 ///
726 /// The number of samples is determined by the size of the input buffers.
727 ///
728 /// # Arguments
729 ///
730 /// * `buffer1` - A mutable reference to a slice that will be filled with the first stereo
731 /// audio samples, interleaved between left and right channels.
732 /// * `buffer2` - A mutable reference to a slice that will be filled with audio samples for the
733 /// channels 2 and 3.
734 /// The length of buffer1 should equal the length of buffer2.
735 ///
736 /// # Returns
737 ///
738 /// A Result containing either `()` on success or an `OplError` on failure.
739 ///
740 /// # Example
741 ///
742 /// ```
743 /// use opl3_rs::Opl3Chip;
744 ///
745 /// let mut chip = Opl3Chip::new(44100);
746 /// let mut buffer1 = [0i16; 1024 * 2];
747 /// let mut buffer2 = [0i16; 1024 * 2];
748 /// _ = chip.generate_4ch_stream(&mut buffer1, &mut buffer2);
749 /// ```
750 pub fn generate_4ch_stream(
751 &mut self,
752 buffer1: &mut [i16],
753 buffer2: &mut [i16],
754 ) -> Result<(), OplError> {
755 if buffer1.len() != buffer2.len() {
756 return Err(OplError::BufferMismatch);
757 }
758 if buffer1.len() < 4 || buffer2.len() < 4 {
759 return Err(OplError::BufferUndersized);
760 }
761 unsafe {
762 bindings::Opl3Generate4ChStream(
763 &mut *self.chip,
764 buffer1.as_mut_ptr(),
765 buffer2.as_mut_ptr(),
766 buffer1.len() as u32 / 2,
767 );
768 }
769 Ok(())
770 }
771}
772
773#[cfg(test)]
774mod tests {
775 use super::*;
776
777 #[test]
778 fn timer_reloads_from_preset() {
779 let mut device = Opl3Device::new(44_100);
780
781 device.write_register(OPL_TIMER_1_REGISTER, 254, OplRegisterFile::Primary, false);
782 device.write_register(
783 OPL_TIMER_CONTROL_REGISTER,
784 OPL_TIMER_1_START,
785 OplRegisterFile::Primary,
786 false,
787 );
788
789 device.run(OPL_TIMER_1_RATE as f64);
790 assert_eq!(device.read_status(), 0);
791
792 device.run(OPL_TIMER_1_RATE as f64);
793 assert_eq!(device.read_status(), OPL_IRQ_FLAG | OPL_TIMER_1_MASK);
794 assert_eq!(device.timers[0].counter, 254);
795 }
796}