├── rustfmt.toml ├── _clang-format ├── .gitignore ├── .github └── workflows │ ├── ci.yml │ └── cd.yml ├── src ├── cxx │ ├── fp_plugclass.cpp │ ├── fp_def.h │ ├── wrapper.h │ └── wrapper.cpp ├── host │ └── prompt.rs ├── voice.rs ├── plugin.rs ├── lib.rs ├── host.rs └── plugin │ └── message.rs ├── install.mac.sh ├── Cargo.toml ├── README.md ├── LICENSE ├── install.win.bat └── examples └── simple.rs /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 100 2 | -------------------------------------------------------------------------------- /_clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: LLVM 2 | IndentWidth: 4 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | debug.sh 4 | *.flp 5 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | build: 13 | 14 | runs-on: ${{ matrix.os }} 15 | strategy: 16 | matrix: 17 | os: [ windows-latest, macOS-latest, ubuntu-latest ] 18 | 19 | steps: 20 | - uses: actions/checkout@v2 21 | - name: Build 22 | run: cargo build --verbose 23 | - name: Run tests 24 | run: cargo test --verbose 25 | -------------------------------------------------------------------------------- /src/cxx/fp_plugclass.cpp: -------------------------------------------------------------------------------- 1 | #ifdef _USE_AFX 2 | #include "stdafx.h" 3 | #endif 4 | #include "fp_plugclass.h" 5 | 6 | #if defined (__APPLE__) || defined (__linux__) 7 | #include 8 | #endif 9 | 10 | // destroy the object 11 | void _stdcall TFruityPlug::DestroyObject() 12 | { 13 | delete this; 14 | } 15 | 16 | TFruityPlug::TFruityPlug() 17 | : HostTag(0), 18 | Info(NULL), 19 | EditorHandle(0), 20 | MonoRender(false) 21 | { 22 | memset(&Reserved, 0, sizeof(Reserved)); 23 | } 24 | 25 | TFruityPlug::~TFruityPlug() 26 | { 27 | } 28 | -------------------------------------------------------------------------------- /install.mac.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | # installs release build into FL effects folder 4 | # Usage: 5 | # install.mac.sh name destination_name [gen] 6 | # if [gen] is specified, the plugin will be installed in as a generator. 7 | 8 | set -e 9 | 10 | name=$1 11 | dest_name=$2 12 | PLUG_PATH="/Applications/FL Studio 20.app/Contents/Resources/FL/Plugins/Fruity" 13 | MIDDLE_DIR="Effects" 14 | 15 | if [ $# -eq 3 ]; then 16 | MIDDLE_DIR="Generators" 17 | fi 18 | 19 | INSTALL_DIR="${PLUG_PATH}/${MIDDLE_DIR}/${dest_name}" 20 | 21 | rm -rf "${INSTALL_DIR}" 22 | mkdir "${INSTALL_DIR}" 23 | mv "target/release/examples/lib${name}.dylib" "${INSTALL_DIR}/${dest_name}_x64.dylib" 24 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Tonika "] 3 | description = "FL Studio SDK" 4 | edition = "2018" 5 | license-file = "LICENSE" 6 | name = "fpsdk" 7 | readme = "README.md" 8 | repository = "https://github.com/tonikasoft/fpsdk" 9 | version = "1.0.3" 10 | 11 | [package.metadata.docs.rs] 12 | targets = [ "x86_64-apple-darwin", "x86_64-pc-windows-msvc" ] 13 | 14 | [dependencies] 15 | bitflags = "1.2" 16 | hresult = "0.0.1" 17 | log = "0.4" 18 | 19 | [build-dependencies] 20 | cc = { version = "1.0", features = ["parallel"] } 21 | 22 | [dev-dependencies] 23 | bincode = "1.2" 24 | log = "0.4" 25 | serde = { version = "1.0", features = ["derive"] } 26 | simple-logging = "2.0" 27 | simplelog = "0.7" 28 | 29 | [[example]] 30 | name = "simple" 31 | path = "examples/simple.rs" 32 | crate-type = ["cdylib"] 33 | -------------------------------------------------------------------------------- /.github/workflows/cd.yml: -------------------------------------------------------------------------------- 1 | name: CD 2 | 3 | on: 4 | release: 5 | types: [ published ] 6 | 7 | jobs: 8 | build: 9 | 10 | runs-on: ${{ matrix.os }} 11 | strategy: 12 | matrix: 13 | os: [ windows-latest, macOS-latest, ubuntu-latest ] 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Build 18 | run: cargo build --verbose 19 | - name: Run tests 20 | run: cargo test --verbose 21 | 22 | deploy: 23 | 24 | needs: build 25 | runs-on: macOS-latest 26 | 27 | steps: 28 | - uses: actions/checkout@v2 29 | - name: Login to crates.io 30 | run: cargo login ${CRATES_IO_TOKEN} 31 | env: 32 | CRATES_IO_TOKEN: ${{ secrets.CRATES_IO_TOKEN }} 33 | - name: Publish on crates.io 34 | run: cargo publish 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![docs](https://docs.rs/fpsdk/badge.svg) 2 | 3 | # fpsdk 4 | 5 | Rust port of [FL Studio SDK](https://www.image-line.com/developers/index.php). 6 | 7 | The FL Studio SDK provides you the API libraries and developer tools necessary 8 | to build, test, and debug plugins for FL Studio. 9 | 10 | 11 | 12 | 13 | ## Example 14 | 15 | The example demonstrates how to use this library. 16 | 17 | To build it, run: 18 | 19 | ``` 20 | cargo build --release --example simple 21 | ``` 22 | 23 | To install it: 24 | 25 | ``` 26 | ./install.mac.sh simple Simple -g # for macOS 27 | ./install.win.bat simple Simple -g # for Windows 28 | ``` 29 | 30 | Check out the corresponding script for your system for usage notes. 31 | 32 | The plugin's log file is created at FL's resources root. It's `/Applications/FL 33 | Studio 20.app/Contents/Resources/FL` for macOS and `:\Program 34 | Files\Image-Line\FL Studio 20` for Windows. 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Tonika 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /install.win.bat: -------------------------------------------------------------------------------- 1 | :: Usage: 2 | :: install.win.bat name destination_name type_of_plugin [plugins_dir] 3 | 4 | @echo off 5 | 6 | set name=%1 7 | set dest_name=%2 8 | set plugins_dir="C:\Program Files\Image-Line\FL Studio 20\Plugins\Fruity" 9 | set args_count=0 10 | set incorrect_args_count=false 11 | set type_flag=false 12 | 13 | for %%x in (%*) do Set /A args_count+=1 14 | 15 | if %args_count% lss 3 set incorrect_args_count=true 16 | if %args_count% gtr 4 set incorrect_args_count=true 17 | if "%incorrect_args_count%"=="true" ( 18 | echo "Usage: install.win.bat name destination_name type_of_plugin [plugins_dir]" 19 | exit /B 87 20 | ) 21 | 22 | if "%3"=="-e" ( 23 | set type=Effects 24 | set type_flag=true 25 | ) 26 | 27 | if "%3"=="-g" ( 28 | set type=Generators 29 | set type_flag=true 30 | ) 31 | 32 | if "%type_flag%"=="false" ( 33 | echo "please type '-e' or '-g'" 34 | exit /B 87 35 | ) 36 | 37 | if %args_count%==4 ( 38 | set plugins_dir=%4 39 | ) 40 | 41 | rd /s /q %plugins_dir%\%type%\%dest_name% 42 | md %plugins_dir%\%type%\%dest_name% 43 | move target\release\examples\%name%.dll %plugins_dir%\%type%\%dest_name%\%dest_name%_x64.dll 44 | -------------------------------------------------------------------------------- /src/cxx/fp_def.h: -------------------------------------------------------------------------------- 1 | #ifndef FP_DEF_H 2 | #define FP_DEF_H 3 | 4 | #include "math.h" 5 | #if !defined (__APPLE__) && !defined (__linux__) 6 | #include "mmsystem.h" 7 | #endif 8 | 9 | #define WaveT_Bits 14 // 14 bits for the length of the wavetable 10 | #define WaveT_Size (1 << WaveT_Bits) // length of the wavetable 11 | #define WaveT_Shift (32-WaveT_Bits) // shift for full DWORD conversion 12 | #define WaveT_Step 1 << WaveT_Shift // speed for 1 sample in the wavetable 13 | #define WaveT_PMask (0xFFFFFFFF >> WaveT_Shift) // mask to limit the position to the range of the wavetable 14 | #define WaveT_FMask (0xFFFFFFFF >> WaveT_Bits) // mask to get the frac part of the position 15 | 16 | 17 | 18 | #define MIDIMsg_PortMask 0xFFFFFF; 19 | #define MIDIMsg_Null 0xFFFFFFFF; 20 | 21 | const int FromMIDI_Max = 65536; // see REC_FromMIDI 22 | const int FromMIDI_Half = 32768; 23 | 24 | 25 | 26 | 27 | // published wavetables 28 | typedef float TWaveT[WaveT_Size]; 29 | typedef TWaveT *PWaveT; 30 | 31 | // interlaced stereo 32Bit float buffer 32 | typedef float TWAV32FS[1][2]; 33 | typedef TWAV32FS *PWAV32FS; 34 | typedef float TWAV32FM[1]; 35 | typedef TWAV32FM *PWAV32FM; 36 | 37 | 38 | // MIDI out message structure (3 bytes standard MIDI message + port) 39 | typedef struct 40 | { 41 | unsigned char Status; 42 | unsigned char Data1; 43 | unsigned char Data2; 44 | unsigned char Port; 45 | } TMIDIOutMsg, *PMIDIOutMsg; 46 | 47 | // extended wav format 48 | typedef struct 49 | { 50 | #if !defined (__APPLE__) && !defined (__linux__) 51 | WAVEFORMATEX WaveFormatEx; 52 | #endif 53 | union 54 | { 55 | struct 56 | { 57 | unsigned short wValidBitsPerSample; // bits of precision 58 | unsigned long dwChannelMask; // which channels are present in stream 59 | #if !defined (__APPLE__) && !defined (__linux__) 60 | GUID SubFormat; 61 | #endif 62 | } stream; 63 | unsigned short wSamplesPerBlock; // valid if wBitsPerSample==0 64 | unsigned short wReserved; // if neither applies, set to zero 65 | }; 66 | } TWaveFormatExtensible, *PWaveFormatExtensible; 67 | 68 | 69 | // Bar:Step:Tick 70 | typedef struct 71 | { 72 | int Bar; 73 | int Step; 74 | int Tick; 75 | } TSongTime, *PSongTime; 76 | 77 | // time sig info (easily converted to standard x/x time sig, but more powerful) 78 | typedef struct 79 | { 80 | int StepsPerBar; 81 | int StepsPerBeat; 82 | int PPQ; 83 | } TTimeSigInfo, *PTimeSigInfo; 84 | 85 | 86 | #endif // FP_DEF_H 87 | -------------------------------------------------------------------------------- /src/host/prompt.rs: -------------------------------------------------------------------------------- 1 | //! Show user popup window to ask for some value. 2 | //! 3 | //! Firstly, init a builder either with [`Prompt::builder`](struct.Prompt.html#method.builder) or 4 | //! [`PromptBuilder::default`] to build your prompt, then use its 5 | //! [`PromptBuilder::show`](struct.PromptBuilder.html#method.show) method to get the result 6 | //! ([`Prompt`](struct.Prompt.html)). 7 | use std::ffi::CString; 8 | use std::os::raw::{c_char, c_int, c_void}; 9 | 10 | use crate::host::Host; 11 | use crate::AsRawPtr; 12 | 13 | /// The result returned by [`PromptBuilder::show`](../struct.PromptBuilder.html#method.show) if the 14 | /// user closed the window by clicking OK. 15 | #[derive(Debug)] 16 | pub struct Prompt { 17 | /// The text typed in by the user. 18 | pub value: String, 19 | /// The color selected by the user, if [`PromptBuilder::with_color`] called. Otherwise it's 20 | /// `None`. 21 | pub color: Option, 22 | } 23 | 24 | impl Prompt { 25 | /// Init [`PromptBuilder`](../struct.PromptBuilder.html). 26 | pub fn builder() -> PromptBuilder { 27 | Default::default() 28 | } 29 | } 30 | 31 | /// Use this to show [`Prompt`](../struct.Promtp.html). 32 | #[derive(Debug, Default)] 33 | pub struct PromptBuilder { 34 | x: Option, 35 | y: Option, 36 | with_color: bool, 37 | } 38 | 39 | impl PromptBuilder { 40 | /// Set horizontal position. Otherwise it's centered horizontally. 41 | pub fn with_x(mut self, x: u32) -> Self { 42 | self.x = Some(x); 43 | self 44 | } 45 | 46 | /// Set vertical position. Otherwise it's centered vertically. 47 | pub fn with_y(mut self, y: u32) -> Self { 48 | self.y = Some(y); 49 | self 50 | } 51 | 52 | /// Call if you want user to set color also. 53 | pub fn with_color(mut self) -> Self { 54 | self.with_color = true; 55 | self 56 | } 57 | 58 | /// Show prompt the user and return [`Prompt`](../struct.Prompt.html), if he/she closed the 59 | /// window by clicking OK. The method returns `None` otherwise. 60 | pub fn show(self, host: &mut Host, message: String) -> Option { 61 | let mut color = self.with_color as c_int - 1; 62 | let value = CString::default().into_raw(); 63 | 64 | if unsafe { 65 | !prompt_show( 66 | *host.host_ptr.get_mut(), 67 | self.x.map(|v| v as c_int).unwrap_or(-1), 68 | self.y.map(|v| v as c_int).unwrap_or(-1), 69 | message.as_raw_ptr() as *mut c_char, 70 | value, 71 | &mut color, 72 | ) 73 | } { 74 | unsafe { CString::from_raw(value) }; 75 | return None; 76 | } 77 | 78 | Some(Prompt { 79 | value: self.value_from_raw(value), 80 | color: self.color_to_result(color), 81 | }) 82 | } 83 | 84 | fn value_from_raw(&self, ptr: *mut c_char) -> String { 85 | unsafe { CString::from_raw(ptr) } 86 | .to_string_lossy() 87 | .to_string() 88 | } 89 | 90 | fn color_to_result(&self, color: c_int) -> Option { 91 | if self.with_color { 92 | Some(i32::from_be(color)) 93 | } else { 94 | None 95 | } 96 | } 97 | } 98 | 99 | extern "C" { 100 | fn prompt_show( 101 | host: *mut c_void, 102 | x: c_int, 103 | y: c_int, 104 | msg: *mut c_char, 105 | result: *mut c_char, 106 | color: &mut c_int, 107 | ) -> bool; 108 | } 109 | -------------------------------------------------------------------------------- /src/cxx/wrapper.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "fp_plugclass.h" 4 | 5 | struct PluginAdapter; 6 | 7 | struct FlMessage { 8 | intptr_t id; 9 | intptr_t index; 10 | intptr_t value; 11 | }; 12 | 13 | // from plugin.rs 14 | struct Info { 15 | unsigned int sdk_version; 16 | char *long_name; 17 | char *short_name; 18 | unsigned int flags; 19 | unsigned int num_params; 20 | unsigned int def_poly; 21 | unsigned int num_out_ctrls; 22 | unsigned int num_out_voices; 23 | }; 24 | 25 | // from voice.rs 26 | struct LevelParams { 27 | float pan; 28 | float vol; 29 | float pitch; 30 | float mod_x; 31 | float mod_y; 32 | }; 33 | 34 | struct Params { 35 | LevelParams init_levels; 36 | LevelParams final_levels; 37 | }; 38 | 39 | class PluginWrapper : public TFruityPlug { 40 | public: 41 | PluginWrapper(TFruityPlugHost *host, TPluginTag tag, PluginAdapter *adapter, 42 | PFruityPlugInfo info); 43 | virtual ~PluginWrapper(); 44 | 45 | // from TFruityPlug 46 | virtual intptr_t _stdcall Dispatcher(intptr_t id, intptr_t index, 47 | intptr_t value); 48 | virtual void _stdcall Idle_Public(); 49 | virtual void _stdcall SaveRestoreState(IStream *stream, BOOL save); 50 | virtual void _stdcall GetName(int section, int index, int value, 51 | char *name); 52 | virtual int _stdcall ProcessEvent(int event_id, int event_value, int flags); 53 | virtual int _stdcall ProcessParam(int index, int value, int rec_flags); 54 | virtual void _stdcall Eff_Render(PWAV32FS source_buffer, 55 | PWAV32FS dest_buffer, int length); 56 | virtual void _stdcall Gen_Render(PWAV32FS dest_buffer, int &length); 57 | virtual TVoiceHandle _stdcall TriggerVoice(PVoiceParams voice_params, 58 | intptr_t set_tag); 59 | virtual void _stdcall Voice_Release(TVoiceHandle handle); 60 | virtual void _stdcall Voice_Kill(TVoiceHandle handle); 61 | virtual int _stdcall Voice_ProcessEvent(TVoiceHandle handle, int event_id, 62 | int event_value, int flags); 63 | virtual int _stdcall Voice_Render(TVoiceHandle handle, PWAV32FS dest_buffer, 64 | int &length); 65 | virtual void _stdcall NewTick(); 66 | virtual void _stdcall MIDITick(); 67 | virtual void _stdcall MIDIIn(int &msg); 68 | virtual void _stdcall MsgIn(intptr_t msg); 69 | virtual int _stdcall OutputVoice_ProcessEvent(TOutVoiceHandle handle, 70 | int event_id, int event_value, 71 | int flags); 72 | virtual void _stdcall OutputVoice_Kill(TVoiceHandle handle); 73 | 74 | protected: 75 | TFruityPlugHost *host; 76 | PluginAdapter *adapter; 77 | }; 78 | 79 | // Unsafe Rust FFI 80 | // 81 | // PluginAdapter methods 82 | extern "C" void *create_plug_instance_c(void *host, intptr_t tag, 83 | void *adapter); 84 | extern "C" Info *plugin_info(PluginAdapter *adapter); 85 | extern "C" intptr_t plugin_dispatcher(PluginAdapter *adapter, 86 | FlMessage message); 87 | extern "C" int plugin_process_event(PluginAdapter *adapter, FlMessage event); 88 | extern "C" intptr_t plugin_process_param(PluginAdapter *adapter, 89 | FlMessage event); 90 | extern "C" char *plugin_name_of(const PluginAdapter *adapter, 91 | FlMessage message); 92 | extern "C" void plugin_idle(PluginAdapter *adapter); 93 | extern "C" void plugin_tick(PluginAdapter *adapter); 94 | extern "C" void plugin_midi_tick(PluginAdapter *adapter); 95 | extern "C" void plugin_eff_render(PluginAdapter *adapter, 96 | const float source[1][2], float dest[1][2], 97 | int len); 98 | extern "C" void plugin_gen_render(PluginAdapter *adapter, float dest[1][2], 99 | int len); 100 | extern "C" void plugin_midi_in(PluginAdapter *adapter, int &message); 101 | extern "C" void plugin_save_state(PluginAdapter *adapter, IStream *istream); 102 | extern "C" void plugin_load_state(PluginAdapter *adapter, IStream *istream); 103 | extern "C" void plugin_loop_in(PluginAdapter *adapter, intptr_t message); 104 | 105 | // Voice handler 106 | extern "C" intptr_t voice_handler_trigger(PluginAdapter *adapter, Params params, 107 | intptr_t tag); 108 | extern "C" void voice_handler_release(PluginAdapter *adapter, void *voice); 109 | extern "C" void voice_handler_kill(PluginAdapter *adapter, void *voice); 110 | extern "C" intptr_t voice_handler_on_event(PluginAdapter *adapter, void *voice, 111 | FlMessage message); 112 | extern "C" void out_voice_handler_kill(PluginAdapter *adapter, intptr_t tag); 113 | extern "C" intptr_t out_voice_handler_on_event(PluginAdapter *adapter, 114 | intptr_t tag, FlMessage message); 115 | 116 | // IStream 117 | extern "C" int istream_read(void *istream, unsigned char *data, 118 | unsigned int size, unsigned int *read); 119 | extern "C" int istream_write(void *istream, const unsigned char *data, 120 | unsigned int size, unsigned int *write); 121 | 122 | // Host 123 | extern "C" intptr_t host_on_message(void *host, TPluginTag tag, 124 | FlMessage message); 125 | extern "C" void host_on_parameter(void *host, TPluginTag tag, int index, 126 | int value); 127 | extern "C" void host_on_controller(void *host, TPluginTag tag, intptr_t index, 128 | intptr_t value); 129 | extern "C" void host_on_hint(void *host, TPluginTag tag, char *text); 130 | extern "C" void host_midi_out(void *host, TPluginTag tag, unsigned char status, 131 | unsigned char data1, unsigned char data2, 132 | unsigned char port); 133 | extern "C" void host_midi_out_del(void *host, TPluginTag tag, 134 | unsigned char status, unsigned char data1, 135 | unsigned char data2, unsigned char port); 136 | extern "C" void host_loop_out(void *host, TPluginTag tag, intptr_t msg); 137 | extern "C" void host_loop_kill(void *host, TPluginTag tag, intptr_t msg); 138 | extern "C" void host_lock_mix(void *host); 139 | extern "C" void host_unlock_mix(void *host); 140 | extern "C" void host_lock_plugin(void *host, TPluginTag tag); 141 | extern "C" void host_unlock_plugin(void *host, TPluginTag tag); 142 | extern "C" void host_suspend_out(void *host); 143 | extern "C" void host_resume_out(void *host); 144 | extern "C" TIOBuffer host_get_input_buf(void *host, TPluginTag tag, 145 | intptr_t offset); 146 | extern "C" TIOBuffer host_get_output_buf(void *host, TPluginTag tag, 147 | intptr_t offset); 148 | extern "C" void *host_get_insert_buf(void *host, TPluginTag tag, 149 | intptr_t offset); 150 | extern "C" void *host_get_mix_buf(void *host, intptr_t offset); 151 | extern "C" void *host_get_send_buf(void *host, intptr_t offset); 152 | 153 | extern "C" bool prompt_show(void *host, int x, int y, char *msg, char *result, 154 | int &color); 155 | 156 | // Host voice-related 157 | extern "C" void host_release_voice(void *host, intptr_t tag); 158 | extern "C" void host_kill_voice(void *host, intptr_t tag); 159 | extern "C" intptr_t host_on_voice_event(void *host, intptr_t tag, 160 | FlMessage message); 161 | extern "C" intptr_t host_trig_out_voice(void *host, Params *params, int index, 162 | intptr_t tag); 163 | extern "C" void host_release_out_voice(void *host, intptr_t tag); 164 | extern "C" void host_kill_out_voice(void *host, intptr_t tag); 165 | extern "C" intptr_t host_on_out_voice_event(void *host, intptr_t tag, 166 | FlMessage message); 167 | 168 | // Utility 169 | extern "C" void fplog(const char *msg); 170 | extern "C" intptr_t init_p_notes_params(int target, int flags, int ch_num, 171 | int pat_num, TNoteParams *notes, 172 | int len); 173 | 174 | extern "C" void free_rbox_raw(void *raw_ptr); 175 | extern "C" void free_rstring(char *raw_str); 176 | // FFI to make C string (`char *`) managed by C side. Because `char *` 177 | // produced by `CString::into_raw` leads to memory leak. Here's what docs 178 | // say about `CString::into_raw`: 179 | // 180 | // The pointer which this function returns must be returned to Rust and 181 | // reconstituted using from_raw to be properly deallocated. Specifically, 182 | // one should not use the standard C free() function to deallocate this 183 | // string. 184 | extern "C" char *alloc_real_cstr(char *rust_cstr); 185 | -------------------------------------------------------------------------------- /src/voice.rs: -------------------------------------------------------------------------------- 1 | //! Voices used by generators to track events like their instantiation, release, freeing and 2 | //! processing some events. 3 | use std::os::raw::c_void; 4 | 5 | use crate::plugin::PluginAdapter; 6 | use crate::{intptr_t, AsRawPtr, FlMessage, ValuePtr}; 7 | 8 | crate::implement_tag!(); 9 | 10 | /// Implement this trait for your type if you make a generator plugin. 11 | /// 12 | /// All methods can be called either from GUI or mixer thread. 13 | pub trait ReceiveVoiceHandler: Send + Sync { 14 | /// The host calls this to let it create a voice. 15 | /// 16 | /// The `tag` parameter is an identifier the host uses to identify the voice. 17 | fn trigger(&mut self, params: Params, tag: Tag) -> &mut dyn Voice; 18 | /// This gets called by the host when the voice enters the envelope release state (note off). 19 | fn release(&mut self, tag: Tag); 20 | /// Called when the voice has to be discarded. 21 | fn kill(&mut self, tag: Tag); 22 | /// Process a voice event. 23 | fn on_event(&mut self, _tag: Tag, _event: Event) -> Box { 24 | Box::new(0) 25 | } 26 | /// Getter for [`SendVoiceHandler`](trait.SendVoiceHandler.html). 27 | fn out_handler(&mut self) -> Option<&mut dyn SendVoiceHandler> { 28 | None 29 | } 30 | } 31 | 32 | /// You should implement this trait to your voice type. 33 | pub trait Voice: Send + Sync { 34 | /// Get ID of the voice. 35 | fn tag(&self) -> Tag; 36 | } 37 | 38 | /// This is the type for the parameters for a voice. Normally, you'll only use `final_levels`. The 39 | /// final levels are the initial (voice) levels altered by the channel levels. But the initial 40 | /// levels are also available for, for example, note layering. In any case the initial levels are 41 | /// made to be checked once the voice is triggered, while the other ones are to be checked every 42 | /// time. 43 | #[derive(Clone, Debug)] 44 | #[repr(C)] 45 | pub struct Params { 46 | /// Made to be checked once the voice is triggered. 47 | pub init_levels: LevelParams, 48 | /// Made to be checked every time. 49 | pub final_levels: LevelParams, 50 | } 51 | 52 | /// This structure holds the parameters for a channel. They're used both for final voice levels 53 | /// (voice levels+parent channel levels) and original voice levels. `LevelParams` is used in 54 | /// [`Params`](struct.Params.html). 55 | /// 56 | /// **All of these parameters can go outside their defined range!** 57 | #[derive(Clone, Debug)] 58 | #[repr(C)] 59 | pub struct LevelParams { 60 | /// Panning (-1..1). 61 | pub pan: f32, 62 | /// Volume/velocity (0..1). 63 | pub vol: f32, 64 | /// Pitch (in cents) (semitone=pitch/100). 65 | pub pitch: f32, 66 | /// Modulation X or filter cutoff (-1..1). 67 | pub mod_x: f32, 68 | /// Modulation Y or filter resonance (-1..1). 69 | pub mod_y: f32, 70 | } 71 | 72 | /// Voice events. 73 | #[derive(Debug)] 74 | pub enum Event { 75 | /// Monophonic mode can retrigger releasing voices. 76 | Retrigger, 77 | /// Retrieve the note length in ticks. The result is not reliable. 78 | /// 79 | /// Function result holds the length of the note, or -1 if it's not defined. 80 | GetLength, 81 | /// Retrieve the color for a note. A note can currently have up to 16 colors in the pianoroll. 82 | /// This can be mapped to a MIDI channel. 83 | /// 84 | /// Functions result holds the note color (0..15). 85 | GetColor, 86 | /// (FL 7.0) Retrieve note on velocity. This is computed from 87 | /// [`Params.init_levels.vol`](struct.Params.html). This should be called from `trigger` 88 | /// method. 89 | /// 90 | /// Function result holds the `f32` velocity (0.0..1.0). 91 | GetVelocity, 92 | /// (FL 7.0) Retrieve release velocity (0.0..1.0) in result. Use this if some release velocity 93 | /// mapping is involved. This should be called from `release` method. 94 | /// 95 | /// Function result holds the `f32` velocity (0.0..1.0) (to be called from `release` method). 96 | GetRelVelocity, 97 | /// (FL 7.0) Retrieve release time multiplicator. Use this for direct release multiplicator. 98 | /// This should be called from `release` method. 99 | /// 100 | /// Function result holds the `f32` value (0.0..2.0). 101 | GetRelTime, 102 | /// (FL 7.0) Call this to set if velocity is linked to volume or not. The default is on. 103 | SetLinkVelocity(bool), 104 | /// Unknown event. 105 | Unknown, 106 | } 107 | 108 | impl From for Event { 109 | fn from(message: FlMessage) -> Self { 110 | match message.id { 111 | 0 => Event::Retrigger, 112 | 1 => Event::GetLength, 113 | 2 => Event::GetColor, 114 | 3 => Event::GetVelocity, 115 | 4 => Event::GetRelVelocity, 116 | 5 => Event::GetRelTime, 117 | 6 => Event::SetLinkVelocity(message.index != 0), 118 | _ => Event::Unknown, 119 | } 120 | } 121 | } 122 | 123 | impl From for Option { 124 | fn from(event: Event) -> Self { 125 | match event { 126 | Event::Retrigger => Some(FlMessage { 127 | id: 0, 128 | index: 0, 129 | value: 0, 130 | }), 131 | Event::GetLength => Some(FlMessage { 132 | id: 1, 133 | index: 0, 134 | value: 0, 135 | }), 136 | Event::GetColor => Some(FlMessage { 137 | id: 2, 138 | index: 0, 139 | value: 0, 140 | }), 141 | Event::GetVelocity => Some(FlMessage { 142 | id: 3, 143 | index: 0, 144 | value: 0, 145 | }), 146 | Event::GetRelVelocity => Some(FlMessage { 147 | id: 4, 148 | index: 0, 149 | value: 0, 150 | }), 151 | Event::GetRelTime => Some(FlMessage { 152 | id: 5, 153 | index: 0, 154 | value: 0, 155 | }), 156 | Event::SetLinkVelocity(value) => Some(FlMessage { 157 | id: 6, 158 | index: value as isize, 159 | value: 0, 160 | }), 161 | Event::Unknown => None, 162 | } 163 | } 164 | } 165 | 166 | /// Additional methods used by [`ReceiveVoiceHandler`](trait.ReceiveVoiceHandler.html) in VFX 167 | /// plugins for the output voices. 168 | pub trait SendVoiceHandler: Send + Sync { 169 | /// The host calls this to let it create a voice. 170 | /// 171 | /// - `tag` is an identifier the host uses to identify the voice. 172 | /// - `index` is voice output index in patcher. 173 | /// 174 | fn trigger(&mut self, _params: Params, _index: usize, _tag: Tag) -> Option<&mut dyn Voice> { 175 | None 176 | } 177 | /// This gets called by the host when the voice enters the envelope release state (note off). 178 | fn release(&mut self, _tag: Tag) {} 179 | /// Called when the voice has to be discarded. 180 | fn kill(&mut self, tag: Tag); 181 | /// Process a voice event. 182 | /// 183 | /// See [`Event`](enum.Event.html) for result variants. 184 | fn on_event(&mut self, _tag: Tag, _event: Event) -> Option { 185 | None 186 | } 187 | } 188 | 189 | /// [`ReceiveVoiceHandler::trigger`](trait.ReceiveVoiceHandler.html#tymethod.trigger) FFI. 190 | /// 191 | /// It supposed to be used internally. Don't use it. 192 | /// 193 | /// # Safety 194 | /// 195 | /// Unsafe 196 | #[doc(hidden)] 197 | #[no_mangle] 198 | unsafe extern "C" fn voice_handler_trigger( 199 | adapter: *mut PluginAdapter, 200 | params: Params, 201 | tag: intptr_t, 202 | ) -> intptr_t { 203 | (*adapter) 204 | .0 205 | .voice_handler() 206 | .map(|handler| { 207 | let voice_ptr: *mut &mut dyn Voice = 208 | Box::leak(Box::new(handler.trigger(params, Tag(tag)))); 209 | voice_ptr as *mut c_void as intptr_t 210 | }) 211 | .unwrap_or(-1) 212 | } 213 | 214 | /// [`ReceiveVoiceHandler::release`](trait.ReceiveVoiceHandler.html#tymethod.release) FFI. 215 | /// 216 | /// It supposed to be used internally. Don't use it. 217 | /// 218 | /// # Safety 219 | /// 220 | /// Unsafe 221 | #[doc(hidden)] 222 | #[no_mangle] 223 | unsafe extern "C" fn voice_handler_release( 224 | adapter: *mut PluginAdapter, 225 | voice: *mut &mut dyn Voice, 226 | ) { 227 | // We don't call Box::from_raw because: 228 | // 1. Host calls this then voice_handler_kill — this way we'll get double deallocation 229 | // 2. Given FL SDK documentation, we shouldn't deallocate voices here 230 | if let Some(handler) = (*adapter).0.voice_handler() { 231 | handler.release((*voice).tag()) 232 | } 233 | } 234 | 235 | /// [`ReceiveVoiceHandler::kill`](trait.ReceiveVoiceHandler.html#tymethod.kill) FFI. 236 | /// 237 | /// It supposed to be used internally. Don't use it. 238 | /// 239 | /// # Safety 240 | /// 241 | /// Unsafe 242 | #[doc(hidden)] 243 | #[no_mangle] 244 | unsafe extern "C" fn voice_handler_kill(adapter: *mut PluginAdapter, voice: *mut &mut dyn Voice) { 245 | let r_voice = Box::from_raw(voice); 246 | if let Some(handler) = (*adapter).0.voice_handler() { 247 | handler.kill(r_voice.tag()) 248 | } 249 | } 250 | 251 | /// [`ReceiveVoiceHandler::kill_out`](trait.ReceiveVoiceHandler.html#tymethod.kill_out) FFI. 252 | /// 253 | /// It supposed to be used internally. Don't use it. 254 | /// 255 | /// # Safety 256 | /// 257 | /// Unsafe 258 | #[doc(hidden)] 259 | #[no_mangle] 260 | unsafe extern "C" fn out_voice_handler_kill(adapter: *mut PluginAdapter, tag: intptr_t) { 261 | (*adapter).0.voice_handler().and_then(|handler| { 262 | handler.out_handler().map(|out_handler| { 263 | out_handler.kill(Tag(tag)); 264 | }) 265 | }); 266 | } 267 | 268 | /// [`ReceiveVoiceHandler::on_event`](trait.ReceiveVoiceHandler.html#tymethod.on_event) FFI. 269 | /// 270 | /// It supposed to be used internally. Don't use it. 271 | /// 272 | /// # Safety 273 | /// 274 | /// Unsafe 275 | #[doc(hidden)] 276 | #[no_mangle] 277 | unsafe extern "C" fn voice_handler_on_event( 278 | adapter: *mut PluginAdapter, 279 | voice: *mut &mut dyn Voice, 280 | message: FlMessage, 281 | ) -> intptr_t { 282 | (*adapter) 283 | .0 284 | .voice_handler() 285 | .map(|handler| { 286 | handler 287 | .on_event((*voice).tag(), message.into()) 288 | .as_raw_ptr() 289 | }) 290 | .unwrap_or(-1) 291 | } 292 | 293 | /// [`SendVoiceHandler::on_event`](trait.SendVoiceHandler.html#method.on_event) FFI. 294 | /// 295 | /// It supposed to be used internally. Don't use it. 296 | /// 297 | /// # Safety 298 | /// 299 | /// Unsafe 300 | #[doc(hidden)] 301 | #[no_mangle] 302 | unsafe extern "C" fn out_voice_handler_on_event( 303 | adapter: *mut PluginAdapter, 304 | tag: intptr_t, 305 | message: FlMessage, 306 | ) -> intptr_t { 307 | (*adapter) 308 | .0 309 | .voice_handler() 310 | .and_then(|handler| handler.out_handler()) 311 | .and_then(|out_handler| out_handler.on_event(Tag(tag), message.into())) 312 | .map(|result| result.0) 313 | .unwrap_or(-1) 314 | } 315 | 316 | /// Translate FL voice volume to linear velocity (0.0..1.0). 317 | pub fn vol_to_vel(vol: f32) -> f32 { 318 | inv_log_vol(vol * 10.0, 2610.0 / 127.0) 319 | } 320 | 321 | /// Translate FL voice volume to linear velocity (0.0..127.0). 322 | pub fn vol_to_midi_vel(vol: f32) -> f32 { 323 | inv_log_vol(vol * 10.0, 2610.0 / 127.0) * 127.0 324 | } 325 | 326 | fn inv_log_vol(value: f32, max_value: f32) -> f32 { 327 | (value + 1.0).ln() / (max_value + 1.0).ln() 328 | } 329 | -------------------------------------------------------------------------------- /src/cxx/wrapper.cpp: -------------------------------------------------------------------------------- 1 | #include "wrapper.h" 2 | #include "fp_plugclass.h" 3 | #include 4 | #include 5 | 6 | intptr_t init_p_notes_params(int target, int flags, int ch_num, int pat_num, 7 | TNoteParams *notes, int len) { 8 | TNotesParams *params = (TNotesParams *)malloc(sizeof(TNotesParams) + 9 | sizeof(TNoteParams) * len); 10 | params->Target = target; 11 | params->Flags = flags; 12 | params->PatNum = pat_num; 13 | params->ChanNum = ch_num; 14 | params->Count = len; 15 | params->NoteParams[0] = *notes; 16 | memmove(params->NoteParams, notes, sizeof(TNoteParams) * len); 17 | 18 | return (intptr_t)params; 19 | } 20 | 21 | char *alloc_real_cstr(char *rust_cstr) { 22 | char *result = (char *)malloc(strlen(rust_cstr) + 1); 23 | strcpy(result, rust_cstr); 24 | free_rstring(rust_cstr); 25 | 26 | return result; 27 | } 28 | 29 | int istream_read(void *istream, unsigned char *data, unsigned int size, 30 | unsigned int *read) { 31 | if (!data || size < 1) 32 | return 0x80004003; // E_POINTER 33 | 34 | return (int)((IStream *)istream)->Read(data, size, (unsigned long *)read); 35 | } 36 | 37 | int istream_write(void *istream, const unsigned char *data, unsigned int size, 38 | unsigned int *write) { 39 | if (!data || size < 1) 40 | return 0x80004003; // E_POINTER 41 | 42 | return (int)((IStream *)istream)->Write(data, size, (unsigned long *)write); 43 | } 44 | 45 | void *create_plug_instance_c(void *host, intptr_t tag, void *adapter) { 46 | Info *info = plugin_info((PluginAdapter *)adapter); 47 | 48 | int reserved[30] = {0}; 49 | PFruityPlugInfo c_info = new TFruityPlugInfo{(int)info->sdk_version, 50 | info->long_name, 51 | info->short_name, 52 | (int)info->flags, 53 | (int)info->num_params, 54 | (int)info->def_poly, 55 | (int)info->num_out_ctrls, 56 | (int)info->num_out_voices, 57 | {*reserved}}; 58 | 59 | free_rbox_raw(info); 60 | 61 | PluginWrapper *wrapper = new PluginWrapper( 62 | (TFruityPlugHost *)host, tag, (PluginAdapter *)adapter, c_info); 63 | 64 | return wrapper; 65 | } 66 | 67 | PluginWrapper::PluginWrapper(TFruityPlugHost *host_ptr, TPluginTag tag, 68 | PluginAdapter *adap, PFruityPlugInfo info) { 69 | Info = info; 70 | HostTag = tag; 71 | EditorHandle = 0; 72 | host = host_ptr; 73 | adapter = adap; 74 | } 75 | 76 | PluginWrapper::~PluginWrapper() { 77 | free(Info->LongName); 78 | free(Info->ShortName); 79 | delete Info; 80 | free_rbox_raw(adapter); 81 | } 82 | 83 | void _stdcall PluginWrapper::SaveRestoreState(IStream *stream, BOOL save) { 84 | if (save) { 85 | plugin_save_state(adapter, stream); 86 | } else { 87 | plugin_load_state(adapter, stream); 88 | } 89 | } 90 | 91 | intptr_t _stdcall PluginWrapper::Dispatcher(intptr_t id, intptr_t index, 92 | intptr_t value) { 93 | 94 | // if (id == FPD_SetEnabled) { 95 | // host->Dispatcher(HostTag, FHD_WantMIDIInput, 0, value); 96 | // } 97 | 98 | FlMessage message = {id, index, value}; 99 | 100 | return plugin_dispatcher(adapter, message); 101 | } 102 | 103 | void _stdcall PluginWrapper::GetName(int section, int index, int value, 104 | char *name) { 105 | FlMessage message = { 106 | (intptr_t)section, 107 | (intptr_t)index, 108 | (intptr_t)value, 109 | }; 110 | 111 | char *name_of = plugin_name_of(adapter, message); 112 | strcpy(name, name_of); 113 | free_rstring(name_of); 114 | } 115 | 116 | int _stdcall PluginWrapper::ProcessEvent(int event_id, int event_value, 117 | int flags) { 118 | FlMessage message = { 119 | (intptr_t)event_id, 120 | (intptr_t)event_value, 121 | (intptr_t)flags, 122 | }; 123 | 124 | plugin_process_event(adapter, message); 125 | 126 | return 0; 127 | } 128 | 129 | int _stdcall PluginWrapper::ProcessParam(int index, int value, int rec_flags) { 130 | FlMessage message = { 131 | (intptr_t)index, 132 | (intptr_t)value, 133 | (intptr_t)rec_flags, 134 | }; 135 | 136 | return (int)plugin_process_param(adapter, message); 137 | } 138 | 139 | void _stdcall PluginWrapper::Idle_Public() { plugin_idle(adapter); } 140 | 141 | void _stdcall PluginWrapper::Eff_Render(PWAV32FS source_buffer, 142 | PWAV32FS dest_buffer, int length) { 143 | plugin_eff_render(adapter, *source_buffer, *dest_buffer, length); 144 | } 145 | 146 | void _stdcall PluginWrapper::Gen_Render(PWAV32FS dest_buffer, int &length) { 147 | plugin_gen_render(adapter, *dest_buffer, length); 148 | } 149 | 150 | TVoiceHandle _stdcall PluginWrapper::TriggerVoice(PVoiceParams voice_params, 151 | intptr_t set_tag) { 152 | LevelParams init_levels = { 153 | voice_params->InitLevels.Pan, voice_params->InitLevels.Vol, 154 | voice_params->InitLevels.Pitch, voice_params->InitLevels.FCut, 155 | voice_params->InitLevels.FRes, 156 | }; 157 | 158 | LevelParams final_levels = { 159 | voice_params->FinalLevels.Pan, voice_params->FinalLevels.Vol, 160 | voice_params->FinalLevels.Pitch, voice_params->FinalLevels.FCut, 161 | voice_params->FinalLevels.FRes, 162 | }; 163 | 164 | Params params = { 165 | init_levels, 166 | final_levels, 167 | }; 168 | 169 | return (TVoiceHandle)voice_handler_trigger(adapter, params, set_tag); 170 | } 171 | 172 | void _stdcall PluginWrapper::Voice_Release(TVoiceHandle handle) { 173 | voice_handler_release(adapter, (void *)handle); 174 | } 175 | 176 | void _stdcall PluginWrapper::Voice_Kill(TVoiceHandle handle) { 177 | voice_handler_kill(adapter, (void *)handle); 178 | } 179 | 180 | int _stdcall PluginWrapper::Voice_ProcessEvent(TVoiceHandle handle, 181 | int event_id, int event_value, 182 | int flags) { 183 | FlMessage message = { 184 | (intptr_t)event_id, 185 | (intptr_t)event_value, 186 | (intptr_t)flags, 187 | }; 188 | 189 | return (int)voice_handler_on_event(adapter, (void *)handle, message); 190 | } 191 | 192 | int _stdcall PluginWrapper::Voice_Render(TVoiceHandle, PWAV32FS, int &) { 193 | // Deprecated: 194 | // https://forum.image-line.com/viewtopic.php?f=100&t=199515#p1371655 195 | return 0; 196 | } 197 | 198 | void _stdcall PluginWrapper::NewTick() { plugin_tick(adapter); } 199 | 200 | void _stdcall PluginWrapper::MIDITick() { plugin_midi_tick(adapter); } 201 | 202 | void _stdcall PluginWrapper::MIDIIn(int &msg) { plugin_midi_in(adapter, msg); } 203 | 204 | void _stdcall PluginWrapper::MsgIn(intptr_t msg) { 205 | plugin_loop_in(adapter, msg); 206 | } 207 | 208 | int _stdcall PluginWrapper::OutputVoice_ProcessEvent(TOutVoiceHandle handle, 209 | int event_id, 210 | int event_value, 211 | int flags) { 212 | FlMessage message = { 213 | (intptr_t)event_id, 214 | (intptr_t)event_value, 215 | (intptr_t)flags, 216 | }; 217 | 218 | return (int)out_voice_handler_on_event(adapter, handle, message); 219 | } 220 | 221 | void _stdcall PluginWrapper::OutputVoice_Kill(TVoiceHandle handle) { 222 | out_voice_handler_kill(adapter, handle); 223 | } 224 | 225 | // host 226 | intptr_t host_on_message(void *host, TPluginTag tag, FlMessage message) { 227 | return ((TFruityPlugHost *)host) 228 | ->Dispatcher(tag, message.id, message.index, message.value); 229 | } 230 | 231 | void host_on_parameter(void *host, TPluginTag tag, int index, int value) { 232 | ((TFruityPlugHost *)host)->OnParamChanged(tag, index, value); 233 | } 234 | 235 | void host_on_controller(void *host, TPluginTag tag, intptr_t index, 236 | intptr_t value) { 237 | ((TFruityPlugHost *)host)->OnControllerChanged(tag, index, value); 238 | } 239 | 240 | void host_on_hint(void *host, TPluginTag tag, char *text) { 241 | ((TFruityPlugHost *)host)->OnHint(tag, text); 242 | } 243 | 244 | void host_midi_out(void *host, TPluginTag tag, unsigned char status, 245 | unsigned char data1, unsigned char data2, 246 | unsigned char port) { 247 | TMIDIOutMsg *msg = (TMIDIOutMsg *)malloc(sizeof(TMIDIOutMsg)); 248 | msg->Status = status; 249 | msg->Data1 = data1; 250 | msg->Data2 = data2; 251 | msg->Port = port; 252 | 253 | ((TFruityPlugHost *)host)->MIDIOut(tag, (intptr_t)msg); 254 | } 255 | 256 | void host_midi_out_del(void *host, TPluginTag tag, unsigned char status, 257 | unsigned char data1, unsigned char data2, 258 | unsigned char port) { 259 | TMIDIOutMsg *msg = (TMIDIOutMsg *)malloc(sizeof(TMIDIOutMsg)); 260 | msg->Status = status; 261 | msg->Data1 = data1; 262 | msg->Data2 = data2; 263 | msg->Port = port; 264 | 265 | ((TFruityPlugHost *)host)->MIDIOut_Delayed(tag, (intptr_t)msg); 266 | } 267 | 268 | void host_loop_out(void *host, TPluginTag tag, intptr_t msg) { 269 | ((TFruityPlugHost *)host)->PlugMsg_Delayed(tag, msg); 270 | } 271 | 272 | void host_loop_kill(void *host, TPluginTag tag, intptr_t msg) { 273 | ((TFruityPlugHost *)host)->PlugMsg_Kill(tag, msg); 274 | } 275 | 276 | void host_lock_mix(void *host) { ((TFruityPlugHost *)host)->LockMix(); } 277 | 278 | void host_unlock_mix(void *host) { ((TFruityPlugHost *)host)->UnlockMix(); } 279 | 280 | void host_lock_plugin(void *host, TPluginTag tag) { 281 | ((TFruityPlugHost *)host)->LockPlugin(tag); 282 | } 283 | 284 | void host_unlock_plugin(void *host, TPluginTag tag) { 285 | ((TFruityPlugHost *)host)->UnlockPlugin(tag); 286 | } 287 | 288 | void host_suspend_out(void *host) { 289 | ((TFruityPlugHost *)host)->SuspendOutput(); 290 | } 291 | 292 | void host_resume_out(void *host) { ((TFruityPlugHost *)host)->ResumeOutput(); } 293 | 294 | TIOBuffer host_get_input_buf(void *host, TPluginTag tag, intptr_t offset) { 295 | TIOBuffer buf = { 296 | 0, 297 | 0, 298 | }; 299 | ((TFruityPlugHost *)host)->GetInBuffer(tag, offset, &buf); 300 | 301 | return buf; 302 | } 303 | 304 | TIOBuffer host_get_output_buf(void *host, TPluginTag tag, intptr_t offset) { 305 | TIOBuffer buf = { 306 | 0, 307 | 0, 308 | }; 309 | ((TFruityPlugHost *)host)->GetOutBuffer(tag, offset, &buf); 310 | 311 | return buf; 312 | } 313 | 314 | void *host_get_insert_buf(void *host, TPluginTag tag, intptr_t offset) { 315 | return ((TFruityPlugHost *)host)->GetInsBuffer(tag, (int)offset); 316 | } 317 | 318 | void *host_get_mix_buf(void *host, intptr_t offset) { 319 | return ((TFruityPlugHost *)host)->GetMixBuffer((int)offset); 320 | } 321 | 322 | void *host_get_send_buf(void *host, intptr_t offset) { 323 | return ((TFruityPlugHost *)host)->GetSendBuffer(offset); 324 | } 325 | 326 | bool prompt_show(void *host, int x, int y, char *msg, char *result, 327 | int &color) { 328 | 329 | return ((TFruityPlugHost *)host)->PromptEdit(x, y, msg, result, color); 330 | } 331 | 332 | // Host voice-related 333 | 334 | intptr_t host_on_voice_event(void *host, intptr_t tag, FlMessage message) { 335 | return ((TFruityPlugHost *)host) 336 | ->Voice_ProcessEvent((TOutVoiceHandle)tag, message.id, message.index, 337 | message.value); 338 | } 339 | 340 | void host_kill_voice(void *host, intptr_t tag) { 341 | ((TFruityPlugHost *)host)->Voice_Kill(tag, true); 342 | } 343 | 344 | void host_release_voice(void *host, intptr_t tag) { 345 | ((TFruityPlugHost *)host)->Voice_Release(tag); 346 | } 347 | 348 | intptr_t host_trig_out_voice(void *host, Params *params, int index, 349 | intptr_t tag) { 350 | return (intptr_t)((TFruityPlugHost *)host) 351 | ->TriggerOutputVoice((TVoiceParams *)params, (intptr_t)index, tag); 352 | } 353 | 354 | void host_release_out_voice(void *host, intptr_t tag) { 355 | ((TFruityPlugHost *)host)->OutputVoice_Release((TOutVoiceHandle)tag); 356 | } 357 | 358 | void host_kill_out_voice(void *host, intptr_t tag) { 359 | ((TFruityPlugHost *)host)->OutputVoice_Kill((TOutVoiceHandle)tag); 360 | } 361 | 362 | intptr_t host_on_out_voice_event(void *host, intptr_t tag, FlMessage message) { 363 | return ((TFruityPlugHost *)host) 364 | ->OutputVoice_ProcessEvent((TOutVoiceHandle)tag, message.id, 365 | message.index, message.value); 366 | } 367 | -------------------------------------------------------------------------------- /examples/simple.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | #[cfg(unix)] 3 | use std::fs::OpenOptions; 4 | use std::io::{self, Read}; 5 | use std::sync::{Arc, Mutex, Once}; 6 | use std::time::{SystemTime, UNIX_EPOCH}; 7 | 8 | use bincode; 9 | use log::{error, info, trace, LevelFilter}; 10 | use serde::{Deserialize, Serialize}; 11 | #[cfg(windows)] 12 | use simple_logging; 13 | #[cfg(unix)] 14 | use simplelog::{ConfigBuilder, WriteLogger}; 15 | 16 | use fpsdk::host::{self, prompt::PromptBuilder, Event, GetName, Host, OutVoicer, Voicer}; 17 | use fpsdk::plugin::message; 18 | use fpsdk::plugin::{self, Info, InfoBuilder, Plugin, StateReader, StateWriter}; 19 | use fpsdk::voice::{self, ReceiveVoiceHandler, SendVoiceHandler, Voice}; 20 | use fpsdk::{ 21 | create_plugin, AsRawPtr, FromRawPtr, MessageBoxFlags, MidiMessage, Note, Notes, NotesFlags, 22 | ProcessParamFlags, TimeFormat, ValuePtr, 23 | }; 24 | 25 | static ONCE: Once = Once::new(); 26 | const LOG_PATH: &str = "simple.log"; 27 | 28 | #[derive(Debug)] 29 | struct Simple { 30 | host: Host, 31 | tag: plugin::Tag, 32 | param_names: Vec, 33 | state: State, 34 | voice_handler: SimpleVoiceHandler, 35 | } 36 | 37 | #[derive(Debug, Default, Deserialize, Serialize)] 38 | struct State { 39 | _time: u64, 40 | _param_1: f64, 41 | _param_2: i64, 42 | } 43 | 44 | impl Plugin for Simple { 45 | fn new(host: Host, tag: plugin::Tag) -> Self { 46 | init_log(); 47 | 48 | info!("init plugin with tag {}", tag); 49 | 50 | let voice_h = host.voice_handler(); 51 | let out_voice_h = host.out_voice_handler(); 52 | 53 | Self { 54 | voice_handler: SimpleVoiceHandler::new(voice_h, out_voice_h), 55 | host, 56 | tag, 57 | param_names: vec![ 58 | "Parameter 1".into(), 59 | "Parameter 2".into(), 60 | "Parameter 3".into(), 61 | ], 62 | state: Default::default(), 63 | } 64 | } 65 | 66 | fn info(&self) -> Info { 67 | info!("plugin {} will return info", self.tag); 68 | 69 | InfoBuilder::new_full_gen("Simple", "Simple", self.param_names.len() as u32) 70 | // InfoBuilder::new_effect("Simple", "Simple", self.param_names.len() as u32) 71 | .want_new_tick() 72 | .with_out_ctrls(1) 73 | .with_out_voices(1) 74 | // .loop_out() 75 | // Looks like MIDI out doesn't work :( 76 | // https://forum.image-line.com/viewtopic.php?f=100&t=199371 77 | // https://forum.image-line.com/viewtopic.php?f=100&t=199258 78 | // .midi_out() 79 | .build() 80 | } 81 | 82 | fn save_state(&mut self, writer: StateWriter) { 83 | let now = SystemTime::now(); 84 | let time = now.duration_since(UNIX_EPOCH).expect("").as_secs(); 85 | self.state._time = time; 86 | self.state._param_1 = time as f64 * 0.001; 87 | self.state._param_2 = time as i64 / 2; 88 | match bincode::serialize_into(writer, &self.state) { 89 | Ok(_) => info!("state {:?} saved", self.state), 90 | Err(e) => error!("error serializing state {}", e), 91 | } 92 | } 93 | 94 | fn load_state(&mut self, mut reader: StateReader) { 95 | let mut buf = [0; std::mem::size_of::()]; 96 | reader 97 | .read(&mut buf) 98 | .and_then(|_| { 99 | bincode::deserialize::(&buf).map_err(|e| { 100 | io::Error::new( 101 | io::ErrorKind::Other, 102 | format!("error deserializing value {}", e), 103 | ) 104 | }) 105 | }) 106 | .and_then(|value| { 107 | self.state = value; 108 | Ok(info!("read state {:?}", self.state)) 109 | }) 110 | .unwrap_or_else(|e| error!("error reading value from state {}", e)); 111 | } 112 | 113 | fn on_message(&mut self, message: host::Message) -> Box { 114 | self.host.on_message( 115 | self.tag, 116 | message::DebugLogMsg(format!("{} got message from host: {:?}", self.tag, message)), 117 | ); 118 | 119 | if let host::Message::SetEnabled(enabled) = message { 120 | self.on_set_enabled(enabled, message); 121 | } 122 | 123 | // self.host.midi_out(self.tag, MidiMessage { 124 | // status: 0x90, 125 | // data1: 60, 126 | // data2: 100, 127 | // port: 2, 128 | // }); 129 | 130 | Box::new(0) 131 | } 132 | 133 | fn name_of(&self, message: GetName) -> String { 134 | info!("{} host asks name of {:?}", self.tag, message); 135 | 136 | match message { 137 | GetName::Param(index) => self.param_names[index].clone(), 138 | _ => "What?".into(), 139 | } 140 | } 141 | 142 | fn process_event(&mut self, event: Event) { 143 | info!("{} host sends event {:?}", self.tag, event); 144 | } 145 | 146 | fn tick(&mut self) { 147 | // assign to itself to see it in log 148 | self.host.on_controller(self.tag, 0, 12345_u16); 149 | } 150 | 151 | fn idle(&mut self) { 152 | trace!("{} idle", self.tag); 153 | } 154 | 155 | // looks like it doesn't work in SDK 156 | fn loop_in(&mut self, message: ValuePtr) { 157 | trace!("{} loop_in", message.get::()); 158 | } 159 | 160 | fn process_param( 161 | &mut self, 162 | index: usize, 163 | value: ValuePtr, 164 | flags: ProcessParamFlags, 165 | ) -> Box { 166 | info!( 167 | "{} process param: index {}, value {}, flags {:?}", 168 | self.tag, 169 | index, 170 | value.get::(), 171 | flags 172 | ); 173 | 174 | if flags.contains(ProcessParamFlags::INTERNAL_CTRL | ProcessParamFlags::UPDATE_VALUE) { 175 | // will work if assigned to itself 176 | info!("internall control value {}", value.get::()); 177 | } 178 | 179 | Box::new(0) 180 | } 181 | 182 | fn midi_in(&mut self, message: MidiMessage) { 183 | trace!("receive MIDI message {:?}", message); 184 | } 185 | 186 | fn render(&mut self, input: &[[f32; 2]], output: &mut [[f32; 2]]) { 187 | if self.voice_handler.voices.len() < 1 { 188 | // consider it an effect 189 | input.iter().zip(output).for_each(|(inp, outp)| { 190 | outp[0] = inp[0] * 0.25; 191 | outp[1] = inp[1] * 0.25; 192 | }); 193 | } 194 | } 195 | 196 | fn voice_handler(&mut self) -> Option<&mut dyn ReceiveVoiceHandler> { 197 | Some(&mut self.voice_handler) 198 | } 199 | } 200 | 201 | impl Simple { 202 | fn on_set_enabled(&mut self, enabled: bool, message: host::Message) { 203 | self.add_notes(); 204 | self.log_selection(); 205 | self.say_hello_hint(); 206 | 207 | if enabled { 208 | self.on_enabled(message); 209 | } 210 | 211 | self.host.on_parameter( 212 | self.tag, 213 | 0, 214 | ValuePtr::from_raw_ptr(0.123456789_f32.as_raw_ptr()), 215 | ); 216 | } 217 | 218 | fn on_enabled(&mut self, message: host::Message) { 219 | self.show_annoying_message(); 220 | if let Some(prompt) = PromptBuilder::default() 221 | .with_color() 222 | .show(&mut self.host, "Hello".to_string()) 223 | { 224 | info!("prompt value is {}", prompt.value); 225 | info!("prompt color is {:06x}", prompt.color.unwrap()); 226 | }; 227 | // self.host.on_message(self.tag, message::ActivateMidi); 228 | self.host.loop_out( 229 | self.tag, 230 | ValuePtr::from_raw_ptr(format!("{:?}", message).as_raw_ptr()), 231 | ); 232 | } 233 | 234 | fn add_notes(&mut self) { 235 | let notes = Notes { 236 | notes: vec![ 237 | Note { 238 | position: 0, 239 | length: 384, 240 | pan: 0, 241 | vol: 100, 242 | note: 60, 243 | color: 0, 244 | pitch: 0, 245 | mod_x: 1.0, 246 | mod_y: 1.0, 247 | }, 248 | Note { 249 | position: 384, 250 | length: 384, 251 | pan: 0, 252 | vol: 100, 253 | note: 62, 254 | color: 0, 255 | pitch: 0, 256 | mod_x: 1.0, 257 | mod_y: 1.0, 258 | }, 259 | Note { 260 | position: 768, 261 | length: 384, 262 | pan: 0, 263 | vol: 100, 264 | note: 64, 265 | color: 0, 266 | pitch: 0, 267 | mod_x: 1.0, 268 | mod_y: 1.0, 269 | }, 270 | ], 271 | flags: NotesFlags::EMPTY_FIRST, 272 | pattern: None, 273 | channel: None, 274 | }; 275 | self.host 276 | .on_message(self.tag, message::AddToPianoRoll(notes)); 277 | } 278 | 279 | fn show_annoying_message(&mut self) { 280 | self.host.on_message( 281 | self.tag, 282 | message::MessageBox( 283 | "Message".to_string(), 284 | "This message is shown when plugin is enabled. \ 285 | Feel free to comment this out if it annoys you." 286 | .to_string(), 287 | MessageBoxFlags::OK | MessageBoxFlags::ICONINFORMATION, 288 | ), 289 | ); 290 | } 291 | 292 | fn log_selection(&mut self) { 293 | let selection = self 294 | .host 295 | .on_message(self.tag, message::GetSelTime(TimeFormat::Beats)); 296 | self.host.on_message( 297 | self.tag, 298 | message::DebugLogMsg(format!( 299 | "current selection or full song range is: {:?}", 300 | selection 301 | )), 302 | ); 303 | } 304 | 305 | fn say_hello_hint(&mut self) { 306 | self.host.on_hint(self.tag, "^c Hello".to_string()); 307 | } 308 | } 309 | 310 | #[derive(Debug)] 311 | struct SimpleVoiceHandler { 312 | voices: HashMap, 313 | out_handler: SimpleOutVoiceHandler, 314 | send_handler: Arc>, 315 | send_out_handler: Arc>, 316 | } 317 | 318 | impl SimpleVoiceHandler { 319 | fn new(send_handler: Arc>, send_out_handler: Arc>) -> Self { 320 | Self { 321 | voices: HashMap::new(), 322 | out_handler: SimpleOutVoiceHandler::default(), 323 | send_handler, 324 | send_out_handler, 325 | } 326 | } 327 | } 328 | 329 | impl SimpleVoiceHandler { 330 | fn log_velocity(&self, tag: voice::Tag) { 331 | let mut send_handler = self.send_handler.lock().unwrap(); 332 | if let Some(velocity) = send_handler.on_event(tag, voice::Event::GetVelocity) { 333 | trace!("get velocity {} for voice {}", velocity.get::(), tag); 334 | } 335 | } 336 | 337 | fn log_color(&self, tag: voice::Tag) { 338 | let mut send_handler = self.send_handler.lock().unwrap(); 339 | if let Some(color) = send_handler.on_event(tag, voice::Event::GetColor) { 340 | trace!("get color {} for voice {}", color.get::(), tag); 341 | } 342 | } 343 | } 344 | 345 | impl ReceiveVoiceHandler for SimpleVoiceHandler { 346 | fn trigger(&mut self, params: voice::Params, tag: voice::Tag) -> &mut dyn Voice { 347 | let voice = SimpleVoice::new(params.clone(), tag); 348 | trace!("trigger voice {:?}", voice); 349 | self.voices.insert(tag, voice); 350 | 351 | let mut send_out_handler = self.send_out_handler.lock().unwrap(); 352 | 353 | send_out_handler.trigger(params, 0, tag); 354 | 355 | self.log_velocity(tag); 356 | self.log_color(tag); 357 | 358 | self.voices.get_mut(&tag).unwrap() 359 | } 360 | 361 | fn release(&mut self, tag: voice::Tag) { 362 | trace!("release voice {:?}", self.voices.get(&tag)); 363 | self.send_out_handler.lock().unwrap().release(tag); 364 | trace!("send kill voice {}", tag); 365 | self.send_handler.lock().unwrap().kill(tag); 366 | } 367 | 368 | fn kill(&mut self, tag: voice::Tag) { 369 | trace!("host wants to kill voice with tag {}", tag); 370 | trace!("kill voice {:?}", self.voices.remove(&tag)); 371 | trace!( 372 | "remaining voices count {}, {:?}", 373 | self.voices.len(), 374 | self.voices 375 | ); 376 | } 377 | 378 | fn on_event(&mut self, tag: voice::Tag, event: voice::Event) -> Box { 379 | trace!("event {:?} for voice {:?}", event, self.voices.get(&tag)); 380 | Box::new(0) 381 | } 382 | 383 | fn out_handler(&mut self) -> Option<&mut dyn SendVoiceHandler> { 384 | Some(&mut self.out_handler) 385 | } 386 | } 387 | 388 | #[derive(Debug)] 389 | struct SimpleVoice { 390 | tag: voice::Tag, 391 | params: voice::Params, 392 | } 393 | 394 | impl SimpleVoice { 395 | pub fn new(params: voice::Params, tag: voice::Tag) -> Self { 396 | Self { tag, params } 397 | } 398 | } 399 | 400 | impl Voice for SimpleVoice { 401 | fn tag(&self) -> voice::Tag { 402 | self.tag 403 | } 404 | } 405 | 406 | #[derive(Debug, Default)] 407 | struct SimpleOutVoiceHandler; 408 | 409 | impl SendVoiceHandler for SimpleOutVoiceHandler { 410 | fn kill(&mut self, tag: voice::Tag) { 411 | trace!("kill out voice with tag {}", tag); 412 | } 413 | 414 | fn on_event(&mut self, tag: voice::Tag, event: voice::Event) -> Option { 415 | trace!("event {:?} on out voice {}", event, tag); 416 | None 417 | } 418 | } 419 | 420 | fn init_log() { 421 | ONCE.call_once(|| { 422 | _init_log(); 423 | info!("init log"); 424 | }); 425 | } 426 | 427 | #[cfg(windows)] 428 | fn _init_log() { 429 | simple_logging::log_to_file(LOG_PATH, LevelFilter::Trace).unwrap(); 430 | } 431 | 432 | #[cfg(unix)] 433 | fn _init_log() { 434 | // the file is created at FL's resources root directory 435 | // for macOS it's /Applications/FL Studio 20.app/Contents/Resources/FL 436 | // for Windows it's :\Program Files\Image-Line\FL Studio 20 437 | let file = OpenOptions::new() 438 | .append(true) 439 | .create(true) 440 | .open(LOG_PATH) 441 | .unwrap(); 442 | let config = ConfigBuilder::new().set_time_to_local(true).build(); 443 | let _ = WriteLogger::init(LevelFilter::Trace, config, file).unwrap(); 444 | } 445 | 446 | create_plugin!(Simple); 447 | -------------------------------------------------------------------------------- /src/plugin.rs: -------------------------------------------------------------------------------- 1 | //! Plugin related stuff. 2 | 3 | pub mod message; 4 | 5 | use std::ffi::CString; 6 | use std::io::{self, Read, Write}; 7 | use std::os::raw::{c_char, c_int, c_void}; 8 | use std::panic::RefUnwindSafe; 9 | 10 | use hresult::HRESULT; 11 | use log::{debug, error}; 12 | 13 | use crate::host::{self, Event, GetName, Host}; 14 | use crate::voice::ReceiveVoiceHandler; 15 | use crate::{ 16 | alloc_real_cstr, intptr_t, AsRawPtr, FlMessage, MidiMessage, ProcessParamFlags, ValuePtr, 17 | CURRENT_SDK_VERSION, 18 | }; 19 | 20 | crate::implement_tag!(); 21 | 22 | /// Exposes your plugin from DLL. Accepts type name as input. The type should implement 23 | /// [`Plugin`](plugin/trait.Plugin.html) trait. 24 | #[macro_export] 25 | macro_rules! create_plugin { 26 | ($pl:ty) => { 27 | use std::os::raw::c_void; 28 | 29 | extern "C" { 30 | fn create_plug_instance_c( 31 | host: *mut c_void, 32 | tag: $crate::intptr_t, 33 | adapter: *mut c_void, 34 | ) -> *mut c_void; 35 | } 36 | 37 | #[allow(non_snake_case)] 38 | #[no_mangle] 39 | pub unsafe extern "C" fn CreatePlugInstance( 40 | host: *mut c_void, 41 | tag: $crate::intptr_t, 42 | ) -> *mut c_void { 43 | let ho = $crate::host::Host::new(host); 44 | let plugin = <$pl as $crate::plugin::Plugin>::new( 45 | ho, 46 | $crate::plugin::Tag(tag as $crate::intptr_t), 47 | ); 48 | let adapter = $crate::plugin::PluginAdapter(Box::new(plugin)); 49 | create_plug_instance_c(host, tag, Box::into_raw(Box::new(adapter)) as *mut c_void) 50 | } 51 | }; 52 | } 53 | 54 | /// This trait must be implemented for your plugin. 55 | pub trait Plugin: std::fmt::Debug + RefUnwindSafe + Send + Sync + 'static { 56 | /// Initializer. 57 | fn new(host: Host, tag: Tag) -> Self 58 | where 59 | Self: Sized; 60 | /// Get plugin [`Info`](struct.Info.html). 61 | fn info(&self) -> Info; 62 | /// Save plugin's state. 63 | fn save_state(&mut self, writer: StateWriter); 64 | /// Load plugin's state. 65 | fn load_state(&mut self, reader: StateReader); 66 | /// The host calls this function to request something that isn't done in a specialized 67 | /// function. 68 | /// 69 | /// See [`host::Message`](../host/enum.Message.html) for possible messages. 70 | /// 71 | /// Can be called from GUI or mixer threads. 72 | fn on_message(&mut self, message: host::Message<'_>) -> Box; 73 | /// This is called when the host wants to know a text representation of some value. 74 | /// 75 | /// Can be called from GUI or mixer threads. 76 | fn name_of(&self, value: GetName) -> String; 77 | /// Process an event sent by the host. 78 | /// 79 | /// Can be called from GUI or mixer threads. 80 | fn process_event(&mut self, _event: Event) {} 81 | /// Something has to be done concerning a parameter. What exactly has to be done is explained 82 | /// by the `flags` parameter (see [`ProcessParamFlags`](../struct.ProcessParamFlags.html)). 83 | /// 84 | /// - `index` - the index of the parameter. 85 | /// - `value` - the (new) value of the parameter. 86 | /// - `flags` - describes what needs to be done to the parameter. It can be a combination of 87 | /// several flags. 88 | /// 89 | /// If 90 | /// [`ProcessParamFlags::GET_VALUE`]( 91 | /// ../struct.ProcessParamFlags.html#associatedconstant.GET_VALUE) is specified in `flags`, the 92 | /// result has to be the value of the parameter. 93 | /// 94 | /// Can be called from GUI or mixer threads. 95 | fn process_param( 96 | &mut self, 97 | _index: usize, 98 | _value: ValuePtr, 99 | _flags: ProcessParamFlags, 100 | ) -> Box { 101 | Box::new(0) 102 | } 103 | /// This function is called continuously. It allows the plugin to perform certain tasks that 104 | /// are not time-critical and which do not take up a lot of time either. For example, in this 105 | /// function you may show a hint message when the mouse moves over a control in the editor. 106 | /// 107 | /// Called from GUI thread. 108 | fn idle(&mut self) {} 109 | /// Gets called before a new tick is mixed (not played), if the plugin added 110 | /// [`InfoBuilder::want_new_tick`](../plugin/struct.InfoBuilder.html#method.want_new_tick) into 111 | /// [`Info`](../struct.Info.html). 112 | /// 113 | /// Internal controller plugins should call 114 | /// [`host::Host::on_controller`](../host/struct.Host.html#method.on_controller) from 115 | /// here. 116 | /// 117 | /// Called from mixer thread. 118 | fn tick(&mut self) {} 119 | /// This is called before a new midi tick is played (not mixed). 120 | /// 121 | /// Can be called from GUI or mixer threads. 122 | fn midi_tick(&mut self) {} 123 | /// The processing function. The input buffer is empty for generator plugins. 124 | /// 125 | /// The buffers are in interlaced 32Bit float stereo format. 126 | /// 127 | /// Called from mixer thread. 128 | fn render(&mut self, _input: &[[f32; 2]], _output: &mut [[f32; 2]]) {} 129 | /// Get [`ReceiveVoiceHandler`](../voice/trait.ReceiveVoiceHandler.html). 130 | /// 131 | /// Implement this method if you make a generator plugin. 132 | fn voice_handler(&mut self) -> Option<&mut dyn ReceiveVoiceHandler> { 133 | None 134 | } 135 | /// The host will call this when there's new MIDI data available. This function is only called 136 | /// when the plugin has called the 137 | /// [`host::Host::on_message`](../host/struct.Host.html#method.on_message) with 138 | /// [`plugin::message::WantMidiInput`](../plugin/message/struct.WantMidiInput.html) and 139 | /// value set to `true`. 140 | /// 141 | /// Can be called from GUI or mixer threads. 142 | fn midi_in(&mut self, _message: MidiMessage) {} 143 | /// **MAY NOT WORK** 144 | /// 145 | /// This gets called with a new buffered message to the plugin itself. 146 | fn loop_in(&mut self, _message: ValuePtr) {} 147 | } 148 | 149 | /// This structure holds some information about the plugin that is used by the host. It is the 150 | /// same for all instances of the same plugin. 151 | /// 152 | /// It's not supposed to be used directly, instantiate it using 153 | /// [`InfoBuilder`](struct.InfoBuilder.html). 154 | #[repr(C)] 155 | #[derive(Debug)] 156 | pub struct Info { 157 | /// This has to be the version of the SDK used to create the plugin. This value is 158 | /// available in the constant CurrentSDKVersion. 159 | pub sdk_version: u32, 160 | /// The name of the plugin dll, without the extension (.dll). 161 | pub long_name: *mut c_char, 162 | /// Short plugin name, to be used in labels to tell the user which plugin he is working 163 | /// with. 164 | pub short_name: *mut c_char, 165 | flags: u32, 166 | /// The number of parameters for this plugin. 167 | pub num_params: u32, 168 | /// Preferred (default) maximum polyphony (FL Studio manages the polyphony) (0=infinite). 169 | pub def_poly: u32, 170 | /// Number of internal output controllers. 171 | pub num_out_ctrls: u32, 172 | /// Number of internal output voices. 173 | pub num_out_voices: u32, 174 | } 175 | 176 | /// Use this to instantiate [`Info`](struct.Info.html) 177 | #[derive(Clone, Debug)] 178 | pub struct InfoBuilder { 179 | sdk_version: u32, 180 | long_name: String, 181 | short_name: String, 182 | flags: u32, 183 | num_params: u32, 184 | def_poly: u32, 185 | num_out_ctrls: u32, 186 | num_out_voices: u32, 187 | } 188 | 189 | impl InfoBuilder { 190 | /// Initializer for an effect. 191 | /// 192 | /// This is the most basic type. 193 | pub fn new_effect(long_name: &str, short_name: &str, num_params: u32) -> Self { 194 | Self { 195 | sdk_version: CURRENT_SDK_VERSION, 196 | long_name: long_name.to_string(), 197 | short_name: short_name.to_string(), 198 | flags: 0, 199 | num_params, 200 | def_poly: 0, 201 | num_out_ctrls: 0, 202 | num_out_voices: 0, 203 | } 204 | .new_voice_params() 205 | } 206 | 207 | /// Initializer for a full standalone generator. 208 | /// 209 | /// This is a combination of [`generator`](struct.InfoBuilder.html#method.generator) and 210 | /// [`note_input`](struct.InfoBuilder.html#method.get_note_input). 211 | pub fn new_full_gen(long_name: &str, short_name: &str, num_params: u32) -> Self { 212 | InfoBuilder::new_effect(long_name, short_name, num_params) 213 | .generator() 214 | .get_note_input() 215 | } 216 | 217 | /// Initializer for a purely visual plugin, that doesn't process any audio data. 218 | /// 219 | /// It's a basic plugin with [`no_process`](struct.InfoBuilder.html#method.no_process) enabled. 220 | pub fn new_visual(long_name: &str, short_name: &str, num_params: u32) -> Self { 221 | InfoBuilder::new_effect(long_name, short_name, num_params).no_process() 222 | } 223 | 224 | /// Set prefered (default) maximum polyphony. 225 | pub fn with_poly(mut self, poly: u32) -> Self { 226 | self.def_poly = poly; 227 | self 228 | } 229 | 230 | /// Set number of internal output controllers. 231 | pub fn with_out_ctrls(mut self, out_ctrls: u32) -> Self { 232 | self.num_out_ctrls = out_ctrls; 233 | self 234 | } 235 | 236 | /// Set number of internal output voices. 237 | pub fn with_out_voices(mut self, out_voices: u32) -> Self { 238 | self.num_out_voices = out_voices; 239 | self 240 | } 241 | 242 | /// The plugin is a generator (as opposed to an effect). 243 | pub fn generator(mut self) -> Self { 244 | self.flags |= 1; 245 | self 246 | } 247 | 248 | /// The plugin will use a sample that the user loads into the plugin's channel. 249 | pub fn get_chan_custom_shape(mut self) -> Self { 250 | self.flags |= 1 << 3; 251 | self 252 | } 253 | 254 | /// The plugin reacts to note events. 255 | pub fn get_note_input(mut self) -> Self { 256 | self.flags |= 1 << 4; 257 | self 258 | } 259 | 260 | /// The plugin will be notified on each tick and be able to control params (like a built-in 261 | /// MIDI controller). 262 | pub fn want_new_tick(mut self) -> Self { 263 | self.flags |= 1 << 5; 264 | self 265 | } 266 | 267 | /// The plugin won't process buffers at all 268 | /// ([`want_new_tick`](struct.InfoBuilder.html#method.want_new_tick), or special visual plugins 269 | /// (Fruity NoteBook)) 270 | pub fn no_process(mut self) -> Self { 271 | self.flags |= 1 << 6; 272 | self 273 | } 274 | 275 | /// The plugin's editor window should be shown inside the channel properties window. 276 | pub fn no_window(mut self) -> Self { 277 | self.flags |= 1 << 10; 278 | self 279 | } 280 | 281 | /// (not used yet) The plugin doesn't provide its own interface, but relies on the host to 282 | /// create one. 283 | pub fn interfaceless(mut self) -> Self { 284 | self.flags |= 1 << 11; 285 | self 286 | } 287 | 288 | /// (not used yet) The plugin supports timewarps, that is can be told to change the playing 289 | /// position in a voice (direct from disk music tracks, ...). 290 | pub fn time_warp(mut self) -> Self { 291 | self.flags |= 1 << 13; 292 | self 293 | } 294 | 295 | /// The plugin will send MIDI out messages. Only plugins specifying this option will be enabled 296 | /// when rendering to a midi file. 297 | pub fn midi_out(mut self) -> Self { 298 | self.flags |= 1 << 14; 299 | self 300 | } 301 | 302 | /// The plugin is a demo version. Practically this means the host won't save its automation. 303 | pub fn demo_version(mut self) -> Self { 304 | self.flags |= 1 << 15; 305 | self 306 | } 307 | 308 | /// The plugin has access to the send tracks, so it can't be dropped into a send track or into 309 | /// the master. 310 | pub fn can_send(mut self) -> Self { 311 | self.flags |= 1 << 16; 312 | self 313 | } 314 | 315 | /// The plugin will send delayed messages to itself (will require the internal sync clock to be 316 | /// enabled). 317 | pub fn loop_out(mut self) -> Self { 318 | self.flags |= 1 << 17; 319 | self 320 | } 321 | 322 | /// This plugin as a generator will use the sample loaded in its parent channel (see 323 | /// [`host::Message::ChanSampleChanged`]( 324 | /// ../host/enum.Message.html#variant.ChanSampleChanged)). 325 | pub fn get_chan_sample(mut self) -> Self { 326 | self.flags |= 1 << 19; 327 | self 328 | } 329 | 330 | /// Fit to time selector will appear in channel settings window (see 331 | /// [`host::Message::SetFitTime`](../host/enum.Message.html#variant.SetFitTime)). 332 | pub fn want_fit_time(mut self) -> Self { 333 | self.flags |= 1 << 20; 334 | self 335 | } 336 | 337 | /// This must be used (for new plugins). It tells the host to use floating point values for 338 | /// Pitch and Pan in [`VoiceParams`](struct.VoiceParams.html). 339 | fn new_voice_params(mut self) -> Self { 340 | self.flags |= 1 << 21; 341 | self 342 | } 343 | 344 | /// Plugin can't be smart disabled. 345 | pub fn cant_smart_disable(mut self) -> Self { 346 | self.flags |= 1 << 23; 347 | self 348 | } 349 | 350 | /// Plugin wants a settings button on the titlebar (mainly for the wrapper). 351 | pub fn want_settings_button(mut self) -> Self { 352 | self.flags |= 1 << 24; 353 | self 354 | } 355 | 356 | /// Finish builder and init [`Info`](struct.Info.html) 357 | pub fn build(self) -> Info { 358 | let log_err = |e| { 359 | error!("{}", e); 360 | panic!(); 361 | }; 362 | let long_name = CString::new(self.long_name) 363 | .unwrap_or_else(log_err) 364 | .into_raw(); 365 | let short_name = CString::new(self.short_name) 366 | .unwrap_or_else(log_err) 367 | .into_raw(); 368 | 369 | Info { 370 | sdk_version: self.sdk_version, 371 | long_name: unsafe { alloc_real_cstr(long_name) }, 372 | short_name: unsafe { alloc_real_cstr(short_name) }, 373 | flags: self.flags, 374 | num_params: self.num_params, 375 | def_poly: self.def_poly, 376 | num_out_ctrls: self.num_out_ctrls, 377 | num_out_voices: self.num_out_voices, 378 | } 379 | } 380 | } 381 | 382 | /// State reader. 383 | pub struct StateReader(pub(crate) *mut c_void); 384 | 385 | impl Read for StateReader { 386 | /// Read state into buffer. 387 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 388 | let mut read = 0u32; 389 | let buf_ptr = buf.as_mut_ptr(); 390 | let res = unsafe { istream_read(self.0, buf_ptr, buf.len() as u32, &mut read) }; 391 | debug!("StateReader read {} bytes", read); 392 | check_hresult( 393 | HRESULT::from(res), 394 | read as usize, 395 | "Error reading from IStream", 396 | ) 397 | } 398 | } 399 | 400 | extern "C" { 401 | fn istream_read(istream: *mut c_void, data: *mut u8, size: u32, read: *mut u32) -> i32; 402 | } 403 | 404 | /// State writer. 405 | pub struct StateWriter(pub(crate) *mut c_void); 406 | 407 | impl Write for StateWriter { 408 | fn flush(&mut self) -> io::Result<()> { 409 | Ok(()) 410 | } 411 | 412 | /// Write state from buffer. 413 | fn write(&mut self, buf: &[u8]) -> io::Result { 414 | let mut write = 0u32; 415 | let buf_ptr = buf.as_ptr(); 416 | let res = unsafe { istream_write(self.0, buf_ptr, buf.len() as u32, &mut write) }; 417 | check_hresult( 418 | HRESULT::from(res), 419 | write as usize, 420 | "Error writing to IStream", 421 | ) 422 | } 423 | } 424 | 425 | extern "C" { 426 | fn istream_write(istream: *mut c_void, data: *const u8, size: u32, write: *mut u32) -> i32; 427 | } 428 | 429 | fn check_hresult(result: HRESULT, read: usize, error_msg: &str) -> io::Result { 430 | if !result.is_success() { 431 | return Err(io::Error::new(io::ErrorKind::Other, error_msg)); 432 | } 433 | 434 | Ok(read) 435 | } 436 | 437 | /// Type wraps `Plugin` trait object to simplify sharing with C/C++. 438 | /// 439 | /// This is for internal usage only and shouldn't be used directly. 440 | #[doc(hidden)] 441 | #[derive(Debug)] 442 | pub struct PluginAdapter(pub Box); 443 | 444 | /// [`Plugin::info`](trait.Plugin.html#tymethod.info) FFI. 445 | /// 446 | /// It supposed to be used internally. Don't use it. 447 | /// 448 | /// # Safety 449 | /// 450 | /// Unsafe 451 | #[doc(hidden)] 452 | #[no_mangle] 453 | unsafe extern "C" fn plugin_info(adapter: *mut PluginAdapter) -> *mut Info { 454 | Box::into_raw(Box::new((*adapter).0.info())) 455 | } 456 | 457 | /// [`Plugin::on_message`](trait.Plugin.html#tymethod.on_message) FFI. 458 | /// 459 | /// It supposed to be used internally. Don't use it. 460 | /// 461 | /// # Safety 462 | /// 463 | /// Unsafe 464 | #[doc(hidden)] 465 | #[no_mangle] 466 | unsafe extern "C" fn plugin_dispatcher( 467 | adapter: *mut PluginAdapter, 468 | message: FlMessage, 469 | ) -> intptr_t { 470 | (*adapter).0.on_message(message.into()).as_raw_ptr() 471 | } 472 | 473 | /// [`Plugin::name_of`](trait.Plugin.html#tymethod.name_of) FFI. 474 | /// 475 | /// It supposed to be used internally. Don't use it. 476 | /// 477 | /// # Safety 478 | /// 479 | /// Unsafe 480 | #[doc(hidden)] 481 | #[no_mangle] 482 | unsafe extern "C" fn plugin_name_of( 483 | adapter: *const PluginAdapter, 484 | message: FlMessage, 485 | ) -> *mut c_char { 486 | let name = CString::new((*adapter).0.name_of(message.into())).unwrap_or_else(|e| { 487 | error!("{}", e); 488 | panic!(); 489 | }); 490 | name.into_raw() 491 | } 492 | 493 | /// [`Plugin::process_event`](trait.Plugin.html#tymethod.process_event) FFI. 494 | /// 495 | /// It supposed to be used internally. Don't use it. 496 | /// 497 | /// # Safety 498 | /// 499 | /// Unsafe 500 | #[doc(hidden)] 501 | #[no_mangle] 502 | unsafe extern "C" fn plugin_process_event(adapter: *mut PluginAdapter, event: FlMessage) -> c_int { 503 | (*adapter).0.process_event(event.into()); 504 | 0 505 | } 506 | 507 | /// [`Plugin::process_param`](trait.Plugin.html#tymethod.process_param) FFI. 508 | /// 509 | /// It supposed to be used internally. Don't use it. 510 | /// 511 | /// # Safety 512 | /// 513 | /// Unsafe 514 | #[doc(hidden)] 515 | #[no_mangle] 516 | unsafe extern "C" fn plugin_process_param( 517 | adapter: *mut PluginAdapter, 518 | message: FlMessage, 519 | ) -> intptr_t { 520 | (*adapter) 521 | .0 522 | .process_param( 523 | message.id as usize, 524 | ValuePtr(message.index), 525 | ProcessParamFlags::from_bits_truncate(message.value), 526 | ) 527 | .as_raw_ptr() 528 | } 529 | 530 | /// [`Plugin::idle`](trait.Plugin.html#method.idle) FFI. 531 | /// 532 | /// It supposed to be used internally. Don't use it. 533 | /// 534 | /// # Safety 535 | /// 536 | /// Unsafe 537 | #[doc(hidden)] 538 | #[no_mangle] 539 | unsafe extern "C" fn plugin_idle(adapter: *mut PluginAdapter) { 540 | (*adapter).0.idle(); 541 | } 542 | 543 | /// [`Plugin::tick`](trait.Plugin.html#tymethod.tick) FFI. 544 | /// 545 | /// It supposed to be used internally. Don't use it. 546 | /// 547 | /// # Safety 548 | /// 549 | /// Unsafe 550 | #[doc(hidden)] 551 | #[no_mangle] 552 | unsafe extern "C" fn plugin_tick(adapter: *mut PluginAdapter) { 553 | (*adapter).0.tick(); 554 | } 555 | 556 | /// [`Plugin::midi_tick`](trait.Plugin.html#tymethod.midi_tick) FFI. 557 | /// 558 | /// It supposed to be used internally. Don't use it. 559 | /// 560 | /// # Safety 561 | /// 562 | /// Unsafe 563 | #[doc(hidden)] 564 | #[no_mangle] 565 | unsafe extern "C" fn plugin_midi_tick(adapter: *mut PluginAdapter) { 566 | (*adapter).0.midi_tick(); 567 | } 568 | 569 | /// [`Plugin::render`](trait.Plugin.html#tymethod.render) FFI for effects. 570 | /// 571 | /// It supposed to be used internally. Don't use it. 572 | /// 573 | /// # Safety 574 | /// 575 | /// Unsafe 576 | #[doc(hidden)] 577 | #[no_mangle] 578 | unsafe extern "C" fn plugin_eff_render( 579 | adapter: *mut PluginAdapter, 580 | source: *const [f32; 2], 581 | dest: *mut [f32; 2], 582 | length: i32, 583 | ) { 584 | let input = std::slice::from_raw_parts(source, length as usize); 585 | let mut output = std::slice::from_raw_parts_mut(dest, length as usize); 586 | (*adapter).0.render(input, &mut output); 587 | } 588 | 589 | /// [`Plugin::render`](trait.Plugin.html#tymethod.render) FFI for generators. 590 | /// 591 | /// It supposed to be used internally. Don't use it. 592 | /// 593 | /// # Safety 594 | /// 595 | /// Unsafe 596 | #[doc(hidden)] 597 | #[no_mangle] 598 | unsafe extern "C" fn plugin_gen_render( 599 | adapter: *mut PluginAdapter, 600 | dest: *mut [f32; 2], 601 | length: i32, 602 | ) { 603 | let mut output = std::slice::from_raw_parts_mut(dest, length as usize); 604 | (*adapter).0.render(&[[0.0, 0.0]], &mut output); 605 | } 606 | 607 | /// [`Plugin::midi_in`](trait.Plugin.html#tymethod.midi_in) FFI. 608 | /// 609 | /// It supposed to be used internally. Don't use it. 610 | /// 611 | /// # Safety 612 | /// 613 | /// Unsafe 614 | #[doc(hidden)] 615 | #[no_mangle] 616 | unsafe extern "C" fn plugin_midi_in(adapter: *mut PluginAdapter, message: &mut c_int) { 617 | (*adapter).0.midi_in(message.into()); 618 | } 619 | 620 | /// [`Plugin::save_state`](trait.Plugin.html#tymethod.save_state) FFI. 621 | /// 622 | /// It supposed to be used internally. Don't use it. 623 | /// 624 | /// # Safety 625 | /// 626 | /// Unsafe 627 | #[doc(hidden)] 628 | #[no_mangle] 629 | unsafe extern "C" fn plugin_save_state(adapter: *mut PluginAdapter, stream: *mut c_void) { 630 | (*adapter).0.save_state(StateWriter(stream)); 631 | } 632 | 633 | /// [`Plugin::load_state`](trait.Plugin.html#tymethod.load_state) FFI. 634 | /// 635 | /// It supposed to be used internally. Don't use it. 636 | /// 637 | /// # Safety 638 | /// 639 | /// Unsafe 640 | #[doc(hidden)] 641 | #[no_mangle] 642 | unsafe extern "C" fn plugin_load_state(adapter: *mut PluginAdapter, stream: *mut c_void) { 643 | (*adapter).0.load_state(StateReader(stream)); 644 | } 645 | 646 | /// [`Plugin::loop_in`](Plugin.html#method.loop_in) FFI. 647 | /// 648 | /// It supposed to be used internally. Don't use it. 649 | /// 650 | /// # Safety 651 | /// 652 | /// Unsafe 653 | #[doc(hidden)] 654 | #[no_mangle] 655 | unsafe extern "C" fn plugin_loop_in(adapter: *mut PluginAdapter, message: intptr_t) { 656 | (*adapter).0.loop_in(ValuePtr(message)); 657 | } 658 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! The FL Plugin SDK helps you to make plugins for FL Studio. For more information about FL 2 | //! Studio, visit the [website](https://www.image-line.com/flstudio/). 3 | //! 4 | //! Note that this SDK is not meant to make hosts for FL plugins. 5 | //! 6 | //! ## How to use this library 7 | //! 8 | //! You should implement [`Plugin`](plugin/trait.Plugin.html) and export it with 9 | //! [`create_plugin!`](macro.create_plugin.html). 10 | //! 11 | //! To talk to the host use [`Host`](host/struct.Host.html), which is passed to the plugin's 12 | //! constructor. 13 | //! 14 | //! `examples/simple.rs` in the code repo provides you with more details. 15 | //! 16 | //! ## Types of plugins 17 | //! 18 | //! There are two kinds of Fruity plugins: effects and generators. Effects are plugins that receive 19 | //! some audio data from FL Studio and do something to it (apply an effect). Generators on the 20 | //! other hand create sounds that they send to FL Studio. Generators are seen as channels by the 21 | //! user (like the SimSynth and Sytrus). The main reason to make something a generator is that it 22 | //! needs input from the FL Studio pianoroll (although there are other reasons possible). 23 | //! 24 | //! ## Installation 25 | //! 26 | //! Plugins are installed in FL Studio in subfolders of the `FL Studio\Plugins\Fruity` folder on 27 | //! Windows and `FL\ Studio.app/Contents/Resources/FL/Plugins/Fruity` for macOS. 28 | //! 29 | //! Effects go in the **Effects** subfolder, generators are installed in the **Generators** 30 | //! subfolder. Each plugin has its own folder. 31 | //! 32 | //! The name of the folder has to be same as the name of the plugin. On macOS the plugin (.dylib) 33 | //! also has to have `_x64` suffix. 34 | //! 35 | #![deny( 36 | nonstandard_style, 37 | rust_2018_idioms, 38 | trivial_casts, 39 | trivial_numeric_casts 40 | )] 41 | #![warn( 42 | deprecated_in_future, 43 | missing_docs, 44 | unused_import_braces, 45 | unused_labels, 46 | unused_lifetimes, 47 | unused_qualifications, 48 | unreachable_pub 49 | )] 50 | 51 | pub mod host; 52 | pub mod plugin; 53 | pub mod voice; 54 | 55 | use std::ffi::{CStr, CString}; 56 | use std::mem; 57 | use std::os::raw::{c_char, c_int, c_void}; 58 | 59 | use bitflags::bitflags; 60 | use log::{debug, error}; 61 | 62 | /// Current FL SDK version. 63 | pub const CURRENT_SDK_VERSION: u32 = 1; 64 | 65 | /// Size of wavetable used by FL. 66 | pub const WAVETABLE_SIZE: usize = 16384; 67 | 68 | /// intptr_t alias 69 | #[allow(non_camel_case_types)] 70 | #[doc(hidden)] 71 | pub type intptr_t = isize; 72 | 73 | /// An identefier the host uses to identify plugin and voice instances. 74 | /// 75 | /// To make it more type safe, `plugin` and `voice` modules provide their own `Tag` type. 76 | pub(crate) type Tag = intptr_t; 77 | 78 | /// This macro is used internally to implement `Tag` type in a type-safe manner. 79 | #[doc(hidden)] 80 | #[macro_export] 81 | macro_rules! implement_tag { 82 | () => { 83 | use std::fmt; 84 | 85 | /// Identifier. 86 | #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] 87 | pub struct Tag(pub crate::Tag); 88 | 89 | impl fmt::Display for Tag { 90 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 91 | write!(f, "{}", self.0) 92 | } 93 | } 94 | }; 95 | } 96 | 97 | #[derive(Debug, Clone)] 98 | #[repr(C)] 99 | struct FlMessage { 100 | id: intptr_t, 101 | index: intptr_t, 102 | value: intptr_t, 103 | } 104 | 105 | /// For types, which can be represented as `intptr_t`. 106 | pub trait AsRawPtr { 107 | /// Conversion method. 108 | fn as_raw_ptr(&self) -> intptr_t; 109 | } 110 | 111 | macro_rules! primitive_as_raw_ptr { 112 | ($type:ty) => { 113 | impl AsRawPtr for $type { 114 | fn as_raw_ptr(&self) -> intptr_t { 115 | (*self) as intptr_t 116 | } 117 | } 118 | }; 119 | } 120 | 121 | primitive_as_raw_ptr!(i8); 122 | primitive_as_raw_ptr!(u8); 123 | primitive_as_raw_ptr!(i16); 124 | primitive_as_raw_ptr!(u16); 125 | primitive_as_raw_ptr!(i32); 126 | primitive_as_raw_ptr!(u32); 127 | primitive_as_raw_ptr!(i64); 128 | primitive_as_raw_ptr!(u64); 129 | primitive_as_raw_ptr!(usize); 130 | primitive_as_raw_ptr!(*mut c_void); 131 | primitive_as_raw_ptr!(*const c_void); 132 | 133 | impl AsRawPtr for bool { 134 | fn as_raw_ptr(&self) -> intptr_t { 135 | (self.to_owned() as u8).into() 136 | } 137 | } 138 | 139 | impl AsRawPtr for f32 { 140 | fn as_raw_ptr(&self) -> intptr_t { 141 | self.to_bits() as intptr_t 142 | } 143 | } 144 | 145 | impl AsRawPtr for f64 { 146 | fn as_raw_ptr(&self) -> intptr_t { 147 | self.to_bits() as intptr_t 148 | } 149 | } 150 | 151 | impl AsRawPtr for String { 152 | fn as_raw_ptr(&self) -> intptr_t { 153 | let value = CString::new(self.clone()).unwrap_or_else(|e| { 154 | error!("{}", e); 155 | panic!(); 156 | }); 157 | // alloc_real_cstr prevents memory leak caused by CString::into_raw 158 | unsafe { alloc_real_cstr(value.into_raw()) as intptr_t } 159 | } 160 | } 161 | 162 | /// FFI to make C string (`char *`) managed by C side. Because `char *` produced by 163 | /// `CString::into_raw` leads to memory leak: 164 | /// 165 | /// > The pointer which this function returns must be returned to Rust and reconstituted using 166 | /// > from_raw to be properly deallocated. Specifically, one should not use the standard C free() 167 | /// > function to deallocate this string. 168 | #[no_mangle] 169 | extern "C" { 170 | fn alloc_real_cstr(raw_str: *mut c_char) -> *mut c_char; 171 | } 172 | 173 | /// For conversion from `intptr_t`. 174 | pub trait FromRawPtr { 175 | /// Conversion method. 176 | fn from_raw_ptr(value: intptr_t) -> Self 177 | where 178 | Self: Sized; 179 | } 180 | 181 | macro_rules! primitive_from_raw_ptr { 182 | ($type:ty) => { 183 | impl FromRawPtr for $type { 184 | fn from_raw_ptr(value: intptr_t) -> Self { 185 | value as Self 186 | } 187 | } 188 | }; 189 | } 190 | 191 | primitive_from_raw_ptr!(i8); 192 | primitive_from_raw_ptr!(u8); 193 | primitive_from_raw_ptr!(i16); 194 | primitive_from_raw_ptr!(u16); 195 | primitive_from_raw_ptr!(i32); 196 | primitive_from_raw_ptr!(u32); 197 | primitive_from_raw_ptr!(i64); 198 | primitive_from_raw_ptr!(u64); 199 | primitive_from_raw_ptr!(usize); 200 | primitive_from_raw_ptr!(*mut c_void); 201 | primitive_from_raw_ptr!(*const c_void); 202 | 203 | impl FromRawPtr for f32 { 204 | fn from_raw_ptr(value: intptr_t) -> Self { 205 | f32::from_bits(value as i32 as u32) 206 | } 207 | } 208 | 209 | impl FromRawPtr for f64 { 210 | fn from_raw_ptr(value: intptr_t) -> Self { 211 | f64::from_bits(value as i64 as u64) 212 | } 213 | } 214 | 215 | impl FromRawPtr for String { 216 | fn from_raw_ptr(value: intptr_t) -> Self { 217 | let cstr = unsafe { CStr::from_ptr(value as *const c_char) }; 218 | cstr.to_string_lossy().to_string() 219 | } 220 | } 221 | 222 | impl FromRawPtr for bool { 223 | fn from_raw_ptr(value: intptr_t) -> Self { 224 | value != 0 225 | } 226 | } 227 | 228 | /// Raw pointer to value. 229 | #[derive(Debug)] 230 | pub struct ValuePtr(intptr_t); 231 | 232 | impl ValuePtr { 233 | /// Get value. 234 | /// 235 | /// See [`FromRawPtr`](trait.FromRawPtr.html) for implemented types. 236 | pub fn get(&self) -> T { 237 | T::from_raw_ptr(self.0) 238 | } 239 | } 240 | 241 | impl FromRawPtr for ValuePtr { 242 | fn from_raw_ptr(value: intptr_t) -> Self { 243 | ValuePtr(value) 244 | } 245 | } 246 | 247 | /// Time signature. 248 | #[derive(Debug, Clone)] 249 | pub struct TimeSignature { 250 | /// Steps per bar. 251 | pub steps_per_bar: u32, 252 | /// Steps per beat. 253 | pub steps_per_beat: u32, 254 | /// Pulses per quarter note. 255 | pub ppq: u32, 256 | } 257 | 258 | impl From for TimeSignature { 259 | fn from(value: TTimeSigInfo) -> Self { 260 | Self { 261 | steps_per_bar: value.steps_per_bar as u32, 262 | steps_per_beat: value.steps_per_beat as u32, 263 | ppq: value.ppq as u32, 264 | } 265 | } 266 | } 267 | 268 | #[repr(C)] 269 | struct TTimeSigInfo { 270 | steps_per_bar: c_int, 271 | steps_per_beat: c_int, 272 | ppq: c_int, 273 | } 274 | 275 | impl FromRawPtr for TTimeSigInfo { 276 | fn from_raw_ptr(raw_ptr: intptr_t) -> Self { 277 | let sig_ptr = raw_ptr as *mut c_void as *mut TTimeSigInfo; 278 | unsafe { 279 | Self { 280 | steps_per_beat: (*sig_ptr).steps_per_beat, 281 | steps_per_bar: (*sig_ptr).steps_per_bar, 282 | ppq: (*sig_ptr).ppq, 283 | } 284 | } 285 | } 286 | } 287 | 288 | /// Time format. 289 | #[derive(Debug)] 290 | pub enum TimeFormat { 291 | /// Beats. 292 | Beats, 293 | /// Absolute ms. 294 | AbsoluteMs, 295 | /// Running ms. 296 | RunningMs, 297 | /// Time since sound card restart (in ms). 298 | RestartMs, 299 | } 300 | 301 | impl From for u8 { 302 | fn from(format: TimeFormat) -> Self { 303 | match format { 304 | TimeFormat::Beats => 0, 305 | TimeFormat::AbsoluteMs => 1, 306 | TimeFormat::RunningMs => 2, 307 | TimeFormat::RestartMs => 3, 308 | } 309 | } 310 | } 311 | 312 | /// Time. 313 | /// 314 | /// The first value is mixing time. 315 | /// 316 | /// The second value is offset in samples. 317 | #[derive(Debug, Default)] 318 | #[repr(C)] 319 | pub struct Time(pub f64, pub f64); 320 | 321 | impl FromRawPtr for Time { 322 | fn from_raw_ptr(value: intptr_t) -> Self { 323 | unsafe { *Box::from_raw(value as *mut c_void as *mut Time) } 324 | } 325 | } 326 | 327 | /// Song time in **bar:step:tick** format. 328 | #[allow(missing_docs)] 329 | #[derive(Clone, Debug, Default)] 330 | #[repr(C)] 331 | pub struct SongTime { 332 | pub bar: i32, 333 | pub step: i32, 334 | pub tick: i32, 335 | } 336 | 337 | impl FromRawPtr for SongTime { 338 | fn from_raw_ptr(value: intptr_t) -> Self { 339 | unsafe { *Box::from_raw(value as *mut c_void as *mut Self) } 340 | } 341 | } 342 | 343 | /// Name of the color (or MIDI channel) in Piano Roll. 344 | #[derive(Debug)] 345 | pub struct NameColor { 346 | /// User-defined name (can be empty). 347 | pub name: String, 348 | /// Visible name (can be guessed). 349 | pub vis_name: String, 350 | /// Color/MIDI channel index. 351 | pub color: u8, 352 | /// Real index of the item (can be used to translate plugin's own in/out into real mixer track 353 | /// number). 354 | pub index: usize, 355 | } 356 | 357 | // Type used in FFI for [`NameColor`](struct.NameColor.html). 358 | #[repr(C)] 359 | struct TNameColor { 360 | name: [u8; 256], 361 | vis_name: [u8; 256], 362 | color: c_int, 363 | index: c_int, 364 | } 365 | 366 | impl From for NameColor { 367 | fn from(name_color: TNameColor) -> Self { 368 | Self { 369 | name: String::from_utf8_lossy(&name_color.name[..]).to_string(), 370 | vis_name: String::from_utf8_lossy(&name_color.vis_name[..]).to_string(), 371 | color: name_color.color as u8, 372 | index: name_color.index as usize, 373 | } 374 | } 375 | } 376 | 377 | impl From for TNameColor { 378 | fn from(name_color: NameColor) -> Self { 379 | let mut name = [0_u8; 256]; 380 | name.copy_from_slice(name_color.name.as_bytes()); 381 | let mut vis_name = [0_u8; 256]; 382 | vis_name.copy_from_slice(name_color.vis_name.as_bytes()); 383 | Self { 384 | name, 385 | vis_name, 386 | color: name_color.color as c_int, 387 | index: name_color.index as c_int, 388 | } 389 | } 390 | } 391 | 392 | impl FromRawPtr for TNameColor { 393 | fn from_raw_ptr(value: intptr_t) -> Self { 394 | unsafe { *Box::from_raw(value as *mut Self) } 395 | } 396 | } 397 | 398 | /// MIDI message. 399 | #[derive(Debug)] 400 | pub struct MidiMessage { 401 | /// Status byte. 402 | pub status: u8, 403 | /// First data byte. 404 | pub data1: u8, 405 | /// Second data byte. 406 | pub data2: u8, 407 | /// Port number. 408 | pub port: u8, 409 | } 410 | 411 | impl From<&mut c_int> for MidiMessage { 412 | fn from(value: &mut c_int) -> Self { 413 | Self { 414 | status: (*value & 0xff) as u8, 415 | data1: ((*value >> 8) & 0xff) as u8, 416 | data2: ((*value >> 16) & 0xff) as u8, 417 | port: ((*value >> 24) & 0xff) as u8, 418 | } 419 | } 420 | } 421 | 422 | impl From for MidiMessage { 423 | fn from(value: c_int) -> Self { 424 | Self { 425 | status: (value & 0xff) as u8, 426 | data1: ((value >> 8) & 0xff) as u8, 427 | data2: ((value >> 16) & 0xff) as u8, 428 | port: ((value >> 24) & 0xff) as u8, 429 | } 430 | } 431 | } 432 | 433 | /// Collection of notes, which you can add to the piano roll using 434 | /// [`Host::on_message`](host/struct.Host.html#on_message.new) with message 435 | /// [`plugin::message::AddToPianoRoll`](./plugin/message/struct.AddToPianoRoll.html). 436 | #[derive(Debug)] 437 | pub struct Notes { 438 | // 0=step seq (not supported yet), 1=piano roll 439 | //target: i32, 440 | /// Notes. 441 | pub notes: Vec, 442 | /// See [`NotesFlags`](struct.NotesFlags.html). 443 | pub flags: NotesFlags, 444 | /// Pattern number. `None` for current. 445 | pub pattern: Option, 446 | /// Channel number. `None` for plugin's channel, or selected channel if plugin is an effect. 447 | pub channel: Option, 448 | } 449 | 450 | /// This type represents a note in [`Notes`](struct.Notes.html). 451 | #[derive(Debug)] 452 | #[repr(C)] 453 | pub struct Note { 454 | /// Position in PPQ. 455 | pub position: i32, 456 | /// Length in PPQ. 457 | pub length: i32, 458 | /// Pan in range -100..100. 459 | pub pan: i32, 460 | /// Volume. 461 | pub vol: i32, 462 | /// Note number. 463 | pub note: i16, 464 | /// Color or MIDI channel in range of 0..15. 465 | pub color: i16, 466 | /// Fine pitch in range -1200..1200. 467 | pub pitch: i32, 468 | /// Mod X or filter cutoff frequency. 469 | pub mod_x: f32, 470 | /// Mod Y or filter resonance (Q). 471 | pub mod_y: f32, 472 | } 473 | 474 | bitflags! { 475 | /// Notes parameters flags. 476 | pub struct NotesFlags: isize { 477 | /// Delete everything currently on the piano roll before adding the notes. 478 | const EMPTY_FIRST = 1; 479 | /// Put the new notes in the piano roll selection, if there is one. 480 | const USE_SELECTION = 2; 481 | } 482 | } 483 | 484 | // This type in FL SDK is what we represent as Notes. Here we use it for FFI, to send it to C++. 485 | #[repr(C)] 486 | struct TNotesParams { 487 | target: c_int, 488 | flags: c_int, 489 | pat_num: c_int, 490 | chan_num: c_int, 491 | count: c_int, 492 | notes: *mut Note, 493 | } 494 | 495 | impl From for TNotesParams { 496 | fn from(mut notes: Notes) -> Self { 497 | notes.notes.shrink_to_fit(); 498 | let notes_ptr = notes.notes.as_mut_ptr(); 499 | let len = notes.notes.len(); 500 | mem::forget(notes.notes); 501 | 502 | Self { 503 | target: 1, 504 | flags: notes.flags.bits() as c_int, 505 | pat_num: notes.pattern.map(|v| v as c_int).unwrap_or(-1), 506 | chan_num: notes.channel.map(|v| v as c_int).unwrap_or(-1), 507 | count: len as c_int, 508 | notes: notes_ptr, 509 | } 510 | } 511 | } 512 | 513 | /// Describes an item that should be added to a control's right-click popup menu. 514 | #[derive(Debug)] 515 | pub struct ParamMenuEntry { 516 | /// Name. 517 | pub name: String, 518 | /// Flags. 519 | pub flags: ParamMenuItemFlags, 520 | } 521 | 522 | bitflags! { 523 | /// Parameter popup menu item flags. 524 | pub struct ParamMenuItemFlags: i32 { 525 | /// The item is disabled 526 | const DISABLED = 1; 527 | /// The item is checked 528 | const CHECKED = 2; 529 | } 530 | } 531 | 532 | impl ParamMenuEntry { 533 | fn from_ffi(ffi_t: *mut TParamMenuEntry) -> Self { 534 | Self { 535 | name: unsafe { CString::from_raw((*ffi_t).name) } 536 | .to_string_lossy() 537 | .to_string(), 538 | flags: ParamMenuItemFlags::from_bits(unsafe { (*ffi_t).flags }) 539 | .unwrap_or_else(ParamMenuItemFlags::empty), 540 | } 541 | } 542 | } 543 | 544 | #[derive(Debug)] 545 | #[repr(C)] 546 | struct TParamMenuEntry { 547 | name: *mut c_char, 548 | flags: c_int, 549 | } 550 | 551 | bitflags! { 552 | /// Parameter flags. 553 | pub struct ParameterFlags: isize { 554 | /// Makes no sense to interpolate parameter values (when values are not levels). 555 | const CANT_INTERPOLATE = 1; 556 | /// Parameter is a normalized (0..1) single float. (Integer otherwise) 557 | const FLOAT = 2; 558 | /// Parameter appears centered in event editors. 559 | const CENTERED = 4; 560 | } 561 | } 562 | 563 | impl AsRawPtr for ParameterFlags { 564 | fn as_raw_ptr(&self) -> intptr_t { 565 | self.bits() 566 | } 567 | } 568 | 569 | bitflags! { 570 | /// Processing mode flags. 571 | pub struct ProcessModeFlags: isize { 572 | /// Realtime rendering. 573 | const NORMAL = 0; 574 | /// Realtime rendering with a higher quality. 575 | const HQ_REALTIME = 1; 576 | /// Non realtime processing (CPU does not matter, quality does) (normally set when 577 | /// rendering only). 578 | const HQ_NON_REALTIME = 2; 579 | /// FL is rendering to file if this flag is set. 580 | const IS_RENDERING = 16; 581 | /// (changed in FL 7.0) 3 bits value for interpolation quality. 582 | /// 583 | /// - 0=none (obsolete) 584 | /// - 1=linear 585 | /// - 2=6 point hermite (default) 586 | /// - 3=32 points sinc 587 | /// - 4=64 points sinc 588 | /// - 5=128 points sinc 589 | /// - 6=256 points sinc 590 | const IP_MASK = 0xFFFF << 8; 591 | } 592 | } 593 | 594 | bitflags! { 595 | /// Processing parameters flags. 596 | pub struct ProcessParamFlags: isize { 597 | /// Update the value of the parameter. 598 | const UPDATE_VALUE = 1; 599 | /// Return the value of the parameter as the result of the function. 600 | const GET_VALUE = 2; 601 | /// Update the hint if there is one. 602 | const SHOW_HINT = 4; 603 | /// Update the parameter control (wheel, slider, ...). 604 | const UPDATE_CONTROL = 16; 605 | /// A value between 0 and 65536 has to be translated to the range of the parameter control. 606 | /// 607 | /// Note that you should also return the translated value, even if 608 | /// [ProcessParamFlags::GET_VALUE]( 609 | /// struct.ProcessParamFlags.html#associatedconstant.GET_VALUE) isn't included. 610 | const FROM_MIDI = 32; 611 | /// (internal) Don't check if wheels are linked. 612 | const NO_LINK = 1024; 613 | /// Sent by an internal controller. Internal controllers should pay attention to these, 614 | /// to avoid Feedback of controller changes. 615 | const INTERNAL_CTRL = 2048; 616 | /// This flag is free to be used by the plugin as it wishes. 617 | const PLUG_RESERVED = 4096; 618 | } 619 | } 620 | 621 | bitflags! { 622 | /// Sample loading flags. 623 | pub struct SampleLoadFlags: isize { 624 | ///This tells the sample loader to show an open box, for the user to select a sample 625 | const SHOW_DIALOG = 1; 626 | /// Force the sample to be reloaded, even if the filename is the same. 627 | /// 628 | /// This is handy in case you modified the sample, for example 629 | const FORCE_RELOAD = 2; 630 | /// Don't load the sample, instead get its filename & make sure that the format is correct 631 | /// 632 | /// (useful after [host::HostMessage::ChanSampleChanged]( 633 | /// enum.HostMessage.html#variant.ChanSampleChanged)) 634 | const GET_NAME = 4; 635 | /// Don't resample to the host sample rate 636 | const NO_RESAMPLING = 5; 637 | } 638 | } 639 | 640 | /// if `Jog`, `StripJog`, `MarkerJumpJog`, `MarkerSelJog`, `Previous` or `Next` don't answer, 641 | /// `PreviousNext` will be tried. So it's best to implement at least `PreviousNext`. 642 | /// 643 | /// if `PunchIn` or `PunchOut` don't answer, `Punch` will be tried 644 | /// 645 | /// if `UndoUp` doesn't answer, `UndoJog` will be tried 646 | /// 647 | /// if `AddAltMarker` doesn't answer, `AddMarker` will be tried 648 | /// 649 | /// if `Cut`, `Copy`, `Paste`, `Insert`, `Delete`, `NextWindow`, `Enter`, `Escape`, `Yes`, `No`, 650 | /// `Fx` don't answer, standard keystrokes will be simulated 651 | #[allow(missing_docs)] 652 | #[derive(Debug)] 653 | pub enum Transport { 654 | /// Generic jog (can be used to select stuff). 655 | Jog(Jog), 656 | /// Alternate generic jog (can be used to relocate stuff). 657 | Jog2(Jog), 658 | /// Touch-sensitive jog strip, value will be in -65536..65536 for leftmost..rightmost. 659 | Strip(Jog), 660 | /// Touch-sensitive jog in jog mode. 661 | StripJog(Jog), 662 | /// Value will be `0` for release, 1,2 for 1,2 fingers centered mode, -1,-2 for 1,2 fingers jog 663 | /// mode (will then send `StripJog`). 664 | StripHold(Jog), 665 | Previous(Button), 666 | Next(Button), 667 | /// Generic track selection. 668 | PreviousNext(Jog), 669 | /// Used to relocate items. 670 | MoveJog(Jog), 671 | /// Play/pause. 672 | Play(Button), 673 | Stop(Button), 674 | Record(Button), 675 | Rewind(Hold), 676 | FastForward(Hold), 677 | Loop(Button), 678 | Mute(Button), 679 | /// Generic or record mode. 680 | Mode(Button), 681 | /// Undo/redo last, or undo down in history. 682 | Undo(Button), 683 | /// Undo up in history (no need to implement if no undo history). 684 | UndoUp(Button), 685 | /// Undo in history (no need to implement if no undo history). 686 | UndoJog(Jog), 687 | /// Live selection. 688 | Punch(Hold), 689 | PunchIn(Button), 690 | PunchOut(Button), 691 | AddMarker(Button), 692 | /// Add alternate marker. 693 | AddAltMarker(Button), 694 | /// Marker jump. 695 | MarkerJumpJog(Jog), 696 | /// Marker selection. 697 | MarkerSelJog(Jog), 698 | Up(Button), 699 | Down(Button), 700 | Left(Button), 701 | Right(Button), 702 | HZoomJog(Jog), 703 | VZoomJog(Jog), 704 | /// Snap on/off. 705 | Snap(Button), 706 | SnapMode(Jog), 707 | Cut(Button), 708 | Copy(Button), 709 | Paste(Button), 710 | Insert(Button), 711 | Delete(Button), 712 | /// TAB. 713 | NextWindow(Button), 714 | /// Window selection. 715 | WindowJog(Jog), 716 | F1(Button), 717 | F2(Button), 718 | F3(Button), 719 | F4(Button), 720 | F5(Button), 721 | F6(Button), 722 | F7(Button), 723 | F8(Button), 724 | F9(Button), 725 | F10(Button), 726 | /// Enter/accept. 727 | Enter(Button), 728 | /// Escape/cancel. 729 | Escape(Button), 730 | Yes(Button), 731 | No(Button), 732 | /// Generic menu. 733 | Menu(Button), 734 | /// Item edit/tool/contextual menu. 735 | ItemMenu(Button), 736 | Save(Button), 737 | SaveNew(Button), 738 | Unknown, 739 | } 740 | 741 | impl From for Transport { 742 | fn from(message: FlMessage) -> Self { 743 | match message.index { 744 | 0 => Transport::Jog(Jog(message.value as i64)), 745 | 1 => Transport::Jog2(Jog(message.value as i64)), 746 | 2 => Transport::Strip(Jog(message.value as i64)), 747 | 3 => Transport::StripJog(Jog(message.value as i64)), 748 | 4 => Transport::StripHold(Jog(message.value as i64)), 749 | 5 => Transport::Previous(Button(message.value as u8)), 750 | 6 => Transport::Next(Button(message.value as u8)), 751 | 7 => Transport::PreviousNext(Jog(message.value as i64)), 752 | 8 => Transport::MoveJog(Jog(message.value as i64)), 753 | 10 => Transport::Play(Button(message.value as u8)), 754 | 11 => Transport::Stop(Button(message.value as u8)), 755 | 12 => Transport::Record(Button(message.value as u8)), 756 | 13 => Transport::Rewind(Hold(message.value != 0)), 757 | 14 => Transport::FastForward(Hold(message.value != 0)), 758 | 15 => Transport::Loop(Button(message.value as u8)), 759 | 16 => Transport::Mute(Button(message.value as u8)), 760 | 17 => Transport::Mode(Button(message.value as u8)), 761 | 20 => Transport::Undo(Button(message.value as u8)), 762 | 21 => Transport::UndoUp(Button(message.value as u8)), 763 | 22 => Transport::UndoJog(Jog(message.value as i64)), 764 | 30 => Transport::Punch(Hold(message.value != 0)), 765 | 31 => Transport::PunchIn(Button(message.value as u8)), 766 | 32 => Transport::PunchOut(Button(message.value as u8)), 767 | 33 => Transport::AddMarker(Button(message.value as u8)), 768 | 34 => Transport::AddAltMarker(Button(message.value as u8)), 769 | 35 => Transport::MarkerJumpJog(Jog(message.value as i64)), 770 | 36 => Transport::MarkerSelJog(Jog(message.value as i64)), 771 | 40 => Transport::Up(Button(message.value as u8)), 772 | 41 => Transport::Down(Button(message.value as u8)), 773 | 42 => Transport::Left(Button(message.value as u8)), 774 | 43 => Transport::Right(Button(message.value as u8)), 775 | 44 => Transport::HZoomJog(Jog(message.value as i64)), 776 | 45 => Transport::VZoomJog(Jog(message.value as i64)), 777 | 48 => Transport::Snap(Button(message.value as u8)), 778 | 49 => Transport::SnapMode(Jog(message.value as i64)), 779 | 50 => Transport::Cut(Button(message.value as u8)), 780 | 51 => Transport::Copy(Button(message.value as u8)), 781 | 52 => Transport::Paste(Button(message.value as u8)), 782 | 53 => Transport::Insert(Button(message.value as u8)), 783 | 54 => Transport::Delete(Button(message.value as u8)), 784 | 58 => Transport::NextWindow(Button(message.value as u8)), 785 | 59 => Transport::WindowJog(Jog(message.value as i64)), 786 | 60 => Transport::F1(Button(message.value as u8)), 787 | 61 => Transport::F2(Button(message.value as u8)), 788 | 62 => Transport::F3(Button(message.value as u8)), 789 | 63 => Transport::F4(Button(message.value as u8)), 790 | 64 => Transport::F5(Button(message.value as u8)), 791 | 65 => Transport::F6(Button(message.value as u8)), 792 | 66 => Transport::F7(Button(message.value as u8)), 793 | 67 => Transport::F8(Button(message.value as u8)), 794 | 68 => Transport::F9(Button(message.value as u8)), 795 | 69 => Transport::F10(Button(message.value as u8)), 796 | 80 => Transport::Enter(Button(message.value as u8)), 797 | 81 => Transport::Escape(Button(message.value as u8)), 798 | 82 => Transport::Yes(Button(message.value as u8)), 799 | 83 => Transport::No(Button(message.value as u8)), 800 | 90 => Transport::Menu(Button(message.value as u8)), 801 | 91 => Transport::ItemMenu(Button(message.value as u8)), 802 | 92 => Transport::Save(Button(message.value as u8)), 803 | 93 => Transport::SaveNew(Button(message.value as u8)), 804 | _ => Transport::Unknown, 805 | } 806 | } 807 | } 808 | 809 | /// `0` for release, `1` for switch (if release is not supported), `2` for hold (if release should 810 | /// be expected). 811 | #[derive(Debug)] 812 | pub struct Button(pub u8); 813 | 814 | /// `false` for release, `true` for hold. 815 | #[derive(Debug)] 816 | pub struct Hold(pub bool); 817 | 818 | /// Value is an integer increment. 819 | #[derive(Debug)] 820 | pub struct Jog(pub i64); 821 | 822 | bitflags! { 823 | /// Message box flags (see 824 | /// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-messagebox). 825 | pub struct MessageBoxFlags: isize { 826 | // To indicate the buttons displayed in the message box, specify one of the following 827 | // values. 828 | 829 | /// The message box contains three push buttons: Abort, Retry, and Ignore. 830 | const ABORTRETRYIGNORE = 0x0000_0002; 831 | /// The message box contains three push buttons: Cancel, Try Again, Continue. Use this 832 | /// message box type instead of ABORTRETRYIGNORE. 833 | const CANCELTRYCONTINUE = 0x0000_0006; 834 | /// Adds a Help button to the message box. When the user clicks the Help button or presses 835 | /// F1, the system sends a WM_HELP message to the owner. 836 | const HELP = 0x0000_4000; 837 | /// The message box contains one push button: OK. This is the default. 838 | const OK = 0x0000_0000; 839 | /// The message box contains two push buttons: OK and Cancel. 840 | const OKCANCEL = 0x0000_0001; 841 | /// The message box contains two push buttons: Retry and Cancel. 842 | const RETRYCANCEL = 0x0000_0005; 843 | /// The message box contains two push buttons: Yes and No. 844 | const YESNO = 0x0000_0004; 845 | /// The message box contains three push buttons: Yes, No, and Cancel. 846 | const YESNOCANCEL = 0x0000_0003; 847 | 848 | // To display an icon in the message box, specify one of the following values. 849 | 850 | /// An exclamation-point icon appears in the message box. 851 | const ICONEXCLAMATION = 0x0000_0030; 852 | /// An exclamation-point icon appears in the message box. 853 | const ICONWARNING = 0x0000_0030; 854 | /// An icon consisting of a lowercase letter i in a circle appears in the message box. 855 | const ICONINFORMATION = 0x0000_0040; 856 | /// An icon consisting of a lowercase letter i in a circle appears in the message box. 857 | const ICONASTERISK = 0x0000_0040; 858 | /// A question-mark icon appears in the message box. The question-mark message icon is no 859 | /// longer recommended because it does not clearly represent a specific type of message and 860 | /// because the phrasing of a message as a question could apply to any message type. In 861 | /// addition, users can confuse the message symbol question mark with Help information. 862 | /// Therefore, do not use this question mark message symbol in your message boxes. The 863 | /// system continues to support its inclusion only for backward compatibility. 864 | const ICONQUESTION = 0x0000_0020; 865 | /// A stop-sign icon appears in the message box. 866 | const ICONSTOP = 0x0000_0010; 867 | /// A stop-sign icon appears in the message box. 868 | const ICONERROR = 0x0000_0010; 869 | /// A stop-sign icon appears in the message box. 870 | const ICONHAND = 0x0000_0010; 871 | 872 | // To indicate the default button, specify one of the following values. 873 | 874 | /// The first button is the default button. 875 | /// 876 | /// DEFBUTTON1 is the default unless DEFBUTTON2, DEFBUTTON3, or DEFBUTTON4 is specified. 877 | const DEFBUTTON1 = 0x0000_0000; 878 | 879 | /// The second button is the default button. 880 | const DEFBUTTON2 = 0x0000_0100; 881 | /// The third button is the default button. 882 | const DEFBUTTON3 = 0x0000_0200; 883 | /// The fourth button is the default button. 884 | const DEFBUTTON4 = 0x0000_0300; 885 | 886 | // To indicate the modality of the dialog box, specify one of the following values. 887 | 888 | /// The user must respond to the message box before continuing work in the window 889 | /// identified by the hWnd parameter. However, the user can move to the windows of other 890 | /// threads and work in those windows. 891 | /// 892 | /// Depending on the hierarchy of windows in the application, the user may be able to move 893 | /// to other windows within the thread. All child windows of the parent of the message box 894 | /// are automatically disabled, but pop-up windows are not. 895 | /// 896 | /// APPLMODAL is the default if neither SYSTEMMODAL nor TASKMODAL is specified. 897 | const APPLMODAL = 0x0000_0000; 898 | 899 | 900 | /// Same as APPLMODAL except that the message box has the WS_EX_TOPMOST style. Use 901 | /// system-modal message boxes to notify the user of serious, potentially damaging errors 902 | /// that require immediate attention (for example, running out of memory). This flag has no 903 | /// effect on the user's ability to interact with windows other than those associated with 904 | /// hWnd. 905 | const SYSTEMMODAL = 0x0000_1000; 906 | /// Same as APPLMODAL except that all the top-level windows belonging to the current thread 907 | /// are disabled if the hWnd parameter is NULL. Use this flag when the calling application 908 | /// or library does not have a window handle available but still needs to prevent input to 909 | /// other windows in the calling thread without suspending other threads. 910 | const TASKMODAL = 0x0000_2000; 911 | 912 | // To specify other options, use one or more of the following values. 913 | 914 | /// Same as desktop of the interactive window station. For more information, see Window 915 | /// Stations. 916 | /// 917 | /// If the current input desktop is not the default desktop, MessageBox does not return 918 | /// until the user switches to the default desktop. 919 | const DEFAULT_DESKTOP_ONLY = 0x0002_0000; 920 | 921 | /// The text is right-justified. 922 | const RIGHT = 0x0008_0000; 923 | /// Displays message and caption text using right-to-left reading order on Hebrew and 924 | /// Arabic systems. 925 | const RTLREADING = 0x0010_0000; 926 | /// The message box becomes the foreground window. Internally, the system calls the 927 | /// SetForegroundWindow function for the message box. 928 | const SETFOREGROUND = 0x0001_0000; 929 | /// The message box is created with the WS_EX_TOPMOST window style. 930 | const TOPMOST = 0x0004_0000; 931 | /// The caller is a service notifying the user of an event. The function displays a message 932 | /// box on the current active desktop, even if there is no user logged on to the computer. 933 | /// 934 | /// Terminal Services: If the calling thread has an impersonation token, the function 935 | /// directs the message box to the session specified in the impersonation token. 936 | /// 937 | /// If this flag is set, the hWnd parameter must be NULL. This is so that the message box 938 | /// can appear on a desktop other than the desktop corresponding to the hWnd. 939 | /// 940 | /// For information on security considerations in regard to using this flag, see 941 | /// Interactive Services. In particular, be aware that this flag can produce interactive 942 | /// content on a locked desktop and should therefore be used for only a very limited set of 943 | /// scenarios, such as resource exhaustion. 944 | const SERVICE_NOTIFICATION = 0x0020_0000; 945 | } 946 | } 947 | 948 | impl AsRawPtr for MessageBoxFlags { 949 | fn as_raw_ptr(&self) -> intptr_t { 950 | self.bits() 951 | } 952 | } 953 | 954 | /// The result returned by a message box. 955 | /// 956 | /// See 957 | /// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-messagebox#return-value 958 | /// for more info. 959 | #[derive(Debug)] 960 | pub enum MessageBoxResult { 961 | /// The OK button was selected. 962 | Ok, 963 | /// The Cancel button was selected. 964 | Cancel, 965 | /// The Abort button was selected. 966 | Abort, 967 | /// The Retry button was selected. 968 | Retry, 969 | /// The Ignore button was selected. 970 | Ignore, 971 | /// The Yes button was selected. 972 | Yes, 973 | /// The No button was selected. 974 | No, 975 | /// The Try Again button was selected. 976 | TryAgain, 977 | /// The Continue button was selected. 978 | Continue, 979 | /// Unknown. 980 | Unknown, 981 | } 982 | 983 | impl FromRawPtr for MessageBoxResult { 984 | fn from_raw_ptr(value: intptr_t) -> Self { 985 | match value { 986 | 1 => MessageBoxResult::Ok, 987 | 2 => MessageBoxResult::Cancel, 988 | 3 => MessageBoxResult::Abort, 989 | 4 => MessageBoxResult::Retry, 990 | 5 => MessageBoxResult::Ignore, 991 | 6 => MessageBoxResult::Yes, 992 | 7 => MessageBoxResult::No, 993 | 10 => MessageBoxResult::TryAgain, 994 | 11 => MessageBoxResult::Continue, 995 | _ => MessageBoxResult::Unknown, 996 | } 997 | } 998 | } 999 | 1000 | #[no_mangle] 1001 | unsafe extern "C" fn fplog(message: *const c_char) { 1002 | debug!("{}", CStr::from_ptr(message).to_string_lossy()); 1003 | } 1004 | 1005 | /// FFI to free rust's Box::into_raw pointer. 1006 | /// 1007 | /// It supposed to be used internally. Don't use it. 1008 | /// 1009 | /// # Safety 1010 | /// 1011 | /// Unsafe 1012 | #[no_mangle] 1013 | unsafe extern "C" fn free_rbox_raw(raw_ptr: *mut c_void) { 1014 | let _ = Box::from_raw(raw_ptr); 1015 | } 1016 | 1017 | /// FFI to free rust's CString pointer. 1018 | /// 1019 | /// It supposed to be used internally. Don't use it. 1020 | /// 1021 | /// # Safety 1022 | /// 1023 | /// Unsafe 1024 | #[no_mangle] 1025 | unsafe extern "C" fn free_rstring(raw_str: *mut c_char) { 1026 | let _ = CString::from_raw(raw_str); 1027 | } 1028 | -------------------------------------------------------------------------------- /src/host.rs: -------------------------------------------------------------------------------- 1 | //! Plugin's host (FL Studio). 2 | pub mod prompt; 3 | 4 | use std::collections::HashMap; 5 | use std::ffi::c_void; 6 | use std::os::raw::{c_char, c_int, c_uchar}; 7 | use std::slice; 8 | use std::sync::atomic::AtomicPtr; 9 | use std::sync::{Arc, Mutex}; 10 | 11 | use log::trace; 12 | 13 | use crate::plugin::{self, message}; 14 | use crate::voice::{self, SendVoiceHandler, Voice}; 15 | use crate::{ 16 | intptr_t, AsRawPtr, FlMessage, FromRawPtr, MidiMessage, ProcessModeFlags, TTimeSigInfo, 17 | TimeSignature, Transport, ValuePtr, WAVETABLE_SIZE, 18 | }; 19 | 20 | /// Plugin host. 21 | #[derive(Debug)] 22 | pub struct Host { 23 | voicer: Arc>, 24 | out_voicer: Arc>, 25 | pub(crate) host_ptr: AtomicPtr, 26 | } 27 | 28 | impl Host { 29 | /// Initializer. 30 | pub fn new(host_ptr: *mut c_void) -> Self { 31 | let voicer = Arc::new(Mutex::new(Voicer::new(AtomicPtr::new(host_ptr)))); 32 | let out_voicer = Arc::new(Mutex::new(OutVoicer::new(AtomicPtr::new(host_ptr)))); 33 | Self { 34 | voicer, 35 | out_voicer, 36 | host_ptr: AtomicPtr::new(host_ptr), 37 | } 38 | } 39 | 40 | // Get the version of FL Studio. It is stored in one integer. If the version of FL Studio 41 | // would be 1.2.3 for example, `version` would be 1002003 42 | // pub fn version(&self) -> i32 { 43 | // todo!() 44 | // } 45 | 46 | /// Send message to host. 47 | /// 48 | /// See [`plugin::message`](../plugin/message/index.html). 49 | pub fn on_message(&mut self, tag: plugin::Tag, message: T) -> T::Return { 50 | message.send(tag, self) 51 | } 52 | 53 | /// Notify the host that a parameter value has changed. 54 | /// 55 | /// In order to make your parameters recordable in FL Studio, you have to call this function 56 | /// whenever a parameter is changed from within your plugin (probably because the user turned a 57 | /// wheel or something). 58 | /// 59 | /// - `tag` - plugin's tag. 60 | /// - `index` - the parameter index 61 | /// - `value` - the new parameter value. 62 | pub fn on_parameter(&mut self, tag: plugin::Tag, index: usize, value: ValuePtr) { 63 | unsafe { 64 | host_on_parameter( 65 | *self.host_ptr.get_mut(), 66 | tag.0, 67 | index as c_int, 68 | value.0 as c_int, 69 | ) 70 | }; 71 | } 72 | 73 | /// Notify the host that an internal controller has changed. 74 | /// 75 | /// Check out [this](https://forum.image-line.com/viewtopic.php?p=26368#p26368) post for more 76 | /// info about an internal controller implemenation. 77 | pub fn on_controller(&mut self, tag: plugin::Tag, index: usize, value: u16) { 78 | unsafe { 79 | host_on_controller( 80 | *self.host_ptr.get_mut(), 81 | tag.0, 82 | index as intptr_t, 83 | value.as_raw_ptr(), 84 | ) 85 | }; 86 | } 87 | 88 | /// Let the host show a hint, as specified by the parameters. 89 | /// 90 | /// - `tag` - the plugin's tag. 91 | /// - `text` - the text to show as a hint. 92 | /// 93 | /// There is one extra feature of parameter hints. It is possible to tell FL Studio to show 94 | /// little icons next to the hint, that have a special meaning. For the moment there are three 95 | /// of those. Note that these have to be inserted at the BEGINNING of the string. 96 | /// 97 | /// - `"^a"` - shows a little icon that informs the user that the parameter that the hint is 98 | /// about can be linked to a MIDI controller. 99 | /// - `"^b"` - informs the user that the parameter is recordable. 100 | /// - `"^c"` - shows a little smiley. No real use, just for fun. 101 | /// - `"^d"` - shows a mouse with the right button clicked, to denote a control that has a 102 | /// popup menu. 103 | /// - `"^e"` - shows an unhappy smiley, to use when something went wrong. 104 | /// - `"^f"` - shows a left-pointing arrow. 105 | /// - `"^g"` - shows a double right-pointing arrow, for fast forward. 106 | /// - `"^h"` - is an exclamation mark, for a warning to the user. 107 | /// - `"^i"` - is an hourglass. 108 | /// - `"^j"` - shows a double left-pointing arrow, for fast reverse. 109 | pub fn on_hint(&mut self, tag: plugin::Tag, text: String) { 110 | unsafe { 111 | host_on_hint( 112 | *self.host_ptr.get_mut(), 113 | tag.0, 114 | text.as_raw_ptr() as *mut c_char, 115 | ) 116 | }; 117 | } 118 | 119 | /// Send a MIDI out message immediately. 120 | /// 121 | /// To be able to use this method, you should enable MIDI out for the plugin (see 122 | /// [`InfoBuilder::midi_out`](../plugin/struct.InfoBuilder.html#method.midi_out)) and send 123 | /// [`plugin::message::ActivateMidi`](../plugin/message/struct.ActivateMidi.html) to the host. 124 | pub fn midi_out(&mut self, tag: plugin::Tag, message: MidiMessage) { 125 | // We could use MidiMessage directly with Box::into_raw, but we can't because of the Rust 126 | // memory layer. We couldn't free the allocated memory properly, because it's managed by 127 | // the host. So we just send parameters and instantiate FL's TMIDIOutMsg on the C side. 128 | // 129 | // The same is inside midi_out_del. 130 | unsafe { 131 | host_midi_out( 132 | *self.host_ptr.get_mut(), 133 | tag.0, 134 | message.status, 135 | message.data1, 136 | message.data2, 137 | message.port, 138 | ) 139 | }; 140 | } 141 | 142 | /// Send a delayed MIDI out message. This message will actually be sent when the MIDI tick has 143 | /// reached the current mixer tick. 144 | /// 145 | /// To be able to use this method, you should enable MIDI out for the plugin (see 146 | /// [`InfoBuilder::midi_out`](../plugin/struct.InfoBuilder.html#method.midi_out)). 147 | pub fn midi_out_del(&mut self, tag: plugin::Tag, message: MidiMessage) { 148 | unsafe { 149 | host_midi_out_del( 150 | *self.host_ptr.get_mut(), 151 | tag.0, 152 | message.status, 153 | message.data1, 154 | message.data2, 155 | message.port, 156 | ) 157 | }; 158 | } 159 | 160 | /// **MAY NOT WORK** 161 | /// 162 | /// Ask for a message to be dispatched to itself when the current mixing tick will be played 163 | /// (to synchronize stuff). 164 | /// 165 | /// See [`Plugin::loop_in`](../plugin/trait.Plugin.html#method.loop_in). 166 | /// 167 | /// The message is guaranteed to be dispatched, however it could be sent immediately if it 168 | /// couldn't be buffered (it's only buffered when playing). 169 | pub fn loop_out(&mut self, tag: plugin::Tag, message: ValuePtr) { 170 | unsafe { host_loop_out(*self.host_ptr.get_mut(), tag.0, message.0) }; 171 | } 172 | 173 | /// Remove the buffered message scheduled by 174 | /// [`Host::loop_out`](struct.Host.html#method.loop_out), so that it will never be dispatched. 175 | pub fn loop_kill(&mut self, tag: plugin::Tag, message: ValuePtr) { 176 | unsafe { host_loop_kill(*self.host_ptr.get_mut(), tag.0, message.0) }; 177 | } 178 | 179 | /// This is a function for thread synchronization. When this is called, no more voices shall be 180 | /// created and there will be no more rendering until 181 | /// [`Host::unlock_mix`](struct.Host.html#method.unlock_mix) has been called. 182 | pub fn lock_mix(&mut self) { 183 | unsafe { host_lock_mix(*self.host_ptr.get_mut()) }; 184 | } 185 | 186 | /// Unlocks the mix thread if it was previously locked with 187 | /// [`Host::lock_mix`](struct.Host.html#method.lock_mix). 188 | pub fn unlock_mix(&mut self) { 189 | unsafe { host_unlock_mix(*self.host_ptr.get_mut()) }; 190 | } 191 | 192 | /// **Warning: this function is not very performant, so avoid using it if possible.** 193 | /// 194 | /// This is an alternative to [`Host::lock_mix`](struct.Host.html#method.lock_mix). 195 | /// It won't freeze the audio. This function can only be called from the GUI thread! 196 | pub fn lock_plugin(&mut self, tag: plugin::Tag) { 197 | unsafe { host_lock_plugin(*self.host_ptr.get_mut(), tag.0) }; 198 | } 199 | 200 | /// **Warning: this function is not very performant, so avoid using it if possible.** 201 | /// 202 | /// Unlocks the mix thread if it was previously locked with 203 | /// [`Host::lock_plugin`](struct.Host.html#method.lock_plugin). 204 | pub fn unlock_plugin(&mut self, tag: plugin::Tag) { 205 | unsafe { host_unlock_plugin(*self.host_ptr.get_mut(), tag.0) }; 206 | } 207 | 208 | /// This is a function for thread synchronization. When this is called, no more voices shall be 209 | /// created and there will be no more rendering until 210 | /// [`Host::resume_out`](struct.Host.html#method.resume_out) has been called. Unlike 211 | /// [`Host::lock_mix`](struct.Host.html#method.lock_mix), this function also stops all sound. 212 | pub fn suspend_out(&mut self) { 213 | unsafe { host_suspend_out(*self.host_ptr.get_mut()) }; 214 | } 215 | 216 | /// Unlocks the mixer thread if it was previously locked with 217 | /// [`Host::suspend_out`](struct.Host.html#method.suspend_out). 218 | pub fn resume_out(&mut self) { 219 | unsafe { host_resume_out(*self.host_ptr.get_mut()) }; 220 | } 221 | 222 | /// Get one of the buffers. 223 | /// 224 | /// - `kind` the kind of the buffer you want to get 225 | /// (see [`host::Buffer`](../host/enum.Buffer.html)). 226 | /// - `length` is the buffer length (use the length of the output buffer passed to the render 227 | /// function). 228 | /// 229 | /// The buffers are valid only during 230 | /// [`Plugin::render`](../plugin/trait.Plugin.html#method.render) (i.e. it's supposed to be used 231 | /// inside this method only). 232 | pub fn buffer( 233 | &mut self, 234 | tag: plugin::Tag, 235 | kind: Buffer, 236 | length: usize, 237 | ) -> Option<&mut [[f32; 2]]> { 238 | match kind { 239 | Buffer::InputRead(value) => self.input_buf(tag, value, length), 240 | Buffer::OutputWrite(value) => self.output_buf(tag, value, length), 241 | Buffer::InsertWrite(value) => self.insert_buf(tag, value, length), 242 | Buffer::MixWrite(value) => self.mix_buf(value, length), 243 | Buffer::SendWrite(value) => self.send_buf(value, length), 244 | } 245 | } 246 | 247 | fn input_buf( 248 | &mut self, 249 | tag: plugin::Tag, 250 | offset: usize, 251 | length: usize, 252 | ) -> Option<&mut [[f32; 2]]> { 253 | let in_buf = 254 | unsafe { host_get_input_buf(*self.host_ptr.get_mut(), tag.0, offset as intptr_t) }; 255 | if in_buf.flags == 0 || length == 0 { 256 | return None; 257 | } 258 | Some(unsafe { slice::from_raw_parts_mut(in_buf.buffer as *mut [f32; 2], length) }) 259 | } 260 | 261 | fn output_buf( 262 | &mut self, 263 | tag: plugin::Tag, 264 | offset: usize, 265 | length: usize, 266 | ) -> Option<&mut [[f32; 2]]> { 267 | let out_buf = 268 | unsafe { host_get_output_buf(*self.host_ptr.get_mut(), tag.0, offset as intptr_t) }; 269 | if out_buf.flags == 0 || length == 0 { 270 | return None; 271 | } 272 | Some(unsafe { slice::from_raw_parts_mut(out_buf.buffer as *mut [f32; 2], length) }) 273 | } 274 | 275 | fn insert_buf( 276 | &mut self, 277 | tag: plugin::Tag, 278 | offset: isize, 279 | length: usize, 280 | ) -> Option<&mut [[f32; 2]]> { 281 | let insert_buf = unsafe { host_get_insert_buf(*self.host_ptr.get_mut(), tag.0, offset) }; 282 | if insert_buf.is_null() || length == 0 { 283 | return None; 284 | } 285 | Some(unsafe { slice::from_raw_parts_mut(insert_buf as *mut [f32; 2], length) }) 286 | } 287 | 288 | fn mix_buf(&mut self, offset: isize, length: usize) -> Option<&mut [[f32; 2]]> { 289 | let mix_buf = unsafe { host_get_mix_buf(*self.host_ptr.get_mut(), offset) }; 290 | if mix_buf.is_null() || length == 0 { 291 | return None; 292 | } 293 | Some(unsafe { slice::from_raw_parts_mut(mix_buf as *mut [f32; 2], length) }) 294 | } 295 | 296 | fn send_buf(&mut self, index: usize, length: usize) -> Option<&mut [[f32; 2]]> { 297 | let mix_buf = unsafe { host_get_send_buf(*self.host_ptr.get_mut(), index as intptr_t) }; 298 | if mix_buf.is_null() || length == 0 { 299 | return None; 300 | } 301 | Some(unsafe { slice::from_raw_parts_mut(mix_buf as *mut [f32; 2], length) }) 302 | } 303 | 304 | /// Get [`Voicer`](struct.Voicer.html) 305 | pub fn voice_handler(&self) -> Arc> { 306 | Arc::clone(&self.voicer) 307 | } 308 | 309 | /// Get [`OutVoicer`](struct.OutVoicer.html). 310 | pub fn out_voice_handler(&self) -> Arc> { 311 | Arc::clone(&self.out_voicer) 312 | } 313 | } 314 | 315 | #[no_mangle] 316 | extern "C" { 317 | fn host_on_parameter(host: *mut c_void, tag: intptr_t, index: c_int, value: c_int); 318 | fn host_on_controller(host: *mut c_void, tag: intptr_t, index: intptr_t, value: intptr_t); 319 | fn host_on_hint(host: *mut c_void, tag: intptr_t, text: *mut c_char); 320 | fn host_midi_out( 321 | host: *mut c_void, 322 | tag: intptr_t, 323 | status: c_uchar, 324 | data1: c_uchar, 325 | data2: c_uchar, 326 | port: c_uchar, 327 | ); 328 | fn host_midi_out_del( 329 | host: *mut c_void, 330 | tag: intptr_t, 331 | status: c_uchar, 332 | data1: c_uchar, 333 | data2: c_uchar, 334 | port: c_uchar, 335 | ); 336 | fn host_loop_out(host: *mut c_void, tag: intptr_t, message: intptr_t); 337 | fn host_loop_kill(host: *mut c_void, tag: intptr_t, message: intptr_t); 338 | fn host_lock_mix(host: *mut c_void); 339 | fn host_unlock_mix(host: *mut c_void); 340 | fn host_lock_plugin(host: *mut c_void, tag: intptr_t); 341 | fn host_unlock_plugin(host: *mut c_void, tag: intptr_t); 342 | fn host_suspend_out(host: *mut c_void); 343 | fn host_resume_out(host: *mut c_void); 344 | fn host_get_input_buf(host: *mut c_void, tag: intptr_t, offset: intptr_t) -> TIOBuffer; 345 | fn host_get_output_buf(host: *mut c_void, tag: intptr_t, offset: intptr_t) -> TIOBuffer; 346 | fn host_get_insert_buf(host: *mut c_void, tag: intptr_t, offset: intptr_t) -> *mut c_void; 347 | fn host_get_mix_buf(host: *mut c_void, offset: intptr_t) -> *mut c_void; 348 | fn host_get_send_buf(host: *mut c_void, offset: intptr_t) -> *mut c_void; 349 | } 350 | 351 | /// Type of the write-only buffer you want to get, using 352 | /// [`Host::buf_write`](../struct.Host.html#method.buf_write). 353 | #[derive(Debug)] 354 | pub enum Buffer { 355 | /// Multi input buffer for effects. 356 | /// 357 | /// The value is the buffer index. 358 | /// 359 | /// **Warning: Index starts at 1, to be compatible with 360 | /// [`Buffer::Insert`](enum.Buffer#variant.Insert) (index 0 would be render's function own 361 | /// buffer).** 362 | InputRead(usize), 363 | /// Multi output buffer for generators and effects. 364 | /// 365 | /// The value is the buffer index. 366 | /// 367 | /// **Warning: Index starts at 1, to be compatible with 368 | /// [`WriteBuffer::Insert`](../enum.WriteBuffer#variant.Insert) (index 0 would be render's 369 | /// function own buffer).** 370 | OutputWrite(usize), 371 | /// The insert buffer following the buffer a generator is currently processing in. This type is 372 | /// reserved for the Fruity wrapper (so it may change in the future). 373 | /// 374 | /// The value is offset to the current buffer. 0 means the same buffer as passed to 375 | /// [`Plugin::render`](../plugin/trait.Plugin.html#method.render), 1 means next insert track. 376 | InsertWrite(isize), 377 | /// Mixer track buffer relative to the current generator's track. 378 | /// 379 | /// The value is track index offset relative to the current generator's track index (i.e. 380 | /// value of 0 returns the current output buffer). 381 | /// 382 | /// Valid only for generators. 383 | MixWrite(isize), 384 | /// The send buffer (see 385 | /// [`host::Message::SetNumSends`](../enum.Message.html#variant.SetNumSends)). 386 | /// 387 | /// The value is the index of the send buffer. 388 | SendWrite(usize), 389 | } 390 | 391 | #[repr(C)] 392 | struct TIOBuffer { 393 | buffer: *mut c_void, 394 | flags: u32, 395 | } 396 | 397 | /// Use this to manually release, kill and notify voices about events. 398 | #[derive(Debug)] 399 | pub struct Voicer { 400 | host_ptr: AtomicPtr, 401 | } 402 | 403 | impl Voicer { 404 | fn new(host_ptr: AtomicPtr) -> Self { 405 | Self { host_ptr } 406 | } 407 | } 408 | 409 | impl SendVoiceHandler for Voicer { 410 | /// Tell the host the specified voice should be silent (Note Off). 411 | fn release(&mut self, tag: voice::Tag) { 412 | trace!("manully release voice {}", tag); 413 | unsafe { host_release_voice(*self.host_ptr.get_mut(), tag.0) }; 414 | } 415 | 416 | /// Tell the host that the specified voice can be killed (freed from memory). 417 | /// 418 | /// This method forces FL Studio to ask the plugin to destroy its voice. 419 | fn kill(&mut self, tag: voice::Tag) { 420 | trace!("manully kill voice {}", tag); 421 | unsafe { host_kill_voice(*self.host_ptr.get_mut(), tag.0) }; 422 | } 423 | 424 | /// Tell the host that some event has happened concerning the specified voice. 425 | fn on_event(&mut self, tag: voice::Tag, event: voice::Event) -> Option { 426 | Option::::from(event).map(|value| { 427 | ValuePtr(unsafe { host_on_voice_event(*self.host_ptr.get_mut(), tag.0, value) }) 428 | }) 429 | } 430 | } 431 | 432 | #[no_mangle] 433 | extern "C" { 434 | fn host_release_voice(host: *mut c_void, tag: intptr_t); 435 | fn host_kill_voice(host: *mut c_void, tag: intptr_t); 436 | fn host_on_voice_event(host: *mut c_void, tag: intptr_t, message: FlMessage) -> intptr_t; 437 | } 438 | 439 | /// Use this for operations with output voices (i.e. for VFX inside [patcher]( 440 | /// https://www.image-line.com/support/flstudio_online_manual/html/plugins/Patcher.htm)). 441 | #[derive(Debug)] 442 | pub struct OutVoicer { 443 | voices: HashMap, 444 | host_ptr: AtomicPtr, 445 | } 446 | 447 | impl OutVoicer { 448 | fn new(host_ptr: AtomicPtr) -> Self { 449 | Self { 450 | voices: HashMap::new(), 451 | host_ptr, 452 | } 453 | } 454 | } 455 | 456 | impl SendVoiceHandler for OutVoicer { 457 | /// It returns `None` if the output has no destination. 458 | fn trigger( 459 | &mut self, 460 | params: voice::Params, 461 | index: usize, 462 | tag: voice::Tag, 463 | ) -> Option<&mut dyn Voice> { 464 | let params_ptr = Box::into_raw(Box::new(params)); 465 | let inner_tag = unsafe { 466 | host_trig_out_voice(*self.host_ptr.get_mut(), params_ptr, index as i32, tag.0) 467 | }; 468 | 469 | if inner_tag == -1 { 470 | // if FVH_Null 471 | unsafe { Box::from_raw(params_ptr) }; // free the memory 472 | trace!("send trigger voice is null"); 473 | return None; 474 | } 475 | 476 | let voice = OutVoice::new(tag, AtomicPtr::new(params_ptr), voice::Tag(inner_tag)); 477 | trace!("send trigger output voice {:?}", voice); 478 | self.voices.insert(tag, voice); 479 | Some(self.voices.get_mut(&tag).unwrap()) 480 | } 481 | 482 | fn release(&mut self, tag: voice::Tag) { 483 | if let Some(voice) = self.voices.get_mut(&tag) { 484 | trace!("send release output voice {:?}", voice); 485 | unsafe { host_release_out_voice(*self.host_ptr.get_mut(), voice.inner_tag().0) } 486 | } 487 | } 488 | 489 | fn kill(&mut self, tag: voice::Tag) { 490 | if let Some(mut voice) = self.voices.remove(&tag) { 491 | trace!("send kill output voice {}", tag); 492 | unsafe { 493 | host_kill_out_voice(*self.host_ptr.get_mut(), voice.inner_tag().0); 494 | Box::from_raw(*voice.params_ptr.get_mut()); 495 | }; 496 | } 497 | } 498 | 499 | fn on_event(&mut self, tag: voice::Tag, event: voice::Event) -> Option { 500 | trace!("send event {:?} for out voice {:?}", event, tag); 501 | let host_ptr = *self.host_ptr.get_mut(); 502 | self.voices.get_mut(&tag).and_then(|voice| { 503 | Option::::from(event).map(|message| { 504 | ValuePtr(unsafe { host_on_out_voice_event(host_ptr, voice.inner_tag().0, message) }) 505 | }) 506 | }) 507 | } 508 | } 509 | 510 | /// Output voice. 511 | #[derive(Debug)] 512 | pub struct OutVoice { 513 | tag: voice::Tag, 514 | params_ptr: AtomicPtr, 515 | inner_tag: voice::Tag, 516 | } 517 | 518 | impl OutVoice { 519 | fn new(tag: voice::Tag, params_ptr: AtomicPtr, inner_tag: voice::Tag) -> Self { 520 | Self { 521 | tag, 522 | params_ptr, 523 | inner_tag, 524 | } 525 | } 526 | 527 | /// Get voice parameters. 528 | pub fn params(&mut self) -> voice::Params { 529 | let boxed_params = unsafe { Box::from_raw(*self.params_ptr.get_mut()) }; 530 | let params = boxed_params.clone(); 531 | self.params_ptr = AtomicPtr::new(Box::into_raw(boxed_params)); 532 | *params 533 | } 534 | 535 | /// Get inner tag. 536 | pub fn inner_tag(&self) -> voice::Tag { 537 | self.inner_tag 538 | } 539 | } 540 | 541 | impl Voice for OutVoice { 542 | fn tag(&self) -> voice::Tag { 543 | self.tag 544 | } 545 | } 546 | 547 | extern "C" { 548 | fn host_trig_out_voice( 549 | host: *mut c_void, 550 | params: *mut voice::Params, 551 | index: i32, 552 | tag: intptr_t, 553 | ) -> intptr_t; 554 | fn host_release_out_voice(host: *mut c_void, tag: intptr_t); 555 | fn host_kill_out_voice(host: *mut c_void, tag: intptr_t); 556 | fn host_on_out_voice_event(host: *mut c_void, tag: intptr_t, message: FlMessage) -> intptr_t; 557 | } 558 | 559 | /// Message from the host to the plugin. 560 | #[derive(Debug)] 561 | pub enum Message<'a> { 562 | /// Contains the handle of the parent window if the editor has to be shown. 563 | ShowEditor(Option<*mut c_void>), 564 | /// Change the processing mode flags. This can be ignored. 565 | /// 566 | /// The value is [ProcessModeFlags](../struct.ProcessModeFlags.html). 567 | ProcessMode(ProcessModeFlags), 568 | /// The continuity of processing is broken. This means that the user has jumped ahead or back 569 | /// in the playlist, for example. When this happens, the plugin needs to clear all buffers and 570 | /// start like new. 571 | /// 572 | /// **Warning: this can be called from the mixer thread!** 573 | Flush, 574 | /// This changes the maximum processing length, expressed in samples. 575 | /// 576 | /// The value is the new length. 577 | SetBlockSize(u32), 578 | /// This changes the sample rate. 579 | /// 580 | /// Value holds the new sample rate. 581 | SetSampleRate(u32), 582 | /// This allows the plugin to define how the editor window should be resized. 583 | /// 584 | /// The first value will hold a pointer to a rectangle (PRect) for the minimum (Left and Top) 585 | /// and maximum (Right and Bottom) width and height of the window. 586 | /// 587 | /// The second value holds a pointer (PPoint) to a point structure that defines by how much the 588 | /// window size should change horizontally and vertically when the user drags the border. 589 | WindowMinMax(*mut c_void, *mut c_void), 590 | /// (not used yet) The host has noticed that too much processing power is used and asks the 591 | /// plugin to kill its weakest voice. 592 | /// 593 | /// The plugin has to return `true` if it did anything, `false` otherwise. 594 | KillVoice, 595 | /// Only full generators have to respond to this message. It's meant to allow the cutoff and 596 | /// resonance parameters of a voice to be used for other purposes, if the generator doesn't use 597 | /// them as cutoff and resonance. 598 | /// 599 | /// - return `0u8` if the plugin doesn't support the default per-voice level value. 600 | /// - return `1u8` if the plugin supports the default per-voice level value (filter cutoff (0) 601 | /// or filter resonance (1)). 602 | /// - return `2u8` if the plugin supports the per-voice level value, but for another function 603 | /// (then check [`GetName::VoiceLevel`](../host/enum.GetName.html#variant.VoiceLevel) to 604 | /// provide your own names). 605 | UseVoiceLevels(u8), 606 | /// Called when the user selects a preset. 607 | /// 608 | /// The value tells you which one to set. 609 | SetPreset(u64), 610 | /// A sample has been loaded into the parent channel. This is given to the plugin as a 611 | /// wavetable, in the same format as the WaveTables member of TFruityPlugin. Also see 612 | /// FPF_GetChanCustomShape. 613 | /// 614 | /// The value holds the new shape. 615 | ChanSampleChanged(&'a [f32]), 616 | /// The host has enabled/disabled the plugin. 617 | /// 618 | /// The value will contain the new state (`false` for disabled, `true` for enabled) 619 | /// 620 | /// **Warning: this can be called from the mixer thread!** 621 | SetEnabled(bool), 622 | /// The host is playing or stopped. 623 | /// 624 | /// The value is playing status. 625 | /// 626 | /// **Warning: can be called from the mixing thread.** 627 | SetPlaying(bool), 628 | /// The song position has jumped from one position to another non-consecutive position. 629 | /// 630 | /// **Warning: can be called from the mixing thread.** 631 | SongPosChanged, 632 | /// The time signature has changed. 633 | /// 634 | /// The value is [`TimeSignature`](../struct.TimeSignature.html). 635 | SetTimeSig(TimeSignature), 636 | /// This is called to let the plugin tell the host which files need to be collected or put in 637 | /// zip files. 638 | /// 639 | /// The value holds the file index, which starts at 0. 640 | /// 641 | /// The name of the file is passed to the host as a `String` in the result of the 642 | /// dispatcher function. The host keeps calling this until the plugin returns zero. 643 | CollectFile(usize), 644 | /// (private message to known plugins, ignore) tells the plugin to update a specific, 645 | /// non-automated param. 646 | SetInternalParam, 647 | /// This tells the plugin how many send tracks there are (fixed to 4, but could be set by the 648 | /// user at any time in a future update). 649 | /// 650 | /// The value holds the number of send tracks. 651 | SetNumSends(u64), 652 | /// Called when a file has been dropped onto the parent channel's button. 653 | /// 654 | /// The value holds filename. 655 | LoadFile(String), 656 | /// Set fit to time in beats. 657 | /// 658 | /// The value holds the time. 659 | SetFitTime(f32), 660 | /// Sets the number of samples in each tick. This value changes when the tempo, ppq or sample 661 | /// rate have changed. 662 | /// 663 | /// **Warning: can be called from the mixing thread.** 664 | SetSamplesPerTick(f32), 665 | /// Sets the frequency at which Idle is called. 666 | /// 667 | /// The value holds the new time (milliseconds). 668 | SetIdleTime(u64), 669 | /// (FL 7.0) The host has focused/unfocused the editor (focused in the value) (plugin can use 670 | /// this to steal keyboard focus). 671 | SetFocus(bool), 672 | /// (FL 8.0) This is sent by the host for special transport messages, from a controller. 673 | /// 674 | /// The value is the type of message (see [`Transport`](../enum.Transport.html)). 675 | /// 676 | /// Result should be `true` if handled, `false` otherwise. 677 | Transport(Transport), 678 | /// (FL 8.0) Live MIDI input preview. This allows the plugin to steal messages (mostly for 679 | /// transport purposes). 680 | /// 681 | /// The value has the packed MIDI message. Only note on/off for now. 682 | /// 683 | /// Result should be `true` if handled, `false` otherwise. 684 | MidiIn(MidiMessage), 685 | /// Mixer routing changed, must use 686 | /// [`PluginMessage::GetInOuts`](../plugin/enum.PluginMessage.html#variant.GetInOuts) if 687 | /// necessary. 688 | RoutingChanged, 689 | /// Retrieves info about a parameter. 690 | /// 691 | /// The value is the parameter number. 692 | /// 693 | /// see [`ParameterFlags`](../struct.ParameterFlags.html) for the result. 694 | GetParamInfo(usize), 695 | /// Called after a project has been loaded, to leave a chance to kill automation (that could be 696 | /// loaded after the plugin is created) if necessary. 697 | ProjLoaded, 698 | /// (private message to the plugin wrapper) Load a (VST, DX) plugin state, 699 | /// 700 | WrapperLoadState, 701 | /// Called when the settings button on the titlebar is switched. 702 | /// 703 | /// On/off in value. 704 | ShowSettings(bool), 705 | /// Input (the first value)/output (the second value) latency of the output, in samples (only 706 | /// for information) 707 | SetIoLatency(u32, u32), 708 | /// (message from Patcher) retrieves the preferred number of audio inputs (the value is `0`), 709 | /// audio outputs (the value is `1`) or voice outputs (the value is `2`) 710 | /// 711 | /// Result has to be: 712 | /// 713 | /// * `0i32` - default number. 714 | /// * `-1i32` - none. 715 | PreferredNumIo(u8), 716 | /// Unknown message. 717 | Unknown, 718 | } 719 | 720 | impl From for Message<'_> { 721 | fn from(message: FlMessage) -> Self { 722 | trace!("host::Message::from {:?}", message); 723 | 724 | let result = match message.id { 725 | 0 => Message::from_show_editor(message), 726 | 1 => Message::from_process_mode(message), 727 | 2 => Message::Flush, 728 | 3 => Message::SetBlockSize(message.value as u32), 729 | 4 => Message::SetSampleRate(message.value as u32), 730 | 5 => Message::WindowMinMax(message.index as *mut c_void, message.value as *mut c_void), 731 | 6 => Message::KillVoice, 732 | 7 => Message::UseVoiceLevels(message.index as u8), 733 | 9 => Message::SetPreset(message.index as u64), 734 | 10 => Message::from_chan_sample_changed(message), 735 | 11 => Message::SetEnabled(message.value != 0), 736 | 12 => Message::SetPlaying(message.value != 0), 737 | 13 => Message::SongPosChanged, 738 | 14 => Message::SetTimeSig(TTimeSigInfo::from_raw_ptr(message.value).into()), 739 | 15 => Message::CollectFile(message.index as usize), 740 | 16 => Message::SetInternalParam, 741 | 17 => Message::SetNumSends(message.value as u64), 742 | 18 => Message::LoadFile(String::from_raw_ptr(message.value)), 743 | 19 => Message::SetFitTime(f32::from_bits(message.value as i32 as u32)), 744 | 20 => Message::SetSamplesPerTick(f32::from_bits(message.value as i32 as u32)), 745 | 21 => Message::SetIdleTime(message.value as u64), 746 | 22 => Message::SetFocus(message.value != 0), 747 | 23 => Message::Transport(message.into()), 748 | 24 => Message::MidiIn((message.value as c_int).into()), 749 | 25 => Message::RoutingChanged, 750 | 26 => Message::GetParamInfo(message.index as usize), 751 | 27 => Message::ProjLoaded, 752 | 28 => Message::WrapperLoadState, 753 | 29 => Message::ShowSettings(message.value != 0), 754 | 30 => Message::SetIoLatency(message.index as u32, message.value as u32), 755 | 32 => Message::PreferredNumIo(message.index as u8), 756 | _ => Message::Unknown, 757 | }; 758 | 759 | trace!("host::Message::{:?}", result); 760 | 761 | result 762 | } 763 | } 764 | 765 | impl Message<'_> { 766 | fn from_show_editor(message: FlMessage) -> Self { 767 | if message.value == 1 { 768 | Message::ShowEditor(None) 769 | } else { 770 | Message::ShowEditor(Some(message.value as *mut c_void)) 771 | } 772 | } 773 | 774 | fn from_process_mode(message: FlMessage) -> Self { 775 | let flags = ProcessModeFlags::from_bits_truncate(message.value); 776 | Message::ProcessMode(flags) 777 | } 778 | 779 | fn from_chan_sample_changed(message: FlMessage) -> Self { 780 | let slice = 781 | unsafe { std::slice::from_raw_parts_mut(message.value as *mut f32, WAVETABLE_SIZE) }; 782 | Message::ChanSampleChanged(slice) 783 | } 784 | } 785 | 786 | /// The host sends this message when it wants to know a text representation of some value. 787 | /// 788 | /// See [`Plugin::name_of`](../plugin/trait.Plugin.html#tymethod.name_of) 789 | #[derive(Debug)] 790 | pub enum GetName { 791 | /// Retrieve the name of a parameter. 792 | /// 793 | /// Value specifies parameter index. 794 | Param(usize), 795 | /// Retrieve the text representation of the value of a parameter for use in the event editor. 796 | /// 797 | /// The first value specifies parameter index. 798 | /// 799 | /// The second value specifies value. 800 | ParamValue(usize, isize), 801 | /// Retrieve the name of a note in piano roll. 802 | /// 803 | /// The first value specifies note index. 804 | /// 805 | /// The second one specifies the color (or MIDI channel). 806 | Semitone(u8, u8), 807 | /// (not used yet) Retrieve the name of a patch. 808 | /// 809 | /// The value specifies patch index. 810 | Patch(usize), 811 | /// (optional) Retrieve the name of a per-voice parameter, specified by the value. 812 | /// 813 | /// Default is filter cutoff (value=0) and resonance (value=1). 814 | VoiceLevel(usize), 815 | /// Longer description for per-voice parameter (works like 816 | /// [`VoiceLevel`](enum.GetName.html#variant.VoiceLevel)) 817 | VoiceLevelHint(usize), 818 | /// This is called when the host wants to know the name of a preset, for plugins that support 819 | /// presets (see 820 | /// [`PluginMessage::SetNumPresets`](../plugin/enum.PluginMessage.html#variant.SetNumPresets)). 821 | /// 822 | /// The value specifies preset index. 823 | Preset(usize), 824 | /// For plugins that output controllers, retrieve the name of output controller. 825 | /// 826 | /// The value specifies controller index. 827 | OutCtrl(usize), 828 | /// Retrieve name of per-voice color (MIDI channel). 829 | /// 830 | /// The value specifies the color. 831 | VoiceColor(u8), 832 | /// For plugins that output voices, retrieve the name of output voice. 833 | /// 834 | /// The value specifies voice index. 835 | OutVoice(usize), 836 | /// Message ID is unknown 837 | Unknown, 838 | } 839 | 840 | impl From for GetName { 841 | fn from(message: FlMessage) -> Self { 842 | trace!("GetName::from {:?}", message); 843 | 844 | let result = match message.id { 845 | 0 => GetName::Param(message.index as usize), 846 | 1 => GetName::ParamValue(message.index as usize, message.value), 847 | 2 => GetName::Semitone(message.index as u8, message.value as u8), 848 | 3 => GetName::Patch(message.index as usize), 849 | 4 => GetName::VoiceLevel(message.index as usize), 850 | 5 => GetName::VoiceLevelHint(message.index as usize), 851 | 6 => GetName::Preset(message.index as usize), 852 | 7 => GetName::OutCtrl(message.index as usize), 853 | 8 => GetName::VoiceColor(message.index as u8), 854 | 9 => GetName::OutVoice(message.index as usize), 855 | _ => GetName::Unknown, 856 | }; 857 | 858 | trace!("GetName::{:?}", result); 859 | 860 | result 861 | } 862 | } 863 | 864 | impl From for Option { 865 | fn from(value: GetName) -> Self { 866 | match value { 867 | GetName::Param(index) => Some(FlMessage { 868 | id: 0, 869 | index: index.as_raw_ptr(), 870 | value: 0, 871 | }), 872 | GetName::ParamValue(index, value) => Some(FlMessage { 873 | id: 1, 874 | index: index.as_raw_ptr(), 875 | value, 876 | }), 877 | GetName::Semitone(index, value) => Some(FlMessage { 878 | id: 2, 879 | index: index.as_raw_ptr(), 880 | value: value.as_raw_ptr(), 881 | }), 882 | GetName::Patch(index) => Some(FlMessage { 883 | id: 3, 884 | index: index.as_raw_ptr(), 885 | value: 0, 886 | }), 887 | GetName::VoiceLevel(index) => Some(FlMessage { 888 | id: 4, 889 | index: index.as_raw_ptr(), 890 | value: 0, 891 | }), 892 | GetName::VoiceLevelHint(index) => Some(FlMessage { 893 | id: 5, 894 | index: index.as_raw_ptr(), 895 | value: 0, 896 | }), 897 | GetName::Preset(index) => Some(FlMessage { 898 | id: 6, 899 | index: index.as_raw_ptr(), 900 | value: 0, 901 | }), 902 | GetName::OutCtrl(index) => Some(FlMessage { 903 | id: 7, 904 | index: index.as_raw_ptr(), 905 | value: 0, 906 | }), 907 | GetName::VoiceColor(index) => Some(FlMessage { 908 | id: 8, 909 | index: index.as_raw_ptr(), 910 | value: 0, 911 | }), 912 | GetName::OutVoice(index) => Some(FlMessage { 913 | id: 9, 914 | index: index.as_raw_ptr(), 915 | value: 0, 916 | }), 917 | GetName::Unknown => None, 918 | } 919 | } 920 | } 921 | 922 | /// Event IDs. 923 | #[derive(Debug)] 924 | pub enum Event { 925 | /// The tempo has changed. 926 | /// 927 | /// First value holds the tempo. 928 | /// 929 | /// Second value holds the average samples per tick. 930 | Tempo(f32, u32), 931 | /// The maximum polyphony has changed. This is only of intrest to standalone generators. 932 | /// 933 | /// Value will hold the new maximum polyphony. A value <= 0 will mean infinite polyphony. 934 | MaxPoly(i32), 935 | /// The MIDI channel panning has changed. 936 | /// 937 | /// First value holds the new pan (0..127). 938 | /// 939 | /// Second value holds pan in -64..64 range. 940 | MidiPan(u8, i8), 941 | /// The MIDI channel volume has changed. 942 | /// 943 | /// First value holds the new volume (0..127). 944 | /// 945 | /// Second value also holds the new volume. It's in the range 0..1. 946 | MidiVol(u8, f32), 947 | /// The MIDI channel pitch has changed. 948 | /// 949 | /// Value will hold the new value in *cents*. 950 | /// 951 | /// This has to be translated according to the current pitch bend range. 952 | MidiPitch(i32), 953 | /// Unknown event. 954 | Unknown, 955 | } 956 | 957 | impl From for Event { 958 | fn from(message: FlMessage) -> Self { 959 | trace!("Event::from {:?}", message); 960 | 961 | let result = match message.id { 962 | 0 => Event::Tempo(f32::from_raw_ptr(message.index), message.value as u32), 963 | 1 => Event::MaxPoly(message.index as i32), 964 | 2 => Event::MidiPan(message.index as u8, message.value as i8), 965 | 3 => Event::MidiVol(message.index as u8, f32::from_raw_ptr(message.value)), 966 | 4 => Event::MidiPitch(message.index as i32), 967 | _ => Event::Unknown, 968 | }; 969 | 970 | trace!("Event::{:?}", result); 971 | 972 | result 973 | } 974 | } 975 | -------------------------------------------------------------------------------- /src/plugin/message.rs: -------------------------------------------------------------------------------- 1 | //! Plugin messages. 2 | use std::mem; 3 | use std::os::raw::{c_int, c_void}; 4 | 5 | use crate::host::{GetName, Host}; 6 | use crate::plugin; 7 | use crate::{ 8 | intptr_t, AsRawPtr, FlMessage, MessageBoxFlags, MessageBoxResult, NameColor, Note, Notes, 9 | ParamMenuEntry, SongTime, TNameColor, TParamMenuEntry, Tag, Time, TimeFormat, ValuePtr, 10 | }; 11 | 12 | /// Messsage which you can send to the host using 13 | /// [`Host::on_message`](../../host/struct.Host.html#method.on_message). 14 | pub trait Message { 15 | /// The result returned after sending the message. 16 | type Return; 17 | 18 | /// Send the message. 19 | fn send(self, tag: plugin::Tag, host: &mut Host) -> Self::Return; 20 | } 21 | 22 | macro_rules! impl_message { 23 | ($message: ident) => { 24 | impl Message for $message { 25 | type Return = (); 26 | 27 | fn send(self, tag: plugin::Tag, host: &mut Host) -> Self::Return { 28 | unsafe { 29 | host_on_message(*host.host_ptr.get_mut(), tag.0, self.into()); 30 | } 31 | } 32 | } 33 | }; 34 | } 35 | 36 | macro_rules! impl_message_ty { 37 | ($message: ident, $res: ty) => { 38 | impl Message for $message { 39 | type Return = $res; 40 | 41 | fn send(self, tag: plugin::Tag, host: &mut Host) -> Self::Return { 42 | ValuePtr(unsafe { host_on_message(*host.host_ptr.get_mut(), tag.0, self.into()) }) 43 | .get::<$res>() 44 | } 45 | } 46 | }; 47 | } 48 | 49 | extern "C" { 50 | fn host_on_message(host: *mut c_void, plugin_tag: Tag, message: FlMessage) -> intptr_t; 51 | } 52 | 53 | /// Tells the host that the user has clicked an item of the control popup menu. 54 | /// 55 | /// The first value holds the parameter index. 56 | /// 57 | /// The second value holds the popup item index. 58 | #[derive(Debug)] 59 | pub struct ParamMenu(pub usize, pub usize); 60 | 61 | impl_message!(ParamMenu); 62 | 63 | impl From for FlMessage { 64 | fn from(message: ParamMenu) -> Self { 65 | FlMessage { 66 | id: 0, 67 | index: message.0.as_raw_ptr(), 68 | value: message.1.as_raw_ptr(), 69 | } 70 | } 71 | } 72 | 73 | /// Notify the host that the editor has been resized. 74 | #[derive(Debug)] 75 | pub struct EditorResized; 76 | 77 | impl_message!(EditorResized); 78 | 79 | impl From for FlMessage { 80 | fn from(_message: EditorResized) -> Self { 81 | FlMessage { 82 | id: 2, 83 | index: 0, 84 | value: 0, 85 | } 86 | } 87 | } 88 | 89 | /// Notify the host that names ([`Plugin::name_of`](../trait.Plugin.html#tymethod.name_of)) have 90 | /// changed, with the type of names in value (see [`GetName`](../../host/enum.GetName.html)). 91 | #[derive(Debug)] 92 | pub struct NamesChanged(pub GetName); 93 | 94 | impl_message!(NamesChanged); 95 | 96 | impl From for FlMessage { 97 | fn from(message: NamesChanged) -> Self { 98 | FlMessage { 99 | id: 3, 100 | index: 0, 101 | value: Option::::from(message.0) 102 | .map(|msg| msg.id) 103 | .unwrap_or_default(), 104 | } 105 | } 106 | } 107 | 108 | /// This makes the host enable its MIDI output. This is useful when a MIDI out plugin is 109 | /// created (a plugin which will send midi messages to external midi hardware, most likely). 110 | #[derive(Debug)] 111 | pub struct ActivateMidi; 112 | 113 | impl_message!(ActivateMidi); 114 | 115 | impl From for FlMessage { 116 | fn from(_: ActivateMidi) -> Self { 117 | FlMessage { 118 | id: 4, 119 | index: 0, 120 | value: 0, 121 | } 122 | } 123 | } 124 | 125 | /// The plugin either wants to be notified about MIDI messages (for processing or filtering), or 126 | /// wants to stop being notified about them. 127 | /// 128 | /// Value tells the host whether the plugin want to be notified or not (`true` to be added to the 129 | /// list of plugins that are notified, `false` to be removed from the list). 130 | #[derive(Debug)] 131 | pub struct WantMidiInput(pub bool); 132 | 133 | impl_message!(WantMidiInput); 134 | 135 | impl From for FlMessage { 136 | fn from(message: WantMidiInput) -> Self { 137 | FlMessage { 138 | id: 5, 139 | index: 0, 140 | value: message.0.as_raw_ptr(), 141 | } 142 | } 143 | } 144 | 145 | /// Ask the host to kill the automation linked to the plugin. This can for example be used for a 146 | /// demo version of the plugin. The host will kill all automation information in the range ... So to kill the automation for all parameters, you'd call the method 148 | /// with = 0 and = - 1. 149 | /// 150 | /// The first value is the first parameter index for which to kill the automation. 151 | /// 152 | /// The second value is the last parameter index for which to kill the automation (inclusive). 153 | #[derive(Debug)] 154 | pub struct KillAutomation(pub usize, pub usize); 155 | 156 | impl_message!(KillAutomation); 157 | 158 | impl From for FlMessage { 159 | fn from(message: KillAutomation) -> Self { 160 | FlMessage { 161 | id: 8, 162 | index: message.0.as_raw_ptr(), 163 | value: message.1.as_raw_ptr(), 164 | } 165 | } 166 | } 167 | 168 | /// This tells the host how many presets are supported by the plugin (this is mainly used by the 169 | /// wrapper plugin). 170 | /// 171 | /// The value holds the number of presets. 172 | #[derive(Debug)] 173 | pub struct SetNumPresets(pub usize); 174 | 175 | impl_message!(SetNumPresets); 176 | 177 | impl From for FlMessage { 178 | fn from(message: SetNumPresets) -> Self { 179 | FlMessage { 180 | id: 9, 181 | index: 0, 182 | value: message.0.as_raw_ptr(), 183 | } 184 | } 185 | } 186 | 187 | /// Sets a new short name for the parent. 188 | /// 189 | /// The value is the new name. 190 | #[derive(Debug)] 191 | pub struct SetNewName(pub String); 192 | 193 | impl_message!(SetNewName); 194 | 195 | impl From for FlMessage { 196 | fn from(message: SetNewName) -> Self { 197 | FlMessage { 198 | id: 10, 199 | index: 0, 200 | value: message.0.as_raw_ptr(), 201 | } 202 | } 203 | } 204 | 205 | /// Used by the VSTi wrapper, because the dumb VSTGUI needs idling for his knobs. 206 | #[derive(Debug)] 207 | pub struct VstiIdle; 208 | 209 | impl_message!(VstiIdle); 210 | 211 | impl From for FlMessage { 212 | fn from(_message: VstiIdle) -> Self { 213 | FlMessage { 214 | id: 11, 215 | index: 0, 216 | value: 0, 217 | } 218 | } 219 | } 220 | 221 | // Ask the host to open a selector for its channel sample (Also see FPF_UseChanSample). 222 | // 223 | // HYBRID GENERATORS ARE DEPRECATED. 224 | // pub struct SelectChanSample; 225 | 226 | /// Tell the host that the plugin wants to receive the idle message (or not). Idle messages are 227 | /// received by default. 228 | #[derive(Debug)] 229 | pub enum WantIdle { 230 | /// Disabled. 231 | Disabled, 232 | /// Enabled when UI is visible (default). 233 | EnabledVisible, 234 | /// Always enabled. 235 | EnabledAlways, 236 | } 237 | 238 | impl_message!(WantIdle); 239 | 240 | impl From for FlMessage { 241 | fn from(message: WantIdle) -> Self { 242 | FlMessage { 243 | id: 13, 244 | index: 0, 245 | value: message.into(), 246 | } 247 | } 248 | } 249 | 250 | impl From for intptr_t { 251 | fn from(message: WantIdle) -> Self { 252 | match message { 253 | WantIdle::Disabled => 0, 254 | WantIdle::EnabledVisible => 1, 255 | WantIdle::EnabledAlways => 2, 256 | } 257 | } 258 | } 259 | 260 | /// Ask the host to search for a file in its search paths. 261 | /// 262 | /// Value should hold the simple filename. 263 | /// 264 | /// The full path is returned as result of the function (`String`). 265 | #[derive(Debug)] 266 | pub struct LocateDataFile(pub String); 267 | 268 | impl_message_ty!(LocateDataFile, String); 269 | 270 | impl From for FlMessage { 271 | fn from(message: LocateDataFile) -> Self { 272 | FlMessage { 273 | id: 14, 274 | index: 0, 275 | value: message.0.as_raw_ptr(), 276 | } 277 | } 278 | } 279 | 280 | /// Translate tick time into Bar:Step:Tick (warning: it's *not* Bar:Beat:Tick). 281 | /// 282 | /// The value should hold the tick time to translate. 283 | /// 284 | /// The result is [`SongTime`](../struct.SongTime.html). 285 | #[derive(Debug)] 286 | pub struct TicksToTime(pub u32); 287 | 288 | impl Message for TicksToTime { 289 | type Return = SongTime; 290 | 291 | fn send(self, tag: plugin::Tag, host: &mut Host) -> Self::Return { 292 | let message = FlMessage::from(self); 293 | let time_ptr = message.index; 294 | unsafe { host_on_message(*host.host_ptr.get_mut(), tag.0, message) }; 295 | ValuePtr(time_ptr).get::() 296 | } 297 | } 298 | 299 | impl From for FlMessage { 300 | fn from(message: TicksToTime) -> Self { 301 | let time = SongTime::default(); 302 | FlMessage { 303 | id: 16, 304 | index: (Box::into_raw(Box::new(time)) as *mut c_void).as_raw_ptr(), 305 | value: message.0.as_raw_ptr(), 306 | } 307 | } 308 | } 309 | 310 | /// Ask the host to add one or more notes to the piano roll. 311 | #[derive(Debug)] 312 | pub struct AddToPianoRoll(pub Notes); 313 | 314 | impl_message!(AddToPianoRoll); 315 | 316 | impl From for FlMessage { 317 | fn from(mut message: AddToPianoRoll) -> Self { 318 | message.0.notes.shrink_to_fit(); 319 | let notes_ptr = message.0.notes.as_mut_ptr(); 320 | let len = message.0.notes.len(); 321 | 322 | let p_notes_params = unsafe { 323 | init_p_notes_params( 324 | 1, 325 | message.0.flags.bits() as c_int, 326 | message.0.channel.map(|v| v as c_int).unwrap_or(-1), 327 | message.0.pattern.map(|v| v as c_int).unwrap_or(-1), 328 | notes_ptr, 329 | len as c_int, 330 | ) 331 | }; 332 | 333 | FlMessage { 334 | id: 17, 335 | index: 0, 336 | value: p_notes_params, 337 | } 338 | } 339 | } 340 | 341 | extern "C" { 342 | // target: 343 | // 0=step seq (not supported yet), 1=piano roll 344 | // 345 | // so we always use 1 for this 346 | fn init_p_notes_params( 347 | target: c_int, 348 | flags: c_int, 349 | ch_num: c_int, 350 | pat_num: c_int, 351 | notes: *mut Note, 352 | count: c_int, 353 | ) -> intptr_t; 354 | } 355 | 356 | /// Before the popup menu is shown, you must fill it with the entries set by the host. You use this 357 | /// message to find out which those are. 358 | /// 359 | /// First value is the parameter index. 360 | /// 361 | /// Second value holds the popup item index. 362 | /// 363 | /// The result is [`Option`](../struct.ParamMenuEntry.html). 364 | #[derive(Debug)] 365 | pub struct GetParamMenuEntry(pub usize, pub usize); 366 | 367 | impl Message for GetParamMenuEntry { 368 | type Return = Option; 369 | 370 | fn send(self, tag: plugin::Tag, host: &mut Host) -> Self::Return { 371 | let message = FlMessage::from(self); 372 | let result = unsafe { host_on_message(*host.host_ptr.get_mut(), tag.0, message) }; 373 | 374 | if (result as *mut c_void).is_null() { 375 | return None; 376 | } 377 | 378 | Some(ParamMenuEntry::from_ffi( 379 | result as *mut c_void as *mut TParamMenuEntry, 380 | )) 381 | } 382 | } 383 | 384 | impl From for FlMessage { 385 | fn from(message: GetParamMenuEntry) -> Self { 386 | FlMessage { 387 | id: 18, 388 | index: message.0.as_raw_ptr(), 389 | value: message.1.as_raw_ptr(), 390 | } 391 | } 392 | } 393 | 394 | /// This will make FL to show a message box. 395 | /// 396 | /// The first value is the message box title. 397 | /// 398 | /// The second value is the message. 399 | /// 400 | /// The third value is flags (see [`MessageBoxFlags`](../../struct.MessageBoxFlags.html)). 401 | /// 402 | /// The result is [`MessageBoxResult`](../../enum.MessageBoxResult.html). 403 | #[derive(Debug)] 404 | pub struct MessageBox(pub String, pub String, pub MessageBoxFlags); 405 | 406 | impl_message_ty!(MessageBox, MessageBoxResult); 407 | 408 | impl From for FlMessage { 409 | fn from(message: MessageBox) -> Self { 410 | FlMessage { 411 | id: 19, 412 | index: format!("{}|{}", message.0, message.1).as_raw_ptr(), 413 | value: message.2.as_raw_ptr(), 414 | } 415 | } 416 | } 417 | 418 | /// Turn on a preview note. 419 | /// 420 | /// The first value is the note number. 421 | /// 422 | /// The second value is the color (or MIDI channel). 423 | /// 424 | /// The third value is the velocity. 425 | #[derive(Debug)] 426 | pub struct NoteOn(pub u8, pub u8, pub u8); 427 | 428 | impl_message!(NoteOn); 429 | 430 | impl From for FlMessage { 431 | fn from(message: NoteOn) -> Self { 432 | FlMessage { 433 | id: 20, 434 | index: dword_from_note_and_ch(message.0, message.1).as_raw_ptr(), 435 | value: message.2.as_raw_ptr(), 436 | } 437 | } 438 | } 439 | 440 | /// Turn a preview note off. 441 | /// 442 | /// The value is note number. 443 | #[derive(Debug)] 444 | pub struct NoteOff(pub u8); 445 | 446 | impl_message!(NoteOff); 447 | 448 | impl From for FlMessage { 449 | fn from(message: NoteOff) -> Self { 450 | FlMessage { 451 | id: 21, 452 | index: message.0.as_raw_ptr(), 453 | value: 0, 454 | } 455 | } 456 | } 457 | 458 | /// This shows a hint message in the FL hint area. It's the same as OnHint, but shows it 459 | /// immediately (to show a progress while you're doing something). 460 | /// 461 | /// The value is the message. 462 | #[derive(Debug)] 463 | pub struct OnHintDirect(pub String); 464 | 465 | impl_message!(OnHintDirect); 466 | 467 | impl From for FlMessage { 468 | fn from(message: OnHintDirect) -> Self { 469 | FlMessage { 470 | id: 22, 471 | index: 0, 472 | value: message.0.as_raw_ptr(), 473 | } 474 | } 475 | } 476 | 477 | /// Use this code to set a new color for the parent. 478 | /// 479 | /// The value is the color. 480 | /// 481 | /// (also see [`SetNewName`](../struct.SetNewName.html)). 482 | #[derive(Debug)] 483 | pub struct SetNewColor(pub u8); 484 | 485 | impl_message!(SetNewColor); 486 | 487 | impl From for FlMessage { 488 | fn from(message: SetNewColor) -> Self { 489 | FlMessage { 490 | id: 23, 491 | index: 0, 492 | value: message.0.as_raw_ptr(), 493 | } 494 | } 495 | } 496 | 497 | // This returns the module instance of the host in Windows. This could be an exe or a DLL, so it 498 | // won't be the process itself. 499 | // #[derive(Debug)] 500 | // pub struct GetInstance; 501 | 502 | // impl From for FlMessage { 503 | // fn from(_: GetInstance) -> Self { 504 | // FlMessage { 505 | // id: 24, 506 | // index: 0, 507 | // value: 0, 508 | // } 509 | // } 510 | // } 511 | 512 | /// Ask the host to kill anything linked to an internal controller. This is used when undeclaring 513 | /// internal controllers. 514 | /// 515 | /// The first value is the index of the first internal controller to kill. 516 | /// 517 | /// The second value is the index of the last internal controller to kill. 518 | #[derive(Debug)] 519 | pub struct KillIntCtrl(pub usize, pub usize); 520 | 521 | impl_message!(KillIntCtrl); 522 | 523 | impl From for FlMessage { 524 | fn from(message: KillIntCtrl) -> Self { 525 | FlMessage { 526 | id: 25, 527 | index: message.0.as_raw_ptr(), 528 | value: message.1.as_raw_ptr(), 529 | } 530 | } 531 | } 532 | 533 | /// Call this to override the number of parameters that this plugin instance has. This is meant for 534 | /// plugins that have a different set of parameters per instance. 535 | /// 536 | /// The value holds the new number of parameters. 537 | #[derive(Debug)] 538 | pub struct SetNumParams(pub usize); 539 | 540 | impl_message!(SetNumParams); 541 | 542 | impl From for FlMessage { 543 | fn from(message: SetNumParams) -> Self { 544 | FlMessage { 545 | id: 27, 546 | index: 0, 547 | value: message.0.as_raw_ptr(), 548 | } 549 | } 550 | } 551 | 552 | /// Ask the host to create a filename relative to the FL Studio data folder. This makes it much 553 | /// faster to look for this file (samples, for example) when the song is loaded again. 554 | /// 555 | /// The value is the full name. 556 | /// 557 | /// The result is the packed filename `String`. 558 | #[derive(Debug)] 559 | pub struct PackDataFile(pub String); 560 | 561 | impl_message_ty!(PackDataFile, String); 562 | 563 | impl From for FlMessage { 564 | fn from(message: PackDataFile) -> Self { 565 | FlMessage { 566 | id: 28, 567 | index: 0, 568 | value: message.0.as_raw_ptr(), 569 | } 570 | } 571 | } 572 | 573 | /// Ask the host where the FL Studio engine DLL is. This may be different from the location of the 574 | /// executable. It can be used to discover the location of the FL Studio data path. 575 | /// 576 | /// The result is the path `String`. 577 | #[derive(Debug)] 578 | pub struct GetProgPath; 579 | 580 | impl_message_ty!(GetProgPath, String); 581 | 582 | impl From for FlMessage { 583 | fn from(_: GetProgPath) -> Self { 584 | FlMessage { 585 | id: 29, 586 | index: 0, 587 | value: 0, 588 | } 589 | } 590 | } 591 | 592 | /// Set the plugin latency, if any. 593 | /// 594 | /// The value is the latency in samples. 595 | #[derive(Debug)] 596 | pub struct SetLatency(pub u32); 597 | 598 | impl_message!(SetLatency); 599 | 600 | impl From for FlMessage { 601 | fn from(message: SetLatency) -> Self { 602 | FlMessage { 603 | id: 30, 604 | index: 0, 605 | value: message.0.as_raw_ptr(), 606 | } 607 | } 608 | } 609 | 610 | /// (FL 6.0) Ask the host to show the preset downloader/selector for this plugin. 611 | #[derive(Debug)] 612 | pub struct CallDownloader; 613 | 614 | impl_message!(CallDownloader); 615 | 616 | impl From for FlMessage { 617 | fn from(_: CallDownloader) -> Self { 618 | FlMessage { 619 | id: 31, 620 | index: 0, 621 | value: 0, 622 | } 623 | } 624 | } 625 | 626 | /// (FL 7.0) Edits sample in Edison. 627 | /// 628 | /// The first value holds the sample filename. 629 | /// 630 | /// The second value is `true` if an existing instance of Edison can be re-used or `false` 631 | /// otherwise. 632 | #[derive(Debug)] 633 | pub struct EditSample(pub String, pub bool); 634 | 635 | impl_message!(EditSample); 636 | 637 | impl From for FlMessage { 638 | fn from(message: EditSample) -> Self { 639 | FlMessage { 640 | id: 32, 641 | index: message.1.as_raw_ptr(), 642 | value: message.0.as_raw_ptr(), 643 | } 644 | } 645 | } 646 | 647 | /// (FL 7.0) Call this to let FL know that this plugin is thread-safe (or not). The default is not. 648 | /// You should do your own thread-sync using 649 | /// [`Host::lock_mix`](../../host/struct.Host.html#method.lock_mix). 650 | /// 651 | /// The value is `false` for `not safe` and `true` for `safe`. 652 | /// 653 | /// **Important: this should only be used from a generator plugin!** 654 | #[derive(Debug)] 655 | pub struct SetThreadSafe(pub bool); 656 | 657 | impl_message!(SetThreadSafe); 658 | 659 | impl From for FlMessage { 660 | fn from(message: SetThreadSafe) -> Self { 661 | FlMessage { 662 | id: 33, 663 | index: 0, 664 | value: message.0.as_raw_ptr(), 665 | } 666 | } 667 | } 668 | 669 | /// (FL 7.0) The plugin asks FL to enable or disable smart disabling. This is mainly for 670 | /// generators, so they can get MIDI input (if applicable). 671 | /// 672 | /// The value holds the switch. 673 | #[derive(Debug)] 674 | pub struct SmartDisable(pub bool); 675 | 676 | impl_message!(SmartDisable); 677 | 678 | impl From for FlMessage { 679 | fn from(message: SmartDisable) -> Self { 680 | FlMessage { 681 | id: 34, 682 | index: 0, 683 | value: message.0.as_raw_ptr(), 684 | } 685 | } 686 | } 687 | 688 | /// (FL 8.0) Sets the unique identifying string for this plugin. This will be used to save/restore 689 | /// custom data related to this plugin. Handy for wrapper plugins. 690 | /// 691 | /// The value is the identifying string. 692 | #[derive(Debug)] 693 | pub struct SetUid(pub String); 694 | 695 | impl_message!(SetUid); 696 | 697 | impl From for FlMessage { 698 | fn from(message: SetUid) -> Self { 699 | FlMessage { 700 | id: 35, 701 | index: 0, 702 | value: message.0.as_raw_ptr(), 703 | } 704 | } 705 | } 706 | 707 | /// (FL 8.0) Get the mixer time, relative to the current time. 708 | /// 709 | /// The first value is the time format required. 710 | /// 711 | /// The second value is offset in samples. 712 | /// 713 | /// The result is [`Time`](../struct.Time.html). 714 | #[derive(Debug)] 715 | pub struct GetMixingTime(pub TimeFormat, pub u64); 716 | 717 | impl Message for GetMixingTime { 718 | type Return = Time; 719 | 720 | fn send(self, tag: plugin::Tag, host: &mut Host) -> Self::Return { 721 | get_time_send(self, tag, host) 722 | } 723 | } 724 | 725 | fn get_time_send>(msg: T, tag: plugin::Tag, host: &mut Host) -> Time { 726 | let message: FlMessage = msg.into(); 727 | let time_ptr = message.value; 728 | unsafe { host_on_message(*host.host_ptr.get_mut(), tag.0, message) }; 729 | ValuePtr(time_ptr).get::