#![warn(missing_docs)]
#![doc = include_str!("./docs.md")]
use thiserror::Error;
mod bindings;
unsafe impl Send for Opl3Chip {}
const OPL_TIMER_1_REGISTER: u8 = 0x02;
const OPL_TIMER_2_REGISTER: u8 = 0x03;
const OPL_TIMER_CONTROL_REGISTER: u8 = 0x04;
const OPL_IRQ_FLAG: u8 = 0b1000_0000;
const OPL_TIMER_1_MASK: u8 = 0b0100_0000;
const OPL_TIMER_2_MASK: u8 = 0b0010_0000;
const OPL_TIMER_1_START: u8 = 0b0000_0001;
const OPL_TIMER_2_START: u8 = 0b0000_0010;
const OPL_TICK_RATE: f64 = 80.0; const OPL_TIMER_1_RATE: u32 = 80; const OPL_TIMER_2_RATE: u32 = 320;
#[derive(Error, Debug)]
pub enum OplError {
#[error("Buffer slice provided was too small")]
BufferUndersized,
#[error("Buffer slices must be equal in length")]
BufferMismatch,
#[error("Register number out of range")]
RegisterOutOfRange,
#[error("Failed to lock mutex")]
MutexLockFailed,
}
#[derive(Debug)]
pub enum OplRegisterFile {
Primary,
Secondary,
}
#[derive(Copy, Clone, Default)]
pub struct Opl3DeviceStats {
pub data_writes: usize,
pub addr_writes: usize,
pub status_reads: usize,
pub samples_generated: usize,
}
#[derive(Default, Debug)]
struct OplTimer {
enabled: bool,
masked: bool,
rate: u32,
preset: u8,
counter: u8,
usec_accumulator: f64,
elapsed: bool,
}
impl OplTimer {
fn new(rate: u32) -> Self {
OplTimer {
enabled: false,
masked: false,
rate,
preset: 0,
counter: 0,
usec_accumulator: 0.0,
elapsed: false,
}
}
fn mask(&mut self, masked: bool) {
self.masked = masked;
}
fn is_elapsed(&self) -> bool {
if self.masked {
false
} else {
self.elapsed
}
}
fn enable(&mut self, state: bool) {
self.enabled = state;
}
#[allow(dead_code)]
fn reset(&mut self) {
self.counter = self.preset;
self.elapsed = false;
}
fn reset_elapsed(&mut self) {
self.elapsed = false;
}
fn tick(&mut self, usec: f64) {
self.usec_accumulator += usec;
while self.usec_accumulator >= self.rate as f64 {
self.usec_accumulator -= self.rate as f64;
self.count();
}
}
#[inline]
fn count(&mut self) {
if self.enabled {
if self.counter == 255 {
self.elapsed = true;
self.counter = self.preset;
} else {
self.counter += 1;
}
}
}
}
pub struct Opl3Device {
addr_reg: [u8; 2],
sample_rate: u32,
registers: [[u8; 256]; 2],
timers: [OplTimer; 2],
stats: Opl3DeviceStats,
inner_chip: Opl3Chip,
samples_fpart: f64,
usec_accumulator: f64,
}
impl Opl3Device {
pub fn new(sample_rate: u32) -> Self {
Opl3Device {
addr_reg: [0, 0],
sample_rate,
registers: [[0; 256], [0; 256]],
timers: [
OplTimer::new(OPL_TIMER_1_RATE),
OplTimer::new(OPL_TIMER_2_RATE),
],
stats: Opl3DeviceStats::default(),
inner_chip: Opl3Chip::new(sample_rate),
samples_fpart: 0.0,
usec_accumulator: 0.0,
}
}
pub fn stats(&self) -> Opl3DeviceStats {
self.stats
}
pub fn run(&mut self, usec: f64) -> usize {
self.usec_accumulator += usec;
while self.usec_accumulator >= OPL_TICK_RATE {
self.usec_accumulator -= OPL_TICK_RATE;
self.timers[0].tick(OPL_TICK_RATE);
self.timers[1].tick(OPL_TICK_RATE);
}
let samples_f = (usec / 1_000_000.0 * self.sample_rate as f64) + self.samples_fpart;
let samples = samples_f as usize;
self.samples_fpart = samples_f - samples_f.floor();
samples
}
pub fn read_status(&mut self) -> u8 {
self.stats.status_reads = self.stats.status_reads.saturating_add(1);
let mut status_reg = 0;
status_reg |= if self.timers[0].is_elapsed() {
OPL_TIMER_1_MASK
} else {
0
};
status_reg |= if self.timers[1].is_elapsed() {
OPL_TIMER_2_MASK
} else {
0
};
status_reg |= if self.timers[0].is_elapsed() || self.timers[1].is_elapsed() {
OPL_IRQ_FLAG
} else {
0
};
status_reg
}
pub fn write_address(&mut self, addr: u8, file: OplRegisterFile) -> Result<(), OplError> {
match file {
OplRegisterFile::Primary => self.addr_reg[0] = addr,
OplRegisterFile::Secondary => self.addr_reg[1] = addr,
}
Ok(())
}
pub fn write_data(
&mut self,
data: u8,
file: OplRegisterFile,
buffered: bool,
) -> Result<(), OplError> {
let addr = match file {
OplRegisterFile::Primary => self.addr_reg[0],
OplRegisterFile::Secondary => self.addr_reg[1],
};
self.write_register(addr, data, file, buffered);
Ok(())
}
pub fn read_register(&self, reg: u8, file: OplRegisterFile) -> u8 {
match file {
OplRegisterFile::Primary => self.registers[0][reg as usize],
OplRegisterFile::Secondary => self.registers[1][reg as usize],
}
}
pub fn write_register(&mut self, reg: u8, value: u8, file: OplRegisterFile, buffered: bool) {
let reg16 = match file {
OplRegisterFile::Primary => {
self.registers[0][reg as usize] = value;
reg as u16
}
OplRegisterFile::Secondary => {
self.registers[1][reg as usize] = value;
reg as u16 | 0x100
}
};
if let OplRegisterFile::Primary = file {
match reg {
OPL_TIMER_1_REGISTER => {
self.timers[0].counter = value;
self.timers[0].preset = value;
}
OPL_TIMER_2_REGISTER => {
self.timers[1].counter = value;
self.timers[1].preset = value;
}
OPL_TIMER_CONTROL_REGISTER => {
if (value & OPL_IRQ_FLAG) != 0 {
self.timers[0].reset_elapsed();
self.timers[1].reset_elapsed();
} else {
self.timers[0].mask((value & OPL_TIMER_1_MASK) != 0);
self.timers[1].mask((value & OPL_TIMER_2_MASK) != 0);
self.timers[0].enable((value & OPL_TIMER_1_START) != 0);
self.timers[1].enable((value & OPL_TIMER_2_START) != 0);
}
}
_ => {}
}
}
self.stats.data_writes = self.stats.data_writes.saturating_add(1);
if buffered {
self.inner_chip.write_register_buffered(reg16, value);
} else {
self.inner_chip.write_register(reg16, value);
}
}
pub fn reset(&mut self, sample_rate: Option<u32>) -> Result<(), OplError> {
let new_sample_rate = sample_rate.unwrap_or(self.sample_rate);
self.inner_chip.reset(new_sample_rate);
for file in 0..2 {
for reg in 0..256 {
self.registers[file][reg] = 0;
}
}
self.stats = Opl3DeviceStats::default();
Ok(())
}
pub fn generate(&mut self, sample: &mut [i16]) -> Result<(), OplError> {
self.inner_chip.generate(sample)
}
pub fn generate_samples(&mut self, buffer: &mut [i16]) -> Result<(), OplError> {
self.inner_chip.generate_stream(buffer)
}
}
pub struct Opl3Chip {
chip: *mut bindings::Opl3Chip,
}
impl Drop for Opl3Chip {
fn drop(&mut self) {
unsafe {
let layout = std::alloc::Layout::new::<bindings::Opl3Chip>();
std::alloc::dealloc(self.chip as *mut u8, layout);
}
}
}
impl Opl3Chip {
pub fn new(sample_rate: u32) -> Self {
unsafe {
let layout = std::alloc::Layout::new::<bindings::Opl3Chip>();
let chip = std::alloc::alloc(layout) as *mut bindings::Opl3Chip;
bindings::Opl3Reset(chip, sample_rate);
Opl3Chip { chip }
}
}
pub fn reset(&mut self, sample_rate: u32) {
unsafe {
bindings::Opl3Reset(&mut *self.chip, sample_rate);
}
}
pub fn generate(&mut self, sample: &mut [i16]) -> Result<(), OplError> {
if sample.len() < 2 {
return Err(OplError::BufferUndersized);
}
unsafe {
bindings::Opl3Generate(&mut *self.chip, sample.as_mut_ptr());
}
Ok(())
}
pub fn generate_resampled(&mut self, sample: &mut [i16]) -> Result<(), OplError> {
if sample.len() < 2 {
return Err(OplError::BufferUndersized);
}
unsafe {
bindings::Opl3GenerateResampled(&mut *self.chip, sample.as_mut_ptr());
}
Ok(())
}
pub fn write_register(&mut self, reg: u16, value: u8) {
unsafe {
bindings::Opl3WriteReg(&mut *self.chip, reg, value);
}
}
pub fn write_register_buffered(&mut self, reg: u16, value: u8) {
unsafe {
bindings::Opl3WriteRegBuffered(&mut *self.chip, reg, value);
}
}
pub fn generate_stream(&mut self, buffer: &mut [i16]) -> Result<(), OplError> {
if buffer.len() < 2 {
return Err(OplError::BufferUndersized);
}
unsafe {
bindings::Opl3GenerateStream(
&mut *self.chip,
buffer.as_mut_ptr(),
buffer.len() as u32 / 2,
);
}
Ok(())
}
pub fn generate_4ch(&mut self, sample: &mut [i16]) -> Result<(), OplError> {
if sample.len() < 4 {
return Err(OplError::BufferUndersized);
}
unsafe {
bindings::Opl3Generate4Ch(&mut *self.chip, sample.as_mut_ptr());
}
Ok(())
}
pub fn generate_4ch_resampled(&mut self, sample: &mut [i16]) -> Result<(), OplError> {
if sample.len() < 4 {
return Err(OplError::BufferUndersized);
}
unsafe {
bindings::Opl3Generate4ChResampled(&mut *self.chip, sample.as_mut_ptr());
}
Ok(())
}
pub fn generate_4ch_stream(
&mut self,
buffer1: &mut [i16],
buffer2: &mut [i16],
) -> Result<(), OplError> {
if buffer1.len() != buffer2.len() {
return Err(OplError::BufferMismatch);
}
if buffer1.len() < 4 || buffer2.len() < 4 {
return Err(OplError::BufferUndersized);
}
unsafe {
bindings::Opl3Generate4ChStream(
&mut *self.chip,
buffer1.as_mut_ptr(),
buffer2.as_mut_ptr(),
buffer1.len() as u32 / 2,
);
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn timer_reloads_from_preset() {
let mut device = Opl3Device::new(44_100);
device.write_register(OPL_TIMER_1_REGISTER, 254, OplRegisterFile::Primary, false);
device.write_register(
OPL_TIMER_CONTROL_REGISTER,
OPL_TIMER_1_START,
OplRegisterFile::Primary,
false,
);
device.run(OPL_TIMER_1_RATE as f64);
assert_eq!(device.read_status(), 0);
device.run(OPL_TIMER_1_RATE as f64);
assert_eq!(device.read_status(), OPL_IRQ_FLAG | OPL_TIMER_1_MASK);
assert_eq!(device.timers[0].counter, 254);
}
}