From 749a9d225048f844caf53fa1a55c9a3f62f9cf32 Mon Sep 17 00:00:00 2001 From: transistor Date: Tue, 18 Jan 2022 20:24:18 -0800 Subject: [PATCH] Added binary to test simple synth sounds for ym2612 --- docs/log.txt | 26 ++++ frontends/moa-common/src/audio.rs | 19 ++- frontends/moa-minifb/src/bin/moa-ym2612.rs | 103 ++++++++++++ src/host/audio.rs | 8 + src/host/traits.rs | 1 + src/peripherals/ym2612.rs | 172 +++++++++++---------- 6 files changed, 249 insertions(+), 80 deletions(-) create mode 100644 frontends/moa-minifb/src/bin/moa-ym2612.rs diff --git a/docs/log.txt b/docs/log.txt index 5093bb7..7c8d67d 100644 --- a/docs/log.txt +++ b/docs/log.txt @@ -240,6 +240,32 @@ before 2021-10-25 to more properly implement the priority shadow/highlight modes +Audio +----- + +2021-12-12 +- this is when I committed the audio support, but I'm not sure when I started. It was a little earlier +- cpal uses a callback to get the next buffer of data, so the buffer needs to be assembled outside of + the callback, with each device creating a Source with a buffer, that the mixer/output can draw upon +- initially I had it give an iterator to load the buffer, but that doesn't work for ym2612 generation + because it itself needs to mix a bunch of sources together to get the output buffer +- I made it use a circular buffer so that unused data can be skipped to keep the simulation in sync +- from various glitches I was able to get it to playback tones smoothly, with only the occasional pop + + +2022-01-17 +- finally have done more, added the various register locations to set the frequency of the operators, + and added a way to combine the samples according to the algorithm of ops to get sound +- had to add `.reset()` to start from the beginning when a note is played, in order to prevent clicks + from the waveform all of a sudden jumping in level when the note starts +- started adding a binary to control just the ym2612 for testing, so I can isolate issues, and a lot + of minor issues have turned up + +2022-01-18 +- some kind of buffer problem causing clicking, where the waveform resets, possibly related to circular buf +- a quick attempt at fixing it shows that the audio source buffer is only copied to the mixer buffer + when it's written to the buffer (and overfills). Attempt to not write to the buffer means audio stops + when the source buffer is full diff --git a/frontends/moa-common/src/audio.rs b/frontends/moa-common/src/audio.rs index 7764c32..b4a1a15 100644 --- a/frontends/moa-common/src/audio.rs +++ b/frontends/moa-common/src/audio.rs @@ -72,6 +72,14 @@ impl CircularBuffer { } } + pub fn free_space(&self) -> usize { + if self.out > self.inp { + self.out - self.inp - 1 + } else { + self.buffer.len() - self.inp + self.out - 1 + } + } + fn next_in(&self) -> usize { if self.inp + 1 < self.buffer.len() { self.inp + 1 @@ -122,9 +130,14 @@ impl AudioSource { } } + pub fn space_available(&self) -> usize { + self.buffer.free_space() + } + pub fn fill_with(&mut self, buffer: &[f32]) { for sample in buffer.iter() { - let sample = 0.25 * *sample; + // TODO this is here to keep it quiet for testing, but should be removed later + let sample = 0.5 * *sample; self.buffer.insert(sample); self.buffer.insert(sample); if self.buffer.is_full() { @@ -156,6 +169,10 @@ impl Audio for AudioSource { self.sample_rate } + fn space_available(&self) -> usize { + self.space_available() + } + fn write_samples(&mut self, buffer: &[f32]) { self.fill_with(buffer); } diff --git a/frontends/moa-minifb/src/bin/moa-ym2612.rs b/frontends/moa-minifb/src/bin/moa-ym2612.rs new file mode 100644 index 0000000..cf881d4 --- /dev/null +++ b/frontends/moa-minifb/src/bin/moa-ym2612.rs @@ -0,0 +1,103 @@ + +use moa_minifb; +use moa::peripherals::ym2612::{Ym2612}; + +use moa::error::Error; +use moa::system::System; +use moa::host::gfx::Frame; +use moa::devices::{ClockElapsed, Address, Addressable, Steppable, Transmutable, TransmutableBox, wrap_transmutable}; +use moa::host::keys::{Key}; +use moa::host::traits::{Host, HostData, WindowUpdater, KeyboardUpdater}; + + +pub struct SynthControlsUpdater(HostData); + +impl KeyboardUpdater for SynthControlsUpdater { + fn update_keyboard(&mut self, key: Key, state: bool) { + match key { + Key::Enter => { self.0.set(state); if state { println!("start"); } }, + _ => { }, + } + } +} + +struct SynthControl { + button: HostData, + last: bool, +} + +impl SynthControl { + pub fn new(button: HostData) -> Self { + Self { + button, + last: false, + } + } +} + +impl Steppable for SynthControl { + fn step(&mut self, system: &System) -> Result { + let current = self.button.get(); + + if current != self.last { + self.last = current; + system.get_bus().write_u8(0x00, 0x28)?; + system.get_bus().write_u8(0x01, if current { 0xF0 } else { 0x00 })?; + } + + Ok(1_000_000) + } +} + +impl Transmutable for SynthControl { + fn as_steppable(&mut self) -> Option<&mut dyn Steppable> { + Some(self) + } +} + +fn set_register(device: &mut dyn Addressable, bank: u8, reg: u8, data: u8) -> Result<(), Error> { + let addr = (bank as Address) * 2; + device.write_u8(addr, reg)?; + device.write_u8(addr + 1, data)?; + Ok(()) +} + +fn initialize_ym(ym_sound: TransmutableBox) -> Result<(), Error> { + let mut borrow = ym_sound.borrow_mut(); + let device = borrow.as_addressable().unwrap(); + + set_register(device, 0, 0x30, 0x71)?; + set_register(device, 0, 0x34, 0x0D)?; + set_register(device, 0, 0x38, 0x33)?; + set_register(device, 0, 0x3C, 0x01)?; + + set_register(device, 0, 0xA4, 0x22)?; + set_register(device, 0, 0xA0, 0x69)?; + set_register(device, 0, 0xB0, 0x30)?; + Ok(()) +} + +fn main() { + let matches = moa_minifb::new("YM2612 Tester/Synth") + .get_matches(); + + moa_minifb::run(matches, |host| { + let mut system = System::new(); + + let button = HostData::new(false); + let control = wrap_transmutable(SynthControl::new(button.clone())); + system.add_device("control", control)?; + + let ym_sound = wrap_transmutable(Ym2612::create(host)?); + initialize_ym(ym_sound.clone())?; + system.add_addressable_device(0x00, ym_sound)?; + + let frame = Frame::new_shared(384, 128); + host.add_window(Frame::new_updater(frame.clone()))?; + host.register_keyboard(Box::new(SynthControlsUpdater(button)))?; + + Ok(system) + }); +} + + diff --git a/src/host/audio.rs b/src/host/audio.rs index 8ac69e0..ecebfca 100644 --- a/src/host/audio.rs +++ b/src/host/audio.rs @@ -17,6 +17,10 @@ impl SineWave { position: 0, } } + + pub fn reset(&mut self) { + self.position = 0; + } } impl Iterator for SineWave { @@ -44,6 +48,10 @@ impl SquareWave { position: 0, } } + + pub fn reset(&mut self) { + self.position = 0; + } } impl Iterator for SquareWave { diff --git a/src/host/traits.rs b/src/host/traits.rs index fd37d49..afda639 100644 --- a/src/host/traits.rs +++ b/src/host/traits.rs @@ -49,6 +49,7 @@ pub trait KeyboardUpdater: Send { pub trait Audio { fn samples_per_second(&self) -> usize; + fn space_available(&self) -> usize; fn write_samples(&mut self, buffer: &[f32]); } diff --git a/src/peripherals/ym2612.rs b/src/peripherals/ym2612.rs index 8dce31e..6b84320 100644 --- a/src/peripherals/ym2612.rs +++ b/src/peripherals/ym2612.rs @@ -42,10 +42,19 @@ impl Operator { self.wave.frequency = frequency * self.multiplier; } + pub fn reset(&mut self) { + self.wave.reset(); + } + pub fn set_multiplier(&mut self, frequency: f32, multiplier: f32) { self.multiplier = multiplier; self.set_frequency(frequency); } + + pub fn get_sample(&mut self) -> f32 { + // TODO this would need to take into account the volume and envelope + self.wave.next().unwrap() + } } #[derive(Clone)] @@ -73,52 +82,55 @@ impl Channel { } } + pub fn reset(&mut self) { + for operator in self.operators.iter_mut() { + operator.reset(); + } + } + 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 + self.operators[0].get_sample() + * self.operators[1].get_sample() + * self.operators[2].get_sample() + * self.operators[3].get_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 + let sample1 = (self.operators[0].get_sample() + self.operators[1].get_sample()) / 2.0; + let sample2 = self.operators[2].get_sample(); + let sample3 = self.operators[3].get_sample(); + sample1 * sample2 * sample3 }, 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(); + let mut sample = (self.operators[0].get_sample() + (self.operators[1].get_sample() * self.operators[2].get_sample())) / 2.0; + sample *= self.operators[3].get_sample(); 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(); + let mut sample = ((self.operators[0].get_sample() * self.operators[1].get_sample()) + self.operators[2].get_sample()) / 2.0; + sample *= self.operators[3].get_sample(); 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 + let sample1 = self.operators[0].get_sample() * self.operators[1].get_sample(); + let sample2 = self.operators[2].get_sample() * self.operators[3].get_sample(); + (sample1 + sample2) / 2.0 }, 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(); + let sample1 = self.operators[0].get_sample(); + let sample2 = (self.operators[1].get_sample() + self.operators[2].get_sample() + self.operators[3].get_sample()) / 3.0; 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() + let sample1 = self.operators[0].get_sample() * self.operators[1].get_sample(); + (sample1 + self.operators[2].get_sample() + self.operators[3].get_sample()) / 3.0 }, 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(); + let sample = self.operators[0].get_sample() + + self.operators[1].get_sample() + + self.operators[2].get_sample() + + self.operators[3].get_sample(); sample / 4.0 }, } @@ -150,45 +162,43 @@ impl Ym2612 { } pub fn set_register(&mut self, bank: usize, reg: usize, data: u8) { - match reg { - 0x28 => { - let ch = (data as usize) & 0x07; - self.channels[ch].on = data >> 4; - println!("Note: {}: {:x}", ch, self.channels[ch].on); - }, - 0x30 => { - 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; + if reg == 0x28 { + let ch = (data as usize) & 0x07; + self.channels[ch].on = data >> 4; + self.channels[ch].reset(); + println!("Note: {}: {:x}", ch, self.channels[ch].on); + } else if (reg & 0xF0) == 0x30 { + 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; + debug!("{}: channel {} operator {} set to multiplier {}", DEV_NAME, ch + 1, op + 1, multiplier); + self.channels[ch].operators[op].set_multiplier(frequency, multiplier) + } else if reg >= 0xA4 && reg <= 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 & 0x38) >> 3; + } else if reg >= 0xA0 && reg <= 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), + let frequency = fnumber_to_frequency(self.channel_frequencies[ch]); + debug!("{}: channel {} set to frequency {}", DEV_NAME, ch + 1, frequency); + self.channels[ch].set_frequency(frequency); + } else if reg >= 0xB0 && reg <= 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, + }; + } else { + warning!("{}: !!! unhandled write to register {:0x} with {:0x}", DEV_NAME, reg, data); } } } @@ -224,24 +234,28 @@ impl Steppable for Ym2612 { // 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; + let rate = self.source.samples_per_second(); + let available = self.source.space_available(); + let samples = if available < rate / 1000 { available } else { rate / 1000 }; + //if self.source.space_available() >= samples { + let mut buffer = vec![0.0; samples]; + for i in 0..samples { + 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; + 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; } } - - if count > 0 { - buffer[i] = sample / count as f32; - } - } - self.source.write_samples(&buffer); + self.source.write_samples(&buffer); + //} Ok(1_000_000) // Every 1ms of simulated time }