gb-emu/gb-vst/src/lib.rs

319 lines
10 KiB
Rust
Raw Normal View History

2023-03-07 08:51:23 +11:00
use async_ringbuf::AsyncHeapConsumer;
use futures::executor;
use gb_emu_lib::{
2023-03-17 13:58:47 +11:00
connect::{
2023-03-19 10:39:57 +11:00
AudioOutput, DownsampleType, EmulatorMessage, EmulatorOptions, JoypadButtons, NoCamera,
RomFile, SerialTarget,
2023-03-17 13:58:47 +11:00
},
2023-03-07 08:51:23 +11:00
EmulatorCore,
};
2023-03-16 10:48:08 +11:00
use nih_plug::midi::MidiResult::Basic;
2023-03-05 20:18:06 +11:00
use nih_plug::prelude::*;
2023-03-07 08:51:23 +11:00
use std::sync::{
2023-03-15 17:45:56 +11:00
mpsc::{self, channel, Receiver, Sender},
2023-03-07 21:45:11 +11:00
Arc, Mutex,
2023-03-07 08:51:23 +11:00
};
use ui::{Emulator, EmulatorRenderer};
2023-03-04 09:15:25 +11:00
2023-03-16 10:48:08 +11:00
#[cfg(feature = "savestate")]
use gb_emu_lib::connect::CpuSaveState;
#[cfg(feature = "savestate")]
use nih_plug::params::persist::PersistentField;
2023-03-05 20:18:06 +11:00
mod ui;
2023-03-16 10:48:08 +11:00
#[cfg(feature = "savestate")]
#[derive(Default)]
struct SaveStateParam {
state: Arc<Mutex<Option<CpuSaveState<[u8; 4]>>>>,
}
2023-03-16 10:48:08 +11:00
#[cfg(feature = "savestate")]
impl PersistentField<'_, Option<CpuSaveState<[u8; 4]>>> for SaveStateParam {
fn set(&self, new_value: Option<CpuSaveState<[u8; 4]>>) {
*self.state.lock().unwrap() = new_value;
}
fn map<F, R>(&self, f: F) -> R
where
F: Fn(&Option<CpuSaveState<[u8; 4]>>) -> R,
{
f(&self.state.lock().unwrap())
}
}
2023-03-05 20:18:06 +11:00
#[derive(Params, Default)]
struct EmuParams {
2023-03-16 10:48:08 +11:00
#[cfg(feature = "savestate")]
#[persist = "save_state"]
last_save_state: SaveStateParam,
}
2023-03-04 09:15:25 +11:00
2023-03-07 08:51:23 +11:00
struct EmuVars {
rx: AsyncHeapConsumer<[f32; 2]>,
sender: Sender<EmulatorMessage>,
2023-03-19 10:39:57 +11:00
emulator_core: EmulatorCore<[u8; 4], EmulatorRenderer, NoCamera>,
2023-03-15 17:45:56 +11:00
serial_tx: Sender<u8>,
2023-03-07 08:51:23 +11:00
}
2023-03-04 09:15:25 +11:00
#[derive(Default)]
2023-03-07 08:51:23 +11:00
pub struct GameboyEmu {
vars: Option<EmuVars>,
2023-03-08 11:50:19 +11:00
frame_receiver: Arc<FrameReceiver>,
2023-03-08 15:19:10 +11:00
key_handler: Arc<JoypadSender>,
params: Arc<EmuParams>,
2023-03-07 08:51:23 +11:00
}
2023-03-08 11:50:19 +11:00
type FrameReceiver = Mutex<Option<Receiver<Vec<[u8; 4]>>>>;
2023-03-08 15:19:10 +11:00
type JoypadSender = Mutex<Option<Sender<(JoypadButtons, bool)>>>;
2023-03-08 11:50:19 +11:00
const FRAMES_TO_BUFFER: usize = 1;
2023-03-09 10:50:18 +11:00
const DOWNSAMPLE_TYPE: DownsampleType = DownsampleType::ZeroOrderHold;
2023-04-06 15:11:19 +10:00
const ROM: &[u8; 65536] = include_bytes!("../../test-roms/mGB1_3_1.gb");
2023-03-16 18:35:04 +11:00
const BOOTROM: Option<&[u8; 256]> = None;
2023-03-15 13:15:32 +11:00
2023-03-04 09:15:25 +11:00
impl Plugin for GameboyEmu {
const NAME: &'static str = "Gameboy";
const VENDOR: &'static str = "Alex Janka";
const URL: &'static str = "alexjanka.com";
const EMAIL: &'static str = "alex@alexjanka.com";
const VERSION: &'static str = "0.1";
2023-03-05 20:18:06 +11:00
const AUDIO_IO_LAYOUTS: &'static [AudioIOLayout] = &[AudioIOLayout {
2023-03-15 13:15:32 +11:00
main_input_channels: None,
2023-03-05 20:18:06 +11:00
main_output_channels: NonZeroU32::new(2),
aux_input_ports: &[],
aux_output_ports: &[],
// Individual ports and the layout as a whole can be named here. By default these names
// are generated as needed. This layout will be called 'Stereo', while the other one is
// given the name 'Mono' based no the number of input and output channels.
names: PortNames::const_default(),
}];
2023-03-04 09:15:25 +11:00
2023-03-15 13:15:32 +11:00
const MIDI_INPUT: MidiConfig = MidiConfig::MidiCCs;
const SAMPLE_ACCURATE_AUTOMATION: bool = true;
2023-03-04 09:15:25 +11:00
type SysExMessage = ();
type BackgroundTask = ();
fn params(&self) -> Arc<dyn Params> {
self.params.clone()
2023-03-04 09:15:25 +11:00
}
fn process(
&mut self,
2023-03-07 08:51:23 +11:00
buffer: &mut Buffer,
2023-03-05 20:18:06 +11:00
_: &mut AuxiliaryBuffers,
2023-03-15 17:45:56 +11:00
context: &mut impl ProcessContext<Self>,
2023-03-04 09:15:25 +11:00
) -> ProcessStatus {
2023-03-07 08:51:23 +11:00
if let Some(ref mut vars) = self.vars {
2023-03-15 17:45:56 +11:00
while let Some(event) = context.next_event() {
if let Some(Basic(as_bytes)) = event.as_midi() {
match event {
NoteEvent::NoteOn {
timing: _,
voice_id: _,
channel,
note: _,
velocity: _,
} => {
if channel < 5 {
vars.serial_tx.send(0x90 + channel).unwrap();
vars.serial_tx.send(as_bytes[1]).unwrap();
vars.serial_tx.send(as_bytes[2]).unwrap();
}
}
NoteEvent::NoteOff {
timing: _,
voice_id: _,
channel,
note: _,
velocity: _,
} => {
if channel < 5 {
vars.serial_tx.send(0x80 + channel).unwrap();
vars.serial_tx.send(as_bytes[1]).unwrap();
vars.serial_tx.send(as_bytes[2]).unwrap();
}
}
NoteEvent::MidiPitchBend {
timing: _,
channel,
value: _,
} => {
if channel < 5 {
vars.serial_tx.send(0xE0 + channel).unwrap();
vars.serial_tx.send(as_bytes[1]).unwrap();
vars.serial_tx.send(as_bytes[2]).unwrap();
}
}
NoteEvent::MidiCC {
timing: _,
channel,
cc: _,
value: _,
} => {
if channel < 5 {
vars.serial_tx.send(0xB0 + channel).unwrap();
vars.serial_tx.send(as_bytes[1]).unwrap();
vars.serial_tx.send(as_bytes[2]).unwrap();
}
}
NoteEvent::MidiProgramChange {
timing: _,
channel,
program: _,
} => {
if channel < 5 {
vars.serial_tx.send(0xC0 + channel).unwrap();
vars.serial_tx.send(as_bytes[1]).unwrap();
vars.serial_tx.send(as_bytes[2]).unwrap();
}
}
_ => {}
}
}
}
2023-03-07 08:51:23 +11:00
if buffer.channels() != 2 {
panic!()
}
for sample in buffer.iter_samples() {
2023-03-07 09:53:56 +11:00
if vars.rx.is_empty() {
vars.emulator_core.run_until_buffer_full();
}
2023-03-07 08:51:23 +11:00
if let Some(a) = executor::block_on(vars.rx.pop()) {
for (source, dest) in a.iter().zip(sample) {
*dest = *source;
}
}
}
vars.emulator_core.run_until_buffer_full();
2023-03-15 17:45:56 +11:00
} else {
while context.next_event().is_some() {}
2023-03-07 08:51:23 +11:00
}
self.update_save_state();
2023-03-07 08:51:23 +11:00
ProcessStatus::KeepAlive
2023-03-04 09:15:25 +11:00
}
2023-03-05 20:18:06 +11:00
fn editor(&self, _: AsyncExecutor<Self>) -> Option<Box<dyn Editor>> {
2023-03-08 15:19:10 +11:00
Some(Box::new(Emulator::new(
self.frame_receiver.clone(),
self.key_handler.clone(),
)))
2023-03-05 20:18:06 +11:00
}
2023-03-04 09:15:25 +11:00
2023-03-05 20:18:06 +11:00
fn initialize(
&mut self,
_audio_io_layout: &AudioIOLayout,
2023-03-07 08:51:23 +11:00
buffer_config: &BufferConfig,
2023-03-05 20:18:06 +11:00
_context: &mut impl InitContext<Self>,
) -> bool {
if let Some(ref mut vars) = self.vars {
2023-03-09 14:12:28 +11:00
let (output, rx) = AudioOutput::new(
2023-03-09 10:50:18 +11:00
buffer_config.sample_rate,
false,
FRAMES_TO_BUFFER,
DOWNSAMPLE_TYPE,
);
vars.emulator_core.replace_output(output);
vars.rx = rx;
} else {
2023-03-16 18:35:04 +11:00
let bootrom = BOOTROM.map(|v| RomFile::Raw(v.to_vec()));
2023-03-15 13:15:32 +11:00
let rom = RomFile::Raw(ROM.to_vec());
2023-03-11 20:44:55 +11:00
let (sender, receiver) = channel::<EmulatorMessage>();
2023-03-07 08:51:23 +11:00
2023-03-09 14:12:28 +11:00
let (output, rx) = AudioOutput::new(
2023-03-09 10:50:18 +11:00
buffer_config.sample_rate,
false,
FRAMES_TO_BUFFER,
DOWNSAMPLE_TYPE,
);
2023-03-07 21:45:11 +11:00
2023-03-17 13:33:38 +11:00
let (window, frame_receiver, key_handler) = EmulatorRenderer::new();
2023-03-07 08:51:23 +11:00
*self.frame_receiver.lock().unwrap() = Some(frame_receiver);
*self.key_handler.lock().unwrap() = Some(key_handler);
2023-03-07 08:51:23 +11:00
2023-03-15 17:45:56 +11:00
let (serial_tx, gb_serial_rx) = mpsc::channel::<u8>();
let serial_target = SerialTarget::Custom {
rx: Some(gb_serial_rx),
tx: None,
};
2023-03-16 10:48:08 +11:00
#[cfg(feature = "savestate")]
2023-03-15 17:45:56 +11:00
let mut emulator_core = if let Some(state) =
self.params.last_save_state.state.lock().unwrap().take()
{
EmulatorCore::from_save_state(state, rom, receiver, window, output, serial_target)
} else {
2023-03-17 13:58:47 +11:00
let options = gb_emu_lib::Options::new(window, rom, output)
2023-03-15 17:45:56 +11:00
.with_bootrom(bootrom)
.with_serial_target(serial_target)
.force_no_save();
2023-03-15 13:15:32 +11:00
2023-03-17 13:58:47 +11:00
EmulatorCore::init(receiver, options)
2023-03-15 17:45:56 +11:00
};
2023-03-16 10:48:08 +11:00
#[cfg(not(feature = "savestate"))]
let mut emulator_core = {
2023-03-17 13:58:47 +11:00
let options = EmulatorOptions::new(window, rom, output)
2023-03-16 10:48:08 +11:00
.with_bootrom(bootrom)
.with_serial_target(serial_target)
.force_no_save();
2023-03-17 13:58:47 +11:00
EmulatorCore::init(receiver, options)
2023-03-16 10:48:08 +11:00
};
emulator_core.run_until_buffer_full();
self.vars = Some(EmuVars {
rx,
sender,
emulator_core,
2023-03-15 17:45:56 +11:00
serial_tx,
});
self.update_save_state();
}
2023-03-07 08:51:23 +11:00
2023-03-05 20:18:06 +11:00
true
}
2023-03-07 08:51:23 +11:00
fn deactivate(&mut self) {
self.update_save_state();
2023-03-07 08:51:23 +11:00
if let Some(ref mut vars) = self.vars {
match vars.sender.send(EmulatorMessage::Stop) {
Ok(_) => self.vars = None,
Err(e) => nih_log!("error {e} sending message to emulator"),
}
}
}
2023-03-04 09:15:25 +11:00
}
impl GameboyEmu {
fn update_save_state(&mut self) {
2023-03-16 10:48:08 +11:00
#[cfg(feature = "savestate")]
if let Some(ref mut vars) = self.vars {
*self.params.last_save_state.state.lock().unwrap() =
Some(vars.emulator_core.get_save_state());
}
}
}
2023-03-05 20:18:06 +11:00
impl Vst3Plugin for GameboyEmu {
const VST3_CLASS_ID: [u8; 16] = *b"alexjankagbemula";
2023-03-04 09:15:25 +11:00
2023-03-05 20:18:06 +11:00
const VST3_SUBCATEGORIES: &'static [Vst3SubCategory] =
2023-03-15 13:15:32 +11:00
&[Vst3SubCategory::Instrument, Vst3SubCategory::Synth];
2023-03-04 09:15:25 +11:00
}
nih_export_vst3!(GameboyEmu);