mirror of
https://github.com/transistorfet/moa.git
synced 2024-11-28 20:50:13 +00:00
Added audio support
It's better than it was but there are still minor drop outs due to a buffer underrun I think (could be other timing issues related to the update loop or something else). Right now, the audio chips just have some code to produce sine waves for testing.
This commit is contained in:
parent
0247279e4b
commit
39068fec2a
@ -5,8 +5,10 @@ edition = "2018"
|
||||
|
||||
[features]
|
||||
tty = ["nix"]
|
||||
audio = ["cpal"]
|
||||
|
||||
[dependencies]
|
||||
moa = { path = "../../" }
|
||||
nix = { version = "0.23", optional = true }
|
||||
cpal = { version = "0.13", optional = true }
|
||||
|
||||
|
375
frontends/moa-common/src/audio.rs
Normal file
375
frontends/moa-common/src/audio.rs
Normal file
@ -0,0 +1,375 @@
|
||||
|
||||
use moa::host::traits::{HostData, Audio};
|
||||
use cpal::{Data, Sample, Stream, SampleRate, SampleFormat, StreamConfig, traits::{DeviceTrait, HostTrait, StreamTrait}};
|
||||
|
||||
|
||||
const SAMPLE_RATE: usize = 48000;
|
||||
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CircularBuffer<T> {
|
||||
pub inp: usize,
|
||||
pub out: usize,
|
||||
pub init: T,
|
||||
pub buffer: Vec<T>,
|
||||
}
|
||||
|
||||
impl<T: Copy> CircularBuffer<T> {
|
||||
pub fn new(size: usize, init: T) -> Self {
|
||||
Self {
|
||||
inp: 0,
|
||||
out: 0,
|
||||
init,
|
||||
buffer: vec![init; size],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.buffer.len()
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.inp = 0;
|
||||
self.out = 0;
|
||||
}
|
||||
|
||||
pub fn resize(&mut self, newlen: usize) {
|
||||
if self.buffer.len() != newlen {
|
||||
self.buffer = vec![self.init; newlen];
|
||||
self.clear();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, item: T) {
|
||||
let next = self.next_in();
|
||||
if next != self.out {
|
||||
self.buffer[self.inp] = item;
|
||||
self.inp = next;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn drop_next(&mut self, mut count: usize) {
|
||||
let avail = self.used_space();
|
||||
if count > avail {
|
||||
count = avail;
|
||||
}
|
||||
|
||||
self.out += count;
|
||||
if self.out >= self.buffer.len() {
|
||||
self.out -= self.buffer.len();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_full(&self) -> bool {
|
||||
self.next_in() == self.out
|
||||
}
|
||||
|
||||
pub fn used_space(&self) -> usize {
|
||||
if self.inp >= self.out {
|
||||
self.inp - self.out
|
||||
} else {
|
||||
self.buffer.len() - self.out + self.inp
|
||||
}
|
||||
}
|
||||
|
||||
fn next_in(&self) -> usize {
|
||||
if self.inp + 1 < self.buffer.len() {
|
||||
self.inp + 1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy> Iterator for CircularBuffer<T> {
|
||||
type Item = T;
|
||||
|
||||
fn next(&mut self) -> Option<T> {
|
||||
if self.out == self.inp {
|
||||
None
|
||||
} else {
|
||||
let value = self.buffer[self.out];
|
||||
self.out += 1;
|
||||
if self.out >= self.buffer.len() {
|
||||
self.out = 0;
|
||||
}
|
||||
Some(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub struct AudioSource {
|
||||
sample_rate: usize,
|
||||
frame_size: usize,
|
||||
sequence_num: usize,
|
||||
mixer: HostData<AudioMixer>,
|
||||
buffer: CircularBuffer<f32>,
|
||||
}
|
||||
|
||||
impl AudioSource {
|
||||
pub fn new(mixer: HostData<AudioMixer>) -> Self {
|
||||
let sample_rate = mixer.lock().sample_rate();
|
||||
let frame_size = mixer.lock().frame_size();
|
||||
let buffer = CircularBuffer::new(frame_size * 2, 0.0);
|
||||
|
||||
Self {
|
||||
sample_rate,
|
||||
frame_size,
|
||||
sequence_num: 0,
|
||||
mixer,
|
||||
buffer,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fill_with(&mut self, samples: usize, iter: &mut Iterator<Item=f32>) {
|
||||
for i in 0..samples {
|
||||
let sample = 0.25 * iter.next().unwrap();
|
||||
self.buffer.insert(sample);
|
||||
self.buffer.insert(sample);
|
||||
if self.buffer.is_full() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if self.buffer.used_space() >= self.frame_size {
|
||||
let mut locked_mixer = self.mixer.lock();
|
||||
|
||||
let mixer_sequence_num = locked_mixer.sequence_num();
|
||||
if mixer_sequence_num == self.sequence_num {
|
||||
return;
|
||||
}
|
||||
self.sequence_num = mixer_sequence_num;
|
||||
|
||||
for i in 0..locked_mixer.buffer.len() {
|
||||
locked_mixer.buffer[i] += self.buffer.next().unwrap_or(0.0);
|
||||
}
|
||||
|
||||
self.frame_size = locked_mixer.frame_size();
|
||||
self.buffer.resize(self.frame_size * 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Audio for AudioSource {
|
||||
fn samples_per_second(&self) -> usize {
|
||||
self.sample_rate
|
||||
}
|
||||
|
||||
fn write_samples(&mut self, samples: usize, iter: &mut Iterator<Item=f32>) {
|
||||
self.fill_with(samples, iter);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AudioMixer {
|
||||
sample_rate: usize,
|
||||
//buffer: CircularBuffer<f32>,
|
||||
buffer: Vec<f32>,
|
||||
sequence_num: usize,
|
||||
}
|
||||
|
||||
impl AudioMixer {
|
||||
pub fn new(sample_rate: usize) -> HostData<AudioMixer> {
|
||||
HostData::new(AudioMixer {
|
||||
sample_rate,
|
||||
//buffer: CircularBuffer::new(1280 * 2, 0.0),
|
||||
buffer: vec![0.0; 1280 * 2],
|
||||
sequence_num: 0,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new_default() -> HostData<AudioMixer> {
|
||||
AudioMixer::new(SAMPLE_RATE)
|
||||
}
|
||||
|
||||
pub fn sample_rate(&self) -> usize {
|
||||
self.sample_rate
|
||||
}
|
||||
|
||||
pub fn frame_size(&self) -> usize {
|
||||
self.buffer.len()
|
||||
}
|
||||
|
||||
pub fn sequence_num(&self) -> usize {
|
||||
self.sequence_num
|
||||
}
|
||||
|
||||
pub fn resize_frame(&mut self, newlen: usize) {
|
||||
if self.buffer.len() != newlen {
|
||||
self.buffer = vec![0.0; newlen];
|
||||
}
|
||||
}
|
||||
|
||||
pub fn assembly_frame(&mut self, data: &mut [f32]) {
|
||||
self.resize_frame(data.len());
|
||||
for i in 0..data.len() {
|
||||
data[i] = Sample::from(&self.buffer[i]);
|
||||
self.buffer[i] = 0.0;
|
||||
}
|
||||
self.sequence_num = self.sequence_num.wrapping_add(1);
|
||||
|
||||
/*
|
||||
let mut buffer = vec![0.0; data.len()];
|
||||
|
||||
for source in &self.sources {
|
||||
let mut locked_source = source.lock();
|
||||
// TODO these are quick hacks to delay or shrink the buffer if it's too small or big
|
||||
if locked_source.used_space() < data.len() {
|
||||
continue;
|
||||
}
|
||||
let excess = locked_source.used_space() - (data.len() * 2);
|
||||
if excess > 0 {
|
||||
locked_source.drop_next(excess);
|
||||
}
|
||||
|
||||
for addr in buffer.iter_mut() {
|
||||
*addr += locked_source.next().unwrap_or(0.0);
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0..data.len() {
|
||||
let sample = buffer[i] / self.sources.len() as f32;
|
||||
data[i] = Sample::from(&sample);
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
let mut locked_source = self.sources[1].lock();
|
||||
for i in 0..data.len() {
|
||||
let sample = locked_source.next().unwrap_or(0.0);
|
||||
data[i] = Sample::from(&sample);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
// TODO you need a way to add data to the mixer... the question is do you need to keep track of real time
|
||||
// If you have a counter that calculates the amount of time until the next sample based on the size of
|
||||
// the buffer given to the data_callback, then when submitting data, the audio sources can know that they
|
||||
// the next place to write to is a given position in the mixer buffer (maybe not the start of the buffer).
|
||||
|
||||
// But what do you do if there needs to be some skipping. If the source is generating data in 1 to 10 ms
|
||||
// chunks according to simulated time, there might be a case where it tries to write too much data because
|
||||
// it's running fast. (If it's running slow, you can insert silence)
|
||||
}
|
||||
|
||||
|
||||
pub struct AudioOutput {
|
||||
stream: Stream,
|
||||
mixer: HostData<AudioMixer>,
|
||||
}
|
||||
|
||||
impl AudioOutput {
|
||||
pub fn create_audio_output(mixer: HostData<AudioMixer>) -> AudioOutput {
|
||||
let device = cpal::default_host()
|
||||
.default_output_device()
|
||||
.expect("No sound output device available");
|
||||
|
||||
let config: StreamConfig = device
|
||||
.supported_output_configs()
|
||||
.expect("error while querying configs")
|
||||
.find(|config| config.sample_format() == SampleFormat::F32 && config.channels() == 2)
|
||||
.expect("no supported config?!")
|
||||
.with_sample_rate(SampleRate(SAMPLE_RATE as u32))
|
||||
.into();
|
||||
|
||||
let channels = config.channels as usize;
|
||||
|
||||
let data_callback = {
|
||||
let mixer = mixer.clone();
|
||||
move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
|
||||
mixer.lock().assembly_frame(data);
|
||||
|
||||
/*
|
||||
let mut locked_mixer = mixer.lock();
|
||||
//println!(">>> {} into {}", locked_mixer.buffer.used_space(), data.len());
|
||||
|
||||
// TODO these are quick hacks to delay or shrink the buffer if it's too small or big
|
||||
if locked_mixer.buffer.used_space() < data.len() {
|
||||
return;
|
||||
}
|
||||
if locked_mixer.buffer.used_space() > data.len() * 2 {
|
||||
for _ in 0..(locked_mixer.buffer.used_space() - (data.len() * 2)) {
|
||||
locked_mixer.buffer.next();
|
||||
}
|
||||
}
|
||||
|
||||
for addr in data.iter_mut() {
|
||||
let sample = locked_mixer.buffer.next().unwrap_or(0.0);
|
||||
*addr = Sample::from(&sample);
|
||||
}
|
||||
//locked_mixer.buffer.clear();
|
||||
*/
|
||||
}
|
||||
};
|
||||
|
||||
let stream = device.build_output_stream(
|
||||
&config,
|
||||
data_callback,
|
||||
move |err| {
|
||||
// react to errors here.
|
||||
println!("ERROR");
|
||||
},
|
||||
).unwrap();
|
||||
|
||||
stream.play().unwrap();
|
||||
|
||||
AudioOutput {
|
||||
stream,
|
||||
mixer,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
pub fn create_audio_output2(mut updater: Box<dyn AudioUpdater>) -> AudioOutput {
|
||||
let device = cpal::default_host()
|
||||
.default_output_device()
|
||||
.expect("No sound output device available");
|
||||
|
||||
let config: StreamConfig = device
|
||||
.supported_output_configs()
|
||||
.expect("error while querying configs")
|
||||
.find(|config| config.sample_format() == SampleFormat::F32 && config.channels() == 2)
|
||||
.expect("no supported config?!")
|
||||
.with_sample_rate(SampleRate(SAMPLE_RATE as u32))
|
||||
.into();
|
||||
|
||||
let channels = config.channels as usize;
|
||||
let mixer = AudioMixer::new(SAMPLE_RATE);
|
||||
|
||||
let data_callback = {
|
||||
let mixer = mixer.clone();
|
||||
move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
|
||||
let samples = data.len() / 2;
|
||||
let mut buffer = vec![0.0; samples];
|
||||
updater.update_audio_frame(samples, mixer.lock().sample_rate(), &mut buffer);
|
||||
|
||||
for (i, channels) in data.chunks_mut(2).enumerate() {
|
||||
let sample = Sample::from(&buffer[i]);
|
||||
channels[0] = sample;
|
||||
channels[1] = sample;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let stream = device.build_output_stream(
|
||||
&config,
|
||||
data_callback,
|
||||
move |err| {
|
||||
// react to errors here.
|
||||
println!("ERROR");
|
||||
},
|
||||
).unwrap();
|
||||
|
||||
stream.play().unwrap();
|
||||
|
||||
AudioOutput {
|
||||
stream,
|
||||
mixer,
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
@ -1,3 +1,7 @@
|
||||
|
||||
#[cfg(feature = "tty")]
|
||||
pub mod tty;
|
||||
|
||||
#[cfg(feature = "audio")]
|
||||
pub mod audio;
|
||||
|
||||
|
@ -8,6 +8,7 @@ default-run = "moa-genesis"
|
||||
|
||||
[dependencies]
|
||||
moa = { path = "../../" }
|
||||
moa-common = { path = "../moa-common/", features = ["audio"] }
|
||||
minifb = "0.19"
|
||||
clap = "3.0.0-beta.5"
|
||||
|
||||
|
@ -8,9 +8,11 @@ use clap::{App, ArgMatches};
|
||||
|
||||
use moa::error::Error;
|
||||
use moa::system::System;
|
||||
use moa::host::traits::{Host, ControllerUpdater, KeyboardUpdater, WindowUpdater};
|
||||
use moa::host::traits::{Host, HostData, ControllerUpdater, KeyboardUpdater, WindowUpdater, Audio};
|
||||
use moa::host::controllers::{ControllerDevice, ControllerEvent};
|
||||
|
||||
use moa_common::audio::{AudioOutput, AudioMixer, AudioSource};
|
||||
|
||||
mod keys;
|
||||
mod controllers;
|
||||
|
||||
@ -77,6 +79,7 @@ pub struct MiniFrontendBuilder {
|
||||
pub window: Option<Box<dyn WindowUpdater>>,
|
||||
pub controller: Option<Box<dyn ControllerUpdater>>,
|
||||
pub keyboard: Option<Box<dyn KeyboardUpdater>>,
|
||||
pub mixer: Option<HostData<AudioMixer>>,
|
||||
pub finalized: bool,
|
||||
}
|
||||
|
||||
@ -86,6 +89,7 @@ impl MiniFrontendBuilder {
|
||||
window: None,
|
||||
controller: None,
|
||||
keyboard: None,
|
||||
mixer: Some(AudioMixer::new_default()),
|
||||
finalized: false,
|
||||
}
|
||||
}
|
||||
@ -98,7 +102,8 @@ impl MiniFrontendBuilder {
|
||||
let window = std::mem::take(&mut self.window);
|
||||
let controller = std::mem::take(&mut self.controller);
|
||||
let keyboard = std::mem::take(&mut self.keyboard);
|
||||
MiniFrontend::new(window, controller, keyboard)
|
||||
let mixer = std::mem::take(&mut self.mixer);
|
||||
MiniFrontend::new(window, controller, keyboard, mixer.unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
@ -130,6 +135,11 @@ impl Host for MiniFrontendBuilder {
|
||||
self.keyboard = Some(input);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_audio_source(&mut self) -> Result<Box<dyn Audio>, Error> {
|
||||
let source = AudioSource::new(self.mixer.as_ref().unwrap().clone());
|
||||
Ok(Box::new(source))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -139,16 +149,18 @@ pub struct MiniFrontend {
|
||||
pub window: Option<Box<dyn WindowUpdater>>,
|
||||
pub controller: Option<Box<dyn ControllerUpdater>>,
|
||||
pub keyboard: Option<Box<dyn KeyboardUpdater>>,
|
||||
pub audio: AudioOutput,
|
||||
}
|
||||
|
||||
impl MiniFrontend {
|
||||
pub fn new(window: Option<Box<dyn WindowUpdater>>, controller: Option<Box<dyn ControllerUpdater>>, keyboard: Option<Box<dyn KeyboardUpdater>>) -> Self {
|
||||
pub fn new(window: Option<Box<dyn WindowUpdater>>, controller: Option<Box<dyn ControllerUpdater>>, keyboard: Option<Box<dyn KeyboardUpdater>>, mixer: HostData<AudioMixer>) -> Self {
|
||||
Self {
|
||||
buffer: vec![0; (WIDTH * HEIGHT) as usize],
|
||||
modifiers: 0,
|
||||
window,
|
||||
controller,
|
||||
keyboard,
|
||||
audio: AudioOutput::create_audio_output(mixer),
|
||||
}
|
||||
}
|
||||
|
||||
@ -187,6 +199,7 @@ impl MiniFrontend {
|
||||
while window.is_open() && !window.is_key_down(Key::Escape) {
|
||||
if let Some(system) = system.as_mut() {
|
||||
system.run_for(16_600_000).unwrap();
|
||||
//system.run_until_break().unwrap();
|
||||
}
|
||||
|
||||
if let Some(keys) = window.get_keys_pressed(minifb::KeyRepeat::No) {
|
||||
|
59
src/host/audio.rs
Normal file
59
src/host/audio.rs
Normal file
@ -0,0 +1,59 @@
|
||||
|
||||
use std::f32::consts::PI;
|
||||
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SineWave {
|
||||
pub frequency: f32,
|
||||
pub sample_rate: usize,
|
||||
pub position: usize,
|
||||
}
|
||||
|
||||
impl SineWave {
|
||||
pub fn new(frequency: f32, sample_rate: usize) -> Self {
|
||||
Self {
|
||||
frequency,
|
||||
sample_rate,
|
||||
position: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for SineWave {
|
||||
type Item = f32;
|
||||
|
||||
fn next(&mut self) -> Option<f32> {
|
||||
self.position += 1;
|
||||
let result = (2.0 * PI * self.frequency * self.position as f32 / (self.sample_rate as f32)).sin();
|
||||
Some(result)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SquareWave {
|
||||
pub frequency: f32,
|
||||
pub sample_rate: usize,
|
||||
pub position: usize,
|
||||
}
|
||||
|
||||
impl SquareWave {
|
||||
pub fn new(frequency: f32, sample_rate: usize) -> Self {
|
||||
Self {
|
||||
frequency,
|
||||
sample_rate,
|
||||
position: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for SquareWave {
|
||||
type Item = f32;
|
||||
|
||||
fn next(&mut self) -> Option<f32> {
|
||||
self.position += 1;
|
||||
let samples_per_hz = self.sample_rate as f32 / self.frequency;
|
||||
let result = if (self.position as f32 % samples_per_hz) < (samples_per_hz / 2.0) { 1.0 } else { -1.0 };
|
||||
Some(result)
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,8 @@
|
||||
|
||||
pub mod traits;
|
||||
|
||||
#[cfg(feature = "tty")]
|
||||
pub mod tty;
|
||||
|
||||
pub mod gfx;
|
||||
pub mod audio;
|
||||
pub mod keys;
|
||||
pub mod controllers;
|
||||
|
||||
|
@ -10,7 +10,7 @@ pub trait Host {
|
||||
Err(Error::new("This frontend doesn't support PTYs"))
|
||||
}
|
||||
|
||||
fn add_window(&mut self, updater: Box<dyn WindowUpdater>) -> Result<(), Error> {
|
||||
fn add_window(&mut self, _updater: Box<dyn WindowUpdater>) -> Result<(), Error> {
|
||||
Err(Error::new("This frontend doesn't support windows"))
|
||||
}
|
||||
|
||||
@ -21,8 +21,13 @@ pub trait Host {
|
||||
fn register_keyboard(&mut self, _input: Box<dyn KeyboardUpdater>) -> Result<(), Error> {
|
||||
Err(Error::new("This frontend doesn't support the keyboard"))
|
||||
}
|
||||
|
||||
fn create_audio_source(&mut self) -> Result<Box<dyn Audio>, Error> {
|
||||
Err(Error::new("This frontend doesn't support the sound"))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub trait Tty {
|
||||
fn device_name(&self) -> String;
|
||||
fn read(&mut self) -> Option<u8>;
|
||||
@ -32,6 +37,7 @@ 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 {
|
||||
@ -42,6 +48,11 @@ pub trait KeyboardUpdater: Send {
|
||||
fn update_keyboard(&mut self, key: Key, state: bool);
|
||||
}
|
||||
|
||||
pub trait Audio {
|
||||
fn samples_per_second(&self) -> usize;
|
||||
fn write_samples(&mut self, samples: usize, iter: &mut Iterator<Item=f32>);
|
||||
}
|
||||
|
||||
pub trait BlitableSurface {
|
||||
fn set_size(&mut self, width: u32, height: u32);
|
||||
fn blit<B: Iterator<Item=u32>>(&mut self, pos_x: u32, pos_y: u32, bitmap: B, width: u32, height: u32);
|
||||
|
@ -10,8 +10,8 @@ use crate::devices::{wrap_transmutable, Address, Addressable, Debuggable};
|
||||
|
||||
use crate::cpus::m68k::{M68k, M68kType};
|
||||
use crate::cpus::z80::{Z80, Z80Type};
|
||||
use crate::peripherals::ym2612::YM2612;
|
||||
use crate::peripherals::sn76489::SN76489;
|
||||
use crate::peripherals::ym2612::Ym2612;
|
||||
use crate::peripherals::sn76489::Sn76489;
|
||||
use crate::peripherals::genesis;
|
||||
use crate::peripherals::genesis::coprocessor::{CoprocessorBankRegister, CoprocessorBankArea};
|
||||
|
||||
@ -63,8 +63,8 @@ pub fn build_genesis<H: Host>(host: &mut H, options: SegaGenesisOptions) -> Resu
|
||||
// Build the Coprocessor's Bus
|
||||
let bank_register = Signal::new(0);
|
||||
let coproc_ram = wrap_transmutable(MemoryBlock::new(vec![0; 0x00002000]));
|
||||
let coproc_ym_sound = wrap_transmutable(YM2612::new());
|
||||
let coproc_sn_sound = wrap_transmutable(SN76489::new());
|
||||
let coproc_ym_sound = wrap_transmutable(Ym2612::create(host)?);
|
||||
let coproc_sn_sound = wrap_transmutable(Sn76489::create(host)?);
|
||||
let coproc_register = wrap_transmutable(CoprocessorBankRegister::new(bank_register.clone()));
|
||||
let coproc_area = wrap_transmutable(CoprocessorBankArea::new(bank_register, system.bus.clone()));
|
||||
|
||||
@ -75,8 +75,10 @@ pub fn build_genesis<H: Host>(host: &mut H, options: SegaGenesisOptions) -> Resu
|
||||
coproc_bus.borrow_mut().insert(0x7f11, coproc_sn_sound.clone());
|
||||
coproc_bus.borrow_mut().insert(0x8000, coproc_area);
|
||||
let coproc = Z80::new(Z80Type::Z80, 3_579_545, BusPort::new(0, 16, 8, coproc_bus.clone()));
|
||||
let reset = coproc.reset.clone();
|
||||
let bus_request = coproc.bus_request.clone();
|
||||
let mut reset = coproc.reset.clone();
|
||||
let mut bus_request = coproc.bus_request.clone();
|
||||
reset.set(true);
|
||||
bus_request.set(true);
|
||||
|
||||
// Add coprocessor devices to the system bus so the 68000 can access them too
|
||||
system.add_addressable_device(0x00a00000, coproc_ram)?;
|
||||
@ -86,7 +88,6 @@ pub fn build_genesis<H: Host>(host: &mut H, options: SegaGenesisOptions) -> Resu
|
||||
system.add_device("coproc", wrap_transmutable(coproc))?;
|
||||
|
||||
|
||||
|
||||
let controllers = genesis::controllers::GenesisController::create(host)?;
|
||||
let interrupt = controllers.get_interrupt_signal();
|
||||
system.add_addressable_device(0x00a10000, wrap_transmutable(controllers)).unwrap();
|
||||
|
@ -2,56 +2,103 @@
|
||||
use crate::error::Error;
|
||||
use crate::system::System;
|
||||
use crate::devices::{ClockElapsed, Address, Addressable, Steppable, Transmutable};
|
||||
use crate::host::audio::{SineWave, SquareWave};
|
||||
use crate::host::traits::{Host, Audio};
|
||||
|
||||
|
||||
const DEV_NAME: &'static str = "sn76489";
|
||||
|
||||
pub struct SN76489 {
|
||||
/*
|
||||
pub struct Sn76489Updater(HostData<SineWave>);
|
||||
|
||||
impl AudioUpdater for Sn76489Updater {
|
||||
fn update_audio_frame(&mut self, samples: usize, sample_rate: usize, buffer: &mut [f32]) {
|
||||
let mut sine = self.0.lock();
|
||||
//for i in 0..samples {
|
||||
// buffer[i] = sine.next().unwrap();
|
||||
//}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
pub struct Sn76489 {
|
||||
pub regs: [u8; 8],
|
||||
pub first_byte: Option<u8>,
|
||||
pub source: Box<dyn Audio>,
|
||||
pub sine: SquareWave,
|
||||
}
|
||||
|
||||
impl SN76489 {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
impl Sn76489 {
|
||||
pub fn create<H: Host>(host: &mut H) -> Result<Self, Error> {
|
||||
let source = host.create_audio_source()?;
|
||||
let sine = SquareWave::new(600.0, source.samples_per_second());
|
||||
|
||||
}
|
||||
Ok(Self {
|
||||
regs: [0; 8],
|
||||
first_byte: None,
|
||||
source,
|
||||
sine,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Addressable for SN76489 {
|
||||
impl Steppable for Sn76489 {
|
||||
fn step(&mut self, _system: &System) -> Result<ClockElapsed, Error> {
|
||||
// TODO since you expect this step function to be called every 1ms of simulated time
|
||||
// you could assume that you should produce (sample_rate / 1000) samples
|
||||
|
||||
if self.sine.frequency > 200.0 {
|
||||
self.sine.frequency -= 1.0;
|
||||
}
|
||||
|
||||
let rate = self.source.samples_per_second();
|
||||
self.source.write_samples(rate / 1000, &mut self.sine);
|
||||
//println!("{}", self.sine.frequency);
|
||||
Ok(1_000_000) // Every 1ms of simulated time
|
||||
}
|
||||
}
|
||||
|
||||
impl Addressable for Sn76489 {
|
||||
fn len(&self) -> usize {
|
||||
0x01
|
||||
}
|
||||
|
||||
fn read(&mut self, addr: Address, data: &mut [u8]) -> Result<(), Error> {
|
||||
match addr {
|
||||
_ => {
|
||||
warning!("{}: !!! unhandled read from {:0x}", DEV_NAME, addr);
|
||||
},
|
||||
}
|
||||
debug!("{}: read from register {:x} of {:?}", DEV_NAME, addr, data);
|
||||
warning!("{}: !!! device can't be read", DEV_NAME);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write(&mut self, addr: Address, data: &[u8]) -> Result<(), Error> {
|
||||
debug!("{}: write to register {:x} with {:x}", DEV_NAME, addr, data[0]);
|
||||
match addr {
|
||||
_ => {
|
||||
if addr != 0 {
|
||||
warning!("{}: !!! unhandled write {:0x} to {:0x}", DEV_NAME, data[0], addr);
|
||||
},
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if (data[0] & 0x80) == 0 {
|
||||
// TODO update noise byte
|
||||
} else {
|
||||
let reg = (data[0] & 0x70) >> 4;
|
||||
if reg == 6 {
|
||||
self.first_byte = Some(data[0]);
|
||||
} else {
|
||||
self.regs[reg as usize] = data[0] & 0x0F;
|
||||
}
|
||||
}
|
||||
debug!("{}: write to register {:x} with {:x}", DEV_NAME, addr, data[0]);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl Transmutable for SN76489 {
|
||||
impl Transmutable for Sn76489 {
|
||||
fn as_addressable(&mut self) -> Option<&mut dyn Addressable> {
|
||||
Some(self)
|
||||
}
|
||||
|
||||
//fn as_steppable(&mut self) -> Option<&mut dyn Steppable> {
|
||||
// Some(self)
|
||||
//}
|
||||
fn as_steppable(&mut self) -> Option<&mut dyn Steppable> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,29 +1,106 @@
|
||||
|
||||
use std::num::NonZeroU8;
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::system::System;
|
||||
use crate::devices::{ClockElapsed, Address, Addressable, Steppable, Transmutable};
|
||||
use crate::host::audio::{SineWave, SquareWave};
|
||||
use crate::host::traits::{Host, Audio};
|
||||
|
||||
const DEV_NAME: &'static str = "ym2612";
|
||||
|
||||
pub struct YM2612 {
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Operator {
|
||||
pub wave: SquareWave,
|
||||
}
|
||||
|
||||
impl YM2612 {
|
||||
pub fn new() -> Self {
|
||||
impl Operator {
|
||||
pub fn new(sample_rate: usize) -> Self {
|
||||
Self {
|
||||
|
||||
wave: SquareWave::new(400.0, sample_rate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Addressable for YM2612 {
|
||||
#[derive(Clone)]
|
||||
pub struct Channel {
|
||||
pub operators: Vec<Operator>,
|
||||
pub on: u8,
|
||||
}
|
||||
|
||||
impl Channel {
|
||||
pub fn new(sample_rate: usize) -> Self {
|
||||
Self {
|
||||
operators: vec![Operator::new(sample_rate); 4],
|
||||
on: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
pub struct Ym2612 {
|
||||
pub source: Box<dyn Audio>,
|
||||
pub selected_reg: Option<NonZeroU8>,
|
||||
|
||||
pub channels: Vec<Channel>,
|
||||
}
|
||||
|
||||
impl Ym2612 {
|
||||
pub fn create<H: Host>(host: &mut H) -> Result<Self, Error> {
|
||||
let source = host.create_audio_source()?;
|
||||
let sample_rate = source.samples_per_second();
|
||||
Ok(Self {
|
||||
source,
|
||||
selected_reg: None,
|
||||
channels: vec![Channel::new(sample_rate); 6],
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_register(&mut self, bank: u8, 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);
|
||||
},
|
||||
_ => warning!("{}: !!! unhandled write to register {:0x} with {:0x}", DEV_NAME, reg, data),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
// you could assume that you should produce (sample_rate / 1000) samples
|
||||
|
||||
//if self.sine.frequency < 2000.0 {
|
||||
// self.sine.frequency += 1.0;
|
||||
//}
|
||||
|
||||
//let rate = self.source.samples_per_second();
|
||||
//self.source.write_samples(rate / 1000, &mut self.sine);
|
||||
//println!("{}", self.sine.frequency);
|
||||
|
||||
//if self.on {
|
||||
// let rate = self.source.samples_per_second();
|
||||
// self.source.write_samples(rate / 1000, &mut self.sine);
|
||||
//}
|
||||
Ok(1_000_000) // Every 1ms of simulated time
|
||||
}
|
||||
}
|
||||
|
||||
impl Addressable for Ym2612 {
|
||||
fn len(&self) -> usize {
|
||||
0x04
|
||||
}
|
||||
|
||||
fn read(&mut self, addr: Address, data: &mut [u8]) -> Result<(), Error> {
|
||||
match addr {
|
||||
0 | 1 | 2 | 3 => {
|
||||
// Read the status byte (busy/overflow)
|
||||
data[0] = 0;
|
||||
}
|
||||
_ => {
|
||||
warning!("{}: !!! unhandled read from {:0x}", DEV_NAME, addr);
|
||||
},
|
||||
@ -35,6 +112,15 @@ impl Addressable for YM2612 {
|
||||
fn write(&mut self, addr: Address, data: &[u8]) -> Result<(), Error> {
|
||||
debug!("{}: write to register {:x} with {:x}", DEV_NAME, addr, data[0]);
|
||||
match addr {
|
||||
0 => {
|
||||
self.selected_reg = NonZeroU8::new(data[0]);
|
||||
},
|
||||
1 => {
|
||||
match self.selected_reg {
|
||||
None => {},
|
||||
Some(reg) => self.set_register(0, reg.get() as usize, data[0]),
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
warning!("{}: !!! unhandled write {:0x} to {:0x}", DEV_NAME, data[0], addr);
|
||||
},
|
||||
@ -43,15 +129,13 @@ impl Addressable for YM2612 {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl Transmutable for YM2612 {
|
||||
impl Transmutable for Ym2612 {
|
||||
fn as_addressable(&mut self) -> Option<&mut dyn Addressable> {
|
||||
Some(self)
|
||||
}
|
||||
|
||||
//fn as_steppable(&mut self) -> Option<&mut dyn Steppable> {
|
||||
// Some(self)
|
||||
//}
|
||||
fn as_steppable(&mut self) -> Option<&mut dyn Steppable> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
39
todo.txt
39
todo.txt
@ -1,33 +1,28 @@
|
||||
|
||||
* I'm trying to explore the alternative of having the frontend call a function and pass a closure that takes a frame (or buffer) and then draws
|
||||
calls the update function from there...
|
||||
* you can't put a closure into the WindowUpdate trait because you pass it in as Box<dyn WindowUpdater> which is a trait object, and you
|
||||
can't mix generics and trait objects...
|
||||
* you could maybe pass a closure in if you pass the updater as a generic WindowUpdater, although then you can only have one per application
|
||||
* you could have a shared buffer that you submit to the frontend, and you both update it whenever (less synchronized which might not be good)
|
||||
* for the mixer, it might be easier to have a buffer for each source, but then you'd need to have a list of all sources, even though
|
||||
each source has a copy of the mixer as well... Likely there'd be a sub object in Source which is the buffer and anything else needed
|
||||
by the mixer
|
||||
|
||||
* I'm leaning towards having an object that data is written to by the device. The device can decide how often to update. The issue is
|
||||
knowing what data to exclude or insert when mixing the incoming buffers
|
||||
* Removing at a sample-level granularity would compress or lengthen the waveforms, so it would be better to mix/drop a whole chunk at
|
||||
once (either predetermined by the audio system or determined by each device by the amount of samples it writes at once). The chunk
|
||||
size could either be specified by the device in microseconds or something, or can be inferred by the sample_rate and the size of the
|
||||
chunk.
|
||||
|
||||
* how do you know how big an audio frame should be? How do other emulators do audio without stretching or compressing the waveforms, and
|
||||
can/should I do mixing as well, given that I have 2 sources, and at least for those two, they should be connected to the same output
|
||||
* you could make the sound device be an object that is passed back to the simulation section like SimplePty. You need to either register
|
||||
a callback with the frontend sound system that is called when it needs data, or you write to a shared buffer which is passed back to the
|
||||
frontend when it needs it, or it has a copy it can use directly
|
||||
|
||||
* can you make some kind of signal that goes high when a frame has been drawn, and also all the system to pause execution at that point
|
||||
|
||||
* for Signal/Register, you could possibly unify them, or you could distinguish them even more
|
||||
* should you rename Register to AsyncSignal or something
|
||||
* copy the callback over to Signal, or even make a trait that implements it for both? Or should you make a special object that is observable
|
||||
which should always use the callback, and Signal would always be used when the callback wasn't used
|
||||
* think more about what kinds of signals are used:
|
||||
- one setter with multiple passive listeners
|
||||
- one one-shot setter (no reset) with one active listener that resets the signal
|
||||
-
|
||||
|
||||
* add sound
|
||||
* should you rename devices.rs traits.rs?
|
||||
* rewrite the frame swapper thing to either not use the swapper or somethnig... it's just very sloppy and needs improving
|
||||
* modify the frame swapper and frontend to avoid the extra buffer copy
|
||||
* add command line arguments to speed up or slow down either the frame rate limiter or the simulated time per frame
|
||||
|
||||
* can you make the connections between things (like memory adapters), be expressed in a way that's more similar to the electrical design?
|
||||
like specifying that address pins 10-7 should be ignored/unconnected, pin 11 will connect to "chip select", etc
|
||||
* should you add a unique ID to devices, such that they can be indexed, and their step functions can reset the next_run count and run them immediately
|
||||
|
||||
|
||||
* should you simulate bus arbitration?
|
||||
@ -47,9 +42,15 @@ Debugger:
|
||||
|
||||
Genesis/Mega Drive:
|
||||
|
||||
* the 68000/Z80 bank switching is probably buggy, and there's that other banking stuff in the 0xC00000 range, which isn't implemented at all
|
||||
* add support for the H/V counters at 0xC00008
|
||||
* need to implement the 1.5ms reset in the genesis controllers
|
||||
* fix ym7101 to better handle V/H interrupts (right now it sets and then the next step will clear, but it'd be nice if it could 'edge trigger')
|
||||
* make the ym7101 set/reset the v_int occurred flag based on the interrupt controller
|
||||
* refactor to allow per-line horizontal scrolling, which might need a pattern iterator than only does a line at a time
|
||||
* refactor ym7101 into multiple files perhaps. You can separate the DMA stuff, the address/interfacing parts, and the graphics state
|
||||
* fix sprite/cell priorities so that they're drawn correctly
|
||||
* add support for the sprite overflow flag (low priority)
|
||||
|
||||
|
||||
Macintosh:
|
||||
|
Loading…
Reference in New Issue
Block a user