├── src ├── widgets │ ├── mod.rs │ └── knob.rs ├── base │ ├── param.rs │ ├── mod.rs │ └── config.rs ├── util.rs ├── vst │ ├── mod.rs │ ├── editor.rs │ └── plugin.rs ├── gui │ ├── mod.rs │ ├── events.rs │ └── window.rs └── lib.rs ├── logo.png ├── screenshot.png ├── examples ├── assets │ ├── Roboto-Light.ttf │ ├── Roboto-Medium.ttf │ └── RobotoMono-Bold.ttf ├── no_gui.rs └── overdrive.rs ├── .gitignore ├── Cargo.toml ├── README.md └── scripts └── vst-bundler.sh /src/widgets/mod.rs: -------------------------------------------------------------------------------- 1 | mod knob; 2 | pub use self::knob::*; 3 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monomadic/dd-core/HEAD/logo.png -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monomadic/dd-core/HEAD/screenshot.png -------------------------------------------------------------------------------- /examples/assets/Roboto-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monomadic/dd-core/HEAD/examples/assets/Roboto-Light.ttf -------------------------------------------------------------------------------- /examples/assets/Roboto-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monomadic/dd-core/HEAD/examples/assets/Roboto-Medium.ttf -------------------------------------------------------------------------------- /examples/assets/RobotoMono-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monomadic/dd-core/HEAD/examples/assets/RobotoMono-Bold.ttf -------------------------------------------------------------------------------- /src/base/param.rs: -------------------------------------------------------------------------------- 1 | #[derive(Default)] 2 | pub struct Param { 3 | pub name: String, 4 | pub value: f32, 5 | } 6 | -------------------------------------------------------------------------------- /src/base/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod param; 2 | pub mod config; 3 | 4 | use PluginConfig; 5 | use HostCallback; 6 | use AudioBuffer; 7 | 8 | pub trait BasePlugin where Self : Sized { 9 | fn new(HostCallback) -> (Self, PluginConfig); 10 | fn process_dsp(&mut self, buffer: AudioBuffer, config: &mut PluginConfig); 11 | } 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *~ 3 | *# 4 | *.o 5 | *.so 6 | *.swp 7 | *.dylib 8 | *.dSYM 9 | *.dll 10 | *.rlib 11 | *.dummy 12 | *.exe 13 | *-test 14 | /bin/main 15 | /bin/test-internal 16 | /bin/test-external 17 | /doc/ 18 | /target/ 19 | /build/ 20 | /.rust/ 21 | rusti.sh 22 | /examples/**/target 23 | 24 | Cargo.lock 25 | *.vst 26 | .idea -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | use std::path::{Path, PathBuf}; 2 | use std::env; 3 | 4 | pub fn plugin_dir() -> PathBuf { 5 | // #[cfg(target_os = "windows")] 6 | // return fs::get_folder_path().unwrap(); 7 | // #[cfg(target_os = "macos")] 8 | // return ::std::path::PathBuf::from("."); 9 | 10 | env::current_exe().unwrap() 11 | } 12 | -------------------------------------------------------------------------------- /src/vst/mod.rs: -------------------------------------------------------------------------------- 1 | mod editor; 2 | pub mod plugin; 3 | use gui::Window; 4 | 5 | use Graphics; 6 | use PluginConfig; 7 | use HostCallback; 8 | use BasePlugin; 9 | 10 | #[derive(Default)] 11 | pub struct VSTPlugin

where P: BasePlugin + Graphics { 12 | pub window: Option, 13 | pub plugin: P, 14 | config: PluginConfig, 15 | } 16 | -------------------------------------------------------------------------------- /src/base/config.rs: -------------------------------------------------------------------------------- 1 | use HostCallback; 2 | use Category; 3 | use Param; 4 | 5 | pub struct PluginConfig { 6 | pub name: String, 7 | pub vendor: String, 8 | pub host: HostCallback, 9 | pub params: Vec, 10 | pub unique_id: i32, 11 | pub inputs: i32, 12 | pub outputs: i32, 13 | pub category: Category, 14 | } 15 | 16 | impl Default for PluginConfig { 17 | fn default() -> Self { 18 | PluginConfig { 19 | category: Category::Effect, 20 | inputs: 2, 21 | outputs: 2, 22 | host: Default::default(), 23 | name: "Default Plugin".to_string(), 24 | params: Vec::new(), 25 | unique_id: 00011112, 26 | vendor: "DDCore".to_string(), 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dd_core" 3 | version = "0.1.0" 4 | authors = ["Rob Saunders "] 5 | 6 | [dependencies] 7 | vst2 = { git = "https://github.com/overdrivenpotato/rust-vst2" } 8 | winit = { git = "https://github.com/robsaunders/winit-vst" } 9 | # winit = { path = "../winit-vst" } 10 | conrod = { version = "0.52.0", features = ["piston", "glium", "winit"] } 11 | log = "0.3" 12 | simplelog = "0.4.2" 13 | log-panics = "1.1.0" 14 | 15 | [lib] 16 | name = "dd_core" 17 | 18 | [replace] 19 | "winit:0.5.11" = { git = "https://github.com/robsaunders/winit-vst" } 20 | # "winit:0.5.11" = { path = "../winit-vst" } 21 | 22 | [[example]] 23 | name = "overdrive" 24 | crate-type = ["cdylib"] 25 | 26 | [[example]] 27 | name = "no_gui" 28 | crate-type = ["cdylib"] 29 | -------------------------------------------------------------------------------- /src/gui/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod window; 2 | pub mod events; 3 | 4 | use std::collections::HashMap; 5 | 6 | pub use self::window::Window; 7 | use BasePlugin; 8 | 9 | #[derive(Debug)] 10 | pub enum GUIError { 11 | CreationError(String), 12 | } 13 | 14 | use conrod; 15 | use PluginConfig; 16 | pub trait Graphics { 17 | fn get_config(&mut self) -> GraphicsConfig; 18 | fn setup_display(&mut self, window: &mut Window); 19 | fn do_layout(&mut self, ui: conrod::UiCell, config: &mut PluginConfig, ids: &mut HashMap); 20 | } 21 | 22 | use conrod::text::{Font}; 23 | pub struct GraphicsConfig { 24 | pub widget_ids: Vec, 25 | pub theme: conrod::Theme, 26 | pub fonts: Font, 27 | } 28 | 29 | pub fn widget_id(ids: &mut HashMap, id: &str) -> conrod::widget::Id { 30 | *ids.get(id).unwrap() 31 | } -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate vst2; 3 | 4 | #[macro_use] 5 | extern crate log; 6 | extern crate simplelog; 7 | 8 | #[macro_use] 9 | pub extern crate conrod; 10 | 11 | extern crate winit; 12 | 13 | pub mod vst; 14 | pub mod util; 15 | pub mod gui; 16 | pub mod widgets; 17 | mod base; 18 | 19 | // external objects 20 | pub use vst2::*; 21 | pub use vst2::plugin::{ HostCallback, Category }; 22 | pub use vst2::buffer::AudioBuffer; 23 | pub use vst2::host::Host; 24 | pub use base::config::PluginConfig; 25 | pub use base::param::Param; 26 | pub use base::BasePlugin; 27 | pub use gui::{ Graphics, GraphicsConfig }; 28 | pub use gui::widget_id; 29 | 30 | // internal objects 31 | use vst::VSTPlugin; 32 | 33 | #[macro_export] 34 | macro_rules! create_plugin { 35 | ($config:ty) => { 36 | // info!("starting VST plugin."); 37 | plugin_main!(vst::VSTPlugin<$config>); 38 | } 39 | } 40 | 41 | #[macro_export] 42 | macro_rules! hashmap { 43 | { $($key:expr => $value:expr),+ } => { 44 | { 45 | let mut m = HashMap::new(); 46 | $( m.insert($key, $value); )+ m 47 | } 48 | }; 49 | } 50 | 51 | #[macro_export] 52 | macro_rules! string_vec { 53 | ($($x:expr),*) => (vec![$($x.to_string()),*]); 54 | } 55 | -------------------------------------------------------------------------------- /src/vst/editor.rs: -------------------------------------------------------------------------------- 1 | use vst2::editor::{Editor, KnobMode, KeyCode}; 2 | use vst::VSTPlugin; 3 | use std::os::raw::c_void; 4 | 5 | use gui::Window; 6 | 7 | use PluginConfig; 8 | use BasePlugin; 9 | use Graphics; 10 | 11 | impl Editor for VSTPlugin

{ 12 | fn size(&self) -> (i32, i32) { 13 | (500, 300) 14 | } 15 | 16 | fn position(&self) -> (i32, i32) { 17 | (0, 0) 18 | } 19 | 20 | fn open(&mut self, window: *mut c_void) { 21 | info!("VST plugin called open()"); 22 | 23 | match Window::new(window as *mut c_void, &mut self.plugin) { 24 | Ok(w) => { 25 | info!("Window created ok in editor."); 26 | self.window = Some(w); 27 | }, 28 | Err(why) => { error!("{:?}", why) } 29 | } 30 | } 31 | 32 | fn close(&mut self) { 33 | info!("VST plugin called close()"); 34 | self.window = None; 35 | } 36 | 37 | fn is_open(&mut self) -> bool { self.window.is_some() } 38 | 39 | // fn set_knob_mode(&mut self, mode: KnobMode) -> bool { info!("VST plugin called KnobMode()"); false } 40 | // fn key_up(&mut self, keycode: KeyCode) -> bool { info!("VST plugin called key_up()"); false } 41 | // fn key_down(&mut self, keycode: KeyCode) -> bool { info!("VST plugin called key_down()"); false } 42 | 43 | fn idle(&mut self) { 44 | if let Some(ref mut window) = self.window { 45 | window.draw(&mut self.config, &mut self.plugin); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | go-staticgen 2 | 3 | _**NOTE:** This library has been abandoned, it does not even compile with the latest rust due to dependency issues. Updating winit constantly was a painful experience and it does not fit with the style we require for continued development. Instead, I have been working on a new initiative at https://github.com/rust-dsp/rtb-rs - please follow the development there instead._ 4 | 5 | Simple library for developing VST2 plugins in 100% rust. The Steinberg SDK is not required (thankfully), and I believe therefore your code will not be subject to Steinbergs lame license. Most complexity is abstracted away and the user need only focus on what's important - processing dsp and accessing a simple immediate mode UI via conrod. I'll be looking to move away from conrod as rust matures and better alternatives appear (or I develop my own), as conrod is a truly horrible experience, but for now, using it via dd_core isn't too painful. 6 | 7 | Support for audiounit and lv2 will come in the future - any help on this would be highly appreciated! 8 | 9 | This library would not exist without overdrivenpotato and boscops hard work on the `rust-vst2` crate. 10 | 11 | Currently the code compiles on Mac OS and Windows, with Mac being the first-class citizen over Windows. Increased and ongoing support for Linux and Windows would be nice but I currently only use Mac OS. 12 | 13 | You could look at build.sh for an example of how to compile and package a plugin on mac - the vst-bundler script from rust-vst2 is included in the scripts directory. 14 | 15 | More examples coming soon! 16 | 17 | ![Screenshot](screenshot.png) 18 | -------------------------------------------------------------------------------- /scripts/vst-bundler.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo $PATH 4 | 5 | # Make sure we have the arguments we need 6 | if [[ -z $1 || -z $2 ]]; then 7 | echo "Generates a macOS bundle from a compiled dylib file" 8 | echo "Example:" 9 | echo -e "\t$0 Plugin target/release/plugin.dylib" 10 | echo -e "\tCreates a Plugin.vst bundle" 11 | else 12 | # Make the bundle folder 13 | mkdir -p "$1.vst/Contents/MacOS" 14 | 15 | # Create the PkgInfo 16 | echo "BNDL????" > "$1.vst/Contents/PkgInfo" 17 | 18 | #build the Info.Plist 19 | echo " 20 | 21 | 22 | 23 | CFBundleDevelopmentRegion 24 | English 25 | 26 | CFBundleExecutable 27 | $1 28 | 29 | CFBundleGetInfoString 30 | vst 31 | 32 | CFBundleIconFile 33 | 34 | 35 | CFBundleIdentifier 36 | com.rust-vst2.$1 37 | 38 | CFBundleInfoDictionaryVersion 39 | 6.0 40 | 41 | CFBundleName 42 | $1 43 | 44 | CFBundlePackageType 45 | BNDL 46 | 47 | CFBundleVersion 48 | 1.0 49 | 50 | CFBundleSignature 51 | $((RANDOM % 9999)) 52 | 53 | CSResourcesFileMapped 54 | 55 | 56 | 57 | " > "$1.vst/Contents/Info.plist" 58 | 59 | # move the provided library to the correct location 60 | cp "$2" "$1.vst/Contents/MacOS/$1" 61 | 62 | echo "Created bundle $1.vst" 63 | fi -------------------------------------------------------------------------------- /examples/no_gui.rs: -------------------------------------------------------------------------------- 1 | 2 | extern crate dd_core; 3 | // use dd_core::conrod::widget::*; 4 | 5 | // use dd_core::conrod; 6 | use dd_core::*; 7 | 8 | use std::collections::HashMap; 9 | 10 | use std::path::{Path, PathBuf}; 11 | 12 | #[derive(Default)] 13 | struct TestPlugin {} 14 | 15 | impl BasePlugin for TestPlugin { 16 | fn new(host: HostCallback) -> (Self, PluginConfig) {( 17 | TestPlugin { 18 | }, 19 | PluginConfig { 20 | name: "DDOverdrive".to_string(), 21 | vendor: "DeathDisco".to_string(), 22 | host: host, 23 | unique_id: 222666, 24 | inputs: 2, 25 | outputs: 2, 26 | category: Category::Effect, 27 | params: vec![ 28 | Param{ name: "Gain".to_string(), value: 0.001 }, 29 | Param{ name: "Threshold".to_string(), value: 0.001 }, 30 | ], 31 | }) 32 | } 33 | 34 | fn process_dsp(&mut self, buffer: AudioBuffer, config: &mut PluginConfig) { 35 | // Split out the input and output buffers into two vectors 36 | let (inputs, outputs) = buffer.split(); 37 | 38 | // For each buffer, transform the samples 39 | for (input_buffer, output_buffer) in inputs.iter().zip(outputs) { 40 | for (input_sample, output_sample) in input_buffer.iter().zip(output_buffer) { 41 | 42 | if *input_sample >= 0.0 { 43 | *output_sample = input_sample.min(config.params[1].value) / config.params[1].value * config.params[0].value; 44 | } 45 | else { 46 | *output_sample = input_sample.max(-config.params[1].value) / config.params[1].value * config.params[0].value; 47 | } 48 | 49 | } 50 | } 51 | } 52 | } 53 | 54 | create_plugin!(TestPlugin); 55 | -------------------------------------------------------------------------------- /src/gui/events.rs: -------------------------------------------------------------------------------- 1 | use conrod::backend::glium::glium; 2 | use std; 3 | 4 | pub struct EventLoop { 5 | ui_needs_update: bool, 6 | last_update: std::time::Instant, 7 | } 8 | 9 | impl EventLoop { 10 | 11 | pub fn new() -> Self { 12 | EventLoop { 13 | last_update: std::time::Instant::now(), 14 | ui_needs_update: true, 15 | } 16 | } 17 | 18 | /// Produce an iterator yielding all available events. 19 | pub fn next(&mut self, display: &glium::Display) -> Vec { 20 | // We don't want to loop any faster than 60 FPS, so wait until it has been at least 16ms 21 | // since the last yield. 22 | let last_update = self.last_update; 23 | let sixteen_ms = std::time::Duration::from_millis(16); 24 | let duration_since_last_update = std::time::Instant::now().duration_since(last_update); 25 | if duration_since_last_update < sixteen_ms { 26 | std::thread::sleep(sixteen_ms - duration_since_last_update); 27 | } 28 | 29 | // Collect all pending events. 30 | let mut events = Vec::new(); 31 | events.extend(display.poll_events()); 32 | 33 | // If there are no events and the `Ui` does not need updating, wait for the next event. 34 | if events.is_empty() && !self.ui_needs_update { 35 | events.extend(display.wait_events().next()); 36 | } 37 | 38 | self.ui_needs_update = false; 39 | self.last_update = std::time::Instant::now(); 40 | 41 | events 42 | } 43 | 44 | /// Notifies the event loop that the `Ui` requires another update whether or not there are any 45 | /// pending events. 46 | /// 47 | /// This is primarily used on the occasion that some part of the `Ui` is still animating and 48 | /// requires further updates to do so. 49 | pub fn needs_update(&mut self) { 50 | self.ui_needs_update = true; 51 | } 52 | 53 | } -------------------------------------------------------------------------------- /src/widgets/knob.rs: -------------------------------------------------------------------------------- 1 | use conrod; 2 | use conrod::widget; 3 | use conrod::widget::{ Widget }; 4 | 5 | pub struct Knob<'a> { 6 | // inherited crap 7 | common: widget::CommonBuilder, 8 | // knobs label 9 | maybe_label: Option<&'a str>, 10 | style: Style, 11 | } 12 | 13 | widget_style!{ 14 | style Style { 15 | /// Color of the knob. 16 | - color: conrod::Color { theme.shape_color } 17 | /// Color of the knob's label. 18 | - label_color: conrod::Color { theme.label_color } 19 | /// Font size of the knob's label. 20 | - label_font_size: conrod::FontSize { theme.font_size_medium } 21 | /// Specify a unique font for the label. 22 | - label_font_id: Option { theme.font_id } 23 | } 24 | } 25 | 26 | widget_ids! { 27 | struct Ids { 28 | point_path, 29 | } 30 | } 31 | 32 | pub struct State { 33 | ids: Ids, 34 | } 35 | 36 | impl<'a> Knob<'a> { 37 | pub fn new() -> Self { 38 | Knob { 39 | common: widget::CommonBuilder::new(), 40 | maybe_label: None, 41 | style: Style::new(), 42 | } 43 | } 44 | } 45 | 46 | 47 | impl<'a> Widget for Knob<'a> { 48 | type State = State; 49 | type Style = Style; 50 | type Event = Option<()>; 51 | 52 | fn common(&self) -> &widget::CommonBuilder { 53 | &self.common 54 | } 55 | 56 | fn common_mut(&mut self) -> &mut widget::CommonBuilder { 57 | &mut self.common 58 | } 59 | 60 | fn init_state(&self, id_gen: widget::id::Generator) -> Self::State { 61 | State { ids: Ids::new(id_gen) } 62 | } 63 | 64 | fn style(&self) -> Self::Style { 65 | self.style.clone() 66 | } 67 | 68 | fn update(self, args: widget::UpdateArgs) -> Self::Event { 69 | let widget::UpdateArgs { id, state, rect, mut ui, style, .. } = args; 70 | 71 | use conrod::Colorable; 72 | 73 | let points = vec!([0.0, 0.1], [1.0, 100.0]).into_iter(); 74 | let color = style.color(ui.theme()); 75 | 76 | widget::PointPath::new(points) 77 | .graphics_for(id) 78 | .parent(id) 79 | .thickness(1.0) 80 | .color(color) 81 | .set(state.ids.point_path, ui); 82 | 83 | None 84 | } 85 | } 86 | 87 | -------------------------------------------------------------------------------- /src/vst/plugin.rs: -------------------------------------------------------------------------------- 1 | use vst2::buffer::AudioBuffer; 2 | use vst2::plugin::{Category, Plugin, Info, HostCallback}; 3 | use vst2::editor::Editor; 4 | use vst2::host::Host; 5 | 6 | use simplelog; 7 | use std::fs::File; 8 | 9 | use gui::Window; 10 | 11 | use vst::{ VSTPlugin }; 12 | 13 | use PluginConfig; 14 | use BasePlugin; 15 | use Graphics; 16 | 17 | impl

Plugin for VSTPlugin

where P: BasePlugin + Graphics { 18 | fn new(host: HostCallback) -> Self { 19 | 20 | #[cfg(any(target_os = "macos", target_os = "linux"))] 21 | let _ = simplelog::CombinedLogger::init( 22 | vec![ 23 | simplelog::WriteLogger::new(simplelog::LogLevelFilter::Info, simplelog::Config::default(), File::create("/tmp/simplesynth.log").expect("log to open correctly.")), 24 | ] 25 | ); 26 | 27 | let (plugin, config) = P::new(host); 28 | 29 | VSTPlugin { 30 | window: None, 31 | plugin: plugin, 32 | config: config, 33 | } 34 | } 35 | 36 | fn get_info(&self) -> Info { 37 | Info { 38 | name: self.config.name.clone(), 39 | vendor: self.config.vendor.clone(), 40 | unique_id: self.config.unique_id, 41 | category: self.config.category, 42 | inputs: self.config.inputs, 43 | outputs: self.config.outputs, 44 | parameters: self.config.params.len() as i32, 45 | 46 | ..Info::default() 47 | } 48 | } 49 | 50 | fn can_be_automated(&self, index: i32) -> bool { true } 51 | 52 | fn get_editor(&mut self) -> Option<&mut Editor> { 53 | Some(self) 54 | } 55 | 56 | fn get_parameter(&self, index: i32) -> f32 { 57 | self.config.params[index as usize].value 58 | } 59 | 60 | fn set_parameter(&mut self, index: i32, value: f32) { 61 | self.config.params[index as usize].value = value.max(0.01); 62 | } 63 | 64 | fn get_parameter_name(&self, index: i32) -> String { 65 | self.config.params[index as usize].name.clone() 66 | } 67 | 68 | fn get_parameter_text(&self, index: i32) -> String { 69 | format!("{}", self.config.params[index as usize].value * 100.0) 70 | } 71 | 72 | fn get_parameter_label(&self, index: i32) -> String { 73 | "%".to_string() 74 | } 75 | 76 | fn process(&mut self, buffer: AudioBuffer) { 77 | self.plugin.process_dsp(buffer, &mut self.config); 78 | } 79 | } 80 | 81 | -------------------------------------------------------------------------------- /examples/overdrive.rs: -------------------------------------------------------------------------------- 1 | extern crate dd_core; 2 | use dd_core::*; 3 | use dd_core::widgets::*; 4 | use dd_core::conrod; 5 | use dd_core::conrod::widget::*; 6 | use dd_core::conrod::text::{FontCollection}; 7 | 8 | use std::collections::HashMap; 9 | 10 | #[derive(Default)] 11 | struct TestPlugin {} 12 | 13 | impl BasePlugin for TestPlugin { 14 | fn new(host: HostCallback) -> (Self, PluginConfig) {( 15 | TestPlugin { 16 | }, 17 | PluginConfig { 18 | name: "DDOverdrive".to_string(), 19 | vendor: "DeathDisco".to_string(), 20 | host: host, 21 | unique_id: 222666, 22 | inputs: 2, 23 | outputs: 2, 24 | category: Category::Effect, 25 | params: vec![ 26 | Param{ name: "Gain".to_string(), value: 0.001 }, 27 | Param{ name: "Threshold".to_string(), value: 0.001 }, 28 | ], 29 | }) 30 | } 31 | 32 | fn process_dsp(&mut self, buffer: AudioBuffer, config: &mut PluginConfig) { 33 | // Split out the input and output buffers into two vectors 34 | let (inputs, outputs) = buffer.split(); 35 | 36 | // For each buffer, transform the samples 37 | for (input_buffer, output_buffer) in inputs.iter().zip(outputs) { 38 | for (input_sample, output_sample) in input_buffer.iter().zip(output_buffer) { 39 | 40 | if *input_sample >= 0.0 { 41 | *output_sample = input_sample.min(config.params[1].value) / config.params[1].value * config.params[0].value; 42 | } 43 | else { 44 | *output_sample = input_sample.max(-config.params[1].value) / config.params[1].value * config.params[0].value; 45 | } 46 | 47 | } 48 | } 49 | } 50 | } 51 | 52 | use conrod::position::{Align, Direction, Padding, Position, Relative}; 53 | impl Graphics for TestPlugin { 54 | fn get_config(&mut self) -> GraphicsConfig { 55 | GraphicsConfig { 56 | widget_ids: string_vec! [ 57 | "body", 58 | "title", 59 | "knob", 60 | "gain_slider", 61 | "threshold_slider" 62 | ], 63 | theme: conrod::Theme { 64 | name: "DeathDisco".to_string(), 65 | padding: Padding::none(), 66 | x_position: Position::Relative(Relative::Align(Align::Start), None), 67 | y_position: Position::Relative(Relative::Direction(Direction::Backwards, 20.0), None), 68 | background_color: conrod::color::DARK_CHARCOAL, 69 | shape_color: conrod::color::LIGHT_CHARCOAL, 70 | border_color: conrod::color::BLACK, 71 | border_width: 1.0, 72 | label_color: conrod::color::WHITE, 73 | font_id: None, 74 | font_size_large: 26, 75 | font_size_medium: 18, 76 | font_size_small: 12, 77 | widget_styling: conrod::theme::StyleMap::default(), 78 | mouse_drag_threshold: 0.0, 79 | double_click_threshold: std::time::Duration::from_millis(500), 80 | }, 81 | fonts: FontCollection::from_bytes(include_bytes!("assets/Roboto-Light.ttf") as &[u8]).into_font().unwrap(), 82 | } 83 | } 84 | 85 | fn setup_display(&mut self, _: &mut dd_core::gui::Window) {} 86 | 87 | fn do_layout(&mut self, ref mut ui: conrod::UiCell, config: &mut PluginConfig, ids: &mut HashMap) { 88 | use conrod::{color, Labelable, Colorable, Sizeable, Widget, Positionable}; 89 | use conrod::widget::Canvas; 90 | 91 | // background 92 | Canvas::new() 93 | .color(color::Color::Rgba(0.1, 0.1, 0.1, 1.0)) 94 | .set(widget_id(ids, "body"), ui); 95 | 96 | Text::new("ddOverdrive") 97 | .top_left_of(widget_id(ids, "body")) 98 | .color(conrod::color::WHITE) 99 | .font_size(12) 100 | .set(widget_id(ids, "title"), ui); 101 | 102 | Knob::new() 103 | .set(widget_id(ids, "knob"), ui); 104 | 105 | // gain_slider 106 | if let Some(val) = Slider::new(config.params[0].value, 0.0, 1.0) 107 | .w_h(300.0, 30.0) 108 | .x_y(0.0, 50.0) 109 | .color(color::LIGHT_BLUE) 110 | .label("GAIN") 111 | .set(widget_id(ids, "gain_slider"), ui) { 112 | config.params[0].value = val; 113 | config.host.automate(0 as i32, config.params[0].value); 114 | } 115 | 116 | // threshold_slider 117 | if let Some(val) = Slider::new(config.params[1].value, 0.0, 1.0) 118 | .w_h(300.0, 30.0) 119 | .x_y(0.0, -50.0) 120 | .color(color::LIGHT_PURPLE) 121 | .label("THRESHOLD") 122 | .set(widget_id(ids, "threshold_slider"), ui) { 123 | config.params[1].value = val; 124 | config.host.automate(1 as i32, config.params[1].value); 125 | } 126 | } 127 | } 128 | 129 | create_plugin!(TestPlugin); 130 | -------------------------------------------------------------------------------- /src/gui/window.rs: -------------------------------------------------------------------------------- 1 | use conrod; 2 | use conrod::glium; 3 | use conrod::backend::glium::glium::glutin::WindowBuilder; 4 | use conrod::backend::glium::glium::DisplayBuild; 5 | use conrod::widget::Id; 6 | use vst2::plugin::HostCallback; 7 | use std::os::raw::c_void; 8 | use std::collections::HashMap; 9 | use winit; 10 | 11 | use gui::GUIError; 12 | use Graphics; 13 | use GraphicsConfig; 14 | use PluginConfig; 15 | 16 | pub struct Window { 17 | pub ui: conrod::Ui, 18 | pub display: glium::Display, 19 | pub image_map: conrod::image::Map, 20 | pub ids: HashMap, 21 | pub renderer: conrod::backend::glium::Renderer, 22 | } 23 | 24 | pub fn ui_event(event: conrod::event::Input) { 25 | match event { 26 | // glium::glutin::Event::KeyboardInput(_, _, Some(glium::glutin::VirtualKeyCode::Escape)) | 27 | // glium::glutin::Event::Closed => { return; }, 28 | // _ => { info!(" -- glium event {:?}", event)}, 29 | _ => (), 30 | } 31 | } 32 | 33 | fn set_ids(generator: &mut conrod::widget::id::Generator, labels: Vec) -> HashMap { 34 | let mut ids = HashMap::new(); 35 | for label in labels { 36 | ids.insert(label, generator.next()); 37 | } 38 | ids 39 | } 40 | 41 | impl Window { 42 | pub fn new(handle: *mut c_void, plugin: &mut P) -> Result { 43 | 44 | let wb = winit::WindowBuilder::new() 45 | .with_visibility(true) 46 | .with_transparency(false) 47 | .with_dimensions(500, 300) 48 | .with_parent(handle); 49 | 50 | match WindowBuilder::from_winit_builder(wb) 51 | .with_decorations(false) 52 | // .with_vsync() 53 | // .with_multisampling(8) 54 | // .with_dimensions(500, 300) 55 | // .with_visibility(true) 56 | // .with_transparency(false) 57 | // .with_gl_robustness(Robustness::RobustLoseContextOnReset) 58 | .build_glium() { 59 | Err(why) => Err(GUIError::CreationError(format!(".build_glium() failed: {:?}", why))), 60 | Ok(display) => { 61 | info!("Window spawned OK with conrod."); 62 | 63 | let app = Window::setup_display(display, plugin); 64 | 65 | match app { 66 | Ok(a) => Ok(a), 67 | Err(why) => Err(GUIError::CreationError(format!(".setup_display() failed: {:?}", why))) 68 | } 69 | } 70 | } 71 | } 72 | 73 | pub fn setup_display(window: glium::Display, plugin: &mut P) -> Result { 74 | let (width, height) = try!(window.get_window() 75 | .ok_or(GUIError::CreationError("could not .get_window()".to_string())) 76 | .and_then({|window| 77 | window.get_inner_size().ok_or(GUIError::CreationError("could not get_inner_size() on window.".to_string())) 78 | })); 79 | 80 | let config = plugin.get_config(); 81 | 82 | let mut ui = conrod::UiBuilder::new([width as f64, height as f64]).theme(config.theme).build(); 83 | 84 | ui.fonts.insert(config.fonts); 85 | 86 | let renderer = match conrod::backend::glium::Renderer::new(&window) { 87 | Ok(r) => r, 88 | Err(why) => { return Err(GUIError::CreationError(format!(".conrod::backend::glium::Renderer::new() failed: {:?}", why))) }, 89 | }; 90 | 91 | let image_map = conrod::image::Map::new(); 92 | 93 | let ids = set_ids(&mut ui.widget_id_generator(), config.widget_ids); 94 | 95 | let mut cw = Window{ 96 | ui: ui, 97 | display: window, 98 | image_map: image_map, 99 | renderer: renderer, 100 | ids: ids, 101 | }; 102 | 103 | plugin.setup_display(&mut cw); 104 | 105 | Ok(cw) 106 | } 107 | 108 | pub fn draw

(&mut self, config: &mut PluginConfig, plugin: &mut P) where P: Graphics { 109 | use std; 110 | 111 | let last_update = std::time::Instant::now(); 112 | 'main: loop { 113 | 114 | // We don't want to loop any faster than 60 FPS, so wait until it has been at least 115 | // 16ms since the last yield. 116 | let sixteen_ms = std::time::Duration::from_millis(16); 117 | let now = std::time::Instant::now(); 118 | let duration_since_last_update = now.duration_since(last_update); 119 | if duration_since_last_update < sixteen_ms { 120 | std::thread::sleep(sixteen_ms - duration_since_last_update); 121 | } 122 | 123 | let mut events: Vec<_> = self.display.poll_events().collect(); 124 | // if events.is_empty() { 125 | // events.extend(self.display.wait_events().next()); 126 | // } 127 | 128 | // Send any relevant events to the conrod thread. 129 | for event in events { 130 | 131 | // Use the `winit` backend feature to convert the winit event to a conrod one. 132 | if let Some(event) = conrod::backend::winit::convert(event.clone(), &self.display) { 133 | self.ui.handle_event(event.clone()); 134 | ui_event(event); 135 | } 136 | 137 | match event { 138 | // Break from the loop upon `Escape`. 139 | glium::glutin::Event::KeyboardInput(_, _, Some(glium::glutin::VirtualKeyCode::Escape)) | 140 | glium::glutin::Event::Closed => { 141 | info!("closing event loop."); 142 | break 'main 143 | }, 144 | _ => {}, 145 | } 146 | 147 | // if let Some(e) = conrod::backend::winit::convert(event.clone(), &self.display) { 148 | // ui_event(e.clone()); 149 | // self.ui.handle_event(e); 150 | // } 151 | } 152 | 153 | // set_widgets(self.ui.set_widgets(), &mut self.ids, plugin); 154 | plugin.do_layout(self.ui.set_widgets(), config, &mut self.ids); 155 | 156 | let mut target = self.display.draw(); 157 | 158 | // Render the `Ui` and then display it on the screen. 159 | if let Some(primitives) = self.ui.draw_if_changed() { 160 | self.renderer.fill(&self.display, primitives, &self.image_map); 161 | self.renderer.draw(&self.display, &mut target, &self.image_map).expect("renderer to draw"); 162 | } 163 | 164 | target.finish().expect("target to finish()"); 165 | break 'main; 166 | } 167 | } 168 | } 169 | --------------------------------------------------------------------------------