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 {}