2023-03-07 08:51:23 +11:00
|
|
|
use async_ringbuf::AsyncHeapConsumer;
|
|
|
|
use futures::executor;
|
|
|
|
use gb_emu_lib::{
|
2023-03-15 13:15:32 +11:00
|
|
|
connect::{AudioOutput, CpuSaveState, DownsampleType, EmulatorMessage, JoypadButtons, RomFile},
|
2023-03-07 08:51:23 +11:00
|
|
|
EmulatorCore,
|
|
|
|
};
|
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-07 21:45:11 +11:00
|
|
|
mpsc::{channel, Receiver, Sender},
|
|
|
|
Arc, Mutex,
|
2023-03-07 08:51:23 +11:00
|
|
|
};
|
|
|
|
use ui::{Emulator, EmulatorRenderer};
|
2023-03-04 09:15:25 +11:00
|
|
|
|
2023-03-05 20:18:06 +11:00
|
|
|
mod ui;
|
|
|
|
|
|
|
|
#[derive(Params, Default)]
|
|
|
|
struct EmuParams {}
|
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-08 11:01:18 +11:00
|
|
|
emulator_core: EmulatorCore<[u8; 4]>,
|
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>,
|
2023-03-15 13:15:32 +11:00
|
|
|
last_save_state: Option<CpuSaveState<[u8; 4]>>,
|
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
|
|
|
|
2023-03-09 10:31:06 +11:00
|
|
|
const FRAMES_TO_BUFFER: usize = 1;
|
2023-03-09 10:39:00 +11:00
|
|
|
const INCLUDE_BOOTROM: bool = false;
|
2023-03-09 10:50:18 +11:00
|
|
|
const DOWNSAMPLE_TYPE: DownsampleType = DownsampleType::ZeroOrderHold;
|
2023-03-09 10:25:29 +11:00
|
|
|
|
2023-03-15 13:15:32 +11:00
|
|
|
const ROM: &[u8; 65536] = include_bytes!("../../test-roms/mGB1_3_0.gb");
|
|
|
|
const BOOTROM: &[u8; 256] = include_bytes!("../../bootrom/dmg_boot.bin");
|
|
|
|
|
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> {
|
2023-03-05 20:18:06 +11:00
|
|
|
Arc::new(EmuParams::default())
|
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,
|
|
|
|
_: &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 {
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-03-09 10:31:06 +11:00
|
|
|
vars.emulator_core.run_until_buffer_full();
|
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 {
|
2023-03-08 15:41:17 +11:00
|
|
|
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,
|
|
|
|
);
|
2023-03-08 15:41:17 +11:00
|
|
|
|
|
|
|
vars.emulator_core.replace_output(output);
|
|
|
|
vars.rx = rx;
|
|
|
|
} else {
|
2023-03-11 20:44:55 +11:00
|
|
|
let bootrom = if INCLUDE_BOOTROM {
|
2023-03-15 13:15:32 +11:00
|
|
|
Some(RomFile::Raw(BOOTROM.to_vec()))
|
2023-03-11 20:44:55 +11:00
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
|
2023-03-15 13:15:32 +11:00
|
|
|
let rom = RomFile::Raw(ROM.to_vec());
|
2023-03-11 20:44:55 +11:00
|
|
|
|
2023-03-08 15:41:17 +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-08 15:41:17 +11:00
|
|
|
let (renderer, frame_receiver, key_handler) = EmulatorRenderer::new();
|
2023-03-07 08:51:23 +11:00
|
|
|
|
2023-03-08 15:41:17 +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 13:15:32 +11:00
|
|
|
let window = Box::new(renderer);
|
|
|
|
|
|
|
|
let mut emulator_core = if let Some(state) = self.last_save_state.take() {
|
|
|
|
EmulatorCore::from_save_state(state, rom, receiver, window, output)
|
|
|
|
} else {
|
|
|
|
let options = gb_emu_lib::Options {
|
|
|
|
rom,
|
|
|
|
save_path: None,
|
|
|
|
no_save: true,
|
|
|
|
bootrom,
|
|
|
|
connect_serial: false,
|
|
|
|
verbose: false,
|
|
|
|
};
|
|
|
|
|
|
|
|
EmulatorCore::init(receiver, options, window, output, None)
|
|
|
|
};
|
2023-03-08 15:41:17 +11:00
|
|
|
emulator_core.run_until_buffer_full();
|
|
|
|
|
|
|
|
self.vars = Some(EmuVars {
|
|
|
|
rx,
|
|
|
|
sender,
|
|
|
|
emulator_core,
|
|
|
|
});
|
|
|
|
}
|
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) {
|
|
|
|
nih_log!("deactivating");
|
|
|
|
if let Some(ref mut vars) = self.vars {
|
2023-03-15 13:15:32 +11:00
|
|
|
self.last_save_state = Some(vars.emulator_core.get_save_state());
|
2023-03-07 08:51:23 +11:00
|
|
|
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
|
|
|
}
|
|
|
|
|
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);
|