use async_ringbuf::AsyncHeapConsumer; use futures::executor; use gb_emu_lib::{ connect::{AudioOutput, EmulatorMessage, RomFile}, EmulatorCore, }; use nih_plug::prelude::*; use std::sync::{ mpsc::{channel, Sender}, Arc, }; use ui::{Emulator, EmulatorRenderer}; mod ui; const WIDTH: u32 = 320; const HEIGHT: u32 = 240; #[derive(Params, Default)] struct EmuParams {} struct EmuVars { rx: AsyncHeapConsumer<[f32; 2]>, sender: Sender, emulator_core: EmulatorCore, } #[derive(Default)] pub struct GameboyEmu { vars: Option, } const ROM: &[u8; 32768] = include_bytes!("../../test-roms/Tetris.gb"); 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"; const AUDIO_IO_LAYOUTS: &'static [AudioIOLayout] = &[AudioIOLayout { main_input_channels: NonZeroU32::new(2), 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(), }]; type SysExMessage = (); type BackgroundTask = (); fn params(&self) -> Arc { Arc::new(EmuParams::default()) } fn process( &mut self, buffer: &mut Buffer, _: &mut AuxiliaryBuffers, _: &mut impl ProcessContext, ) -> ProcessStatus { nih_log!("processing"); if let Some(ref mut vars) = self.vars { vars.emulator_core.run_until_buffer_full(); if buffer.channels() != 2 { panic!() } let mut got_any = false; let mut got_nonzero = false; for sample in buffer.iter_samples() { if let Some(a) = executor::block_on(vars.rx.pop()) { got_any = true; if (a[0] != 0.) || (a[1] != 0.) { got_nonzero = true; } for (source, dest) in a.iter().zip(sample) { *dest = *source; } } } if got_any { if got_nonzero { nih_log!("got some real data"); } else { nih_log!("got some 0's"); } } } else { nih_log!("vars not initialised"); } ProcessStatus::KeepAlive } fn editor(&self, _: AsyncExecutor) -> Option> { Some(Box::new(Emulator::new())) } fn initialize( &mut self, _audio_io_layout: &AudioIOLayout, buffer_config: &BufferConfig, _context: &mut impl InitContext, ) -> bool { let options = gb_emu_lib::Options { rom: RomFile::Raw(ROM.to_vec()), save_path: None, no_save: true, bootrom_path: None, connect_serial: false, verbose: true, cycle_count: false, }; let (sender, receiver) = channel::(); let (output, rx) = AudioOutput::new_unfilled(buffer_config.sample_rate); let renderer = Box::::default(); let mut emulator_core = EmulatorCore::init(receiver, options, renderer, output, None); emulator_core.run_until_buffer_full(); self.vars = Some(EmuVars { rx, sender, emulator_core, }); true } fn deactivate(&mut self) { nih_log!("deactivating"); 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"), } } } } impl Vst3Plugin for GameboyEmu { const VST3_CLASS_ID: [u8; 16] = *b"alexjankagbemula"; const VST3_SUBCATEGORIES: &'static [Vst3SubCategory] = &[Vst3SubCategory::Distortion, Vst3SubCategory::Dynamics]; } nih_export_vst3!(GameboyEmu);