diff --git a/.gitignore b/.gitignore index 0ad6802..1be0cff 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ /target Cargo.lock debug.sh -debug.flp +*.flp diff --git a/examples/simple.rs b/examples/simple.rs index c2fa782..f454f83 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; #[cfg(unix)] use std::fs::OpenOptions; use std::io::{self, Read}; @@ -13,18 +14,20 @@ use simple_logging; use simplelog::{ConfigBuilder, WriteLogger}; use fpsdk::host::{Event, GetName, Host, HostMessage}; -use fpsdk::plugin::{Info, InfoBuilder, Plugin, PluginTag, StateReader, StateWriter}; -use fpsdk::{create_plugin, AsRawPtr, MidiMessage, ProcessParamFlags, ValuePtr}; +use fpsdk::plugin::{Info, InfoBuilder, Plugin, StateReader, StateWriter}; +use fpsdk::voice::{self, Voice, VoiceHandler}; +use fpsdk::{create_plugin, AsRawPtr, MidiMessage, ProcessParamFlags, Tag, ValuePtr}; static ONCE: Once = Once::new(); const LOG_PATH: &str = "simple.log"; #[derive(Debug)] -struct Test { +struct Simple { host: Host, - tag: PluginTag, + tag: Tag, param_names: Vec, state: State, + voice_handler: SimpleVoiceHandler, } #[derive(Debug, Default, Deserialize, Serialize)] @@ -34,8 +37,8 @@ struct State { _param_2: i64, } -impl Plugin for Test { - fn new(host: Host, tag: i32) -> Self { +impl Plugin for Simple { + fn new(host: Host, tag: Tag) -> Self { init_log(); info!("init plugin with tag {}", tag); @@ -48,19 +51,21 @@ impl Plugin for Test { "Parameter 2".into(), "Parameter 3".into(), ], - state: State::default(), + state: Default::default(), + voice_handler: Default::default(), } } fn info(&self) -> Info { info!("plugin {} will return info", self.tag); - InfoBuilder::new_effect("Simple", "Simple", self.param_names.len() as u32) - .want_new_tick() + InfoBuilder::new_full_gen("Simple", "Simple", self.param_names.len() as u32) + // InfoBuilder::new_effect("Simple", "Simple", self.param_names.len() as u32) + // .want_new_tick() .build() } - fn tag(&self) -> PluginTag { + fn tag(&self) -> Tag { self.tag } @@ -143,10 +148,63 @@ impl Plugin for Test { } fn render(&mut self, input: &[[f32; 2]], output: &mut [[f32; 2]]) { - input.iter().zip(output).for_each(|(inp, outp)| { - outp[0] = inp[0] * 0.25; - outp[1] = inp[1] * 0.25; - }); + if self.voice_handler.voices.len() < 1 { + // consider it an effect + input.iter().zip(output).for_each(|(inp, outp)| { + outp[0] = inp[0] * 0.25; + outp[1] = inp[1] * 0.25; + }); + } + } + + fn voice_handler(&mut self) -> &mut dyn VoiceHandler { + &mut self.voice_handler + } +} + +#[derive(Debug, Default)] +struct SimpleVoiceHandler { + voices: HashMap, +} + +impl VoiceHandler for SimpleVoiceHandler { + fn trigger(&mut self, params: voice::Params, tag: Tag) -> &mut dyn Voice { + let voice = SimpleVoice::new(params, tag); + trace!("trigger voice {:?}", voice); + self.voices.insert(tag, voice); + self.voices.get_mut(&tag).unwrap() + } + + fn release(&mut self, tag: Tag) { + trace!("release voice {:?}", self.voices.get(&tag)); + } + + fn kill(&mut self, tag: Tag) { + trace!("host wants to kill voice with tag {}", tag); + trace!("kill voice {:?}", self.voices.remove(&tag)); + trace!("remaining voices count {}, {:?}", self.voices.len(), self.voices); + } + + fn on_event(&mut self, tag: Tag, event: voice::Event) { + trace!("event {:?} for voice {:?}", event, self.voices.get(&tag)); + } +} + +#[derive(Debug)] +struct SimpleVoice { + tag: Tag, + params: voice::Params, +} + +impl SimpleVoice { + pub fn new(params: voice::Params, tag: Tag) -> Self { + Self { tag, params } + } +} + +impl Voice for SimpleVoice { + fn tag(&self) -> Tag { + self.tag } } @@ -159,7 +217,7 @@ fn init_log() { #[cfg(windows)] fn _init_log() { - simple_logging::log_to_file(LOG_PATH, LevelFilter::Debug).unwrap(); + simple_logging::log_to_file(LOG_PATH, LevelFilter::Trace).unwrap(); } #[cfg(unix)] @@ -173,7 +231,7 @@ fn _init_log() { .open(LOG_PATH) .unwrap(); let config = ConfigBuilder::new().set_time_to_local(true).build(); - let _ = WriteLogger::init(LevelFilter::Debug, config, file).unwrap(); + let _ = WriteLogger::init(LevelFilter::Trace, config, file).unwrap(); } -create_plugin!(Test); +create_plugin!(Simple); diff --git a/src/cxx/wrapper.cpp b/src/cxx/wrapper.cpp index d033239..978cbf8 100644 --- a/src/cxx/wrapper.cpp +++ b/src/cxx/wrapper.cpp @@ -131,20 +131,50 @@ void _stdcall PluginWrapper::Gen_Render(PWAV32FS DestBuffer, int &Length) { TVoiceHandle _stdcall PluginWrapper::TriggerVoice(PVoiceParams VoiceParams, intptr_t SetTag) { - return TVoiceHandle(); + LevelParams init_levels = { + VoiceParams->InitLevels.Pan, VoiceParams->InitLevels.Vol, + VoiceParams->InitLevels.Pitch, VoiceParams->InitLevels.FCut, + VoiceParams->InitLevels.FRes, + }; + + LevelParams final_levels = { + VoiceParams->FinalLevels.Pan, VoiceParams->FinalLevels.Vol, + VoiceParams->FinalLevels.Pitch, VoiceParams->FinalLevels.FCut, + VoiceParams->FinalLevels.FRes, + }; + + Params params = { + init_levels, + final_levels, + }; + + return (TVoiceHandle)voice_handler_trigger(adapter, params, (int)SetTag); } -void _stdcall PluginWrapper::Voice_Release(TVoiceHandle Handle) {} +void _stdcall PluginWrapper::Voice_Release(TVoiceHandle Handle) { + voice_handler_release(adapter, (void *)Handle); +} -void _stdcall PluginWrapper::Voice_Kill(TVoiceHandle Handle) {} +void _stdcall PluginWrapper::Voice_Kill(TVoiceHandle Handle) { + voice_handler_kill(adapter, (void *)Handle); +} int _stdcall PluginWrapper::Voice_ProcessEvent(TVoiceHandle Handle, int EventID, int EventValue, int Flags) { + Message message = { + (intptr_t)EventID, + (intptr_t)EventValue, + (intptr_t)Flags, + }; + + voice_handler_on_event(adapter, (void *)Handle, message); + return 0; } -int _stdcall PluginWrapper::Voice_Render(TVoiceHandle Handle, - PWAV32FS DestBuffer, int &Length) { +int _stdcall PluginWrapper::Voice_Render(TVoiceHandle, PWAV32FS, int &) { + // Deprecated: + // https://forum.image-line.com/viewtopic.php?f=100&t=199515#p1371655 return 0; } diff --git a/src/cxx/wrapper.h b/src/cxx/wrapper.h index 6a81036..584ba83 100644 --- a/src/cxx/wrapper.h +++ b/src/cxx/wrapper.h @@ -8,6 +8,7 @@ struct MidiMessage; struct PluginAdapter; struct TimeSignature; +// from plugin.rs struct Info { uint32_t sdk_version; char *long_name; @@ -19,6 +20,20 @@ struct Info { uint32_t num_out_voices; }; +// from voice.rs +struct LevelParams { + float pan; + float vol; + float pitch; + float mod_x; + float mod_y; +}; + +struct Params { + LevelParams init_levels; + LevelParams final_levels; +}; + class PluginWrapper : public TFruityPlug { public: PluginWrapper(TFruityPlugHost *Host, int Tag, PluginAdapter *adapter, @@ -81,8 +96,16 @@ extern "C" void plugin_gen_render(PluginAdapter *adapter, float dest[1][2], extern "C" void plugin_midi_in(PluginAdapter *adapter, MidiMessage message); extern "C" void plugin_save_state(PluginAdapter *adapter, IStream *istream); extern "C" void plugin_load_state(PluginAdapter *adapter, IStream *istream); -extern "C" int32_t istream_read(void *istream, uint8_t *data, - uint32_t size, uint32_t *read); + +extern "C" intptr_t voice_handler_trigger(PluginAdapter *adpater, Params params, + int tag); +extern "C" void voice_handler_release(PluginAdapter *adpater, void *voice); +extern "C" void voice_handler_kill(PluginAdapter *adpater, void *voice); +extern "C" void voice_handler_on_event(PluginAdapter *adpater, void *voice, + Message message); + +extern "C" int32_t istream_read(void *istream, uint8_t *data, uint32_t size, + uint32_t *read); extern "C" int32_t istream_write(void *istream, const uint8_t *data, uint32_t size, uint32_t *write); diff --git a/src/lib.rs b/src/lib.rs index 1bfb982..1be39e5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -93,6 +93,7 @@ pub mod ffi { pub mod host; pub mod plugin; +pub mod voice; use std::ffi::{CStr, CString}; use std::fmt; @@ -104,6 +105,9 @@ use log::{debug, error}; pub use ffi::{MidiMessage, TimeSignature}; use plugin::PluginAdapter; +/// An identefier the host uses to identify plugin and voice instances. +pub type Tag = i32; + /// Current FL SDK version. pub const CURRENT_SDK_VERSION: u32 = 1; diff --git a/src/plugin.rs b/src/plugin.rs index ec0c1bd..6b7f486 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -8,14 +8,12 @@ use hresult::HRESULT; use log::{debug, error}; use crate::host::{Event, GetName, Host, HostMessage}; +use crate::voice::VoiceHandler; use crate::{ - alloc_real_cstr, ffi, intptr_t, AsRawPtr, MidiMessage, ProcessParamFlags, ValuePtr, + alloc_real_cstr, ffi, intptr_t, AsRawPtr, MidiMessage, ProcessParamFlags, Tag, ValuePtr, CURRENT_SDK_VERSION, }; -/// Plugin indentifier -pub type PluginTag = i32; - /// Exposes your plugin from DLL. Accepts type name as input. The type should implement /// [`Plugin`](plugin/trait.Plugin.html) trait. #[macro_export] @@ -42,229 +40,17 @@ macro_rules! create_plugin { }; } -/// Type wraps `Plugin` trait object to simplify sharing with C/C++. -/// -/// This is for internal usage only and shouldn't be used directly. -#[doc(hidden)] -#[derive(Debug)] -pub struct PluginAdapter(pub Box); - -/// [`Plugin::on_message`](trait.Plugin.html#tymethod.on_message) FFI. -/// -/// It supposed to be used internally. Don't use it. -/// -/// # Safety -/// -/// Unsafe -#[doc(hidden)] -#[no_mangle] -pub unsafe extern "C" fn plugin_info(adapter: *mut PluginAdapter) -> *mut Info { - Box::into_raw(Box::new((*adapter).0.info())) -} - -/// [`Plugin::on_message`](trait.Plugin.html#tymethod.on_message) FFI. -/// -/// It supposed to be used internally. Don't use it. -/// -/// # Safety -/// -/// Unsafe -#[doc(hidden)] -#[no_mangle] -pub unsafe extern "C" fn plugin_dispatcher( - adapter: *mut PluginAdapter, - message: ffi::Message, -) -> intptr_t { - (*adapter).0.on_message(message.into()).as_raw_ptr() -} - -/// [`Plugin::name_of`](trait.Plugin.html#tymethod.name_of) FFI. -/// -/// It supposed to be used internally. Don't use it. -/// -/// # Safety -/// -/// Unsafe -#[doc(hidden)] -#[no_mangle] -pub unsafe extern "C" fn plugin_name_of( - adapter: *const PluginAdapter, - message: ffi::Message, -) -> *mut c_char { - let name = CString::new((*adapter).0.name_of(message.into())).unwrap_or_else(|e| { - error!("{}", e); - panic!(); - }); - name.into_raw() -} - -/// [`Plugin::process_event`](trait.Plugin.html#tymethod.process_event) FFI. -/// -/// It supposed to be used internally. Don't use it. -/// -/// # Safety -/// -/// Unsafe -#[doc(hidden)] -#[no_mangle] -pub unsafe extern "C" fn plugin_process_event( - adapter: *mut PluginAdapter, - event: ffi::Message, -) -> intptr_t { - (*adapter).0.process_event(event.into()); - 0 -} - -/// [`Plugin::process_param`](trait.Plugin.html#tymethod.process_param) FFI. -/// -/// It supposed to be used internally. Don't use it. -/// -/// # Safety -/// -/// Unsafe -#[doc(hidden)] -#[no_mangle] -pub unsafe extern "C" fn plugin_process_param( - adapter: *mut PluginAdapter, - message: ffi::Message, -) -> intptr_t { - (*adapter) - .0 - .process_param( - message.id as usize, - ValuePtr(message.index), - ProcessParamFlags::from_bits_truncate(message.value), - ) - .as_raw_ptr() -} - -/// [`Plugin::idle`](trait.Plugin.html#method.idle) FFI. -/// -/// It supposed to be used internally. Don't use it. -/// -/// # Safety -/// -/// Unsafe -#[doc(hidden)] -#[no_mangle] -pub unsafe extern "C" fn plugin_idle(adapter: *mut PluginAdapter) { - (*adapter).0.idle(); -} - -/// [`Plugin::tick`](trait.Plugin.html#tymethod.tick) FFI. -/// -/// It supposed to be used internally. Don't use it. -/// -/// # Safety -/// -/// Unsafe -#[doc(hidden)] -#[no_mangle] -pub unsafe extern "C" fn plugin_tick(adapter: *mut PluginAdapter) { - (*adapter).0.tick(); -} - -/// [`Plugin::midi_tick`](trait.Plugin.html#tymethod.midi_tick) FFI. -/// -/// It supposed to be used internally. Don't use it. -/// -/// # Safety -/// -/// Unsafe -#[doc(hidden)] -#[no_mangle] -pub unsafe extern "C" fn plugin_midi_tick(adapter: *mut PluginAdapter) { - (*adapter).0.midi_tick(); -} - -/// [`Plugin::render`](trait.Plugin.html#tymethod.render) FFI for effects. -/// -/// It supposed to be used internally. Don't use it. -/// -/// # Safety -/// -/// Unsafe -#[doc(hidden)] -#[no_mangle] -pub unsafe extern "C" fn plugin_eff_render( - adapter: *mut PluginAdapter, - source: *const [f32; 2], - dest: *mut [f32; 2], - length: i32, -) { - let input = std::slice::from_raw_parts(source, length as usize); - let mut output = std::slice::from_raw_parts_mut(dest, length as usize); - (*adapter).0.render(input, &mut output); -} - -/// [`Plugin::render`](trait.Plugin.html#tymethod.render) FFI for generators. -/// -/// It supposed to be used internally. Don't use it. -/// -/// # Safety -/// -/// Unsafe -#[doc(hidden)] -#[no_mangle] -pub unsafe extern "C" fn plugin_gen_render( - adapter: *mut PluginAdapter, - dest: *mut [f32; 2], - length: i32, -) { - let mut output = std::slice::from_raw_parts_mut(dest, length as usize); - (*adapter).0.render(&[[0.0, 0.0]], &mut output); -} - -/// [`Plugin::midi_in`](trait.Plugin.html#tymethod.midi_in) FFI. -/// -/// It supposed to be used internally. Don't use it. -/// -/// # Safety -/// -/// Unsafe -#[doc(hidden)] -#[no_mangle] -pub unsafe extern "C" fn plugin_midi_in(adapter: *mut PluginAdapter, message: MidiMessage) { - (*adapter).0.midi_in(message); -} - -/// [`Plugin::save_state`](trait.Plugin.html#tymethod.save_state) FFI. -/// -/// It supposed to be used internally. Don't use it. -/// -/// # Safety -/// -/// Unsafe -#[doc(hidden)] -#[no_mangle] -pub unsafe extern "C" fn plugin_save_state(adapter: *mut PluginAdapter, stream: *mut c_void) { - (*adapter).0.save_state(StateWriter(stream)); -} - -/// [`Plugin::load_state`](trait.Plugin.html#tymethod.load_state) FFI. -/// -/// It supposed to be used internally. Don't use it. -/// -/// # Safety -/// -/// Unsafe -#[doc(hidden)] -#[no_mangle] -pub unsafe extern "C" fn plugin_load_state(adapter: *mut PluginAdapter, stream: *mut c_void) { - (*adapter).0.load_state(StateReader(stream)); -} - /// This trait must be implemented for your plugin. pub trait Plugin: std::fmt::Debug + RefUnwindSafe + Send + Sync + 'static { /// Initializer - fn new(host: Host, tag: PluginTag) -> Self + fn new(host: Host, tag: Tag) -> Self where Self: Sized; /// Get plugin [`Info`](struct.Info.html). fn info(&self) -> Info; /// Get plugin tag. You should store it when [`Plugin::new`](trait.Plugin.html#tymethod.new) is /// called. - fn tag(&self) -> PluginTag; + fn tag(&self) -> Tag; /// Save plugin's state. fn save_state(&mut self, writer: StateWriter); /// Load plugin's state. @@ -332,6 +118,10 @@ pub trait Plugin: std::fmt::Debug + RefUnwindSafe + Send + Sync + 'static { /// /// Called from mixer thread. fn render(&mut self, _input: &[[f32; 2]], _output: &mut [[f32; 2]]) {} + /// Get [`VoiceHandler`](../voice/trait.VoiceHandler.html). + /// + /// Implement this method if you make a generator plugin. + fn voice_handler(&mut self) -> &mut dyn VoiceHandler; /// The host will call this when there's new MIDI data available. This function is only called /// when the plugin has called the /// [`host::Host::on_message`](../host/struct.Host.html#method.on_message) with @@ -410,14 +200,6 @@ impl InfoBuilder { .get_note_input() } - /// Initializer for a hybrid generator. - /// - /// It's a full generator with [`use_sampler`](struct.InfoBuilder.html#method.use_sampler) - /// option. - pub fn new_hybrid_gen(long_name: &str, short_name: &str, num_params: u32) -> Self { - InfoBuilder::new_full_gen(long_name, short_name, num_params).use_sampler() - } - /// Initializer for a purely visual plugin, that doesn't process any audio data. /// /// It's a basic plugin with [`no_process`](struct.InfoBuilder.html#method.no_process) enabled. @@ -449,12 +231,6 @@ impl InfoBuilder { self } - /// The generator plugin will stream into the host sampler. - pub fn use_sampler(mut self) -> Self { - self.flags |= 1 << 2; - self - } - /// The plugin will use a sample that the user loads into the plugin's channel. pub fn get_chan_custom_shape(mut self) -> Self { self.flags |= 1 << 3; @@ -529,14 +305,6 @@ impl InfoBuilder { self } - /// The plugin is a hybrid generator and can release its envelope by itself. If the host's - /// volume envelope is disabled, then the sound will keep going when the voice is stopped, - /// until the plugin has finished its own release. - pub fn hybrid_can_release(mut self) -> Self { - self.flags |= 1 << 18; - self - } - /// This plugin as a generator will use the sample loaded in its parent channel (see /// [`HostMessage::ChanSampleChanged`]( /// ../host/enum.HostMessage.html#variant.ChanSampleChanged)). @@ -639,3 +407,215 @@ fn check_hresult(result: HRESULT, read: usize, error_msg: &str) -> io::Result); + +/// [`Plugin::on_message`](trait.Plugin.html#tymethod.on_message) FFI. +/// +/// It supposed to be used internally. Don't use it. +/// +/// # Safety +/// +/// Unsafe +#[doc(hidden)] +#[no_mangle] +pub unsafe extern "C" fn plugin_info(adapter: *mut PluginAdapter) -> *mut Info { + Box::into_raw(Box::new((*adapter).0.info())) +} + +/// [`Plugin::on_message`](trait.Plugin.html#tymethod.on_message) FFI. +/// +/// It supposed to be used internally. Don't use it. +/// +/// # Safety +/// +/// Unsafe +#[doc(hidden)] +#[no_mangle] +pub unsafe extern "C" fn plugin_dispatcher( + adapter: *mut PluginAdapter, + message: ffi::Message, +) -> intptr_t { + (*adapter).0.on_message(message.into()).as_raw_ptr() +} + +/// [`Plugin::name_of`](trait.Plugin.html#tymethod.name_of) FFI. +/// +/// It supposed to be used internally. Don't use it. +/// +/// # Safety +/// +/// Unsafe +#[doc(hidden)] +#[no_mangle] +pub unsafe extern "C" fn plugin_name_of( + adapter: *const PluginAdapter, + message: ffi::Message, +) -> *mut c_char { + let name = CString::new((*adapter).0.name_of(message.into())).unwrap_or_else(|e| { + error!("{}", e); + panic!(); + }); + name.into_raw() +} + +/// [`Plugin::process_event`](trait.Plugin.html#tymethod.process_event) FFI. +/// +/// It supposed to be used internally. Don't use it. +/// +/// # Safety +/// +/// Unsafe +#[doc(hidden)] +#[no_mangle] +pub unsafe extern "C" fn plugin_process_event( + adapter: *mut PluginAdapter, + event: ffi::Message, +) -> intptr_t { + (*adapter).0.process_event(event.into()); + 0 +} + +/// [`Plugin::process_param`](trait.Plugin.html#tymethod.process_param) FFI. +/// +/// It supposed to be used internally. Don't use it. +/// +/// # Safety +/// +/// Unsafe +#[doc(hidden)] +#[no_mangle] +pub unsafe extern "C" fn plugin_process_param( + adapter: *mut PluginAdapter, + message: ffi::Message, +) -> intptr_t { + (*adapter) + .0 + .process_param( + message.id as usize, + ValuePtr(message.index), + ProcessParamFlags::from_bits_truncate(message.value), + ) + .as_raw_ptr() +} + +/// [`Plugin::idle`](trait.Plugin.html#method.idle) FFI. +/// +/// It supposed to be used internally. Don't use it. +/// +/// # Safety +/// +/// Unsafe +#[doc(hidden)] +#[no_mangle] +pub unsafe extern "C" fn plugin_idle(adapter: *mut PluginAdapter) { + (*adapter).0.idle(); +} + +/// [`Plugin::tick`](trait.Plugin.html#tymethod.tick) FFI. +/// +/// It supposed to be used internally. Don't use it. +/// +/// # Safety +/// +/// Unsafe +#[doc(hidden)] +#[no_mangle] +pub unsafe extern "C" fn plugin_tick(adapter: *mut PluginAdapter) { + (*adapter).0.tick(); +} + +/// [`Plugin::midi_tick`](trait.Plugin.html#tymethod.midi_tick) FFI. +/// +/// It supposed to be used internally. Don't use it. +/// +/// # Safety +/// +/// Unsafe +#[doc(hidden)] +#[no_mangle] +pub unsafe extern "C" fn plugin_midi_tick(adapter: *mut PluginAdapter) { + (*adapter).0.midi_tick(); +} + +/// [`Plugin::render`](trait.Plugin.html#tymethod.render) FFI for effects. +/// +/// It supposed to be used internally. Don't use it. +/// +/// # Safety +/// +/// Unsafe +#[doc(hidden)] +#[no_mangle] +pub unsafe extern "C" fn plugin_eff_render( + adapter: *mut PluginAdapter, + source: *const [f32; 2], + dest: *mut [f32; 2], + length: i32, +) { + let input = std::slice::from_raw_parts(source, length as usize); + let mut output = std::slice::from_raw_parts_mut(dest, length as usize); + (*adapter).0.render(input, &mut output); +} + +/// [`Plugin::render`](trait.Plugin.html#tymethod.render) FFI for generators. +/// +/// It supposed to be used internally. Don't use it. +/// +/// # Safety +/// +/// Unsafe +#[doc(hidden)] +#[no_mangle] +pub unsafe extern "C" fn plugin_gen_render( + adapter: *mut PluginAdapter, + dest: *mut [f32; 2], + length: i32, +) { + let mut output = std::slice::from_raw_parts_mut(dest, length as usize); + (*adapter).0.render(&[[0.0, 0.0]], &mut output); +} + +/// [`Plugin::midi_in`](trait.Plugin.html#tymethod.midi_in) FFI. +/// +/// It supposed to be used internally. Don't use it. +/// +/// # Safety +/// +/// Unsafe +#[doc(hidden)] +#[no_mangle] +pub unsafe extern "C" fn plugin_midi_in(adapter: *mut PluginAdapter, message: MidiMessage) { + (*adapter).0.midi_in(message); +} + +/// [`Plugin::save_state`](trait.Plugin.html#tymethod.save_state) FFI. +/// +/// It supposed to be used internally. Don't use it. +/// +/// # Safety +/// +/// Unsafe +#[doc(hidden)] +#[no_mangle] +pub unsafe extern "C" fn plugin_save_state(adapter: *mut PluginAdapter, stream: *mut c_void) { + (*adapter).0.save_state(StateWriter(stream)); +} + +/// [`Plugin::load_state`](trait.Plugin.html#tymethod.load_state) FFI. +/// +/// It supposed to be used internally. Don't use it. +/// +/// # Safety +/// +/// Unsafe +#[doc(hidden)] +#[no_mangle] +pub unsafe extern "C" fn plugin_load_state(adapter: *mut PluginAdapter, stream: *mut c_void) { + (*adapter).0.load_state(StateReader(stream)); +} diff --git a/src/voice.rs b/src/voice.rs new file mode 100644 index 0000000..7ed1aae --- /dev/null +++ b/src/voice.rs @@ -0,0 +1,153 @@ +//! Voices used by generators to track events like their instantiation, release, freeing and +//! processing some events. + +use crate::plugin::PluginAdapter; +use crate::{ffi, intptr_t, Tag}; + +/// Implement this trait for your type if you make a generator plugin. +/// +/// All methods can be called either from GUI or mixer thread. +pub trait VoiceHandler: Send + Sync { + /// The host calls this to let it create a voice. + /// + /// The `tag` parameter is an identifier the host uses to identify the voice. + fn trigger(&mut self, params: Params, tag: Tag) -> &mut dyn Voice; + /// This gets called by the host when the voice enters the envelope release state (note off). + fn release(&mut self, tag: Tag); + /// Called when the voice has to be discarded. + fn kill(&mut self, tag: Tag); + /// Process a voice event. + fn on_event(&mut self, _tag: Tag, _event: Event) {} +} + +/// You should add this marker to your voice type. +pub trait Voice: Send + Sync { + /// Get ID of the voice. + fn tag(&self) -> Tag; +} + +/// This is the type for the parameters for a voice. Normally, you'll only use `final_levels`. The +/// final levels are the initial (voice) levels altered by the channel levels. But the initial +/// levels are also available for, for example, note layering. In any case the initial levels are +/// made to be checked once the voice is triggered, while the other ones are to be checked every +/// time. +#[derive(Debug)] +#[repr(C)] +pub struct Params { + /// Made to be checked once the voice is triggered. + pub init_levels: LevelParams, + /// Made to be checked every time. + pub final_levels: LevelParams, +} + +/// This structure holds the parameters for a channel. They're used both for final voice levels +/// (voice levels+parent channel levels) and original voice levels. `LevelParams` is used in +/// [`Params`](struct.Params.html). +/// +/// **All of these parameters can go outside their defined range!** +#[derive(Debug)] +#[repr(C)] +pub struct LevelParams { + /// Panning (-1..1). + pub pan: f32, + /// Volume/velocity (0..1). + pub vol: f32, + /// Pitch (in cents) (semitone=pitch/100). + pub pitch: f32, + /// Modulation X or filter cutoff (-1..1). + pub mod_x: f32, + /// Modulation Y or filter resonance (-1..1). + pub mod_y: f32, +} + +/// Voice events. +#[derive(Debug)] +pub enum Event { + /// Monophonic mode can retrigger releasing voices. + Retrigger, + /// Unknown event. + Unknown, +} + +impl From for Event { + fn from(message: ffi::Message) -> Self { + match message.id { + 0 => Event::Retrigger, + _ => Event::Unknown, + } + } +} + +/// [`VoiceHandler::trigger`](trait.VoiceHandler.html#tymethod.trigger) FFI. +/// +/// It supposed to be used internally. Don't use it. +/// +/// # Safety +/// +/// Unsafe +#[doc(hidden)] +#[no_mangle] +pub unsafe extern "C" fn voice_handler_trigger( + adapter: *mut PluginAdapter, + params: Params, + tag: i32, +) -> intptr_t { + let handler = (*adapter).0.voice_handler(); + Box::into_raw(Box::new(handler.trigger(params, tag))) as intptr_t +} + +/// [`VoiceHandler::release`](trait.VoiceHandler.html#tymethod.release) FFI. +/// +/// It supposed to be used internally. Don't use it. +/// +/// # Safety +/// +/// Unsafe +#[doc(hidden)] +#[no_mangle] +pub unsafe extern "C" fn voice_handler_release( + adapter: *mut PluginAdapter, + voice: *mut &mut dyn Voice, +) { + // We don't call Box::from_raw because: + // 1. Host calls this then voice_handler_kill — this way we'll get double deallocation + // 2. Given FL SDK documentation, we shouldn't deallocate voices here + (*adapter).0.voice_handler().release((*voice).tag()); +} + +/// [`VoiceHandler::kill`](trait.VoiceHandler.html#tymethod.kill) FFI. +/// +/// It supposed to be used internally. Don't use it. +/// +/// # Safety +/// +/// Unsafe +#[doc(hidden)] +#[no_mangle] +pub unsafe extern "C" fn voice_handler_kill( + adapter: *mut PluginAdapter, + voice: *mut &mut dyn Voice, +) { + let r_voice = Box::from_raw(voice); + (*adapter).0.voice_handler().kill(r_voice.tag()); +} + +/// [`VoiceHandler::on_event`](trait.VoiceHandler.html#tymethod.on_event) FFI. +/// +/// It supposed to be used internally. Don't use it. +/// +/// # Safety +/// +/// Unsafe +#[doc(hidden)] +#[no_mangle] +pub unsafe extern "C" fn voice_handler_on_event( + adapter: *mut PluginAdapter, + voice: *mut &mut dyn Voice, + message: ffi::Message, +) { + (*adapter) + .0 + .voice_handler() + .on_event((*voice).tag(), message.into()); +}