Skip to main content

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}