mirror of
https://github.com/transistorfet/moa.git
synced 2025-01-03 13:30:57 +00:00
Added frequency setting to ym2612
This commit is contained in:
parent
1413225463
commit
8cf8c07082
@ -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).
|
||||
|
||||
|
@ -122,9 +122,9 @@ impl AudioSource {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fill_with(&mut self, samples: usize, iter: &mut dyn Iterator<Item=f32>) {
|
||||
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<Item=f32>) {
|
||||
self.fill_with(samples, iter);
|
||||
fn write_samples(&mut self, buffer: &[f32]) {
|
||||
self.fill_with(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<I>(matches: ArgMatches, init: I) where I: FnOnce(&mut MiniFrontendBuilder) -> Result<System, Error> + Send + 'static {
|
||||
@ -152,7 +153,8 @@ pub struct MiniFrontend {
|
||||
pub window: Option<Box<dyn WindowUpdater>>,
|
||||
pub controller: Option<Box<dyn ControllerUpdater>>,
|
||||
pub keyboard: Option<Box<dyn KeyboardUpdater>>,
|
||||
pub audio: AudioOutput,
|
||||
pub audio: Option<AudioOutput>,
|
||||
pub mixer: HostData<AudioMixer>,
|
||||
}
|
||||
|
||||
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,
|
||||
|
@ -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<Item=f32>);
|
||||
//fn write_samples(&mut self, buffer: &[f32]);
|
||||
fn write_samples(&mut self, buffer: &[f32]);
|
||||
}
|
||||
|
||||
pub trait BlitableSurface {
|
||||
|
@ -199,7 +199,7 @@ impl Addressable for GenesisControllers {
|
||||
|
||||
impl Steppable for GenesisControllers {
|
||||
fn step(&mut self, system: &System) -> Result<ClockElapsed, Error> {
|
||||
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 {
|
||||
|
@ -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<Operator>,
|
||||
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<dyn Audio>,
|
||||
pub selected_reg: Option<NonZeroU8>,
|
||||
pub selected_reg_0: Option<NonZeroU8>,
|
||||
pub selected_reg_1: Option<NonZeroU8>,
|
||||
|
||||
pub channels: Vec<Channel>,
|
||||
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<ClockElapsed, Error> {
|
||||
// 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]);
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
|
2
todo.txt
2
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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user