Skip to content

Commit

Permalink
remove panics, add errors, implement timers
Browse files Browse the repository at this point in the history
  • Loading branch information
dbalsom committed Jul 4, 2024
1 parent 560ed4a commit f51e4b0
Show file tree
Hide file tree
Showing 6 changed files with 347 additions and 94 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ cc = "1.0"
heck = "0.5.0"

[dependencies]
thiserror = "1.0"

[workspace]
members = [
Expand Down
13 changes: 6 additions & 7 deletions examples/play_tune/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ use std::sync::{Arc, Mutex};

use bpaf::*;
use chrono::Duration;
use crossbeam_channel::unbounded;
use rodio::cpal::traits::HostTrait;
use rodio::DeviceTrait;
use timer::Timer;
Expand All @@ -54,8 +53,8 @@ struct Out {
output_wav: Option<PathBuf>,
}

/// Set up bpaf argument parsing.
fn opts() -> OptionParser<Out> {
// Set up bpaf argument parsing.
let debug = short('d')
.long("debug")
.help("Activate debug mode")
Expand Down Expand Up @@ -87,6 +86,7 @@ fn opts() -> OptionParser<Out> {

fn main() {
// This example uses rodio for playback.

// rodio is built on top of the lower level 'cpal' audio library. If there is something we
// cannot accomplish in rodio, we can fall back to the underlying cpal implementation.

Expand Down Expand Up @@ -127,7 +127,7 @@ fn main() {
let mut wav_out = None;

if let Some(filename) = opts.output_wav {
// If we've specified a wave file for output, open it now and create a BufWriter for it.
// If we've specified a wav file for output, open it now and create a BufWriter for it.
let file = File::create(filename).expect("Couldn't create output file.");
wav_out = Some(BufWriter::new(file));
}
Expand Down Expand Up @@ -158,7 +158,7 @@ fn play_note<W: Write>(
let mut samples = vec![0; 2 * sample_rate as usize];

// Create the music player. We don't use this channel in this example.
let (s, _r) = unbounded();
let (s, _r) = crossbeam_channel::unbounded();
let mut player = MusicPlayer::new(sample_rate, s, false);

// Start the player and play a single note, leaving it sustained.
Expand Down Expand Up @@ -194,7 +194,7 @@ fn play_note<W: Write>(
std::thread::sleep(std::time::Duration::from_secs(1));
}

/// Play music using the MusicPlayer. This function sets up a timer callback to execute OPL3
/// Play music using the MusicPlayer. This function sets up a timer callback to execute OPL
/// commands and generate audio samples. The callback is fired at a fixed rate (100Hz), in a
/// separate thread. Crossbeam channels are used to send the generated samples to the main thread.
/// The message type is an enum of type CallbackMessage, and can incorporate either instructions
Expand All @@ -208,7 +208,7 @@ fn play_music<W: Write + std::io::Seek>(
// Create a channel to receive the audio samples as they are generated by the timer callback.
// The channel here is unbounded, but you could calculate the number of samples you expect to
// receive and use a bounded channel. I am not sure of the performance differences.
let (s, r) = unbounded();
let (s, r) = crossbeam_channel::unbounded();

// Create and initialize the music player.
let mut player = MusicPlayer::new(sample_rate, s, debug);
Expand Down Expand Up @@ -252,7 +252,6 @@ fn play_music<W: Write + std::io::Seek>(
},
)
.expect("Couldn't create wav writer.");

Some(wav_writer)
} else {
None
Expand Down
11 changes: 6 additions & 5 deletions examples/play_tune/src/music_player.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

use crossbeam_channel::Sender;

use opl3_rs::Opl3Device;
use opl3_rs::{Opl3Device, OplRegisterFile};

use crate::opl::*;
use crate::opl_instruments::{OPL_INSTRUMENT_PIANO1, OplInstrument};
Expand Down Expand Up @@ -185,9 +185,10 @@ impl MusicPlayer {

pub fn setup(&mut self) {
self.tempo = 120;
self.opl3.reset(None);
_ = self.opl3.reset(None);

self.opl3.write_register(0x01, 0x20, true); // Set WSE=1
self.opl3
.write_register(0x01, 0x20, OplRegisterFile::Primary, true); // Set WSE=1

set_instrument(&mut self.opl3, 0, &OPL_INSTRUMENT_PIANO1);
set_block(&mut self.opl3, 0, 5);
Expand All @@ -209,7 +210,7 @@ impl MusicPlayer {
}

pub fn generate_direct(&mut self, samples: &mut [i16]) {
self.opl3.generate_samples(samples);
_ = self.opl3.generate_samples(samples);
}

pub fn timer_callback(&mut self) {
Expand All @@ -223,7 +224,7 @@ impl MusicPlayer {
return;
}
self.main_loop();
self.opl3.generate_samples(&mut self.sample_buf);
_ = self.opl3.generate_samples(&mut self.sample_buf);

self.sender
.send(CallbackMessage::HaveSamples(self.sample_buf.clone()))
Expand Down
62 changes: 41 additions & 21 deletions examples/play_tune/src/opl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

use std::cmp;

use opl3_rs::Opl3Device;
use opl3_rs::{Opl3Device, OplRegisterFile};

pub const NOTE_C: u8 = 0;
pub const NOTE_CS: u8 = 1;
Expand Down Expand Up @@ -72,25 +72,37 @@ pub fn set_instrument(opl3: &mut Opl3Device, channel: usize, instrument: &[u8; 1
set_waveform_select(opl3, true);
for (i, byte) in instrument.iter().skip(1).enumerate() {
opl3.write_register(
OPL_INSTRUMENT_BASE_REGS[i % 6] + get_register_offset(channel, (i > 5) as usize),
(OPL_INSTRUMENT_BASE_REGS[i % 6] + get_register_offset(channel, (i > 5) as usize))
as u8,
*byte,
OplRegisterFile::Primary,
true,
);
}
}

pub fn set_waveform_select(opl3: &mut Opl3Device, enable: bool) {
if enable {
opl3.write_register(0x01, opl3.read_register(0x01) | 0x20, true);
opl3.write_register(
0x01,
opl3.read_register(0x01, OplRegisterFile::Primary) | 0x20,
OplRegisterFile::Primary,
true,
);
} else {
opl3.write_register(0x01, opl3.read_register(0x01) & 0xDF, true);
opl3.write_register(
0x01,
opl3.read_register(0x01, OplRegisterFile::Primary) & 0xDF,
OplRegisterFile::Primary,
true,
);
}
}

/// Return the frequency block of the given channel.
pub fn get_block(opl3: &mut Opl3Device, channel: u8) -> u8 {
let offset = cmp::max(0x00, cmp::min(channel as u16, 0x08));
return (opl3.read_register(0xB0 + offset) & 0x1C) >> 2;
let offset = cmp::max(0x00, cmp::min(channel, 0x08));
return (opl3.read_register(0xB0 + offset, OplRegisterFile::Primary) & 0x1C) >> 2;
}

/// Set frequency block for the given channel.
Expand All @@ -104,28 +116,29 @@ pub fn get_block(opl3: &mut Opl3Device, channel: u8) -> u8 {
/// 6 - 3.034 Hz, Range: 3.034 Hz -> 3104.215 Hz
/// 7 - 6.069 Hz, Range: 6.068 Hz -> 6208.431 Hz
pub fn set_block(opl3: &mut Opl3Device, channel: u8, octave: u8) {
let reg: u16 = 0xB0 + cmp::max(0x00, cmp::min(channel as u16, 0x08));
let reg = 0xB0 + cmp::max(0x00, cmp::min(channel, 0x08));
opl3.write_register(
reg,
(opl3.read_register(reg) & 0xE3) | ((octave & 0x07) << 2),
(opl3.read_register(reg, OplRegisterFile::Primary) & 0xE3) | ((octave & 0x07) << 2),
OplRegisterFile::Primary,
true,
);
}

/// Return whether the voice for the given channel is currently on.
pub fn get_key_on(opl3: &mut Opl3Device, channel: u8) -> bool {
let offset: u16 = cmp::max(0x00, cmp::min(channel as u16, 0x08));
return (opl3.read_register(0xB0 + offset) & 0x20) != 0;
let offset = cmp::max(0x00, cmp::min(channel, 0x08));
return (opl3.read_register(0xB0 + offset, OplRegisterFile::Primary) & 0x20) != 0;
}

/// Enable the voice for the given channel.
pub fn set_key_on(opl3: &mut Opl3Device, channel: u8, key_on: bool) {
let reg: u16 = 0xB0 + cmp::max(0x00, cmp::min(channel as u16, 0x08));
let old_reg = opl3.read_register(reg);
let reg = 0xB0 + cmp::max(0x00, cmp::min(channel, 0x08));
let old_reg = opl3.read_register(reg, OplRegisterFile::Primary);
if key_on {
opl3.write_register(reg, old_reg | 0x20, true);
opl3.write_register(reg, old_reg | 0x20, OplRegisterFile::Primary, true);
} else {
opl3.write_register(reg, old_reg & 0xDF, true);
opl3.write_register(reg, old_reg & 0xDF, OplRegisterFile::Primary, true);
}
}

Expand All @@ -150,20 +163,27 @@ pub fn get_note_frequency(opl3: &mut Opl3Device, channel: u8, octave: u8, note:

/// Returns the F-number of the given channel.
pub fn get_frequency(opl3: &mut Opl3Device, channel: u8) -> u16 {
let offset: u16 = cmp::max(0x00, cmp::min(channel as u16, 0x08));
return (((opl3.read_register(0xB0 + offset) & 0x03) as u16) << 8)
| opl3.read_register(0xA0 + offset) as u16;
let offset = cmp::max(0x00, cmp::min(channel, 0x08));
return (((opl3.read_register(0xB0 + offset, OplRegisterFile::Primary) & 0x03) as u16) << 8)
| opl3.read_register(0xA0 + offset, OplRegisterFile::Primary) as u16;
}

/// Set the F-number of the given channel.
/// Returns the register number that was written to.
pub fn set_frequency(opl3: &mut Opl3Device, channel: u8, frequency: u16) -> u16 {
let reg = 0xA0 + cmp::max(0x00, cmp::min(channel as u16, 0x08));
opl3.write_register(reg, (frequency & 0xFF) as u8, true);
let reg = 0xA0 + cmp::max(0x00, cmp::min(channel, 0x08));
opl3.write_register(
reg,
(frequency & 0xFF) as u8,
OplRegisterFile::Primary,
true,
);
opl3.write_register(
reg + 0x10,
(opl3.read_register(reg + 0x10) & 0xFC) | ((frequency & 0x0300) >> 8) as u8,
(opl3.read_register(reg + 0x10, OplRegisterFile::Primary) & 0xFC)
| ((frequency & 0x0300) >> 8) as u8,
OplRegisterFile::Primary,
true,
);
return reg;
return reg as u16;
}
Loading

0 comments on commit f51e4b0

Please sign in to comment.