From 8cf8c070821a81771fd125e25c33654c5263dace Mon Sep 17 00:00:00 2001 From: transistor Date: Sun, 16 Jan 2022 21:42:07 -0800 Subject: [PATCH] Added frequency setting to ym2612 --- README.md | 4 + frontends/moa-common/src/audio.rs | 10 +- frontends/moa-minifb/src/lib.rs | 11 +- src/host/traits.rs | 4 +- src/peripherals/genesis/controllers.rs | 2 +- src/peripherals/ym2612.rs | 185 +++++++++++++++++++++++-- todo.txt | 2 + 7 files changed, 195 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index dc7ed9e..64208e5 100644 --- a/README.md +++ b/README.md @@ -129,3 +129,7 @@ number by the milliseconds per frame, increasing or decreasing the gameplay clock relative to the frontend's update loop. Setting it to 0.5 slows the game down to half speed and setting it to 2 doubles the speed. +The `-a` or `--disable-audio` option will prevent the audio device from being +created, so no audio will be played (although it will still be simulated by any +devices that simulate it). + diff --git a/frontends/moa-common/src/audio.rs b/frontends/moa-common/src/audio.rs index 5dedfc5..7764c32 100644 --- a/frontends/moa-common/src/audio.rs +++ b/frontends/moa-common/src/audio.rs @@ -122,9 +122,9 @@ impl AudioSource { } } - pub fn fill_with(&mut self, samples: usize, iter: &mut dyn Iterator) { - for _ in 0..samples { - let sample = 0.25 * iter.next().unwrap(); + pub fn fill_with(&mut self, buffer: &[f32]) { + for sample in buffer.iter() { + let sample = 0.25 * *sample; self.buffer.insert(sample); self.buffer.insert(sample); if self.buffer.is_full() { @@ -156,8 +156,8 @@ impl Audio for AudioSource { self.sample_rate } - fn write_samples(&mut self, samples: usize, iter: &mut dyn Iterator) { - self.fill_with(samples, iter); + fn write_samples(&mut self, buffer: &[f32]) { + self.fill_with(buffer); } } diff --git a/frontends/moa-minifb/src/lib.rs b/frontends/moa-minifb/src/lib.rs index 476a267..514b5d3 100644 --- a/frontends/moa-minifb/src/lib.rs +++ b/frontends/moa-minifb/src/lib.rs @@ -32,6 +32,7 @@ pub fn new(name: &str) -> App { .arg("-t, --threaded 'Run the simulation in a separate thread'") .arg("-x, --speed=[] 'Adjust the speed of the simulation'") .arg("-d, --debugger 'Start the debugger before running machine'") + .arg("-a, --disable-audio 'Disable audio output'") } pub fn run(matches: ArgMatches, init: I) where I: FnOnce(&mut MiniFrontendBuilder) -> Result + Send + 'static { @@ -152,7 +153,8 @@ pub struct MiniFrontend { pub window: Option>, pub controller: Option>, pub keyboard: Option>, - pub audio: AudioOutput, + pub audio: Option, + pub mixer: HostData, } impl MiniFrontend { @@ -163,7 +165,8 @@ impl MiniFrontend { window, controller, keyboard, - audio: AudioOutput::create_audio_output(mixer), + audio: None, + mixer: mixer, } } @@ -172,6 +175,10 @@ impl MiniFrontend { system.as_mut().map(|system| system.enable_debugging()); } + if matches.occurrences_of("disable-audio") <= 0 { + self.audio = Some(AudioOutput::create_audio_output(self.mixer.clone())); + } + let mut options = minifb::WindowOptions::default(); options.scale = match matches.value_of("scale").map(|s| u8::from_str_radix(s, 10).unwrap()) { Some(1) => minifb::Scale::X1, diff --git a/src/host/traits.rs b/src/host/traits.rs index 7161f8e..fd37d49 100644 --- a/src/host/traits.rs +++ b/src/host/traits.rs @@ -37,7 +37,6 @@ pub trait Tty { pub trait WindowUpdater: Send { fn get_size(&mut self) -> (u32, u32); fn update_frame(&mut self, width: u32, height: u32, bitmap: &mut [u32]); - //fn update_frame(&mut self, draw_buffer: &mut dyn FnMut(u32, u32, &[u32])); } pub trait ControllerUpdater: Send { @@ -50,8 +49,7 @@ pub trait KeyboardUpdater: Send { pub trait Audio { fn samples_per_second(&self) -> usize; - fn write_samples(&mut self, samples: usize, iter: &mut Iterator); - //fn write_samples(&mut self, buffer: &[f32]); + fn write_samples(&mut self, buffer: &[f32]); } pub trait BlitableSurface { diff --git a/src/peripherals/genesis/controllers.rs b/src/peripherals/genesis/controllers.rs index 9021eeb..8ba067b 100644 --- a/src/peripherals/genesis/controllers.rs +++ b/src/peripherals/genesis/controllers.rs @@ -199,7 +199,7 @@ impl Addressable for GenesisControllers { impl Steppable for GenesisControllers { fn step(&mut self, system: &System) -> Result { - let duration = 100_00; // Update every 100us + let duration = 100_000; // Update every 100us self.reset_timer += duration; if self.reset_timer >= 1_500_000 { diff --git a/src/peripherals/ym2612.rs b/src/peripherals/ym2612.rs index aa8b94a..8dce31e 100644 --- a/src/peripherals/ym2612.rs +++ b/src/peripherals/ym2612.rs @@ -4,28 +4,56 @@ use std::num::NonZeroU8; use crate::error::Error; use crate::system::System; use crate::devices::{ClockElapsed, Address, Addressable, Steppable, Transmutable}; -use crate::host::audio::{SquareWave}; +use crate::host::audio::{SineWave}; use crate::host::traits::{Host, Audio}; const DEV_NAME: &'static str = "ym2612"; +const CHANNELS: usize = 8; + +#[derive(Copy, Clone, Debug)] +pub enum OperatorAlgorithm { + A0, + A1, + A2, + A3, + A4, + A5, + A6, + A7, +} + + #[derive(Clone)] pub struct Operator { - pub wave: SquareWave, + pub wave: SineWave, + pub multiplier: f32, } impl Operator { pub fn new(sample_rate: usize) -> Self { Self { - wave: SquareWave::new(400.0, sample_rate) + wave: SineWave::new(400.0, sample_rate), + multiplier: 1.0, } } + + pub fn set_frequency(&mut self, frequency: f32) { + self.wave.frequency = frequency * self.multiplier; + } + + pub fn set_multiplier(&mut self, frequency: f32, multiplier: f32) { + self.multiplier = multiplier; + self.set_frequency(frequency); + } } #[derive(Clone)] pub struct Channel { pub operators: Vec, pub on: u8, + pub base_frequency: f32, + pub algorithm: OperatorAlgorithm, } impl Channel { @@ -33,6 +61,66 @@ impl Channel { Self { operators: vec![Operator::new(sample_rate); 4], on: 0, + base_frequency: 0.0, + algorithm: OperatorAlgorithm::A0, + } + } + + pub fn set_frequency(&mut self, frequency: f32) { + self.base_frequency = frequency; + for operator in self.operators.iter_mut() { + operator.set_frequency(frequency); + } + } + + pub fn get_sample(&mut self) -> f32 { + match self.algorithm { + OperatorAlgorithm::A0 => { + let mut sample = 0.0; + sample *= self.operators[0].wave.next().unwrap(); + sample *= self.operators[1].wave.next().unwrap(); + sample *= self.operators[2].wave.next().unwrap(); + sample *= self.operators[3].wave.next().unwrap(); + sample + }, + OperatorAlgorithm::A1 => { + let mut sample = self.operators[0].wave.next().unwrap() + self.operators[1].wave.next().unwrap(); + sample *= self.operators[2].wave.next().unwrap(); + sample *= self.operators[3].wave.next().unwrap(); + sample + }, + OperatorAlgorithm::A2 => { + let mut sample = self.operators[0].wave.next().unwrap() + (self.operators[1].wave.next().unwrap() * self.operators[2].wave.next().unwrap()); + sample *= self.operators[3].wave.next().unwrap(); + sample + }, + OperatorAlgorithm::A3 => { + let mut sample = (self.operators[0].wave.next().unwrap() * self.operators[1].wave.next().unwrap()) + self.operators[2].wave.next().unwrap(); + sample *= self.operators[3].wave.next().unwrap(); + sample + }, + OperatorAlgorithm::A4 => { + let sample1 = self.operators[0].wave.next().unwrap() * self.operators[1].wave.next().unwrap(); + let sample2 = self.operators[2].wave.next().unwrap() * self.operators[3].wave.next().unwrap(); + sample1 + sample2 + }, + OperatorAlgorithm::A5 => { + let sample1 = self.operators[0].wave.next().unwrap(); + let sample2 = self.operators[1].wave.next().unwrap() + self.operators[2].wave.next().unwrap() + self.operators[3].wave.next().unwrap(); + sample1 * sample2 + }, + OperatorAlgorithm::A6 => { + let sample1 = self.operators[0].wave.next().unwrap() * self.operators[1].wave.next().unwrap(); + sample1 + self.operators[2].wave.next().unwrap() + self.operators[3].wave.next().unwrap() + }, + OperatorAlgorithm::A7 => { + let mut sample = 0.0; + sample += self.operators[0].wave.next().unwrap(); + sample += self.operators[1].wave.next().unwrap(); + sample += self.operators[2].wave.next().unwrap(); + sample += self.operators[3].wave.next().unwrap(); + sample / 4.0 + }, } } } @@ -41,9 +129,11 @@ impl Channel { pub struct Ym2612 { pub source: Box, - pub selected_reg: Option, + pub selected_reg_0: Option, + pub selected_reg_1: Option, pub channels: Vec, + pub channel_frequencies: [(u8, u16); CHANNELS], } impl Ym2612 { @@ -52,12 +142,14 @@ impl Ym2612 { let sample_rate = source.samples_per_second(); Ok(Self { source, - selected_reg: None, + selected_reg_0: None, + selected_reg_1: None, channels: vec![Channel::new(sample_rate); 8], + channel_frequencies: [(0, 0); CHANNELS], }) } - pub fn set_register(&mut self, bank: u8, reg: usize, data: u8) { + pub fn set_register(&mut self, bank: usize, reg: usize, data: u8) { match reg { 0x28 => { let ch = (data as usize) & 0x07; @@ -65,13 +157,55 @@ impl Ym2612 { println!("Note: {}: {:x}", ch, self.channels[ch].on); }, 0x30 => { - let _op = if bank == 0 { 0 } else { 3 }; - } + let (ch, op) = get_ch_op(bank, reg); + let multiplier = if data == 0 { 0.5 } else { (data & 0x0F) as f32 }; + let frequency = self.channels[ch].base_frequency; + self.channels[ch].operators[op].set_multiplier(frequency, multiplier) + }, + 0xA4 | 0xA5 | 0xA6 => { + let ch = (reg & 0x07) - 4 + (bank * 3); + self.channel_frequencies[ch].1 = (self.channel_frequencies[ch].1 & 0xFF) | ((data as u16) & 0x07) << 8; + self.channel_frequencies[ch].0 = (data & 0x1C) >> 3; + }, + 0xA0 | 0xA1 | 0xA2 => { + let ch = (reg & 0x07) + (bank * 3); + self.channel_frequencies[ch].1 = (self.channel_frequencies[ch].1 & 0xFF00) | data as u16; + + let frequency = fnumber_to_frequency(self.channel_frequencies[ch]); + self.channels[ch].set_frequency(frequency); + }, + 0xB0 | 0xB1 | 0xB2 => { + let ch = (reg & 0x07) + (bank * 3); + self.channels[ch].algorithm = match data & 0x07 { + 0 => OperatorAlgorithm::A0, + 1 => OperatorAlgorithm::A1, + 2 => OperatorAlgorithm::A2, + 3 => OperatorAlgorithm::A3, + 4 => OperatorAlgorithm::A4, + 5 => OperatorAlgorithm::A5, + 6 => OperatorAlgorithm::A6, + 7 => OperatorAlgorithm::A7, + _ => OperatorAlgorithm::A0, + }; + }, _ => warning!("{}: !!! unhandled write to register {:0x} with {:0x}", DEV_NAME, reg, data), } } } +#[inline(always)] +pub fn fnumber_to_frequency(fnumber: (u8, u16)) -> f32 { + (fnumber.1 as f32 * 0.0264) * (2 as u32).pow(fnumber.0 as u32) as f32 +} + +#[inline(always)] +pub fn get_ch_op(bank: usize, reg: usize) -> (usize, usize) { + let ch = (reg & 0x03) + (bank * 3); + let op = (reg & 0xC0) >> 2; + (ch, op) +} + + impl Steppable for Ym2612 { fn step(&mut self, _system: &System) -> Result { // TODO since you expect this step function to be called every 1ms of simulated time @@ -89,6 +223,26 @@ impl Steppable for Ym2612 { // let rate = self.source.samples_per_second(); // self.source.write_samples(rate / 1000, &mut self.sine); //} + + let rate = self.source.samples_per_second() / 1000; + let mut buffer = vec![0.0; rate]; + for i in 0..rate { + let mut sample = 0.0; + let mut count = 0; + + for ch in 0..7 { + if self.channels[ch].on != 0 { + sample += self.channels[ch].get_sample(); + count += 1; + } + } + + if count > 0 { + buffer[i] = sample / count as f32; + } + } + self.source.write_samples(&buffer); + Ok(1_000_000) // Every 1ms of simulated time } } @@ -116,12 +270,19 @@ impl Addressable for Ym2612 { debug!("{}: write to register {:x} with {:x}", DEV_NAME, addr, data[0]); match addr { 0 => { - self.selected_reg = NonZeroU8::new(data[0]); + self.selected_reg_0 = NonZeroU8::new(data[0]); }, 1 => { - match self.selected_reg { - None => {}, - Some(reg) => self.set_register(0, reg.get() as usize, data[0]), + if let Some(reg) = self.selected_reg_0 { + self.set_register(0, reg.get() as usize, data[0]); + } + }, + 2 => { + self.selected_reg_1 = NonZeroU8::new(data[0]); + }, + 3 => { + if let Some(reg) = self.selected_reg_1 { + self.set_register(1, reg.get() as usize, data[0]); } }, _ => { diff --git a/todo.txt b/todo.txt index 05ce64c..7e030f5 100644 --- a/todo.txt +++ b/todo.txt @@ -1,4 +1,6 @@ +* make it possible to compile without audio support (minifb frontend requires it atm) + * I need some better function for dealing with memory, like a function that copies data with a loop, or allows offset reading of a fixed piece of data..., the trick is what function are the most common. You can use generics