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                }
371                OPL_TIMER_2_REGISTER => {
372                    self.timers[1].counter = value;
373                }
374                OPL_TIMER_CONTROL_REGISTER => {
375                    if (value & OPL_IRQ_FLAG) != 0 {
376                        // Reset the timer and IRQ flags in the status register.
377                        // All other bits are ignored when this bit is set.
378                        self.timers[0].reset_elapsed();
379                        self.timers[1].reset_elapsed();
380                    } else {
381                        // Mask & enable the timers based on the timer start bits.
382                        self.timers[0].mask((value & OPL_TIMER_1_MASK) != 0);
383                        self.timers[1].mask((value & OPL_TIMER_2_MASK) != 0);
384                        self.timers[0].enable((value & OPL_TIMER_1_START) != 0);
385                        self.timers[1].enable((value & OPL_TIMER_2_START) != 0);
386                    }
387                }
388                _ => {}
389            }
390        }
391
392        self.stats.data_writes = self.stats.data_writes.saturating_add(1);
393        if buffered {
394            self.inner_chip.write_register_buffered(reg16, value);
395        } else {
396            self.inner_chip.write_register(reg16, value);
397        }
398    }
399
400    /// Reset the Opl3Device.
401    /// Reset the state of the OPL3 device, including the internal registers and the internal
402    /// Nuked-OPL3 instance.
403    ///
404    /// # Arguments
405    ///
406    /// * `sample_rate` - An option that either contains the new sample rate to reinitialize with
407    ///                   or None to keep the current sample rate.
408    ///
409    /// # Returns
410    ///
411    /// A Result containing either `()` on success or an `OplError` on failure.
412    pub fn reset(&mut self, sample_rate: Option<u32>) -> Result<(), OplError> {
413        let new_sample_rate = sample_rate.unwrap_or(self.sample_rate);
414        self.inner_chip.reset(new_sample_rate);
415        for file in 0..2 {
416            for reg in 0..256 {
417                self.registers[file][reg] = 0;
418            }
419        }
420        self.stats = Opl3DeviceStats::default();
421        Ok(())
422    }
423
424    /// Generate a 2 channel audio sample in interleaved i16 format.
425    ///
426    /// # Arguments
427    ///
428    /// * `sample` - A mutable reference to a two-element slice that will receive the audio sample.
429    ///              The first element will contain the left channel sample, and the second element
430    ///              will contain the right channel sample.
431    ///
432    /// # Returns
433    ///
434    /// A Result containing either `()` on success or an `OplError` on failure.
435    pub fn generate(&mut self, sample: &mut [i16]) -> Result<(), OplError> {
436        self.inner_chip.generate(sample)
437    }
438
439    /// Generate a stream of 2 channel, interleaved audio samples in i16 format.
440    ///
441    /// # Arguments
442    ///
443    /// * `buffer` - A mutable reference to a buffer slice that will be filled with stereo, i
444    ///              interleaved audio samples.
445    ///
446    /// # Returns
447    ///
448    /// A Result containing either `()` on success or an `OplError` on failure.
449    pub fn generate_samples(&mut self, buffer: &mut [i16]) -> Result<(), OplError> {
450        self.inner_chip.generate_stream(buffer)
451    }
452}
453
454/// The `Opl3Chip` struct provides a safe interface for interacting with the Nuked-OPL3 library.
455pub struct Opl3Chip {
456    chip: *mut bindings::Opl3Chip,
457}
458
459impl Drop for Opl3Chip {
460    /// Drop the Opl3Chip instance by deallocating the memory used by the Nuked-OPL3 instance.
461    fn drop(&mut self) {
462        unsafe {
463            let layout = std::alloc::Layout::new::<bindings::Opl3Chip>();
464            std::alloc::dealloc(self.chip as *mut u8, layout);
465        }
466    }
467}
468
469impl Opl3Chip {
470    /// Creates a new OPL3 chip instance. The chip is initialized with the given sample rate.
471    /// The internal chip device is Pinned to ensure that it is not moved in memory. The Nuked-OPL3
472    /// instance contains many self-referencing pointers, which would be invalidated if moved.
473    ///
474    /// # Arguments
475    ///
476    /// * `sample_rate` - The sample rate to initialize the OPL3 chip with.
477    ///
478    /// # Returns
479    ///
480    /// The new Opl3Chip instance.
481    ///
482    /// # Example
483    ///
484    /// ```
485    /// use opl3_rs::Opl3Chip;
486    ///
487    /// let mut chip = Opl3Chip::new(44100);
488    /// ```
489    pub fn new(sample_rate: u32) -> Self {
490        unsafe {
491            let layout = std::alloc::Layout::new::<bindings::Opl3Chip>();
492            let chip = std::alloc::alloc(layout) as *mut bindings::Opl3Chip;
493            bindings::Opl3Reset(chip, sample_rate);
494            Opl3Chip { chip }
495        }
496    }
497
498    /// Reinitialize the OPL3 chip instance.
499    ///
500    /// # Arguments
501    ///
502    /// * `sample_rate` - The sample rate to initialize the OPL3 chip with.
503    ///                   I have not tested the effects of reinitializing the chip with a different
504    ///                   sample rate than the one initially used.
505    ///
506    /// # Example
507    ///
508    /// ```
509    /// use opl3_rs::Opl3Chip;
510    ///
511    /// let mut chip = Opl3Chip::new(44100);
512    /// chip.reset(44100);
513    /// ```
514    pub fn reset(&mut self, sample_rate: u32) {
515        unsafe {
516            bindings::Opl3Reset(&mut *self.chip, sample_rate);
517        }
518    }
519
520    /// Generate an audio sample.
521    ///
522    /// Internally, this calls Opl3Generate4Ch and returns samples for the first 2 channels.
523    ///
524    /// # Arguments
525    ///
526    /// * `sample` - A mutable slice of 2 elements that will receive the sample.
527    ///
528    /// # Returns
529    ///
530    /// A Result containing either `()` on success or an `OplError` on failure.
531    ///
532    /// # Example
533    ///
534    /// ```
535    /// use opl3_rs::Opl3Chip;
536    ///
537    /// let mut chip = Opl3Chip::new(44100);
538    /// let mut buffer = [0i16; 2];
539    /// _ = chip.generate(&mut buffer);
540    /// ```
541    pub fn generate(&mut self, sample: &mut [i16]) -> Result<(), OplError> {
542        if sample.len() < 2 {
543            return Err(OplError::BufferUndersized);
544        }
545        unsafe {
546            bindings::Opl3Generate(&mut *self.chip, sample.as_mut_ptr());
547        }
548        Ok(())
549    }
550
551    /// Generate a resampled audio sample.
552    ///
553    /// # Arguments
554    ///
555    /// * `sample` - A mutable slice of 2 elements that will receive the sample.
556    ///
557    /// # Returns
558    ///
559    /// A Result containing either `()` on success or an `OplError` on failure.
560    ///
561    /// # Example
562    ///
563    /// ```
564    /// use opl3_rs::Opl3Chip;
565    ///
566    /// let mut chip = Opl3Chip::new(44100);
567    /// let mut buffer = [0i16; 2];
568    /// _ = chip.generate_resampled(&mut buffer);
569    /// ```
570    pub fn generate_resampled(&mut self, sample: &mut [i16]) -> Result<(), OplError> {
571        if sample.len() < 2 {
572            return Err(OplError::BufferUndersized);
573        }
574        unsafe {
575            bindings::Opl3GenerateResampled(&mut *self.chip, sample.as_mut_ptr());
576        }
577        Ok(())
578    }
579
580    /// Writes a value to an OPL register.
581    ///
582    /// # Arguments
583    ///
584    /// * `reg` - The register to write to.
585    /// * `value` - The value to write to the register.
586    ///
587    /// # Example
588    ///
589    /// ```
590    /// use opl3_rs::Opl3Chip;
591    ///
592    /// let mut chip = Opl3Chip::new(44100);
593    /// chip.write_register(0x20, 0x01);
594    /// ```
595    pub fn write_register(&mut self, reg: u16, value: u8) {
596        unsafe {
597            bindings::Opl3WriteReg(&mut *self.chip, reg, value);
598        }
599    }
600
601    /// Write a value to an OPL register, in buffered mode.
602    ///
603    /// The OPL3 normally requires a delay between register writes. This function
604    /// will queue the write operation and execute it after any necessary delay.
605    ///
606    /// # Arguments
607    ///
608    /// * `reg` - The register to write to.
609    /// * `value` - The value to write to the register.
610    ///
611    /// # Example
612    ///
613    /// ```
614    /// use opl3_rs::Opl3Chip;
615    ///
616    /// let mut chip = Opl3Chip::new(44100);
617    /// chip.write_register_buffered(0x20, 0x01);
618    /// ```
619    pub fn write_register_buffered(&mut self, reg: u16, value: u8) {
620        unsafe {
621            bindings::Opl3WriteRegBuffered(&mut *self.chip, reg, value);
622        }
623    }
624
625    /// Generates a stream of resampled audio samples.
626    ///
627    /// The number of samples generated is determined by the size of the buffer provided.
628    ///
629    /// # Arguments
630    ///
631    /// * `buffer` - A mutable reference to a slice of i16 that will be filled with resampled audio
632    ///              samples.
633    ///
634    /// # Returns
635    ///
636    /// A Result containing either `()` on success or an `OplError` on failure.
637    ///
638    /// # Example
639    ///
640    /// ```
641    /// use opl3_rs::Opl3Chip;
642    ///
643    /// let mut chip = Opl3Chip::new(44100);
644    /// let mut buffer = [0i16; 1024 * 2];
645    /// _ = chip.generate_stream(&mut buffer);
646    /// ```
647    pub fn generate_stream(&mut self, buffer: &mut [i16]) -> Result<(), OplError> {
648        if buffer.len() < 2 {
649            return Err(OplError::BufferUndersized);
650        }
651        unsafe {
652            bindings::Opl3GenerateStream(
653                &mut *self.chip,
654                buffer.as_mut_ptr(),
655                buffer.len() as u32 / 2,
656            );
657        }
658        Ok(())
659    }
660
661    /// Generate a 4 channel audio sample.
662    ///
663    /// # Arguments
664    ///
665    /// * `sample` - A mutable, 4-element slice of i16 that will receive the sample.
666    ///
667    /// # Returns
668    ///
669    /// A Result containing either `()` on success or an `OplError` on failure.
670    ///
671    /// # Example
672    ///
673    /// ```
674    /// use opl3_rs::Opl3Chip;
675    ///
676    /// let mut chip = Opl3Chip::new(44100);
677    /// let mut buffer = [0i16; 4];
678    /// _ = chip.generate_4ch(&mut buffer);
679    /// ```
680    pub fn generate_4ch(&mut self, sample: &mut [i16]) -> Result<(), OplError> {
681        if sample.len() < 4 {
682            return Err(OplError::BufferUndersized);
683        }
684        unsafe {
685            bindings::Opl3Generate4Ch(&mut *self.chip, sample.as_mut_ptr());
686        }
687        Ok(())
688    }
689
690    /// Generate a 4-channel resampled audio sample.
691    ///
692    /// # Arguments
693    ///
694    /// * `sample` - A mutable, 4-element slice of i16 that will receive the sample.
695    ///
696    /// # Returns
697    ///
698    /// A Result containing either `()` on success or an `OplError` on failure.
699    ///
700    /// # Example
701    ///
702    /// ```
703    /// use opl3_rs::Opl3Chip;
704    ///
705    /// let mut chip = Opl3Chip::new(44100);
706    /// let mut buffer = [0i16; 4];
707    /// _ = chip.generate_4ch_resampled(&mut buffer);
708    /// ```
709    pub fn generate_4ch_resampled(&mut self, sample: &mut [i16]) -> Result<(), OplError> {
710        if sample.len() < 4 {
711            return Err(OplError::BufferUndersized);
712        }
713        unsafe {
714            bindings::Opl3Generate4ChResampled(&mut *self.chip, sample.as_mut_ptr());
715        }
716        Ok(())
717    }
718
719    /// Generates a stream of 4-channel audio samples, resampled to the configured sample rate.
720    /// The OPL3 was capable of 4-channel output, although this feature was not widely used. Most
721    /// cards simply didn't provide 4-channel outputs, although there now exist modern reproduction
722    /// cards that do.
723    ///
724    /// The number of samples is determined by the size of the input buffers.
725    ///
726    /// # Arguments
727    ///
728    /// * `buffer1` - A mutable reference to a slice that will be filled with the first stereo
729    ///               audio samples, interleaved between left and right channels.
730    /// * `buffer2` - A mutable reference to a slice that will be filled with audio samples for the
731    ///               channels 2 and 3.
732    ///               The length of buffer1 should equal the length of buffer2.
733    ///
734    /// # Returns
735    ///
736    /// A Result containing either `()` on success or an `OplError` on failure.
737    ///
738    /// # Example
739    ///
740    /// ```
741    /// use opl3_rs::Opl3Chip;
742    ///
743    /// let mut chip = Opl3Chip::new(44100);
744    /// let mut buffer1 = [0i16; 1024 * 2];
745    /// let mut buffer2 = [0i16; 1024 * 2];
746    /// _ = chip.generate_4ch_stream(&mut buffer1, &mut buffer2);
747    /// ```
748    pub fn generate_4ch_stream(
749        &mut self,
750        buffer1: &mut [i16],
751        buffer2: &mut [i16],
752    ) -> Result<(), OplError> {
753        if buffer1.len() != buffer2.len() {
754            return Err(OplError::BufferMismatch);
755        }
756        if buffer1.len() < 4 || buffer2.len() < 4 {
757            return Err(OplError::BufferUndersized);
758        }
759        unsafe {
760            bindings::Opl3Generate4ChStream(
761                &mut *self.chip,
762                buffer1.as_mut_ptr(),
763                buffer2.as_mut_ptr(),
764                buffer1.len() as u32 / 2,
765            );
766        }
767        Ok(())
768    }
769}
770
771#[cfg(test)]
772mod tests {}