mirror of
https://github.com/transistorfet/moa.git
synced 2024-06-09 01:29:32 +00:00
Added binary to test simple synth sounds for ym2612
This commit is contained in:
parent
8cf8c07082
commit
749a9d2250
26
docs/log.txt
26
docs/log.txt
|
@ -240,6 +240,32 @@ before 2021-10-25
|
||||||
to more properly implement the priority shadow/highlight modes
|
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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -72,6 +72,14 @@ impl<T: Copy> CircularBuffer<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
fn next_in(&self) -> usize {
|
||||||
if self.inp + 1 < self.buffer.len() {
|
if self.inp + 1 < self.buffer.len() {
|
||||||
self.inp + 1
|
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]) {
|
pub fn fill_with(&mut self, buffer: &[f32]) {
|
||||||
for sample in buffer.iter() {
|
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);
|
||||||
self.buffer.insert(sample);
|
self.buffer.insert(sample);
|
||||||
if self.buffer.is_full() {
|
if self.buffer.is_full() {
|
||||||
|
@ -156,6 +169,10 @@ impl Audio for AudioSource {
|
||||||
self.sample_rate
|
self.sample_rate
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn space_available(&self) -> usize {
|
||||||
|
self.space_available()
|
||||||
|
}
|
||||||
|
|
||||||
fn write_samples(&mut self, buffer: &[f32]) {
|
fn write_samples(&mut self, buffer: &[f32]) {
|
||||||
self.fill_with(buffer);
|
self.fill_with(buffer);
|
||||||
}
|
}
|
||||||
|
|
103
frontends/moa-minifb/src/bin/moa-ym2612.rs
Normal file
103
frontends/moa-minifb/src/bin/moa-ym2612.rs
Normal file
|
@ -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<bool>);
|
||||||
|
|
||||||
|
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<bool>,
|
||||||
|
last: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SynthControl {
|
||||||
|
pub fn new(button: HostData<bool>) -> Self {
|
||||||
|
Self {
|
||||||
|
button,
|
||||||
|
last: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Steppable for SynthControl {
|
||||||
|
fn step(&mut self, system: &System) -> Result<ClockElapsed, Error> {
|
||||||
|
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)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,10 @@ impl SineWave {
|
||||||
position: 0,
|
position: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn reset(&mut self) {
|
||||||
|
self.position = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Iterator for SineWave {
|
impl Iterator for SineWave {
|
||||||
|
@ -44,6 +48,10 @@ impl SquareWave {
|
||||||
position: 0,
|
position: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn reset(&mut self) {
|
||||||
|
self.position = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Iterator for SquareWave {
|
impl Iterator for SquareWave {
|
||||||
|
|
|
@ -49,6 +49,7 @@ pub trait KeyboardUpdater: Send {
|
||||||
|
|
||||||
pub trait Audio {
|
pub trait Audio {
|
||||||
fn samples_per_second(&self) -> usize;
|
fn samples_per_second(&self) -> usize;
|
||||||
|
fn space_available(&self) -> usize;
|
||||||
fn write_samples(&mut self, buffer: &[f32]);
|
fn write_samples(&mut self, buffer: &[f32]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,10 +42,19 @@ impl Operator {
|
||||||
self.wave.frequency = frequency * self.multiplier;
|
self.wave.frequency = frequency * self.multiplier;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn reset(&mut self) {
|
||||||
|
self.wave.reset();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_multiplier(&mut self, frequency: f32, multiplier: f32) {
|
pub fn set_multiplier(&mut self, frequency: f32, multiplier: f32) {
|
||||||
self.multiplier = multiplier;
|
self.multiplier = multiplier;
|
||||||
self.set_frequency(frequency);
|
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)]
|
#[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 {
|
pub fn get_sample(&mut self) -> f32 {
|
||||||
match self.algorithm {
|
match self.algorithm {
|
||||||
OperatorAlgorithm::A0 => {
|
OperatorAlgorithm::A0 => {
|
||||||
let mut sample = 0.0;
|
self.operators[0].get_sample()
|
||||||
sample *= self.operators[0].wave.next().unwrap();
|
* self.operators[1].get_sample()
|
||||||
sample *= self.operators[1].wave.next().unwrap();
|
* self.operators[2].get_sample()
|
||||||
sample *= self.operators[2].wave.next().unwrap();
|
* self.operators[3].get_sample()
|
||||||
sample *= self.operators[3].wave.next().unwrap();
|
|
||||||
sample
|
|
||||||
},
|
},
|
||||||
OperatorAlgorithm::A1 => {
|
OperatorAlgorithm::A1 => {
|
||||||
let mut sample = self.operators[0].wave.next().unwrap() + self.operators[1].wave.next().unwrap();
|
let sample1 = (self.operators[0].get_sample() + self.operators[1].get_sample()) / 2.0;
|
||||||
sample *= self.operators[2].wave.next().unwrap();
|
let sample2 = self.operators[2].get_sample();
|
||||||
sample *= self.operators[3].wave.next().unwrap();
|
let sample3 = self.operators[3].get_sample();
|
||||||
sample
|
sample1 * sample2 * sample3
|
||||||
},
|
},
|
||||||
OperatorAlgorithm::A2 => {
|
OperatorAlgorithm::A2 => {
|
||||||
let mut sample = self.operators[0].wave.next().unwrap() + (self.operators[1].wave.next().unwrap() * self.operators[2].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].wave.next().unwrap();
|
sample *= self.operators[3].get_sample();
|
||||||
sample
|
sample
|
||||||
},
|
},
|
||||||
OperatorAlgorithm::A3 => {
|
OperatorAlgorithm::A3 => {
|
||||||
let mut sample = (self.operators[0].wave.next().unwrap() * self.operators[1].wave.next().unwrap()) + self.operators[2].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].wave.next().unwrap();
|
sample *= self.operators[3].get_sample();
|
||||||
sample
|
sample
|
||||||
},
|
},
|
||||||
OperatorAlgorithm::A4 => {
|
OperatorAlgorithm::A4 => {
|
||||||
let sample1 = self.operators[0].wave.next().unwrap() * self.operators[1].wave.next().unwrap();
|
let sample1 = self.operators[0].get_sample() * self.operators[1].get_sample();
|
||||||
let sample2 = self.operators[2].wave.next().unwrap() * self.operators[3].wave.next().unwrap();
|
let sample2 = self.operators[2].get_sample() * self.operators[3].get_sample();
|
||||||
sample1 + sample2
|
(sample1 + sample2) / 2.0
|
||||||
},
|
},
|
||||||
OperatorAlgorithm::A5 => {
|
OperatorAlgorithm::A5 => {
|
||||||
let sample1 = self.operators[0].wave.next().unwrap();
|
let sample1 = self.operators[0].get_sample();
|
||||||
let sample2 = self.operators[1].wave.next().unwrap() + self.operators[2].wave.next().unwrap() + self.operators[3].wave.next().unwrap();
|
let sample2 = (self.operators[1].get_sample() + self.operators[2].get_sample() + self.operators[3].get_sample()) / 3.0;
|
||||||
sample1 * sample2
|
sample1 * sample2
|
||||||
},
|
},
|
||||||
OperatorAlgorithm::A6 => {
|
OperatorAlgorithm::A6 => {
|
||||||
let sample1 = self.operators[0].wave.next().unwrap() * self.operators[1].wave.next().unwrap();
|
let sample1 = self.operators[0].get_sample() * self.operators[1].get_sample();
|
||||||
sample1 + self.operators[2].wave.next().unwrap() + self.operators[3].wave.next().unwrap()
|
(sample1 + self.operators[2].get_sample() + self.operators[3].get_sample()) / 3.0
|
||||||
},
|
},
|
||||||
OperatorAlgorithm::A7 => {
|
OperatorAlgorithm::A7 => {
|
||||||
let mut sample = 0.0;
|
let sample = self.operators[0].get_sample()
|
||||||
sample += self.operators[0].wave.next().unwrap();
|
+ self.operators[1].get_sample()
|
||||||
sample += self.operators[1].wave.next().unwrap();
|
+ self.operators[2].get_sample()
|
||||||
sample += self.operators[2].wave.next().unwrap();
|
+ self.operators[3].get_sample();
|
||||||
sample += self.operators[3].wave.next().unwrap();
|
|
||||||
sample / 4.0
|
sample / 4.0
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -150,45 +162,43 @@ impl Ym2612 {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_register(&mut self, bank: usize, reg: usize, data: u8) {
|
pub fn set_register(&mut self, bank: usize, reg: usize, data: u8) {
|
||||||
match reg {
|
if reg == 0x28 {
|
||||||
0x28 => {
|
let ch = (data as usize) & 0x07;
|
||||||
let ch = (data as usize) & 0x07;
|
self.channels[ch].on = data >> 4;
|
||||||
self.channels[ch].on = data >> 4;
|
self.channels[ch].reset();
|
||||||
println!("Note: {}: {:x}", ch, self.channels[ch].on);
|
println!("Note: {}: {:x}", ch, self.channels[ch].on);
|
||||||
},
|
} else if (reg & 0xF0) == 0x30 {
|
||||||
0x30 => {
|
let (ch, op) = get_ch_op(bank, reg);
|
||||||
let (ch, op) = get_ch_op(bank, reg);
|
let multiplier = if data == 0 { 0.5 } else { (data & 0x0F) as f32 };
|
||||||
let multiplier = if data == 0 { 0.5 } else { (data & 0x0F) as f32 };
|
let frequency = self.channels[ch].base_frequency;
|
||||||
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)
|
self.channels[ch].operators[op].set_multiplier(frequency, multiplier)
|
||||||
},
|
} else if reg >= 0xA4 && reg <= 0xA6 {
|
||||||
0xA4 | 0xA5 | 0xA6 => {
|
let ch = (reg & 0x07) - 4 + (bank * 3);
|
||||||
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].1 = (self.channel_frequencies[ch].1 & 0xFF) | ((data as u16) & 0x07) << 8;
|
self.channel_frequencies[ch].0 = (data & 0x38) >> 3;
|
||||||
self.channel_frequencies[ch].0 = (data & 0x1C) >> 3;
|
} else if reg >= 0xA0 && reg <= 0xA2 {
|
||||||
},
|
let ch = (reg & 0x07) + (bank * 3);
|
||||||
0xA0 | 0xA1 | 0xA2 => {
|
self.channel_frequencies[ch].1 = (self.channel_frequencies[ch].1 & 0xFF00) | data as u16;
|
||||||
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]);
|
let frequency = fnumber_to_frequency(self.channel_frequencies[ch]);
|
||||||
self.channels[ch].set_frequency(frequency);
|
debug!("{}: channel {} set to frequency {}", DEV_NAME, ch + 1, frequency);
|
||||||
},
|
self.channels[ch].set_frequency(frequency);
|
||||||
0xB0 | 0xB1 | 0xB2 => {
|
} else if reg >= 0xB0 && reg <= 0xB2 {
|
||||||
let ch = (reg & 0x07) + (bank * 3);
|
let ch = (reg & 0x07) + (bank * 3);
|
||||||
self.channels[ch].algorithm = match data & 0x07 {
|
self.channels[ch].algorithm = match data & 0x07 {
|
||||||
0 => OperatorAlgorithm::A0,
|
0 => OperatorAlgorithm::A0,
|
||||||
1 => OperatorAlgorithm::A1,
|
1 => OperatorAlgorithm::A1,
|
||||||
2 => OperatorAlgorithm::A2,
|
2 => OperatorAlgorithm::A2,
|
||||||
3 => OperatorAlgorithm::A3,
|
3 => OperatorAlgorithm::A3,
|
||||||
4 => OperatorAlgorithm::A4,
|
4 => OperatorAlgorithm::A4,
|
||||||
5 => OperatorAlgorithm::A5,
|
5 => OperatorAlgorithm::A5,
|
||||||
6 => OperatorAlgorithm::A6,
|
6 => OperatorAlgorithm::A6,
|
||||||
7 => OperatorAlgorithm::A7,
|
7 => OperatorAlgorithm::A7,
|
||||||
_ => OperatorAlgorithm::A0,
|
_ => OperatorAlgorithm::A0,
|
||||||
};
|
};
|
||||||
},
|
} else {
|
||||||
_ => warning!("{}: !!! unhandled write to register {:0x} with {:0x}", DEV_NAME, reg, data),
|
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);
|
// self.source.write_samples(rate / 1000, &mut self.sine);
|
||||||
//}
|
//}
|
||||||
|
|
||||||
let rate = self.source.samples_per_second() / 1000;
|
let rate = self.source.samples_per_second();
|
||||||
let mut buffer = vec![0.0; rate];
|
let available = self.source.space_available();
|
||||||
for i in 0..rate {
|
let samples = if available < rate / 1000 { available } else { rate / 1000 };
|
||||||
let mut sample = 0.0;
|
//if self.source.space_available() >= samples {
|
||||||
let mut count = 0;
|
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 {
|
for ch in 0..7 {
|
||||||
if self.channels[ch].on != 0 {
|
if self.channels[ch].on != 0 {
|
||||||
sample += self.channels[ch].get_sample();
|
sample += self.channels[ch].get_sample();
|
||||||
count += 1;
|
count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if count > 0 {
|
||||||
|
buffer[i] = sample / count as f32;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.source.write_samples(&buffer);
|
||||||
if count > 0 {
|
//}
|
||||||
buffer[i] = sample / count as f32;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.source.write_samples(&buffer);
|
|
||||||
|
|
||||||
Ok(1_000_000) // Every 1ms of simulated time
|
Ok(1_000_000) // Every 1ms of simulated time
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user