├── osx-opengl ├── README.md ├── Cargo.toml ├── .gitignore ├── LICENSE └── src │ ├── view.rs │ └── main.rs ├── windows-opengl ├── .gitignore ├── Cargo.toml ├── src │ ├── pixel_format.rs │ ├── window_class.rs │ ├── window.rs │ ├── util.rs │ └── main.rs └── Cargo.lock ├── linux-xcb-vst ├── .gitignore ├── README.md ├── src │ ├── lib.rs │ ├── parameters.rs │ ├── x_handle.rs │ ├── atomic_float.rs │ ├── square_oscillator.rs │ ├── gui_vst.rs │ └── editor.rs └── Cargo.toml ├── linux-opengl-vst ├── README.md ├── src │ ├── lib.rs │ ├── parameters │ │ ├── mod.rs │ │ └── atomic_float.rs │ ├── audio_engine │ │ ├── square_oscillator.rs │ │ └── mod.rs │ ├── midi_input_processor │ │ └── mod.rs │ ├── editor │ │ ├── mod.rs │ │ └── window │ │ │ └── mod.rs │ └── gvl_plugin.rs ├── .gitignore ├── Cargo.toml └── LICENSE ├── windows-opengl-vst ├── README.md ├── src │ ├── lib.rs │ ├── parameters │ │ ├── mod.rs │ │ └── atomic_float.rs │ ├── audio_engine │ │ ├── square_oscillator.rs │ │ └── mod.rs │ ├── midi_input_processor │ │ └── mod.rs │ ├── editor │ │ ├── window │ │ │ ├── pixel_format.rs │ │ │ ├── window_class.rs │ │ │ ├── win32_window.rs │ │ │ ├── util.rs │ │ │ └── mod.rs │ │ └── mod.rs │ └── gvw_plugin.rs ├── .gitignore ├── Cargo.toml └── LICENSE ├── README.md └── LICENSE /osx-opengl/README.md: -------------------------------------------------------------------------------- 1 | # osx_opengl -------------------------------------------------------------------------------- /windows-opengl/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | .idea -------------------------------------------------------------------------------- /linux-xcb-vst/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | 4 | # No Cargo.lock for libraries 5 | Cargo.lock 6 | 7 | # clion stuff 8 | .idea/ 9 | -------------------------------------------------------------------------------- /linux-opengl-vst/README.md: -------------------------------------------------------------------------------- 1 | # gvl 2 | 3 | A VST with an OpenGL GUI, for Linux (X11). Mostly just messing around without worrying about proper library structure or cross-platform support. -------------------------------------------------------------------------------- /windows-opengl-vst/README.md: -------------------------------------------------------------------------------- 1 | # gvw 2 | A VST with an OpenGL GUI, for Windows (winapi). Mostly just messing around without worrying about proper library structure or cross-platform support. 3 | -------------------------------------------------------------------------------- /osx-opengl/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "osx_opengl" 3 | version = "0.1.0" 4 | authors = ["crs"] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | cocoa = "0.18.4" 9 | objc = "0.2.2" 10 | gl = "*" 11 | core-foundation = "0.4" -------------------------------------------------------------------------------- /linux-xcb-vst/README.md: -------------------------------------------------------------------------------- 1 | # xcb-raw 2 | 3 | A VST with a bare-minimum GUI, using `rust-vst` and `rust-xcb` 4 | 5 | ## Video demo 6 | 7 | [![Video](https://img.youtube.com/vi/kTdoDVDNxus/0.jpg)](https://www.youtube.com/watch?v=kTdoDVDNxus) 8 | -------------------------------------------------------------------------------- /linux-opengl-vst/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate log; 2 | extern crate simplelog; 3 | extern crate vst; 4 | extern crate rand; 5 | 6 | use vst::plugin_main; 7 | 8 | mod audio_engine; 9 | mod editor; 10 | mod gvl_plugin; 11 | mod midi_input_processor; 12 | mod parameters; 13 | 14 | plugin_main!(gvl_plugin::GvlPlugin); 15 | -------------------------------------------------------------------------------- /linux-xcb-vst/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate vst; 2 | extern crate log; 3 | extern crate simplelog; 4 | extern crate xcb; 5 | 6 | mod x_handle; 7 | mod editor; 8 | mod atomic_float; 9 | mod parameters; 10 | mod square_oscillator; 11 | mod gui_vst; 12 | 13 | use vst::plugin_main; 14 | 15 | plugin_main!(gui_vst::GuiVst); -------------------------------------------------------------------------------- /linux-xcb-vst/src/parameters.rs: -------------------------------------------------------------------------------- 1 | use crate::atomic_float::AtomicFloat; 2 | 3 | pub struct Parameters { 4 | pub param1: AtomicFloat, 5 | pub param2: AtomicFloat, 6 | } 7 | 8 | impl Parameters { 9 | pub fn new() -> Self { 10 | Self { 11 | param1: AtomicFloat::new(1.0), 12 | param2: AtomicFloat::new(0.5), 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /linux-opengl-vst/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | # Generated by CLion 13 | .idea/ -------------------------------------------------------------------------------- /osx-opengl/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | # Generated by CLion 13 | .idea/ 14 | -------------------------------------------------------------------------------- /windows-opengl-vst/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate log; 2 | extern crate simplelog; 3 | extern crate vst; 4 | extern crate gl; 5 | extern crate kernel32; 6 | extern crate opengl32; 7 | extern crate user32; 8 | extern crate winapi; 9 | 10 | use vst::plugin_main; 11 | 12 | mod audio_engine; 13 | mod editor; 14 | mod gvw_plugin; 15 | mod midi_input_processor; 16 | mod parameters; 17 | 18 | plugin_main!(gvw_plugin::GvlPlugin); 19 | -------------------------------------------------------------------------------- /linux-opengl-vst/src/parameters/mod.rs: -------------------------------------------------------------------------------- 1 | mod atomic_float; 2 | use self::atomic_float::AtomicFloat; 3 | 4 | pub struct Parameters { 5 | pub amplitude: AtomicFloat, 6 | pub pulse_width: AtomicFloat, 7 | } 8 | 9 | impl Parameters { 10 | pub fn new() -> Self { 11 | Self { 12 | amplitude: AtomicFloat::new(0.3), 13 | pulse_width: AtomicFloat::new(0.5), 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /windows-opengl-vst/src/parameters/mod.rs: -------------------------------------------------------------------------------- 1 | mod atomic_float; 2 | use self::atomic_float::AtomicFloat; 3 | 4 | pub struct Parameters { 5 | pub amplitude: AtomicFloat, 6 | pub pulse_width: AtomicFloat, 7 | } 8 | 9 | impl Parameters { 10 | pub fn new() -> Self { 11 | Self { 12 | amplitude: AtomicFloat::new(0.3), 13 | pulse_width: AtomicFloat::new(0.5), 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /windows-opengl-vst/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | # Generated by CLion 13 | .idea/ 14 | -------------------------------------------------------------------------------- /windows-opengl/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "windows-window" 3 | version = "0.1.0" 4 | authors = ["Charles "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | winapi = "0.2.*" #TODO: get exact version 9 | user32-sys = "0.2.*" #TODO: get exact version 10 | kernel32-sys = "0.2.*" #TODO: get exact version 11 | gdi32-sys = "0.2.0" 12 | opengl32-sys = "*" #TODO: get exact version 13 | gl = "*" #TODO: get exact version -------------------------------------------------------------------------------- /linux-xcb-vst/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "linux-xcb-vst" 3 | version = "0.1.0" 4 | authors = ["Charles Saracco "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | vst = { git = "https://github.com/rust-dsp/rust-vst" } 9 | log = "0.4" 10 | simplelog = "^0.5.0" 11 | 12 | [dependencies.xcb] 13 | version = "0.8" 14 | features = ["thread"] 15 | 16 | [lib] 17 | name = "guivst" 18 | crate-type = ["dylib"] 19 | # TODO: cdylib insteadl of dylib? 20 | -------------------------------------------------------------------------------- /linux-xcb-vst/src/x_handle.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | pub struct XHandle { 4 | conn: Arc, 5 | screen_num: i32, 6 | } 7 | 8 | impl XHandle { 9 | pub fn new() -> Self { 10 | let (conn, screen_num) = xcb::base::Connection::connect(None).unwrap(); 11 | 12 | Self { 13 | conn: Arc::new(conn), 14 | screen_num, 15 | } 16 | } 17 | 18 | pub fn conn(&self) -> Arc { 19 | self.conn.clone() 20 | } 21 | 22 | pub fn screen_num(&self) -> i32 { 23 | self.screen_num 24 | } 25 | } -------------------------------------------------------------------------------- /linux-xcb-vst/src/atomic_float.rs: -------------------------------------------------------------------------------- 1 | use std::sync::atomic::{AtomicUsize, Ordering}; 2 | 3 | pub struct AtomicFloat { 4 | atomic: AtomicUsize, 5 | } 6 | 7 | impl AtomicFloat { 8 | pub fn new(value: f32) -> AtomicFloat { 9 | AtomicFloat { 10 | atomic: AtomicUsize::new(value.to_bits() as usize), 11 | } 12 | } 13 | 14 | pub fn get(&self) -> f32 { 15 | f32::from_bits(self.atomic.load(Ordering::Relaxed) as u32) 16 | } 17 | 18 | pub fn set(&self, value: f32) { 19 | self.atomic.store(value.to_bits() as usize, Ordering::Relaxed) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /linux-opengl-vst/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "linux-opengl-vst" 3 | version = "0.1.0" 4 | authors = ["Charles Saracco "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | vst = { git = "https://github.com/crsaracco/rust-vst", branch = "gvl-development" } 9 | #vst = { path = "/home/crs/git/github/my-rust-vst" } 10 | log = "0.4" 11 | simplelog = "^0.5.0" 12 | libc = "0.2" 13 | gl = "0.5.2" 14 | xcb = { version = "0.8", features = ["thread", "xlib_xcb", "dri2"] } 15 | x11 = { version = "2.3", features = ["xlib", "glx"]} 16 | rand = "*" 17 | 18 | [lib] 19 | name = "gvl" 20 | crate-type = ["cdylib"] 21 | -------------------------------------------------------------------------------- /linux-opengl-vst/src/parameters/atomic_float.rs: -------------------------------------------------------------------------------- 1 | use std::sync::atomic::{AtomicUsize, Ordering}; 2 | 3 | pub struct AtomicFloat { 4 | atomic: AtomicUsize, 5 | } 6 | 7 | impl AtomicFloat { 8 | pub fn new(value: f32) -> AtomicFloat { 9 | AtomicFloat { 10 | atomic: AtomicUsize::new(value.to_bits() as usize), 11 | } 12 | } 13 | 14 | pub fn get(&self) -> f32 { 15 | f32::from_bits(self.atomic.load(Ordering::Relaxed) as u32) 16 | } 17 | 18 | pub fn set(&self, value: f32) { 19 | self.atomic 20 | .store(value.to_bits() as usize, Ordering::Relaxed) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /windows-opengl-vst/src/parameters/atomic_float.rs: -------------------------------------------------------------------------------- 1 | use std::sync::atomic::{AtomicUsize, Ordering}; 2 | 3 | pub struct AtomicFloat { 4 | atomic: AtomicUsize, 5 | } 6 | 7 | impl AtomicFloat { 8 | pub fn new(value: f32) -> AtomicFloat { 9 | AtomicFloat { 10 | atomic: AtomicUsize::new(value.to_bits() as usize), 11 | } 12 | } 13 | 14 | pub fn get(&self) -> f32 { 15 | f32::from_bits(self.atomic.load(Ordering::Relaxed) as u32) 16 | } 17 | 18 | pub fn set(&self, value: f32) { 19 | self.atomic 20 | .store(value.to_bits() as usize, Ordering::Relaxed) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /windows-opengl-vst/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "windows-opengl-vst" 3 | version = "0.1.0" 4 | authors = ["Charles Saracco "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | vst = { git = "https://github.com/crsaracco/rust-vst", branch = "gvl-development" } 9 | log = "0.4" 10 | simplelog = "^0.5.0" # TODO: get exact version 11 | winapi = "0.2.*" # TODO: get exact version 12 | user32-sys = "0.2.*" # TODO: get exact version 13 | kernel32-sys = "0.2.*" # TODO: get exact version 14 | gdi32-sys = "0.2.0" 15 | opengl32-sys = "*" # TODO: get exact version 16 | gl = "*" # TODO: get exact version 17 | rand = "*" # TODO: get exact version 18 | 19 | [lib] 20 | name = "gvw" 21 | crate-type = ["cdylib"] 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vst2-gui-prototypes 2 | 3 | A bunch of prototypes of GUIs for [`rust-vst`](https://github.com/rust-dsp/rust-vst). 4 | 5 | ## Projects 6 | 7 | - [`linux-xcb-vst`](linux-xcb-vst/) -- A VST with an X11 context (using raw XCB/X11 draw calls), for Linux (or anything that uses X11). Demo video included! 8 | - [`linux-opengl-vst`](linux-opengl-vst/) -- A VST with an OpenGL context, for Linux (or anything that uses X11). 9 | - [`windows-opengl-vst`](windows-opengl-vst/) -- A VST with an OpenGL context, for Windows. 10 | - [`windows-opengl`](windows-opengl/) -- Playing around with OpenGL on Windows. No actual VST here! 11 | - [`osx-opengl`](osx-opengl/) -- Playing around with OpenGL on Mac OS X. No actual VST here! 12 | 13 | ## License 14 | 15 | Everything in this repo is MIT licensed. Check out the `LICENSE` file for more info. 16 | -------------------------------------------------------------------------------- /linux-opengl-vst/src/audio_engine/square_oscillator.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | use std::f64; 3 | 4 | use crate::parameters::Parameters; 5 | 6 | pub struct SquareOscillator { 7 | params: Arc, 8 | frequency: f64, 9 | phase: f64, 10 | } 11 | 12 | impl SquareOscillator { 13 | pub fn new(params: Arc) -> Self { 14 | Self { 15 | params, 16 | frequency: 0.0, 17 | phase: 0.0, 18 | } 19 | } 20 | 21 | pub fn change_frequency(&mut self, frequency: f64) { 22 | self.frequency = frequency; 23 | } 24 | 25 | pub fn next_sample(&mut self, sample_rate: f32) -> f64 { 26 | let mut output: f64 = 1.0; 27 | 28 | if self.phase <= self.params.pulse_width.get() as f64 { 29 | output = -1.0; 30 | } 31 | 32 | self.phase = (self.phase + self.frequency / sample_rate as f64).fract(); 33 | 34 | output * (self.params.amplitude.get() as f64) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /windows-opengl-vst/src/audio_engine/square_oscillator.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | use std::f64; 3 | 4 | use crate::parameters::Parameters; 5 | 6 | pub struct SquareOscillator { 7 | params: Arc, 8 | frequency: f64, 9 | phase: f64, 10 | } 11 | 12 | impl SquareOscillator { 13 | pub fn new(params: Arc) -> Self { 14 | Self { 15 | params, 16 | frequency: 0.0, 17 | phase: 0.0, 18 | } 19 | } 20 | 21 | pub fn change_frequency(&mut self, frequency: f64) { 22 | self.frequency = frequency; 23 | } 24 | 25 | pub fn next_sample(&mut self, sample_rate: f32) -> f64 { 26 | let mut output: f64 = 1.0; 27 | 28 | if self.phase <= self.params.pulse_width.get() as f64 { 29 | output = -1.0; 30 | } 31 | 32 | self.phase = (self.phase + self.frequency / sample_rate as f64).fract(); 33 | 34 | output * (self.params.amplitude.get() as f64) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /linux-xcb-vst/src/square_oscillator.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use crate::parameters::Parameters; 4 | 5 | pub struct SquareOscillator { 6 | parameters: Arc, 7 | frequency: f64, 8 | phase: f64, 9 | } 10 | 11 | impl SquareOscillator { 12 | /// Creates a new Sine wave signal generator. 13 | pub fn new(parameters: Arc) -> Self { 14 | Self { 15 | parameters, 16 | frequency: 0.0, 17 | phase: 0.0, 18 | } 19 | } 20 | 21 | pub fn change_frequency(&mut self, frequency: f64) { 22 | self.frequency = frequency; 23 | } 24 | 25 | pub fn next_sample(&mut self, sample_rate: f64) -> f64 { 26 | let mut output = 1.0; 27 | 28 | if self.phase <= self.parameters.param2.get() as f64 { 29 | output = -1.0; 30 | } 31 | 32 | self.phase = (self.phase + self.frequency / sample_rate).fract(); 33 | 34 | output * self.parameters.param1.get() as f64 35 | } 36 | } -------------------------------------------------------------------------------- /linux-opengl-vst/src/midi_input_processor/mod.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeSet; 2 | 3 | pub struct MidiInputProcessor { 4 | notes: BTreeSet, 5 | } 6 | 7 | impl MidiInputProcessor { 8 | pub fn new() -> Self { 9 | Self { 10 | notes: BTreeSet::new(), 11 | } 12 | } 13 | 14 | pub fn process_midi_event(&mut self, event_data: [u8; 3]) { 15 | match event_data[0] { 16 | 128 => self.note_off(event_data[1]), 17 | 144 => self.note_on(event_data[1]), 18 | _ => (), 19 | } 20 | } 21 | 22 | fn note_on(&mut self, index: u8) { 23 | self.notes.insert(index); 24 | } 25 | 26 | fn note_off(&mut self, index: u8) { 27 | self.notes.remove(&index); 28 | } 29 | 30 | pub fn get_active_notes(&self) -> Vec { 31 | let mut active_notes = Vec::new(); // TODO: Might not want to use Vec 32 | for note in &self.notes { 33 | active_notes.push(*note); 34 | } 35 | active_notes 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /windows-opengl-vst/src/midi_input_processor/mod.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeSet; 2 | 3 | pub struct MidiInputProcessor { 4 | notes: BTreeSet, 5 | } 6 | 7 | impl MidiInputProcessor { 8 | pub fn new() -> Self { 9 | Self { 10 | notes: BTreeSet::new(), 11 | } 12 | } 13 | 14 | pub fn process_midi_event(&mut self, event_data: [u8; 3]) { 15 | match event_data[0] { 16 | 128 => self.note_off(event_data[1]), 17 | 144 => self.note_on(event_data[1]), 18 | _ => (), 19 | } 20 | } 21 | 22 | fn note_on(&mut self, index: u8) { 23 | self.notes.insert(index); 24 | } 25 | 26 | fn note_off(&mut self, index: u8) { 27 | self.notes.remove(&index); 28 | } 29 | 30 | pub fn get_active_notes(&self) -> Vec { 31 | let mut active_notes = Vec::new(); // TODO: Might not want to use Vec 32 | for note in &self.notes { 33 | active_notes.push(*note); 34 | } 35 | active_notes 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Charles Saracco 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 | -------------------------------------------------------------------------------- /osx-opengl/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Charles Saracco 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 | -------------------------------------------------------------------------------- /linux-opengl-vst/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Charles Saracco 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 | -------------------------------------------------------------------------------- /windows-opengl-vst/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Charles Saracco 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 | -------------------------------------------------------------------------------- /windows-opengl/src/pixel_format.rs: -------------------------------------------------------------------------------- 1 | /// NOTE: should only be used to create "dummy" pixel formats. 2 | pub fn dummy_pixel_format(device_context_handler: *mut winapi::HDC__) -> (i32, winapi::PIXELFORMATDESCRIPTOR) { 3 | let mut pixel_format_descriptor = winapi::PIXELFORMATDESCRIPTOR { 4 | nSize: std::mem::size_of::() as u16, 5 | nVersion: 1, 6 | dwFlags: winapi::PFD_DRAW_TO_WINDOW | winapi::PFD_SUPPORT_OPENGL | winapi::PFD_DOUBLEBUFFER, 7 | iPixelType: winapi::PFD_TYPE_RGBA, 8 | cColorBits: 32, 9 | cRedBits: 0, 10 | cRedShift: 0, 11 | cGreenBits: 0, 12 | cGreenShift: 0, 13 | cBlueBits: 0, 14 | cBlueShift: 0, 15 | cAlphaBits: 8, 16 | cAlphaShift: 0, 17 | cAccumBits: 0, 18 | cAccumRedBits: 0, 19 | cAccumGreenBits: 0, 20 | cAccumBlueBits: 0, 21 | cAccumAlphaBits: 0, 22 | cDepthBits: 24, 23 | cStencilBits: 8, 24 | cAuxBuffers: 0, 25 | iLayerType: winapi::PFD_MAIN_PLANE, 26 | bReserved: 0, 27 | dwLayerMask: 0, 28 | dwVisibleMask: 0, 29 | dwDamageMask: 0, 30 | }; 31 | 32 | let pixel_format = unsafe { 33 | gdi32::ChoosePixelFormat(device_context_handler, &pixel_format_descriptor) 34 | }; 35 | if pixel_format == 0 { 36 | panic!("Failed to find a suitable pixel format."); 37 | } 38 | 39 | (pixel_format, pixel_format_descriptor) 40 | } -------------------------------------------------------------------------------- /windows-opengl-vst/src/editor/window/pixel_format.rs: -------------------------------------------------------------------------------- 1 | use log::*; 2 | 3 | /// NOTE: should only be used to create "dummy" pixel formats. 4 | pub fn dummy_pixel_format(device_context_handler: *mut winapi::HDC__) -> (i32, winapi::PIXELFORMATDESCRIPTOR) { 5 | let mut pixel_format_descriptor = winapi::PIXELFORMATDESCRIPTOR { 6 | nSize: std::mem::size_of::() as u16, 7 | nVersion: 1, 8 | dwFlags: winapi::PFD_DRAW_TO_WINDOW | winapi::PFD_SUPPORT_OPENGL | winapi::PFD_DOUBLEBUFFER, 9 | iPixelType: winapi::PFD_TYPE_RGBA, 10 | cColorBits: 32, 11 | cRedBits: 0, 12 | cRedShift: 0, 13 | cGreenBits: 0, 14 | cGreenShift: 0, 15 | cBlueBits: 0, 16 | cBlueShift: 0, 17 | cAlphaBits: 8, 18 | cAlphaShift: 0, 19 | cAccumBits: 0, 20 | cAccumRedBits: 0, 21 | cAccumGreenBits: 0, 22 | cAccumBlueBits: 0, 23 | cAccumAlphaBits: 0, 24 | cDepthBits: 24, 25 | cStencilBits: 8, 26 | cAuxBuffers: 0, 27 | iLayerType: winapi::PFD_MAIN_PLANE, 28 | bReserved: 0, 29 | dwLayerMask: 0, 30 | dwVisibleMask: 0, 31 | dwDamageMask: 0, 32 | }; 33 | 34 | let pixel_format = unsafe { 35 | gdi32::ChoosePixelFormat(device_context_handler, &pixel_format_descriptor) 36 | }; 37 | if pixel_format == 0 { 38 | info!("Failed to find a suitable pixel format."); 39 | panic!("Failed to find a suitable pixel format."); 40 | } 41 | 42 | (pixel_format, pixel_format_descriptor) 43 | } -------------------------------------------------------------------------------- /windows-opengl/src/window_class.rs: -------------------------------------------------------------------------------- 1 | use std::ptr::null_mut; 2 | 3 | use super::util; 4 | 5 | pub struct WindowClass { 6 | name: String, 7 | hinstance: *mut winapi::HINSTANCE__, 8 | } 9 | 10 | impl WindowClass { 11 | pub fn new(name: &str, window_process: winapi::WNDPROC) -> Self { 12 | let win32_name = util::win32_string(name); 13 | 14 | unsafe { 15 | let hinstance = kernel32::GetModuleHandleW(null_mut()); 16 | let window_class = winapi::WNDCLASSW { 17 | style: winapi::CS_OWNDC | winapi::CS_HREDRAW | winapi::CS_VREDRAW, // TODO: explain these flags 18 | lpfnWndProc: window_process, // Custom window process for handling events 19 | hInstance: hinstance, 20 | lpszClassName: win32_name.as_ptr(), // "name of the class" TODO: (???) 21 | cbClsExtra: 0, // TODO: what is? 22 | cbWndExtra: 0, // TODO: what is? 23 | hIcon: null_mut(), // TODO: what is? (I assume the icon of the window) 24 | hCursor: null_mut(), // TODO: what is? (I guess you can change the cursor) 25 | hbrBackground: null_mut(), // TODO: what is? 26 | lpszMenuName: null_mut(), // TODO: what is? 27 | }; 28 | 29 | user32::RegisterClassW(&window_class); 30 | 31 | Self{ 32 | name: name.to_string(), 33 | hinstance, 34 | } 35 | } 36 | } 37 | 38 | pub fn name_win32_ptr(&self) -> *const u16 { 39 | let win32_name = util::win32_string(&self.name); 40 | win32_name.as_ptr() 41 | } 42 | 43 | pub fn hinstance(&self) -> *mut winapi::HINSTANCE__ { 44 | self.hinstance 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /windows-opengl-vst/src/editor/window/window_class.rs: -------------------------------------------------------------------------------- 1 | use std::ptr::null_mut; 2 | 3 | use super::util; 4 | 5 | pub struct WindowClass { 6 | name: String, 7 | hinstance: *mut winapi::HINSTANCE__, 8 | } 9 | 10 | impl WindowClass { 11 | pub fn new(name: &str, window_process: winapi::WNDPROC) -> Self { 12 | let win32_name = util::win32_string(name); 13 | 14 | unsafe { 15 | let hinstance = kernel32::GetModuleHandleW(null_mut()); 16 | let window_class = winapi::WNDCLASSW { 17 | style: winapi::CS_OWNDC | winapi::CS_HREDRAW | winapi::CS_VREDRAW, // TODO: explain these flags 18 | lpfnWndProc: window_process, // Custom window process for handling events 19 | hInstance: hinstance, 20 | lpszClassName: win32_name.as_ptr(), // "name of the class" TODO: (???) 21 | cbClsExtra: 0, // TODO: what is? 22 | cbWndExtra: 0, // TODO: what is? 23 | hIcon: null_mut(), // TODO: what is? (I assume the icon of the window) 24 | hCursor: null_mut(), // TODO: what is? (I guess you can change the cursor) 25 | hbrBackground: null_mut(), // TODO: what is? 26 | lpszMenuName: null_mut(), // TODO: what is? 27 | }; 28 | 29 | user32::RegisterClassW(&window_class); 30 | 31 | Self{ 32 | name: name.to_string(), 33 | hinstance, 34 | } 35 | } 36 | } 37 | 38 | pub fn name_win32_ptr(&self) -> *const u16 { 39 | let win32_name = util::win32_string(&self.name); 40 | win32_name.as_ptr() 41 | } 42 | 43 | pub fn hinstance(&self) -> *mut winapi::HINSTANCE__ { 44 | self.hinstance 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /windows-opengl/src/window.rs: -------------------------------------------------------------------------------- 1 | use std::ptr::null_mut; 2 | 3 | use super::util; 4 | use super::window_class::WindowClass; 5 | 6 | pub struct Window { 7 | handle: winapi::HWND, 8 | } 9 | 10 | impl Window { 11 | pub fn new(window_class: WindowClass, title: &str, dummy: bool) -> Self { 12 | let title = util::win32_string(title); 13 | 14 | // TODO: what are these flags? 15 | let mut dw_style = 0; 16 | if !dummy { 17 | dw_style = winapi::WS_OVERLAPPEDWINDOW; 18 | } 19 | 20 | unsafe { 21 | // Create a window using your window class. Store the instance in hinstance(?) 22 | let window_handle = user32::CreateWindowExW( 23 | 0, // TODO: what is? 24 | window_class.name_win32_ptr(), // Name of the class 25 | title.as_ptr(), // Title of the window 26 | dw_style, 27 | winapi::CW_USEDEFAULT, // Default X coordinate 28 | winapi::CW_USEDEFAULT, // Default Y coordinate 29 | 200, // Default width 30 | 200, // Default height 31 | null_mut(), // No parent (TODO: accept parent as input) 32 | null_mut(), // No menus 33 | window_class.hinstance(), // The instance to the handle...? 34 | null_mut(), // TODO: what is? 35 | ); 36 | 37 | if window_handle.is_null() { 38 | panic!("Failed to create the OpenGL window."); 39 | } 40 | 41 | Self { 42 | handle: window_handle, 43 | } 44 | } 45 | } 46 | 47 | pub fn handle(&self) -> winapi::HWND { 48 | self.handle 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /linux-opengl-vst/src/editor/mod.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::c_void; 2 | use std::sync::{Arc, Mutex}; 3 | 4 | use log::*; 5 | use vst::editor::Editor as VstEditor; 6 | use vst::plugin::HostCallback; 7 | 8 | use crate::parameters::Parameters; 9 | 10 | mod window; 11 | 12 | const DEFAULT_WIDTH: i32 = 1024; 13 | const DEFAULT_HEIGHT: i32 = 1024; 14 | 15 | pub struct Editor { 16 | params: Arc, 17 | window: Option, 18 | host_callback: Arc>, 19 | } 20 | 21 | impl Editor { 22 | pub fn new(host_callback: Arc>, params: Arc) -> Self { 23 | Self { 24 | params, 25 | window: None, 26 | host_callback, 27 | } 28 | } 29 | } 30 | 31 | impl VstEditor for Editor { 32 | fn size(&self) -> (i32, i32) { 33 | info!("Editor::size()"); 34 | 35 | if self.window.is_some() { 36 | return ( 37 | self.window.as_ref().unwrap().get_width() as i32, 38 | self.window.as_ref().unwrap().get_height() as i32, 39 | ); 40 | } 41 | 42 | (DEFAULT_WIDTH, DEFAULT_HEIGHT) 43 | } 44 | 45 | // Typically ignored by DAWs. Just return (0, 0). 46 | fn position(&self) -> (i32, i32) { 47 | (0, 0) 48 | } 49 | 50 | fn close(&mut self) { 51 | info!("Editor::close()"); 52 | self.window = None 53 | } 54 | 55 | fn open(&mut self, parent: *mut c_void) -> bool { 56 | info!("Editor::open()"); 57 | 58 | self.window = Some(window::Window::new(self.host_callback.clone(), self.params.clone(), parent)); 59 | 60 | // success 61 | true 62 | } 63 | 64 | fn is_open(&mut self) -> bool { 65 | info!("Editor::is_open()"); 66 | if self.window.is_some() { 67 | return true; 68 | } 69 | false 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /windows-opengl-vst/src/editor/mod.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::c_void; 2 | use std::sync::{Arc, Mutex}; 3 | 4 | use log::*; 5 | use vst::editor::Editor as VstEditor; 6 | use vst::plugin::HostCallback; 7 | 8 | use crate::parameters::Parameters; 9 | 10 | mod window; 11 | 12 | const DEFAULT_WIDTH: i32 = 1024; 13 | const DEFAULT_HEIGHT: i32 = 1024; 14 | 15 | pub struct Editor { 16 | params: Arc, 17 | window: Option, 18 | host_callback: Arc>, 19 | } 20 | 21 | impl Editor { 22 | pub fn new(host_callback: Arc>, params: Arc) -> Self { 23 | Self { 24 | params, 25 | window: None, 26 | host_callback, 27 | } 28 | } 29 | } 30 | 31 | impl VstEditor for Editor { 32 | fn size(&self) -> (i32, i32) { 33 | info!("Editor::size()"); 34 | 35 | if self.window.is_some() { 36 | return ( 37 | self.window.as_ref().unwrap().get_width() as i32, 38 | self.window.as_ref().unwrap().get_height() as i32, 39 | ); 40 | } 41 | 42 | (DEFAULT_WIDTH, DEFAULT_HEIGHT) 43 | } 44 | 45 | // Typically ignored by DAWs. Just return (0, 0). 46 | fn position(&self) -> (i32, i32) { 47 | (0, 0) 48 | } 49 | 50 | fn close(&mut self) { 51 | info!("Editor::close()"); 52 | self.window = None 53 | } 54 | 55 | fn open(&mut self, parent: *mut c_void) -> bool { 56 | info!("Editor::open()"); 57 | 58 | self.window = Some(window::Window::new(self.host_callback.clone(), self.params.clone(), parent)); 59 | 60 | // success 61 | true 62 | } 63 | 64 | fn is_open(&mut self) -> bool { 65 | info!("Editor::is_open()"); 66 | if self.window.is_some() { 67 | return true; 68 | } 69 | false 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /windows-opengl/src/util.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::{c_void, CString, OsStr}; 2 | use std::iter::once; 3 | use std::os::windows::ffi::OsStrExt; 4 | 5 | pub fn win32_string(value: &str) -> Vec { 6 | OsStr::new(value).encode_wide().chain(once(0)).collect() 7 | } 8 | 9 | pub struct ProcAddressLoader { 10 | opengl32_dll: *mut winapi::HINSTANCE__, 11 | } 12 | 13 | impl ProcAddressLoader { 14 | pub fn new() -> Self { 15 | // Some functions load from opengl32.dll, and the others load from the fancy dummy context 16 | // that we have to spin up. 17 | // TODO: spin up fancy dummy context within this struct? Or maybe make the dummy context its own struct? 18 | let opengl32_dll = unsafe { 19 | kernel32::LoadLibraryA(b"opengl32.dll\0".as_ptr() as *const _) 20 | }; 21 | if opengl32_dll.is_null() { 22 | panic!("WGL: opengl32.dll not found!"); 23 | } else { 24 | println!("WGL: opengl32.dll loaded!"); 25 | } 26 | 27 | Self { 28 | opengl32_dll, 29 | } 30 | } 31 | 32 | pub fn get_proc_address(&self, name: &str) -> *const c_void { 33 | unsafe { 34 | let mut ptr = opengl32::wglGetProcAddress( 35 | CString::new(name).unwrap().as_ptr() as *const _ 36 | ); 37 | 38 | if ptr.is_null() { 39 | ptr = kernel32::GetProcAddress( 40 | self.opengl32_dll, 41 | CString::new(name).unwrap().as_ptr() as *const _ 42 | ); 43 | } 44 | else { 45 | //println!("Loaded with rendering context: {}", name); 46 | } 47 | 48 | if ptr.is_null() { 49 | //println!("Couldn't load function: {}", name); 50 | } 51 | else { 52 | //println!("Loaded with opengl32.dll: {}", name); 53 | } 54 | 55 | ptr 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /windows-opengl-vst/src/editor/window/win32_window.rs: -------------------------------------------------------------------------------- 1 | use std::ptr::null_mut; 2 | use log::*; 3 | 4 | use super::util; 5 | use super::window_class::WindowClass; 6 | 7 | pub struct Win32Window { 8 | handle: winapi::HWND, 9 | } 10 | 11 | impl Win32Window { 12 | pub fn new(parent: *mut std::ffi::c_void, window_class: WindowClass, title: &str, dummy: bool) -> Self { 13 | let title = util::win32_string(title); 14 | 15 | // TODO: what are these flags? 16 | let mut dw_style = 0; 17 | if !dummy { 18 | dw_style = winapi::WS_CHILDWINDOW; 19 | } 20 | 21 | unsafe { 22 | // Create a window using your window class. Store the instance in hinstance(?) 23 | let window_handle = user32::CreateWindowExW( 24 | 0, // TODO: what is this? 25 | window_class.name_win32_ptr(), // Name of the class 26 | title.as_ptr(), // Title of the window 27 | dw_style, 28 | winapi::CW_USEDEFAULT, // Default X coordinate 29 | winapi::CW_USEDEFAULT, // Default Y coordinate 30 | 1024, // Default width 31 | 768, // Default height 32 | parent as *mut winapi::HWND__, // Parent window 33 | null_mut(), // No menus 34 | window_class.hinstance(), // The instance to the handle...? 35 | null_mut(), // TODO: what is this? 36 | ); 37 | 38 | if window_handle.is_null() { 39 | info!("Failed to create the OpenGL window."); 40 | panic!("Failed to create the OpenGL window."); 41 | } 42 | 43 | Self { 44 | handle: window_handle, 45 | } 46 | } 47 | } 48 | 49 | pub fn handle(&self) -> winapi::HWND { 50 | self.handle 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /windows-opengl-vst/src/editor/window/util.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::{c_void, CString, OsStr}; 2 | use std::iter::once; 3 | use std::os::windows::ffi::OsStrExt; 4 | use log::*; 5 | 6 | pub fn win32_string(value: &str) -> Vec { 7 | OsStr::new(value).encode_wide().chain(once(0)).collect() 8 | } 9 | 10 | pub struct ProcAddressLoader { 11 | opengl32_dll: *mut winapi::HINSTANCE__, 12 | } 13 | 14 | impl ProcAddressLoader { 15 | pub fn new() -> Self { 16 | // Some functions load from opengl32.dll, and the others load from the fancy dummy context 17 | // that we have to spin up. 18 | // TODO: spin up fancy dummy context within this struct? Or maybe make the dummy context its own struct? 19 | let opengl32_dll = unsafe { 20 | kernel32::LoadLibraryA(b"opengl32.dll\0".as_ptr() as *const _) 21 | }; 22 | if opengl32_dll.is_null() { 23 | info!("WGL: opengl32.dll not found!"); 24 | panic!("WGL: opengl32.dll not found!"); 25 | } else { 26 | info!("WGL: opengl32.dll loaded!"); 27 | } 28 | 29 | Self { 30 | opengl32_dll, 31 | } 32 | } 33 | 34 | pub fn get_proc_address(&self, name: &str) -> *const c_void { 35 | unsafe { 36 | let mut ptr = opengl32::wglGetProcAddress( 37 | CString::new(name).unwrap().as_ptr() as *const _ 38 | ); 39 | 40 | if ptr.is_null() { 41 | ptr = kernel32::GetProcAddress( 42 | self.opengl32_dll, 43 | CString::new(name).unwrap().as_ptr() as *const _ 44 | ); 45 | } 46 | else { 47 | //println!("Loaded with rendering context: {}", name); 48 | } 49 | 50 | if ptr.is_null() { 51 | //println!("Couldn't load function: {}", name); 52 | } 53 | else { 54 | //println!("Loaded with opengl32.dll: {}", name); 55 | } 56 | 57 | ptr 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /linux-opengl-vst/src/audio_engine/mod.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | use std::{mem, ptr}; 3 | use vst::buffer::AudioBuffer; 4 | 5 | mod square_oscillator; 6 | use self::square_oscillator::SquareOscillator; 7 | use crate::parameters::Parameters; 8 | 9 | pub struct AudioEngine { 10 | oscillators: [SquareOscillator; 128], 11 | sample_rate: f32, 12 | } 13 | 14 | impl AudioEngine { 15 | pub fn new(params: Arc) -> Self { 16 | // Create a bank of 128 different SquareOscillators, tuned to all the different MIDI notes. 17 | // This is a terrible way to actually do it, but it works for now. 18 | // TODO: make this a little smarter. 19 | let oscillators = unsafe { 20 | let mut oscillators: [SquareOscillator; 128] = mem::uninitialized(); 21 | 22 | for (i, osc) in oscillators.iter_mut().enumerate() { 23 | let mut oscillator = SquareOscillator::new(params.clone()); 24 | oscillator.change_frequency(midi_pitch_to_freq(i as u8)); 25 | ptr::write(osc, oscillator); 26 | } 27 | 28 | oscillators 29 | }; 30 | 31 | Self { 32 | oscillators, 33 | sample_rate: 44100.0, 34 | } 35 | } 36 | 37 | pub fn process(&mut self, buffer: &mut AudioBuffer, active_notes: Vec) { 38 | let output_channels = buffer.output_count(); 39 | let num_samples = buffer.samples(); 40 | let (_, output_buffer) = buffer.split(); 41 | 42 | // Precompute the samples that should go to each channel. 43 | // Our oscillator will output the same signal to all channels. 44 | // TODO: put a tiny envelope here (based on sample rate) to prevent clicks 45 | 46 | // TODO: don't allocate inside process() !!! 47 | let mut samples: Vec = Vec::new(); 48 | samples.resize(num_samples, 0.0); 49 | 50 | for sample_num in 0..num_samples { 51 | for note in &active_notes { 52 | samples[sample_num] += 53 | self.oscillators[*note as usize].next_sample(self.sample_rate); 54 | } 55 | } 56 | 57 | // Write the output to each channel. 58 | for channel in 0..output_channels { 59 | let output_channel = output_buffer.get_mut(channel); 60 | let mut sample_counter = 0; 61 | for output_sample in output_channel { 62 | let sample_value = samples[sample_counter] as f32; 63 | *output_sample = sample_value; 64 | sample_counter += 1; 65 | } 66 | } 67 | } 68 | } 69 | 70 | fn midi_pitch_to_freq(pitch: u8) -> f64 { 71 | const A4_PITCH: i8 = 69; 72 | const A4_FREQ: f64 = 440.0; 73 | 74 | (((pitch as i8 - A4_PITCH) as f64) / 12.).exp2() * A4_FREQ 75 | } 76 | -------------------------------------------------------------------------------- /windows-opengl-vst/src/audio_engine/mod.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | use std::{mem, ptr}; 3 | use vst::buffer::AudioBuffer; 4 | 5 | mod square_oscillator; 6 | use self::square_oscillator::SquareOscillator; 7 | use crate::parameters::Parameters; 8 | 9 | pub struct AudioEngine { 10 | oscillators: [SquareOscillator; 128], 11 | sample_rate: f32, 12 | } 13 | 14 | impl AudioEngine { 15 | pub fn new(params: Arc) -> Self { 16 | // Create a bank of 128 different SquareOscillators, tuned to all the different MIDI notes. 17 | // This is a terrible way to actually do it, but it works for now. 18 | // TODO: make this a little smarter. 19 | let oscillators = unsafe { 20 | let mut oscillators: [SquareOscillator; 128] = mem::uninitialized(); 21 | 22 | for (i, osc) in oscillators.iter_mut().enumerate() { 23 | let mut oscillator = SquareOscillator::new(params.clone()); 24 | oscillator.change_frequency(midi_pitch_to_freq(i as u8)); 25 | ptr::write(osc, oscillator); 26 | } 27 | 28 | oscillators 29 | }; 30 | 31 | Self { 32 | oscillators, 33 | sample_rate: 44100.0, 34 | } 35 | } 36 | 37 | pub fn process(&mut self, buffer: &mut AudioBuffer, active_notes: Vec) { 38 | let output_channels = buffer.output_count(); 39 | let num_samples = buffer.samples(); 40 | let (_, output_buffer) = buffer.split(); 41 | 42 | // Precompute the samples that should go to each channel. 43 | // Our oscillator will output the same signal to all channels. 44 | // TODO: put a tiny envelope here (based on sample rate) to prevent clicks 45 | 46 | // TODO: don't allocate inside process() !!! 47 | let mut samples: Vec = Vec::new(); 48 | samples.resize(num_samples, 0.0); 49 | 50 | for sample_num in 0..num_samples { 51 | for note in &active_notes { 52 | samples[sample_num] += 53 | self.oscillators[*note as usize].next_sample(self.sample_rate); 54 | } 55 | } 56 | 57 | // Write the output to each channel. 58 | for channel in 0..output_channels { 59 | let output_channel = output_buffer.get_mut(channel); 60 | let mut sample_counter = 0; 61 | for output_sample in output_channel { 62 | let sample_value = samples[sample_counter] as f32; 63 | *output_sample = sample_value; 64 | sample_counter += 1; 65 | } 66 | } 67 | } 68 | } 69 | 70 | fn midi_pitch_to_freq(pitch: u8) -> f64 { 71 | const A4_PITCH: i8 = 69; 72 | const A4_FREQ: f64 = 440.0; 73 | 74 | (((pitch as i8 - A4_PITCH) as f64) / 12.).exp2() * A4_FREQ 75 | } 76 | -------------------------------------------------------------------------------- /osx-opengl/src/view.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_snake_case)] 2 | 3 | use core::ffi::c_void; 4 | use cocoa::appkit::*; 5 | use cocoa::base::*; 6 | use cocoa::appkit; 7 | 8 | use objc::runtime::{ Object, Sel, Class }; 9 | use objc::declare::{ ClassDecl }; 10 | 11 | use std::sync::{Once, ONCE_INIT}; 12 | 13 | pub fn view_class() -> *const Class { 14 | static mut DELEGATE_CLASS: *const Class = 0 as *const Class; 15 | static INIT: Once = ONCE_INIT; 16 | 17 | INIT.call_once(|| { 18 | let superclass = class!(NSView); 19 | let mut decl = ClassDecl::new("MainView", superclass).unwrap(); 20 | 21 | decl.add_ivar::<*mut c_void>("windowState"); 22 | 23 | unsafe { 24 | 25 | decl.add_method(sel!(acceptsFirstResponder), 26 | acceptsFirstResponder as extern "C" fn(&Object, Sel) -> BOOL); 27 | extern "C" fn acceptsFirstResponder(_this: &Object, _sel: Sel) -> BOOL { YES } 28 | 29 | // Overridden by subclasses to return true if the view should be sent a mouseDown(with:) 30 | // message for an initial mouse-down event 31 | decl.add_method(sel!(acceptsFirstMouse:), 32 | acceptsFirstMouse as extern fn(this: &Object, _: Sel, nsevent_id: id) -> BOOL); 33 | extern "C" fn acceptsFirstMouse(_this: &Object, _sel: Sel, _: id) -> BOOL { println!("acceptsFirstMouse"); YES } 34 | 35 | decl.add_method(sel!(scrollWheel:), processNSEvent as extern "C" fn(&Object, Sel, id)); 36 | decl.add_method(sel!(mouseDown:), processNSEvent as extern "C" fn(&Object, Sel, id)); 37 | decl.add_method(sel!(mouseUp:), processNSEvent as extern "C" fn(&Object, Sel, id)); 38 | decl.add_method(sel!(mouseMoved:), processNSEvent as extern "C" fn(&Object, Sel, id)); 39 | decl.add_method(sel!(keyDown:), processNSEvent as extern "C" fn(&Object, Sel, id)); 40 | decl.add_method(sel!(keyUp:), processNSEvent as extern "C" fn(&Object, Sel, id)); 41 | extern "C" fn processNSEvent(this: &Object, _: Sel, nsevent_id: id) { 42 | unsafe { 43 | use super::*; 44 | let window: id = msg_send![this, window]; 45 | let delegate: id = msg_send![window, delegate]; 46 | let ivar: *const c_void = *(&*delegate).get_ivar("windowState"); 47 | let event: id = nsevent_id; 48 | println!("EVENT? {:?}", event.eventType()); 49 | //let state = &*(ivar as *const WindowState); 50 | 51 | /* 52 | if let Some(event) = ns_event_to_event(nsevent_id) { 53 | (state.event_cb)(event); 54 | } 55 | */ 56 | } 57 | } 58 | 59 | decl.add_method(sel!(applicationDidFinishLaunching:), 60 | did_finish_launching as extern "C" fn(&Object, Sel, id)); 61 | extern "C" fn did_finish_launching(_this: &Object, _sel: Sel, _notification: id) { 62 | println!("applicationDidFinishLaunching"); 63 | } 64 | 65 | DELEGATE_CLASS = decl.register(); 66 | } 67 | }); 68 | unsafe { DELEGATE_CLASS } 69 | } -------------------------------------------------------------------------------- /osx-opengl/src/main.rs: -------------------------------------------------------------------------------- 1 | use cocoa::appkit::{NSApp, NSApplication, NSApplicationActivateIgnoringOtherApps, NSApplicationActivationPolicyRegular, NSBackingStoreBuffered, NSRunningApplication, NSWindow, NSWindowStyleMask, NSOpenGLPixelFormat, NSOpenGLView, NSView}; 2 | use cocoa::base::{id, nil, NO, YES}; 3 | use cocoa::foundation::{NSAutoreleasePool, NSPoint, NSRect, NSSize}; 4 | use cocoa::appkit::NSOpenGLPFAOpenGLProfiles::NSOpenGLProfileVersion3_2Core; 5 | use cocoa::appkit::NSOpenGLPixelFormatAttribute::{NSOpenGLPFAColorSize, NSOpenGLPFAOpenGLProfile, NSOpenGLPFAAlphaSize, NSOpenGLPFADoubleBuffer, NSOpenGLPFAAccelerated}; 6 | use core_foundation::string::CFString; 7 | use core_foundation::bundle::{CFBundleGetBundleWithIdentifier, CFBundleGetFunctionPointerForName}; 8 | use core_foundation::base::TCFType; 9 | use cocoa::appkit::NSOpenGLContext; 10 | #[macro_use] extern crate objc; 11 | 12 | mod view; 13 | 14 | fn main() { 15 | unsafe { 16 | let _pool = NSAutoreleasePool::new(nil); 17 | 18 | let app = NSApp(); 19 | app.setActivationPolicy_(NSApplicationActivationPolicyRegular); 20 | 21 | let rect = NSRect::new(NSPoint::new(0.0, 0.0), NSSize::new(640.0, 480.0)); 22 | 23 | // Create window 24 | let window = NSWindow::alloc(nil) 25 | .initWithContentRect_styleMask_backing_defer_( 26 | rect, 27 | NSWindowStyleMask::NSTitledWindowMask, 28 | NSBackingStoreBuffered, 29 | NO, 30 | ) 31 | .autorelease(); 32 | window.cascadeTopLeftFromPoint_(NSPoint::new(20.0, 20.0)); 33 | window.makeKeyAndOrderFront_(nil); 34 | window.setAcceptsMouseMovedEvents_(YES); 35 | 36 | // OpenGL view 37 | let pixel_format_attributes = [ 38 | NSOpenGLPFAOpenGLProfile as u32, NSOpenGLProfileVersion3_2Core as u32, 39 | NSOpenGLPFAColorSize as u32, 24, 40 | NSOpenGLPFAAlphaSize as u32, 8, 41 | NSOpenGLPFADoubleBuffer as u32, 42 | NSOpenGLPFAAccelerated as u32, 43 | 0, 44 | ]; 45 | 46 | let pixel_format = NSOpenGLPixelFormat::alloc(nil) 47 | .initWithAttributes_(&pixel_format_attributes) 48 | .autorelease(); 49 | 50 | let opengl_view = NSOpenGLView::alloc(nil) 51 | .initWithFrame_pixelFormat_(rect, pixel_format) 52 | .autorelease(); 53 | 54 | window.setContentView_(opengl_view); 55 | NSOpenGLView::display_(opengl_view); 56 | 57 | let context: id = msg_send![opengl_view, openGLContext]; 58 | 59 | gl::load_with(|addr| { 60 | let symbol_name: CFString = std::str::FromStr::from_str(addr).unwrap(); 61 | let framework_name: CFString = std::str::FromStr::from_str("com.apple.opengl").unwrap(); 62 | let framework = CFBundleGetBundleWithIdentifier(framework_name.as_concrete_TypeRef()); 63 | let symbol = CFBundleGetFunctionPointerForName(framework, symbol_name.as_concrete_TypeRef()); 64 | symbol as *const _ 65 | }); 66 | 67 | // Set a delegate for the window to handle events 68 | let event_delegate: id = msg_send![view::view_class(), new]; 69 | window.setDelegate_(event_delegate); 70 | window.makeFirstResponder_(event_delegate); 71 | NSView::initWithFrame_(event_delegate, NSView::frame(opengl_view as id)); 72 | opengl_view.addSubview_(event_delegate); 73 | 74 | // Draw 75 | context.makeCurrentContext(); 76 | gl::ClearColor(0.5, 0.5, 1.0, 1.0); 77 | gl::Clear(gl::COLOR_BUFFER_BIT); 78 | context.flushBuffer(); 79 | msg_send![opengl_view, setNeedsDisplay: YES]; 80 | 81 | // Run the "app" 82 | let current_app = NSRunningApplication::currentApplication(nil); 83 | current_app.activateWithOptions_(NSApplicationActivateIgnoringOtherApps); 84 | app.run(); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /windows-opengl/Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "cfg-if" 3 | version = "0.1.7" 4 | source = "registry+https://github.com/rust-lang/crates.io-index" 5 | 6 | [[package]] 7 | name = "gdi32-sys" 8 | version = "0.2.0" 9 | source = "registry+https://github.com/rust-lang/crates.io-index" 10 | dependencies = [ 11 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 12 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 13 | ] 14 | 15 | [[package]] 16 | name = "gl" 17 | version = "0.12.0" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | dependencies = [ 20 | "gl_generator 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 21 | ] 22 | 23 | [[package]] 24 | name = "gl_generator" 25 | version = "0.11.0" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | dependencies = [ 28 | "khronos_api 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 29 | "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 30 | "xml-rs 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", 31 | ] 32 | 33 | [[package]] 34 | name = "kernel32-sys" 35 | version = "0.2.2" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | dependencies = [ 38 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 39 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 40 | ] 41 | 42 | [[package]] 43 | name = "khronos_api" 44 | version = "3.1.0" 45 | source = "registry+https://github.com/rust-lang/crates.io-index" 46 | 47 | [[package]] 48 | name = "log" 49 | version = "0.4.6" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | dependencies = [ 52 | "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 53 | ] 54 | 55 | [[package]] 56 | name = "opengl32-sys" 57 | version = "0.1.1" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | dependencies = [ 60 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 61 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 62 | ] 63 | 64 | [[package]] 65 | name = "user32-sys" 66 | version = "0.2.0" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | dependencies = [ 69 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 70 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 71 | ] 72 | 73 | [[package]] 74 | name = "winapi" 75 | version = "0.2.8" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | 78 | [[package]] 79 | name = "winapi-build" 80 | version = "0.1.1" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | 83 | [[package]] 84 | name = "windows-window" 85 | version = "0.1.0" 86 | dependencies = [ 87 | "gdi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 88 | "gl 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", 89 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 90 | "opengl32-sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 91 | "user32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 92 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 93 | ] 94 | 95 | [[package]] 96 | name = "xml-rs" 97 | version = "0.8.0" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | 100 | [metadata] 101 | "checksum cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "11d43355396e872eefb45ce6342e4374ed7bc2b3a502d1b28e36d6e23c05d1f4" 102 | "checksum gdi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0912515a8ff24ba900422ecda800b52f4016a56251922d397c576bf92c690518" 103 | "checksum gl 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bd1d817593520b978454bc6328ad152a7373013007c4174de3b890021a45bb8c" 104 | "checksum gl_generator 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "39a23d5e872a275135d66895d954269cf5e8661d234eb1c2480f4ce0d586acbd" 105 | "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 106 | "checksum khronos_api 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" 107 | "checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" 108 | "checksum opengl32-sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ef7d84c332caf3129935dc0751ef01222e449caf2657179d4b304d3fecb739bc" 109 | "checksum user32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ef4711d107b21b410a3a974b1204d9accc8b10dad75d8324b5d755de1617d47" 110 | "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 111 | "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 112 | "checksum xml-rs 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "541b12c998c5b56aa2b4e6f18f03664eef9a4fd0a246a55594efae6cc2d964b5" 113 | -------------------------------------------------------------------------------- /linux-opengl-vst/src/gvl_plugin.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::sync::{Arc, Mutex}; 3 | 4 | use log::*; 5 | use vst::editor::Editor as VstEditor; 6 | use vst::{ 7 | api::{Events, Supported}, 8 | buffer::AudioBuffer, 9 | event::Event, 10 | plugin::{CanDo, Category, HostCallback, Info, Plugin}, 11 | }; 12 | 13 | use crate::audio_engine::AudioEngine; 14 | use crate::editor::Editor; 15 | use crate::midi_input_processor::MidiInputProcessor; 16 | use crate::parameters::Parameters; 17 | 18 | pub struct GvlPlugin { 19 | host: HostCallback, 20 | audio_engine: AudioEngine, 21 | midi_input_processor: MidiInputProcessor, 22 | params: Arc, 23 | editor: Box, 24 | } 25 | 26 | impl vst::plugin::Plugin for GvlPlugin { 27 | fn new(host: HostCallback) -> Self { 28 | // Set up a logger so we can see what's going on in the VST 29 | let mut logger_config = simplelog::Config::default(); 30 | logger_config.time_format = Some("%H:%M:%S%.6f"); 31 | simplelog::CombinedLogger::init(vec![simplelog::WriteLogger::new( 32 | simplelog::LevelFilter::max(), 33 | logger_config, 34 | File::create("/tmp/plugin.log").unwrap(), 35 | )]) 36 | .unwrap(); 37 | info!("===================================================================="); 38 | info!("Plugin::new()"); 39 | 40 | // Create the plugin itself 41 | let host_callback = Arc::new(Mutex::new(host)); 42 | let params = Arc::new(Parameters::new()); 43 | Self { 44 | host, 45 | audio_engine: AudioEngine::new(params.clone()), 46 | midi_input_processor: MidiInputProcessor::new(), 47 | params: params.clone(), 48 | editor: Box::new(Editor::new(host_callback, params.clone())), 49 | } 50 | } 51 | 52 | fn get_info(&self) -> Info { 53 | info!("get_info()"); 54 | Info { 55 | name: "gvl".to_string(), 56 | vendor: "crsaracco".to_string(), 57 | unique_id: 1147000002, // Make sure this is a unique number across all of your VSTs! 58 | category: Category::Synth, 59 | inputs: 0, 60 | midi_inputs: 1, 61 | outputs: 2, 62 | parameters: 2, 63 | initial_delay: 0, 64 | ..Info::default() 65 | } 66 | } 67 | 68 | fn init(&mut self) { 69 | info!("init()"); 70 | info!("host VST version: {}", self.host.vst_version()); 71 | } 72 | 73 | fn process(&mut self, buffer: &mut AudioBuffer) { 74 | self.audio_engine 75 | .process(buffer, self.midi_input_processor.get_active_notes()); 76 | } 77 | 78 | fn can_do(&self, can_do: CanDo) -> Supported { 79 | match can_do { 80 | CanDo::ReceiveMidiEvent => Supported::Yes, 81 | _ => Supported::Maybe, 82 | } 83 | } 84 | 85 | fn process_events(&mut self, events: &Events) { 86 | for event in events.events() { 87 | match event { 88 | Event::Midi(ev) => self.midi_input_processor.process_midi_event(ev.data), 89 | // More events can be handled here. 90 | _ => (), 91 | } 92 | } 93 | } 94 | 95 | fn get_parameter(&self, index: i32) -> f32 { 96 | info!("get_parameter({})", index); 97 | match index { 98 | 0 => self.params.amplitude.get(), 99 | 1 => self.params.pulse_width.get(), 100 | _ => 0.0, 101 | } 102 | } 103 | 104 | fn set_parameter(&mut self, index: i32, value: f32) { 105 | info!("set_parameter()"); 106 | match index { 107 | 0 => self.params.amplitude.set(value), 108 | 1 => self.params.pulse_width.set(value), 109 | _ => (), 110 | } 111 | } 112 | 113 | // "Amplitude", "Pulse width", etc. 114 | fn get_parameter_name(&self, index: i32) -> String { 115 | info!("get_parameter_name({})", index); 116 | match index { 117 | 0 => format!("Amplitude"), 118 | 1 => format!("Pulse width"), 119 | _ => format!(""), 120 | } 121 | } 122 | 123 | // Ignored by Bitwig, so I just put this in `get_parameter_text` instead. 124 | // "db", "sec", "ms", etc. 125 | fn get_parameter_label(&self, index: i32) -> String { 126 | info!("get_parameter_label({})", index); 127 | format!("asdf") 128 | } 129 | 130 | // "1.0", "150", "Plate", etc. 131 | fn get_parameter_text(&self, index: i32) -> String { 132 | info!("get_parameter_text({})", index); 133 | match index { 134 | 0 => format!("{:0.2} %", self.params.amplitude.get() * 100.0), // Amplitude 135 | 1 => format!("{:0.2} %", self.params.pulse_width.get() * 100.0), // Pulse width 136 | _ => format!(""), 137 | } 138 | } 139 | 140 | fn can_be_automated(&self, index: i32) -> bool { 141 | info!("can_be_automated({})", index); 142 | match index { 143 | 0 => true, // Amplitude 144 | 1 => true, // Pulse width 145 | _ => false, 146 | } 147 | } 148 | 149 | fn get_editor(&mut self) -> Option<&mut vst::editor::Editor> { 150 | // info!("Plugin::get_editor()"); 151 | Some(self.editor.as_mut()) 152 | } 153 | } 154 | 155 | impl Default for GvlPlugin { 156 | fn default() -> Self { 157 | Plugin::new(HostCallback::default()) 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /windows-opengl-vst/src/gvw_plugin.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::sync::{Arc, Mutex}; 3 | 4 | use log::*; 5 | use vst::editor::Editor as VstEditor; 6 | use vst::{ 7 | api::{Events, Supported}, 8 | buffer::AudioBuffer, 9 | event::Event, 10 | plugin::{CanDo, Category, HostCallback, Info, Plugin}, 11 | }; 12 | 13 | use crate::audio_engine::AudioEngine; 14 | use crate::editor::Editor; 15 | use crate::midi_input_processor::MidiInputProcessor; 16 | use crate::parameters::Parameters; 17 | 18 | pub struct GvlPlugin { 19 | host: HostCallback, 20 | audio_engine: AudioEngine, 21 | midi_input_processor: MidiInputProcessor, 22 | params: Arc, 23 | editor: Box, 24 | } 25 | 26 | impl vst::plugin::Plugin for GvlPlugin { 27 | fn new(host: HostCallback) -> Self { 28 | // Set up a logger so we can see what's going on in the VST 29 | let mut logger_config = simplelog::Config::default(); 30 | logger_config.time_format = Some("%H:%M:%S%.6f"); 31 | simplelog::CombinedLogger::init(vec![simplelog::WriteLogger::new( 32 | simplelog::LevelFilter::max(), 33 | logger_config, 34 | File::create("C://users/crs/Documents/plugin.log").unwrap(), 35 | )]) 36 | .unwrap(); 37 | info!("===================================================================="); 38 | info!("Plugin::new()"); 39 | 40 | // Create the plugin itself 41 | let host_callback = Arc::new(Mutex::new(host)); 42 | let params = Arc::new(Parameters::new()); 43 | Self { 44 | host, 45 | audio_engine: AudioEngine::new(params.clone()), 46 | midi_input_processor: MidiInputProcessor::new(), 47 | params: params.clone(), 48 | editor: Box::new(Editor::new(host_callback, params.clone())), 49 | } 50 | } 51 | 52 | fn get_info(&self) -> Info { 53 | info!("get_info()"); 54 | Info { 55 | name: "gvl".to_string(), 56 | vendor: "crsaracco".to_string(), 57 | unique_id: 1147000002, // Make sure this is a unique number across all of your VSTs! 58 | category: Category::Synth, 59 | inputs: 0, 60 | midi_inputs: 1, 61 | outputs: 2, 62 | parameters: 2, 63 | initial_delay: 0, 64 | ..Info::default() 65 | } 66 | } 67 | 68 | fn init(&mut self) { 69 | info!("init()"); 70 | info!("host VST version: {}", self.host.vst_version()); 71 | } 72 | 73 | fn process(&mut self, buffer: &mut AudioBuffer) { 74 | self.audio_engine 75 | .process(buffer, self.midi_input_processor.get_active_notes()); 76 | } 77 | 78 | fn can_do(&self, can_do: CanDo) -> Supported { 79 | match can_do { 80 | CanDo::ReceiveMidiEvent => Supported::Yes, 81 | _ => Supported::Maybe, 82 | } 83 | } 84 | 85 | fn process_events(&mut self, events: &Events) { 86 | for event in events.events() { 87 | match event { 88 | Event::Midi(ev) => self.midi_input_processor.process_midi_event(ev.data), 89 | // More events can be handled here. 90 | _ => (), 91 | } 92 | } 93 | } 94 | 95 | fn get_parameter(&self, index: i32) -> f32 { 96 | info!("get_parameter({})", index); 97 | match index { 98 | 0 => self.params.amplitude.get(), 99 | 1 => self.params.pulse_width.get(), 100 | _ => 0.0, 101 | } 102 | } 103 | 104 | fn set_parameter(&mut self, index: i32, value: f32) { 105 | info!("set_parameter()"); 106 | match index { 107 | 0 => self.params.amplitude.set(value), 108 | 1 => self.params.pulse_width.set(value), 109 | _ => (), 110 | } 111 | } 112 | 113 | // "Amplitude", "Pulse width", etc. 114 | fn get_parameter_name(&self, index: i32) -> String { 115 | info!("get_parameter_name({})", index); 116 | match index { 117 | 0 => format!("Amplitude"), 118 | 1 => format!("Pulse width"), 119 | _ => format!(""), 120 | } 121 | } 122 | 123 | // Ignored by Bitwig, so I just put this in `get_parameter_text` instead. 124 | // "db", "sec", "ms", etc. 125 | fn get_parameter_label(&self, index: i32) -> String { 126 | info!("get_parameter_label({})", index); 127 | format!("asdf") 128 | } 129 | 130 | // "1.0", "150", "Plate", etc. 131 | fn get_parameter_text(&self, index: i32) -> String { 132 | info!("get_parameter_text({})", index); 133 | match index { 134 | 0 => format!("{:0.2} %", self.params.amplitude.get() * 100.0), // Amplitude 135 | 1 => format!("{:0.2} %", self.params.pulse_width.get() * 100.0), // Pulse width 136 | _ => format!(""), 137 | } 138 | } 139 | 140 | fn can_be_automated(&self, index: i32) -> bool { 141 | info!("can_be_automated({})", index); 142 | match index { 143 | 0 => true, // Amplitude 144 | 1 => true, // Pulse width 145 | _ => false, 146 | } 147 | } 148 | 149 | fn get_editor(&mut self) -> Option<&mut vst::editor::Editor> { 150 | //info!("Plugin::get_editor()"); 151 | Some(self.editor.as_mut()) 152 | } 153 | } 154 | 155 | impl Default for GvlPlugin { 156 | fn default() -> Self { 157 | Plugin::new(HostCallback::default()) 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /linux-xcb-vst/src/gui_vst.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use vst::{ 3 | plugin::{ 4 | Plugin, 5 | Info, 6 | CanDo, 7 | Category, 8 | HostCallback, 9 | }, 10 | event::Event, 11 | api::{Supported, Events}, 12 | buffer::AudioBuffer, 13 | }; 14 | use log::*; 15 | use std::sync::Arc; 16 | use std::sync::Mutex; 17 | 18 | use crate::x_handle::XHandle; 19 | use crate::editor::Editor; 20 | use crate::parameters::Parameters; 21 | use crate::square_oscillator::SquareOscillator; 22 | 23 | pub struct GuiVst { 24 | host: HostCallback, 25 | editor: Editor, 26 | parameters: Arc, 27 | square_oscillator: SquareOscillator, 28 | sample_rate: f64, 29 | note_duration: f64, 30 | note: Option, 31 | } 32 | 33 | fn midi_pitch_to_freq(pitch: u8) -> f64 { 34 | const A4_PITCH: i8 = 69; 35 | const A4_FREQ: f64 = 440.0; 36 | 37 | (((pitch as i8 - A4_PITCH) as f64) / 12.).exp2() * A4_FREQ 38 | } 39 | 40 | impl GuiVst { 41 | fn process_midi_event(&mut self, data: [u8; 3]) { 42 | match data[0] { 43 | 128 => self.note_off(data[1]), 44 | 144 => self.note_on(data[1]), 45 | _ => () 46 | } 47 | } 48 | 49 | fn note_on(&mut self, note: u8) { 50 | self.note_duration = 0.0; 51 | self.note = Some(note); 52 | self.square_oscillator.change_frequency(midi_pitch_to_freq(note)); 53 | } 54 | 55 | fn note_off(&mut self, note: u8) { 56 | if self.note == Some(note) { 57 | self.note = None; 58 | } 59 | } 60 | } 61 | 62 | impl Default for GuiVst { 63 | fn default() -> Self { 64 | GuiVst::new(HostCallback::default()) 65 | } 66 | } 67 | 68 | impl Plugin for GuiVst { 69 | fn new(host: HostCallback) -> Self { 70 | // Set up a logger so we can see what's going on in the VST 71 | let mut logger_config = simplelog::Config::default(); 72 | logger_config.time_format = Some("%H:%M:%S%.6f"); 73 | 74 | simplelog::CombinedLogger::init( 75 | vec![ 76 | simplelog::WriteLogger::new( 77 | simplelog::LevelFilter::max(), 78 | logger_config, 79 | File::create("/tmp/plugin.log").unwrap() 80 | ), 81 | ] 82 | ).unwrap(); 83 | 84 | let x_handle = Box::new(XHandle::new()); 85 | let parameters = Arc::new(Parameters::new()); 86 | let host_callback = Arc::new(Mutex::new(host)); 87 | 88 | // Set up an Editor that uses this connection. 89 | let editor_parameters = parameters.clone(); 90 | let square_oscillator_parameters = parameters.clone(); 91 | Self { 92 | host, 93 | editor: Editor::new(x_handle, editor_parameters.clone(), host_callback), 94 | parameters, 95 | square_oscillator: SquareOscillator::new(square_oscillator_parameters.clone()), 96 | sample_rate: 44000.0, 97 | note_duration: 0.0, 98 | note: None, 99 | } 100 | } 101 | 102 | fn get_info(&self) -> Info { 103 | Info { 104 | name: "gui-vst".to_string(), 105 | vendor: "crsaracco".to_string(), 106 | unique_id: 1147000001, // Make sure this is a unique number across all of your VSTs! 107 | category: Category::Synth, 108 | inputs: 0, 109 | midi_inputs: 1, 110 | outputs: 2, 111 | parameters: 2, 112 | initial_delay: 0, 113 | ..Info::default() 114 | } 115 | } 116 | 117 | fn init(&mut self) { 118 | info!("init()"); 119 | info!("host VST version: {}", self.host.vst_version()); 120 | } 121 | 122 | // TODO: return None if the editor couldn't be created 123 | // (for example, if the connection to the X server couldn't be established) 124 | fn get_editor(&mut self) -> Option<&mut vst::editor::Editor> { 125 | //info!("get_editor()"); 126 | self.editor.draw_editor(); 127 | Some(&mut self.editor) 128 | } 129 | 130 | fn get_parameter(&self, index: i32) -> f32 { 131 | info!("get_parameter"); 132 | match index { 133 | 0 => self.parameters.param1.get(), 134 | 1 => self.parameters.param2.get(), 135 | _ => 0.0, 136 | } 137 | } 138 | 139 | fn get_parameter_text(&self, index: i32) -> String { 140 | match index { 141 | 0 => format!("{:.1}%", self.parameters.param1.get() * 100.0), 142 | 1 => format!("{:.1}%", self.parameters.param2.get() * 100.0), 143 | _ => "".to_string(), 144 | } 145 | } 146 | 147 | fn get_parameter_name(&self, index: i32) -> String { 148 | match index { 149 | 0 => "Parameter 1", 150 | 1 => "Parameter 2", 151 | _ => "", 152 | }.to_string() 153 | } 154 | 155 | fn set_parameter(&mut self, index: i32, val: f32) { 156 | info!("set_parameter"); 157 | match index { 158 | 0 => { 159 | self.parameters.param1.set(val); 160 | }, 161 | 1 => { 162 | self.parameters.param2.set(val); 163 | }, 164 | _ => (), 165 | } 166 | } 167 | 168 | fn process_events(&mut self, events: &Events) { 169 | for event in events.events() { 170 | match event { 171 | Event::Midi(ev) => self.process_midi_event(ev.data), 172 | // More events can be handled here. 173 | _ => () 174 | } 175 | } 176 | } 177 | 178 | fn can_do(&self, can_do: CanDo) -> Supported { 179 | match can_do { 180 | CanDo::ReceiveMidiEvent => Supported::Yes, 181 | _ => Supported::Maybe 182 | } 183 | } 184 | 185 | fn process(&mut self, buffer: &mut AudioBuffer) { 186 | let output_channels = buffer.output_count(); 187 | let num_samples = buffer.samples(); 188 | let (_, output_buffer) = buffer.split(); 189 | 190 | // Precompute the samples that should go to each channel. 191 | // Our oscillator will output the same signal to all channels. 192 | let mut samples: Vec = Vec::new(); // NOTE: don't actually use Vec in a real synth! 193 | if let Some(_) = self.note { 194 | for _ in 0..(num_samples) { 195 | samples.push(self.square_oscillator.next_sample(self.sample_rate)); 196 | } 197 | } 198 | else { 199 | for _ in 0..(num_samples) { 200 | // NOTE: You want to use some sort of envelope for real music use, otherwise you 201 | // will get clicks at the start and end of playback. 202 | samples.push(0.0); 203 | } 204 | } 205 | 206 | // Write the output to each channel. 207 | for channel in 0..output_channels { 208 | let output_channel = output_buffer.get_mut(channel); 209 | let mut sample_counter = 0; 210 | for output_sample in output_channel { 211 | *output_sample = samples[sample_counter] as f32; 212 | sample_counter += 1; 213 | } 214 | } 215 | } 216 | } -------------------------------------------------------------------------------- /windows-opengl/src/main.rs: -------------------------------------------------------------------------------- 1 | // Resources: 2 | // - https://stackoverflow.com/a/49034511 3 | // - https://www.khronos.org/opengl/wiki/Creating_an_OpenGL_Context_(WGL) 4 | // - https://gist.github.com/nickrolfe/1127313ed1dbf80254b614a721b3ee9c 5 | 6 | extern crate gl; 7 | extern crate kernel32; 8 | extern crate opengl32; 9 | extern crate user32; 10 | extern crate winapi; 11 | 12 | use std::mem; 13 | use std::ptr::null_mut; 14 | use std::ffi::CString; 15 | 16 | use user32::{DefWindowProcW, DispatchMessageW, GetMessageW, TranslateMessage}; 17 | use winapi::HWND; 18 | use winapi::winuser::MSG; 19 | 20 | mod pixel_format; 21 | mod util; 22 | mod window; 23 | mod window_class; 24 | use window::Window; 25 | use window_class::WindowClass; 26 | 27 | fn draw_window(hwnd: HWND) { 28 | unsafe { 29 | gl::ClearColor(0.5f32, 0.5f32, 1.0f32, 1.0f32); 30 | gl::Clear(gl::COLOR_BUFFER_BIT); 31 | gl::Flush(); 32 | let hdc = user32::GetDC(hwnd); 33 | gdi32::SwapBuffers(hdc); 34 | user32::ReleaseDC(hwnd, hdc); 35 | } 36 | } 37 | 38 | unsafe extern "system" fn wnd_proc( 39 | hwnd: HWND, 40 | msg: winapi::UINT, 41 | wparam: winapi::WPARAM, 42 | lparam: winapi::LPARAM, 43 | ) -> winapi::LRESULT { 44 | // TODO: This was a static in C/C++. Does it need to be? 45 | let mut paint_struct = mem::zeroed(); 46 | 47 | match msg { 48 | winapi::WM_DESTROY => { 49 | println!("Destroyed!"); 50 | user32::PostQuitMessage(0); 51 | 0 52 | } 53 | winapi::WM_PAINT => { 54 | println!("Paint"); 55 | 56 | // TODO: This came after the draw_window() function in example code I found. Should it be? 57 | user32::BeginPaint(hwnd, &mut paint_struct); 58 | 59 | draw_window(hwnd); 60 | user32::EndPaint(hwnd, &mut paint_struct); 61 | 62 | DefWindowProcW(hwnd, msg, wparam, lparam) 63 | } 64 | _ => { 65 | //println!("Some other event"); 66 | DefWindowProcW(hwnd, msg, wparam, lparam) 67 | } 68 | } 69 | } 70 | 71 | fn handle_message(window: &mut Window) -> bool { 72 | unsafe { 73 | let mut message: MSG = mem::uninitialized(); 74 | if GetMessageW(&mut message as *mut MSG, window.handle(), 0, 0) > 0 { 75 | TranslateMessage(&message as *const MSG); 76 | DispatchMessageW(&message as *const MSG); 77 | 78 | true 79 | } else { 80 | false 81 | } 82 | } 83 | } 84 | 85 | fn init_opengl(real_hdc: winapi::HDC, proc_address_loader: &util::ProcAddressLoader) -> *mut winapi::HGLRC__ { 86 | use std::os::raw::c_int; 87 | use std::os::raw::c_uint; 88 | 89 | type wglCreateContextAttribsArbType = unsafe extern "system" fn(winapi::HDC, winapi::HGLRC, *const c_int) -> winapi::HGLRC; 90 | type wglChoosePixelFormatArbType = unsafe extern "system" fn(winapi::HDC, *const c_int, *const f32, c_uint, *mut c_int, *mut c_uint) -> c_int; 91 | 92 | // Dummy window context setup (get wglCreateContextAttribsArb and wglChoosePixelFormatArb) 93 | let (wglCreateContextAttribsArb, wglChoosePixelFormatArb) = unsafe { 94 | let dummy_window_class = WindowClass::new( 95 | "wgl_dummy_class_name_dontuse".into(), 96 | Some(user32::DefWindowProcA), 97 | ); 98 | 99 | let dummy_window = Window::new(dummy_window_class, "Dummy Window", true); 100 | 101 | // Create a dummy context handler for this dummy window 102 | { 103 | let device_context_handler = user32::GetDC(dummy_window.handle()); 104 | 105 | let (pixel_format, mut pixel_format_descriptor) = pixel_format::dummy_pixel_format(device_context_handler); 106 | 107 | // Set the pixel format...? 108 | if gdi32::SetPixelFormat( 109 | device_context_handler, 110 | pixel_format, 111 | &pixel_format_descriptor, 112 | ) == 0 113 | { 114 | panic!("Failed to set the pixel format for the dummy window."); 115 | } 116 | 117 | // Describe pixel format......? 118 | gdi32::DescribePixelFormat( 119 | device_context_handler, 120 | pixel_format, 121 | std::mem::size_of::() as u32, 122 | &mut pixel_format_descriptor, 123 | ); 124 | 125 | user32::ReleaseDC(dummy_window.handle(), device_context_handler); 126 | } 127 | 128 | // Fetch context from window 129 | let dummy_hdc = user32::GetDC(dummy_window.handle()); 130 | let dummy_context = opengl32::wglCreateContext(dummy_hdc); 131 | if dummy_context.is_null() { 132 | panic!("Failed to create a dummy OpenGL rendering context."); 133 | } 134 | 135 | if opengl32::wglMakeCurrent(dummy_hdc, dummy_context) == 0 { 136 | panic!("Failed to activate dummy OpenGL rendering context."); 137 | } 138 | 139 | let wglCreateContextAttribsArb = proc_address_loader.get_proc_address("wglCreateContextAttribsARB"); 140 | let wglChoosePixelFormatArb = proc_address_loader.get_proc_address("wglChoosePixelFormatARB"); 141 | 142 | // Destroy context 143 | opengl32::wglMakeCurrent(dummy_hdc, null_mut()); 144 | opengl32::wglDeleteContext(dummy_context); 145 | user32::ReleaseDC(dummy_window.handle(), dummy_hdc); 146 | 147 | (wglCreateContextAttribsArb, wglChoosePixelFormatArb) 148 | }; 149 | 150 | let wglCreateContextAttribsArb: wglCreateContextAttribsArbType = unsafe { 151 | std::mem::transmute(wglCreateContextAttribsArb) 152 | }; 153 | let wglChoosePixelFormatArb: wglChoosePixelFormatArbType = unsafe { 154 | std::mem::transmute(wglChoosePixelFormatArb) 155 | }; 156 | 157 | // Now we can set up pixel formats the "correct" way with wglChoosePixelFormatArb. 158 | 159 | // https://www.opengl.org/registry/specs/ARB/wgl_pixel_format.txt 160 | let wgl_draw_to_window_arb = 0x2001; 161 | let wgl_acceleration_arb = 0x2003; 162 | let wgl_support_opengl_arb = 0x2010; 163 | let wgl_double_buffer_arb = 0x2011; 164 | let wgl_pixel_type_arb = 0x2013; 165 | let wgl_color_bits_arb = 0x2014; 166 | let wgl_depth_bits_arb = 0x2022; 167 | let wgl_stencil_bits_arb = 0x02023; 168 | let wgl_full_acceleration_arb = 0x2027; 169 | let wgl_type_rgba_arb = 0x202b; 170 | let gl_true = 1; 171 | 172 | let pixel_format_attribs = [ 173 | wgl_draw_to_window_arb, gl_true, 174 | wgl_support_opengl_arb, gl_true, 175 | wgl_double_buffer_arb, gl_true, 176 | wgl_acceleration_arb, wgl_full_acceleration_arb, 177 | wgl_pixel_type_arb, wgl_type_rgba_arb, 178 | wgl_color_bits_arb, 32, 179 | wgl_depth_bits_arb, 24, 180 | wgl_stencil_bits_arb, 8, 181 | 0, 182 | ]; 183 | 184 | let mut pixel_format: i32 = 0; 185 | let mut num_formats: u32 = 0; 186 | 187 | unsafe { 188 | wglChoosePixelFormatArb(real_hdc, pixel_format_attribs.as_ptr(), 0 as *const f32, 1, &mut pixel_format, &mut num_formats); 189 | if num_formats == 0 { 190 | panic!("Failed to set the OpenGL 3.3 pixel format."); 191 | } 192 | 193 | let mut pixel_format_descriptor: winapi::PIXELFORMATDESCRIPTOR = mem::uninitialized(); 194 | gdi32::DescribePixelFormat(real_hdc, pixel_format, std::mem::size_of::() as u32, &mut pixel_format_descriptor); 195 | 196 | if gdi32::SetPixelFormat( 197 | real_hdc, 198 | pixel_format, 199 | &pixel_format_descriptor, 200 | ) == 0 201 | { 202 | panic!("Failed to set the pixel format for the OpenGL window."); 203 | } 204 | 205 | // Specify OpenGL 3.3 core as our context type 206 | let wgl_context_major_version_arb = 0x2091; 207 | let wgl_context_minor_version_arb = 0x2092; 208 | let wgl_context_profile_mask_arb = 0x9126; 209 | let wgl_context_core_profile_bit_arb = 0x00000001; 210 | let gl33_attribs = [ 211 | wgl_context_major_version_arb, 3, 212 | wgl_context_minor_version_arb, 3, 213 | wgl_context_profile_mask_arb, wgl_context_core_profile_bit_arb, 214 | 0 215 | ]; 216 | 217 | let gl33_context = wglCreateContextAttribsArb(real_hdc, 0 as *mut winapi::HGLRC__, gl33_attribs.as_ptr()); 218 | if gl33_context as u32 == 0 { 219 | panic!("Failed to create the OpenGL 3.3 rendering context."); 220 | } 221 | if opengl32::wglMakeCurrent(real_hdc, gl33_context) == 0 { 222 | panic!("Failed to activate the OpenGL 3.3 rendering context.") 223 | } 224 | 225 | return gl33_context; 226 | } 227 | 228 | } 229 | 230 | fn main() { 231 | let proc_address_loader = util::ProcAddressLoader::new(); 232 | let window_class = WindowClass::new("OpenGL Window", Some(wnd_proc)); 233 | let mut window = Window::new(window_class, "OpenGL Window", false); 234 | let hdc = unsafe { 235 | user32::GetDC(window.handle()) 236 | }; 237 | let context = init_opengl(hdc, &proc_address_loader); 238 | 239 | // Load all of the OpenGL function pointers. 240 | gl::load_with(|s| { 241 | proc_address_loader.get_proc_address(s) 242 | }); 243 | 244 | unsafe { 245 | user32::ShowWindow(window.handle(), 1); 246 | } 247 | 248 | // Handle events. 249 | loop { 250 | if !handle_message(&mut window) { 251 | break; 252 | } 253 | } 254 | 255 | unsafe { 256 | opengl32::wglMakeCurrent(hdc, null_mut()); 257 | opengl32::wglDeleteContext(context); 258 | user32::ReleaseDC(window.handle(), hdc); 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /linux-xcb-vst/src/editor.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::c_void; 2 | use log::*; 3 | use std::sync::Arc; 4 | use std::borrow::Borrow; 5 | use std::thread; 6 | use vst::plugin::HostCallback; 7 | use vst::host::Host; 8 | use std::sync::Mutex; 9 | 10 | use crate::x_handle::XHandle; 11 | use crate::parameters::Parameters; 12 | 13 | pub struct Editor { 14 | is_open: bool, 15 | x: i32, 16 | y: i32, 17 | width: i32, 18 | height: i32, 19 | x_handle: Box, 20 | window_handle: u32, 21 | draw_context: u32, 22 | parameters: Arc, 23 | host_callback: Arc>, 24 | } 25 | 26 | impl Editor { 27 | pub fn new(x_handle: Box, parameters: Arc, host_callback: Arc>) -> Self { 28 | info!("GuiVstEditor::new()"); 29 | 30 | Self { 31 | is_open: false, 32 | x: 0, 33 | y: 0, 34 | width: 1000, 35 | height: 1000, 36 | x_handle, 37 | window_handle: 0, 38 | draw_context: 0, 39 | parameters, 40 | host_callback, 41 | } 42 | } 43 | 44 | fn create_draw_context(&mut self, parent: u32) { 45 | info!("GuiVstEditor::create_draw_context()"); 46 | let conn = self.x_handle.conn(); 47 | let setup = conn.get_setup(); 48 | let screen = setup.roots().nth(self.x_handle.screen_num() as usize).unwrap(); 49 | 50 | self.draw_context = conn.generate_id(); 51 | let draw_context = self.draw_context; 52 | 53 | xcb::create_gc(conn.borrow(), draw_context, parent, &[ 54 | (xcb::GC_FOREGROUND, screen.white_pixel()), 55 | (xcb::GC_GRAPHICS_EXPOSURES, 0), 56 | ]); 57 | } 58 | 59 | fn create_window(&mut self, parent: u32) { 60 | info!("GuiVstEditor::create_window()"); 61 | 62 | self.create_draw_context(parent); 63 | 64 | let conn = self.x_handle.conn(); 65 | let setup = conn.get_setup(); 66 | let screen = setup.roots().nth(self.x_handle.screen_num() as usize).unwrap(); 67 | 68 | self.window_handle = conn.generate_id(); 69 | xcb::create_window(&conn, 70 | xcb::COPY_FROM_PARENT as u8, 71 | self.window_handle, 72 | parent, 73 | self.x as i16, 74 | self.y as i16, 75 | self.width as u16, 76 | self.height as u16, 77 | 0, 78 | xcb::WINDOW_CLASS_INPUT_OUTPUT as u16, 79 | screen.root_visual(), &[ 80 | (xcb::CW_BACK_PIXEL, screen.black_pixel()), 81 | (xcb::CW_EVENT_MASK, 82 | xcb::EVENT_MASK_EXPOSURE | 83 | xcb::EVENT_MASK_BUTTON_PRESS | 84 | xcb::EVENT_MASK_BUTTON_RELEASE | 85 | xcb::EVENT_MASK_BUTTON_1_MOTION 86 | ), 87 | ] 88 | ); 89 | xcb::map_window(&conn, self.window_handle); 90 | conn.flush(); 91 | 92 | self.draw_editor(); 93 | 94 | // Start handling events on this connection. 95 | let arc_parameters = self.parameters.clone(); 96 | let arc_host_callback = self.host_callback.clone(); 97 | thread::spawn(move || { 98 | Editor::handle_events(conn, arc_parameters, arc_host_callback); 99 | }); 100 | } 101 | 102 | pub fn draw_editor(&mut self) { 103 | //info!("GuiVstEditor::draw_editor()"); 104 | 105 | let conn = self.x_handle.conn(); 106 | let setup = conn.get_setup(); 107 | let screen = setup.roots().nth(self.x_handle.screen_num() as usize).unwrap(); 108 | 109 | // Clear screen 110 | xcb::change_gc( 111 | conn.borrow(), 112 | self.draw_context, 113 | &[ 114 | (xcb::GC_FOREGROUND, screen.black_pixel()), 115 | (xcb::GC_BACKGROUND, screen.black_pixel()), 116 | (xcb::GC_FILL_STYLE, xcb::FILL_STYLE_SOLID), 117 | ] 118 | ); 119 | xcb::poly_fill_rectangle( 120 | conn.borrow(), 121 | self.window_handle, 122 | self.draw_context, 123 | &[xcb::Rectangle::new(0, 0, 1000, 1000)], 124 | ); 125 | 126 | // Draw parameters on screen 127 | xcb::change_gc( 128 | conn.borrow(), 129 | self.draw_context, 130 | &[ 131 | (xcb::GC_FOREGROUND, screen.white_pixel()), 132 | (xcb::GC_BACKGROUND, screen.white_pixel()), 133 | (xcb::GC_FILL_STYLE, xcb::FILL_STYLE_SOLID), 134 | ] 135 | ); 136 | let rectangle_borders = vec!( 137 | xcb::Rectangle::new(50, 300, 900, 100), 138 | xcb::Rectangle::new(50, 600, 900, 100), 139 | ); 140 | let rectangle_values = vec!( 141 | xcb::Rectangle::new(50, 300, (self.parameters.param1.get() * 900.0) as u16, 100), 142 | xcb::Rectangle::new(50, 600, (self.parameters.param2.get() * 900.0) as u16, 100), 143 | ); 144 | xcb::poly_rectangle( 145 | conn.borrow(), 146 | self.window_handle, 147 | self.draw_context, 148 | &rectangle_borders, 149 | ); 150 | xcb::poly_fill_rectangle( 151 | conn.borrow(), 152 | self.window_handle, 153 | self.draw_context, 154 | &rectangle_values, 155 | ); 156 | 157 | // Flush the request 158 | conn.flush(); 159 | } 160 | 161 | fn handle_events(conn: Arc, parameters: Arc, host_callback: Arc>) { 162 | let mut active_element = ActiveElement::None; 163 | loop { 164 | let wait = conn.wait_for_event(); 165 | if let Some(event) = wait { 166 | match event.response_type() { 167 | xcb::BUTTON_PRESS => { 168 | let event = unsafe { xcb::cast_event::(&event) }; 169 | let button = event.detail(); 170 | 171 | // Left mouse button only 172 | if button == 1 { 173 | info!("Button press at: ({}, {})", event.event_x(), event.event_y()); 174 | 175 | if event.event_y() >= 300 && event.event_y() <= 400 && event.event_x() >= 50 && event.event_x() <= 950 { 176 | let param1_value = (event.event_x() - 50) as f64 / 900.0; 177 | parameters.param1.set(param1_value as f32); 178 | active_element = ActiveElement::Param1; 179 | host_callback.lock().unwrap().automate(0, parameters.param1.get()); 180 | } 181 | else if event.event_y() >= 600 && event.event_y() <= 700 && event.event_x() >= 50 && event.event_x() <= 950 { 182 | let param2_value = (event.event_x() - 50) as f64 / 900.0; 183 | parameters.param2.set(param2_value as f32); 184 | active_element = ActiveElement::Param2; 185 | host_callback.lock().unwrap().automate(1, parameters.param2.get()); 186 | } 187 | else { 188 | active_element = ActiveElement::None; 189 | } 190 | 191 | } 192 | 193 | 194 | host_callback.lock().unwrap().automate(1, parameters.param2.get()); 195 | }, 196 | xcb::MOTION_NOTIFY => { 197 | let event = unsafe { xcb::cast_event::(&event) }; 198 | info!("Motion notify event: ({}, {}) - Active: {:?}", event.event_x(), event.event_y(), active_element); 199 | 200 | if active_element == ActiveElement::Param1 { 201 | let mut param1_value = 0.0; 202 | 203 | if event.event_x() < 50 { 204 | // keep param1 value at 0.0 205 | } 206 | else if event.event_x() >= 50 && event.event_x() <= 950 { 207 | param1_value = (event.event_x() - 50) as f64 / 900.0; 208 | } 209 | else { 210 | param1_value = 1.0; 211 | } 212 | parameters.param1.set(param1_value as f32); 213 | host_callback.lock().unwrap().automate(0, parameters.param1.get()); 214 | } 215 | else if active_element == ActiveElement::Param2 { 216 | let mut param2_value = 0.0; 217 | 218 | if event.event_x() < 50 { 219 | // keep param2 value at 0.0 220 | } 221 | else if event.event_x() >= 50 && event.event_x() <= 950 { 222 | param2_value = (event.event_x() - 50) as f64 / 900.0; 223 | } 224 | else { 225 | param2_value = 1.0; 226 | } 227 | parameters.param2.set(param2_value as f32); 228 | host_callback.lock().unwrap().automate(1, parameters.param2.get()); 229 | } 230 | } 231 | xcb::BUTTON_RELEASE => { 232 | let event = unsafe { xcb::cast_event::(&event) }; 233 | let button = event.detail(); 234 | 235 | active_element = ActiveElement::None; 236 | 237 | // Left mouse button only 238 | if button == 1 { 239 | info!("Button release at: ({}, {})", event.event_x(), event.event_y()); 240 | } 241 | }, 242 | _ => { 243 | } 244 | } 245 | } 246 | } 247 | } 248 | } 249 | 250 | impl vst::editor::Editor for Editor { 251 | fn size(&self) -> (i32, i32) { 252 | info!("Editor::size()"); 253 | (self.width, self.height) 254 | } 255 | 256 | fn position(&self) -> (i32, i32) { 257 | info!("Editor::position()"); 258 | (self.x, self.y) 259 | } 260 | 261 | fn close(&mut self) { 262 | info!("Editor::close()"); 263 | self.is_open = false; 264 | } 265 | 266 | fn open(&mut self, parent: *mut c_void) { 267 | info!("Editor::open()"); 268 | self.create_window(parent as u32); 269 | } 270 | 271 | fn is_open(&mut self) -> bool { 272 | info!("Editor::is_open()"); 273 | self.is_open 274 | } 275 | } 276 | 277 | #[derive(PartialEq, Debug)] 278 | enum ActiveElement { 279 | None, 280 | Param1, 281 | Param2, 282 | } 283 | -------------------------------------------------------------------------------- /windows-opengl-vst/src/editor/window/mod.rs: -------------------------------------------------------------------------------- 1 | // Resources: 2 | // - https://stackoverflow.com/a/49034511 3 | // - https://www.khronos.org/opengl/wiki/Creating_an_OpenGL_Context_(WGL) 4 | // - https://gist.github.com/nickrolfe/1127313ed1dbf80254b614a721b3ee9c 5 | // 6 | // Look into these for window crashing on event handling: 7 | // - https://github.com/wrl/rutabaga/blob/master/src/platform/win/window.c#L373 8 | // - https://github.com/wrl/rutabaga/blob/master/src/platform/win/window.c#L93-L120 9 | // - https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-getwindowlongptra 10 | // 11 | // Look into this for non-deterministic window class names: 12 | // - https://github.com/wrl/rutabaga/blob/master/src/platform/win/window.c#L126-L151 13 | 14 | use std::mem; 15 | use std::ptr::null_mut; 16 | use std::ffi::CString; 17 | use std::ffi::c_void; 18 | use std::sync::{Arc, Mutex}; 19 | 20 | use vst::plugin::HostCallback; 21 | use vst::host::Host; 22 | use user32::{DefWindowProcW, DispatchMessageW, GetMessageW, TranslateMessage}; 23 | use winapi::HWND; 24 | use winapi::winuser::MSG; 25 | use log::*; 26 | use rand::Rng; 27 | use winapi::LONG_PTR; 28 | 29 | use crate::parameters::Parameters; 30 | 31 | mod pixel_format; 32 | mod util; 33 | mod win32_window; 34 | mod window_class; 35 | 36 | use win32_window::Win32Window; 37 | use window_class::WindowClass; 38 | 39 | fn draw_window(hwnd: HWND) { 40 | unsafe { 41 | gl::ClearColor(0.5f32, 0.5f32, 1.0f32, 1.0f32); 42 | gl::Clear(gl::COLOR_BUFFER_BIT); 43 | gl::Flush(); 44 | let hdc = user32::GetDC(hwnd); 45 | gdi32::SwapBuffers(hdc); 46 | user32::ReleaseDC(hwnd, hdc); 47 | } 48 | } 49 | 50 | unsafe extern "system" fn wnd_proc( 51 | hwnd: HWND, 52 | msg: winapi::UINT, 53 | wparam: winapi::WPARAM, 54 | lparam: winapi::LPARAM, 55 | ) -> winapi::LRESULT { 56 | // TODO: This was a static in C/C++. Does it need to be? Maybe put it into the WindowLongPtr? 57 | let mut paint_struct = mem::zeroed(); 58 | 59 | let mut state = unsafe { &mut *(user32::GetWindowLongPtrW(hwnd, winapi::winuser::GWLP_USERDATA) as *mut WindowState) }; 60 | 61 | match msg { 62 | winapi::WM_DESTROY => { 63 | info!("wnd_proc: Destroyed!"); 64 | 0 65 | }, 66 | winapi::WM_PAINT => { 67 | info!("wnd_proc: Paint"); 68 | 69 | // TODO: This came after the draw_window() function in example code I found. Should it be? 70 | user32::BeginPaint(hwnd, &mut paint_struct); 71 | 72 | draw_window(hwnd); 73 | user32::EndPaint(hwnd, &mut paint_struct); 74 | 75 | DefWindowProcW(hwnd, msg, wparam, lparam) 76 | }, 77 | winapi::WM_LBUTTONDOWN => { 78 | info!("wnd_proc: CLICK!"); 79 | let new_param_val = state.rng.gen_range(0.0, 1.0); 80 | state.params.pulse_width.set(new_param_val); 81 | state.host_callback.lock().unwrap().automate(1, new_param_val); 82 | 0 83 | }, 84 | _ => { 85 | DefWindowProcW(hwnd, msg, wparam, lparam) 86 | } 87 | } 88 | } 89 | 90 | 91 | 92 | fn handle_message(hwnd: HWND) -> bool { 93 | unsafe { 94 | let mut message: MSG = mem::uninitialized(); 95 | if GetMessageW(&mut message as *mut MSG, hwnd, 0, 0) > 0 { 96 | TranslateMessage(&message as *const MSG); 97 | DispatchMessageW(&message as *const MSG); 98 | 99 | true 100 | } else { 101 | false 102 | } 103 | } 104 | } 105 | 106 | fn init_opengl(real_hdc: winapi::HDC, proc_address_loader: &util::ProcAddressLoader) -> *mut winapi::HGLRC__ { 107 | use std::os::raw::c_int; 108 | use std::os::raw::c_uint; 109 | 110 | type wglCreateContextAttribsArbType = unsafe extern "system" fn(winapi::HDC, winapi::HGLRC, *const c_int) -> winapi::HGLRC; 111 | type wglChoosePixelFormatArbType = unsafe extern "system" fn(winapi::HDC, *const c_int, *const f32, c_uint, *mut c_int, *mut c_uint) -> c_int; 112 | 113 | // Dummy window context setup (get wglCreateContextAttribsArb and wglChoosePixelFormatArb) 114 | let (wglCreateContextAttribsArb, wglChoosePixelFormatArb) = unsafe { 115 | let dummy_window_class = WindowClass::new( 116 | "wgl_dummy_class_name_dontuse".into(), 117 | Some(user32::DefWindowProcA), 118 | ); 119 | 120 | let dummy_window = Win32Window::new(null_mut(), dummy_window_class, "Dummy Window", true); 121 | 122 | // Create a dummy context handler for this dummy window 123 | { 124 | let device_context_handler = user32::GetDC(dummy_window.handle()); 125 | 126 | let (pixel_format, mut pixel_format_descriptor) = pixel_format::dummy_pixel_format(device_context_handler); 127 | 128 | // Set the pixel format...? 129 | if gdi32::SetPixelFormat( 130 | device_context_handler, 131 | pixel_format, 132 | &pixel_format_descriptor, 133 | ) == 0 134 | { 135 | info!("Failed to set the pixel format for the dummy window."); 136 | panic!("Failed to set the pixel format for the dummy window."); 137 | } 138 | 139 | // Describe pixel format......? 140 | gdi32::DescribePixelFormat( 141 | device_context_handler, 142 | pixel_format, 143 | std::mem::size_of::() as u32, 144 | &mut pixel_format_descriptor, 145 | ); 146 | 147 | user32::ReleaseDC(dummy_window.handle(), device_context_handler); 148 | } 149 | 150 | // Fetch context from window 151 | let dummy_hdc = user32::GetDC(dummy_window.handle()); 152 | let dummy_context = opengl32::wglCreateContext(dummy_hdc); 153 | if dummy_context.is_null() { 154 | info!("Failed to create a dummy OpenGL rendering context."); 155 | panic!("Failed to create a dummy OpenGL rendering context."); 156 | } 157 | 158 | if opengl32::wglMakeCurrent(dummy_hdc, dummy_context) == 0 { 159 | info!("Failed to activate dummy OpenGL rendering context."); 160 | panic!("Failed to activate dummy OpenGL rendering context."); 161 | } 162 | 163 | let wglCreateContextAttribsArb = proc_address_loader.get_proc_address("wglCreateContextAttribsARB"); 164 | let wglChoosePixelFormatArb = proc_address_loader.get_proc_address("wglChoosePixelFormatARB"); 165 | 166 | // Destroy context 167 | opengl32::wglMakeCurrent(dummy_hdc, null_mut()); 168 | opengl32::wglDeleteContext(dummy_context); 169 | user32::ReleaseDC(dummy_window.handle(), dummy_hdc); 170 | 171 | (wglCreateContextAttribsArb, wglChoosePixelFormatArb) 172 | }; 173 | 174 | let wglCreateContextAttribsArb: wglCreateContextAttribsArbType = unsafe { 175 | std::mem::transmute(wglCreateContextAttribsArb) 176 | }; 177 | let wglChoosePixelFormatArb: wglChoosePixelFormatArbType = unsafe { 178 | std::mem::transmute(wglChoosePixelFormatArb) 179 | }; 180 | 181 | // Now we can set up pixel formats the "correct" way with wglChoosePixelFormatArb. 182 | 183 | // https://www.opengl.org/registry/specs/ARB/wgl_pixel_format.txt 184 | let wgl_draw_to_window_arb = 0x2001; 185 | let wgl_acceleration_arb = 0x2003; 186 | let wgl_support_opengl_arb = 0x2010; 187 | let wgl_double_buffer_arb = 0x2011; 188 | let wgl_pixel_type_arb = 0x2013; 189 | let wgl_color_bits_arb = 0x2014; 190 | let wgl_depth_bits_arb = 0x2022; 191 | let wgl_stencil_bits_arb = 0x02023; 192 | let wgl_full_acceleration_arb = 0x2027; 193 | let wgl_type_rgba_arb = 0x202b; 194 | let gl_true = 1; 195 | 196 | let pixel_format_attribs = [ 197 | wgl_draw_to_window_arb, gl_true, 198 | wgl_support_opengl_arb, gl_true, 199 | wgl_double_buffer_arb, gl_true, 200 | wgl_acceleration_arb, wgl_full_acceleration_arb, 201 | wgl_pixel_type_arb, wgl_type_rgba_arb, 202 | wgl_color_bits_arb, 32, 203 | wgl_depth_bits_arb, 24, 204 | wgl_stencil_bits_arb, 8, 205 | 0, 206 | ]; 207 | 208 | let mut pixel_format: i32 = 0; 209 | let mut num_formats: u32 = 0; 210 | 211 | unsafe { 212 | wglChoosePixelFormatArb(real_hdc, pixel_format_attribs.as_ptr(), 0 as *const f32, 1, &mut pixel_format, &mut num_formats); 213 | if num_formats == 0 { 214 | info!("Failed to set the OpenGL 3.3 pixel format."); 215 | panic!("Failed to set the OpenGL 3.3 pixel format."); 216 | } 217 | 218 | let mut pixel_format_descriptor: winapi::PIXELFORMATDESCRIPTOR = mem::uninitialized(); 219 | gdi32::DescribePixelFormat(real_hdc, pixel_format, std::mem::size_of::() as u32, &mut pixel_format_descriptor); 220 | 221 | if gdi32::SetPixelFormat( 222 | real_hdc, 223 | pixel_format, 224 | &pixel_format_descriptor, 225 | ) == 0 226 | { 227 | info!("Failed to set the pixel format for the OpenGL window."); 228 | panic!("Failed to set the pixel format for the OpenGL window."); 229 | } 230 | 231 | // Specify OpenGL 3.3 core as our context type 232 | let wgl_context_major_version_arb = 0x2091; 233 | let wgl_context_minor_version_arb = 0x2092; 234 | let wgl_context_profile_mask_arb = 0x9126; 235 | let wgl_context_core_profile_bit_arb = 0x00000001; 236 | let gl33_attribs = [ 237 | wgl_context_major_version_arb, 3, 238 | wgl_context_minor_version_arb, 3, 239 | wgl_context_profile_mask_arb, wgl_context_core_profile_bit_arb, 240 | 0 241 | ]; 242 | 243 | let gl33_context = wglCreateContextAttribsArb(real_hdc, 0 as *mut winapi::HGLRC__, gl33_attribs.as_ptr()); 244 | if gl33_context as u32 == 0 { 245 | info!("Failed to create the OpenGL 3.3 rendering context."); 246 | panic!("Failed to create the OpenGL 3.3 rendering context."); 247 | } 248 | if opengl32::wglMakeCurrent(real_hdc, gl33_context) == 0 { 249 | info!("Failed to activate the OpenGL 3.3 rendering context."); 250 | panic!("Failed to activate the OpenGL 3.3 rendering context.") 251 | } 252 | 253 | return gl33_context; 254 | } 255 | 256 | } 257 | 258 | struct WindowState { 259 | rng: rand::rngs::ThreadRng, 260 | host_callback: Arc>, 261 | params: Arc, 262 | } 263 | 264 | pub struct Window { 265 | win32_window: Win32Window, 266 | hdc: *mut winapi::HDC__, 267 | context: *mut winapi::HGLRC__, 268 | } 269 | 270 | impl Window { 271 | pub fn new(host_callback: Arc>, params: Arc, parent: *mut c_void) -> Self { 272 | let window_state = Box::new(WindowState { 273 | rng: rand::thread_rng(), 274 | host_callback, 275 | params 276 | }); 277 | 278 | let proc_address_loader = util::ProcAddressLoader::new(); 279 | let window_class = WindowClass::new("OpenGL Window", Some(wnd_proc)); 280 | let mut window = Win32Window::new(parent, window_class, "OpenGL Window", false); 281 | let hdc = unsafe { 282 | user32::GetDC(window.handle()) 283 | }; 284 | let context = init_opengl(hdc, &proc_address_loader); 285 | 286 | // Load all of the OpenGL function pointers. 287 | gl::load_with(|s| { 288 | proc_address_loader.get_proc_address(s) 289 | }); 290 | 291 | unsafe { 292 | user32::ShowWindow(window.handle(), 1); 293 | } 294 | 295 | unsafe { 296 | // TODO: make a const for that magic number 297 | let window_state_cvoid = Box::into_raw(window_state) as *mut c_void; 298 | user32::SetWindowLongPtrW(window.handle(), winapi::winuser::GWLP_USERDATA, window_state_cvoid as LONG_PTR); 299 | } 300 | 301 | Self { 302 | win32_window: window, 303 | hdc, 304 | context, 305 | } 306 | } 307 | 308 | pub fn get_width(&self) -> u32 { 309 | 1024 310 | } 311 | 312 | pub fn get_height(&self) -> u32 { 313 | 768 314 | } 315 | } 316 | 317 | impl Drop for Window { 318 | fn drop(&mut self) { 319 | unsafe { 320 | opengl32::wglMakeCurrent(self.hdc, null_mut()); 321 | opengl32::wglDeleteContext(self.context); 322 | user32::ReleaseDC(self.win32_window.handle(), self.hdc); 323 | } 324 | } 325 | } 326 | -------------------------------------------------------------------------------- /linux-opengl-vst/src/editor/window/mod.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, Mutex}; 2 | use std::os::raw::{c_int, c_void}; 3 | use std::ffi::{CStr, CString}; 4 | use std::ptr::null_mut; 5 | use std::thread; 6 | 7 | use vst::plugin::HostCallback; 8 | use vst::host::Host; 9 | use x11::glx::*; 10 | use x11::xlib; 11 | use xcb::dri2; 12 | use log::*; 13 | use rand::Rng; 14 | 15 | use crate::parameters::Parameters; 16 | 17 | type GlXCreateContextAttribsARBProc = 18 | unsafe extern "C" fn (dpy: *mut xlib::Display, fbc: GLXFBConfig, 19 | share_context: GLXContext, direct: xlib::Bool, 20 | attribs: *const c_int) -> GLXContext; 21 | 22 | static mut ctx_error_occurred: bool = false; 23 | unsafe extern "C" fn ctx_error_handler( 24 | _dpy: *mut xlib::Display, 25 | _ev: *mut xlib::XErrorEvent) -> i32 { 26 | ctx_error_occurred = true; 27 | 0 28 | } 29 | 30 | const GLX_CONTEXT_MAJOR_VERSION_ARB: u32 = 0x2091; 31 | const GLX_CONTEXT_MINOR_VERSION_ARB: u32 = 0x2092; 32 | 33 | pub struct Window { 34 | t: thread::JoinHandle<()>, 35 | } 36 | 37 | impl Window { 38 | pub fn new(host_callback: Arc>, params: Arc, parent: *mut std::ffi::c_void) -> Self { 39 | let parent_id = parent as u32; 40 | let t = thread::spawn(move || { 41 | let (conn, screen_num) = xcb::Connection::connect_with_xlib_display().unwrap(); 42 | //conn.set_event_queue_owner(xcb::EventQueueOwner::Xcb); // TODO: need this? 43 | 44 | if glx_dec_version(conn.get_raw_dpy()) < 13 { 45 | panic!("glx-1.3 is not supported"); 46 | } 47 | 48 | let fbc = get_glxfbconfig(conn.get_raw_dpy(), screen_num, &[ 49 | GLX_X_RENDERABLE , 1, 50 | GLX_DRAWABLE_TYPE , GLX_WINDOW_BIT, 51 | GLX_RENDER_TYPE , GLX_RGBA_BIT, 52 | GLX_X_VISUAL_TYPE , GLX_TRUE_COLOR, 53 | GLX_RED_SIZE , 8, 54 | GLX_GREEN_SIZE , 8, 55 | GLX_BLUE_SIZE , 8, 56 | GLX_ALPHA_SIZE , 8, 57 | GLX_DEPTH_SIZE , 24, 58 | GLX_STENCIL_SIZE , 8, 59 | GLX_DOUBLEBUFFER , 1, 60 | 0 61 | ]); 62 | 63 | let vi: *const xlib::XVisualInfo = unsafe { 64 | glXGetVisualFromFBConfig(conn.get_raw_dpy(), fbc) 65 | }; 66 | 67 | let dri2_ev = { 68 | conn.prefetch_extension_data(dri2::id()); 69 | match conn.get_extension_data(dri2::id()) { 70 | None => { panic!("could not load dri2 extension") }, 71 | Some(r) => { r.first_event() } 72 | } 73 | }; 74 | 75 | let (wm_protocols, wm_delete_window) = { 76 | let pc = xcb::intern_atom(&conn, false, "WM_PROTOCOLS"); 77 | let dwc = xcb::intern_atom(&conn, false, "WM_DELETE_WINDOW"); 78 | 79 | let p = match pc.get_reply() { 80 | Ok(p) => p.atom(), 81 | Err(_) => panic!("could not load WM_PROTOCOLS atom") 82 | }; 83 | let dw = match dwc.get_reply() { 84 | Ok(dw) => dw.atom(), 85 | Err(_) => panic!("could not load WM_DELETE_WINDOW atom") 86 | }; 87 | (p, dw) 88 | }; 89 | 90 | let setup = conn.get_setup(); 91 | let screen = unsafe { 92 | setup.roots().nth((*vi).screen as usize).unwrap() 93 | }; 94 | 95 | let cmap = conn.generate_id(); 96 | let win = conn.generate_id(); 97 | 98 | unsafe { 99 | xcb::create_colormap(&conn, xcb::COLORMAP_ALLOC_NONE as u8, 100 | cmap, parent_id, (*vi).visualid as u32); 101 | } 102 | 103 | let cw_values = [ 104 | (xcb::CW_BACK_PIXEL, screen.white_pixel()), 105 | (xcb::CW_BORDER_PIXEL, screen.black_pixel()), 106 | (xcb::CW_EVENT_MASK, 107 | xcb::EVENT_MASK_KEY_PRESS | xcb::EVENT_MASK_EXPOSURE), 108 | (xcb::CW_COLORMAP, cmap) 109 | ]; 110 | 111 | unsafe { 112 | xcb::create_window(&conn, (*vi).depth as u8, win, parent_id, 0, 0, 1024, 1024, 113 | 0, xcb::WINDOW_CLASS_INPUT_OUTPUT as u16, 114 | (*vi).visualid as u32, &cw_values); 115 | } 116 | 117 | unsafe { 118 | xlib::XFree(vi as *mut c_void); 119 | } 120 | 121 | let title = "XCB OpenGL"; 122 | xcb::change_property(&conn, 123 | xcb::PROP_MODE_REPLACE as u8, 124 | win, 125 | xcb::ATOM_WM_NAME, 126 | xcb::ATOM_STRING, 127 | 8, title.as_bytes()); 128 | 129 | let protocols = [wm_delete_window]; 130 | xcb::change_property(&conn, xcb::PROP_MODE_REPLACE as u8, 131 | win, wm_protocols, xcb::ATOM_ATOM, 32, &protocols); 132 | 133 | xcb::map_window(&conn, win); 134 | conn.flush(); 135 | unsafe { 136 | xlib::XSync(conn.get_raw_dpy(), xlib::False); 137 | } 138 | 139 | let glx_exts = unsafe { 140 | CStr::from_ptr( 141 | glXQueryExtensionsString(conn.get_raw_dpy(), screen_num)) 142 | .to_str().unwrap() 143 | }; 144 | 145 | if !check_glx_extension(&glx_exts, "GLX_ARB_create_context") { 146 | panic!("could not find GLX extension GLX_ARB_create_context"); 147 | } 148 | 149 | // with glx, no need of a current context is needed to load symbols 150 | // otherwise we would need to create a temporary legacy GL context 151 | // for loading symbols (at least glXCreateContextAttribsARB) 152 | let glx_create_context_attribs: GlXCreateContextAttribsARBProc = unsafe { 153 | std::mem::transmute(load_gl_func("glXCreateContextAttribsARB")) 154 | }; 155 | 156 | // loading all other symbols 157 | unsafe { 158 | gl::load_with(|n| load_gl_func(&n)); 159 | } 160 | 161 | if !gl::GenVertexArrays::is_loaded() { 162 | panic!("no GL3 support available!"); 163 | } 164 | 165 | // installing an event handler to check if error is generated 166 | unsafe { 167 | ctx_error_occurred = false; 168 | } 169 | let old_handler = unsafe { 170 | xlib::XSetErrorHandler(Some(ctx_error_handler)) 171 | }; 172 | 173 | let context_attribs: [c_int; 5] = [ 174 | GLX_CONTEXT_MAJOR_VERSION_ARB as c_int, 3, 175 | GLX_CONTEXT_MINOR_VERSION_ARB as c_int, 0, 176 | 0 177 | ]; 178 | let ctx = unsafe { 179 | glx_create_context_attribs(conn.get_raw_dpy(), fbc, null_mut(), 180 | xlib::True, &context_attribs[0] as *const c_int) 181 | }; 182 | 183 | conn.flush(); 184 | unsafe { 185 | xlib::XSync(conn.get_raw_dpy(), xlib::False); 186 | xlib::XSetErrorHandler(std::mem::transmute(old_handler)); 187 | } 188 | 189 | unsafe { 190 | if ctx.is_null() || ctx_error_occurred { 191 | panic!("error when creating gl-3.0 context"); 192 | } 193 | 194 | if glXIsDirect(conn.get_raw_dpy(), ctx) == 0 { 195 | panic!("obtained indirect rendering context") 196 | } 197 | } 198 | 199 | handle_events( 200 | host_callback, 201 | params, 202 | Arc::new(conn), 203 | win, 204 | ctx, 205 | wm_protocols, 206 | wm_delete_window, 207 | dri2_ev, 208 | ); 209 | }); 210 | 211 | Self { 212 | t, 213 | } 214 | } 215 | 216 | pub fn get_width(&self) -> u32 { 217 | 1024 218 | } 219 | 220 | pub fn get_height(&self) -> u32 { 221 | 1024 222 | } 223 | } 224 | 225 | 226 | // returns the glx version in a decimal form 227 | // eg. 1.3 => 13 228 | fn glx_dec_version(dpy: *mut xlib::Display) -> i32 { 229 | let mut maj: c_int = 0; 230 | let mut min: c_int = 0; 231 | unsafe { 232 | if glXQueryVersion(dpy, 233 | &mut maj as *mut c_int, 234 | &mut min as *mut c_int) == 0 { 235 | panic!("cannot get glx version"); 236 | } 237 | } 238 | (maj*10 + min) as i32 239 | } 240 | 241 | fn get_glxfbconfig(dpy: *mut xlib::Display, screen_num: i32, 242 | visual_attribs: &[i32]) -> GLXFBConfig { 243 | unsafe { 244 | let mut fbcount: c_int = 0; 245 | let fbcs = glXChooseFBConfig(dpy, screen_num, 246 | visual_attribs.as_ptr(), 247 | &mut fbcount as *mut c_int); 248 | 249 | if fbcount == 0 { 250 | panic!("could not find compatible fb config"); 251 | } 252 | // we pick the first from the list 253 | let fbc = *fbcs; 254 | xlib::XFree(fbcs as *mut c_void); 255 | fbc 256 | } 257 | } 258 | 259 | fn check_glx_extension(glx_exts: &str, ext_name: &str) -> bool { 260 | for glx_ext in glx_exts.split(" ") { 261 | if glx_ext == ext_name { 262 | return true; 263 | } 264 | } 265 | false 266 | } 267 | 268 | unsafe fn load_gl_func (name: &str) -> *mut c_void { 269 | let cname = CString::new(name).unwrap(); 270 | let ptr: *mut c_void = std::mem::transmute(glXGetProcAddress( 271 | cname.as_ptr() as *const u8 272 | )); 273 | if ptr.is_null() { 274 | panic!("could not load {}", name); 275 | } 276 | ptr 277 | } 278 | 279 | unsafe fn check_gl_error() { 280 | let err = gl::GetError(); 281 | if err != gl::NO_ERROR { 282 | println!("got gl error {}", err); 283 | } 284 | } 285 | 286 | fn handle_events( 287 | host_callback: Arc>, 288 | params: Arc, 289 | conn: Arc, 290 | win: u32, 291 | ctx: *mut x11::glx::__GLXcontextRec, 292 | wm_protocols: u32, 293 | wm_delete_window: u32, 294 | dri2_ev: u8, 295 | ) { 296 | let mut rng = rand::thread_rng(); 297 | loop { 298 | info!("Event loop begin"); 299 | if let Some(ev) = conn.wait_for_event() { 300 | let ev_type = ev.response_type() & !0x80; 301 | match ev_type { 302 | xcb::EXPOSE => { 303 | unsafe { 304 | glXMakeCurrent(conn.get_raw_dpy(), win as xlib::XID, ctx); 305 | gl::ClearColor(0.5f32, 0.5f32, 1.0f32, 1.0f32); 306 | gl::Clear(gl::COLOR_BUFFER_BIT); 307 | gl::Flush(); 308 | check_gl_error(); 309 | glXSwapBuffers(conn.get_raw_dpy(), win as xlib::XID); 310 | glXMakeCurrent(conn.get_raw_dpy(), 0, null_mut()); 311 | } 312 | }, 313 | xcb::KEY_PRESS => { 314 | let new_param_val = rng.gen_range(0.0, 1.0); 315 | params.pulse_width.set(new_param_val); 316 | host_callback.lock().unwrap().automate(1, new_param_val); 317 | // break; 318 | }, 319 | xcb::CLIENT_MESSAGE => { 320 | let cmev = unsafe { 321 | xcb::cast_event::(&ev) 322 | }; 323 | if cmev.type_() == wm_protocols && cmev.format() == 32 { 324 | let protocol = cmev.data().data32()[0]; 325 | if protocol == wm_delete_window { 326 | break; 327 | } 328 | } 329 | }, 330 | _ => { 331 | // following stuff is not obvious at all, but is necessary 332 | // to handle GL when XCB owns the event queue 333 | if ev_type == dri2_ev || ev_type == dri2_ev+1 { 334 | // these are libgl dri2 event that need special handling 335 | // see https://bugs.freedesktop.org/show_bug.cgi?id=35945#c4 336 | // and mailing thread starting here: 337 | // http://lists.freedesktop.org/archives/xcb/2015-November/010556.html 338 | 339 | unsafe { 340 | if let Some(proc_) = 341 | xlib::XESetWireToEvent(conn.get_raw_dpy(), 342 | ev_type as i32, None) { 343 | xlib::XESetWireToEvent(conn.get_raw_dpy(), 344 | ev_type as i32, Some(proc_)); 345 | let raw_ev = ev.ptr; 346 | (*raw_ev).sequence = 347 | xlib::XLastKnownRequestProcessed( 348 | conn.get_raw_dpy()) as u16; 349 | let mut dummy: xlib::XEvent = std::mem::zeroed(); 350 | proc_(conn.get_raw_dpy(), 351 | &mut dummy as *mut xlib::XEvent, 352 | raw_ev as *mut xlib::xEvent); 353 | } 354 | } 355 | } 356 | } 357 | } 358 | conn.flush(); 359 | } 360 | else { 361 | break; 362 | } 363 | 364 | info!("Event loop end"); 365 | } 366 | } --------------------------------------------------------------------------------