├── Cargo.toml ├── .gitignore ├── clean.sh ├── dd-sampler ├── assets │ ├── bass.wav │ ├── snare.wav │ └── bass-stereo.wav ├── Cargo.toml └── src │ └── lib.rs ├── dd-dsp ├── src │ ├── midi.rs │ ├── oscillator.rs │ ├── voice.rs │ ├── lib.rs │ ├── instrument.rs │ ├── sample.rs │ ├── sampler.rs │ └── envelope.rs └── Cargo.toml ├── dd-reverb ├── Cargo.toml └── src │ └── lib.rs ├── dd-overdrive ├── Cargo.toml └── src │ └── lib.rs ├── dd-expander ├── Cargo.toml └── src │ └── lib.rs ├── dd-subsynth ├── Cargo.toml └── src │ └── lib.rs ├── dd-rvc1 ├── Cargo.toml └── src │ └── lib.rs ├── dd-chunks ├── Cargo.toml └── src │ └── lib.rs ├── dd-winit ├── Cargo.toml └── src │ └── lib.rs ├── contrib ├── mac-install.sh └── vst-bundler.sh └── README.md /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["dd-subsynth"] 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | **/*.rs.bk 3 | Cargo.lock 4 | .idea 5 | -------------------------------------------------------------------------------- /clean.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | rm target/release/*.dylib 3 | rm target/debug/*.dylib 4 | echo "done." 5 | -------------------------------------------------------------------------------- /dd-sampler/assets/bass.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monomadic/dd-plugs/HEAD/dd-sampler/assets/bass.wav -------------------------------------------------------------------------------- /dd-sampler/assets/snare.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monomadic/dd-plugs/HEAD/dd-sampler/assets/snare.wav -------------------------------------------------------------------------------- /dd-sampler/assets/bass-stereo.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monomadic/dd-plugs/HEAD/dd-sampler/assets/bass-stereo.wav -------------------------------------------------------------------------------- /dd-dsp/src/midi.rs: -------------------------------------------------------------------------------- 1 | /// Convert the midi note into the equivalent frequency. 2 | /// 3 | /// This function assumes A4 is 440hz. 4 | pub fn midi_note_to_hz(note: u8) -> f64 { 5 | const A4: f64 = 440.0; 6 | (A4 / 32.0) * ((note as f64 - 9.0) / 12.0).exp2() 7 | } 8 | -------------------------------------------------------------------------------- /dd-reverb/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dd_reverb" 3 | version = "0.1.0" 4 | authors = ["Rob Saunders "] 5 | 6 | [dependencies] 7 | vst2 = { git = "https://github.com/rust-dsp/rust-vst" } 8 | 9 | [lib] 10 | name = "dd_reverb" 11 | crate-type = ["cdylib"] 12 | -------------------------------------------------------------------------------- /dd-overdrive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dd_overdrive" 3 | version = "0.1.0" 4 | authors = ["Rob Saunders "] 5 | 6 | [dependencies] 7 | vst2 = { git = "https://github.com/rust-dsp/rust-vst" } 8 | 9 | [lib] 10 | name = "dd_overdrive" 11 | crate-type = ["cdylib"] 12 | -------------------------------------------------------------------------------- /dd-dsp/src/oscillator.rs: -------------------------------------------------------------------------------- 1 | //! Sinewave oscillator. 2 | 3 | use std::f64::consts::PI; 4 | pub const TAU : f64 = PI * 2.0; 5 | 6 | use types::*; 7 | 8 | pub fn sine(sample_rate: f64, frequency: f64, playhead: Playhead) -> f64 { 9 | (frequency * TAU * (playhead as f64 / sample_rate)).sin() 10 | } 11 | -------------------------------------------------------------------------------- /dd-expander/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dd_expander" 3 | version = "0.1.0" 4 | authors = ["Rob Saunders "] 5 | 6 | [dependencies] 7 | vst2 = { git = "https://github.com/rust-dsp/rust-vst" } 8 | time = "0.1" 9 | 10 | [lib] 11 | name = "dd_expander" 12 | crate-type = ["cdylib"] 13 | -------------------------------------------------------------------------------- /dd-dsp/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dd_dsp" 3 | version = "0.1.0" 4 | authors = ["Rob Saunders "] 5 | 6 | [dependencies] 7 | vst = { git = "https://github.com/rust-dsp/rust-vst" } 8 | log = "0.3" 9 | time = "0.1" 10 | hound = "3.1.0" 11 | basic_dsp="0.5" 12 | 13 | [lib] 14 | name = "dd_dsp" 15 | -------------------------------------------------------------------------------- /dd-dsp/src/voice.rs: -------------------------------------------------------------------------------- 1 | //use std; 2 | use VoiceState; 3 | use types::*; 4 | 5 | pub struct Voice { 6 | pub started_at: Playhead, // samples since voice started. 7 | pub state: VoiceState, 8 | } 9 | 10 | impl Voice { 11 | pub fn reset(&mut self, playhead: Playhead) { 12 | self.started_at = playhead; 13 | self.state = VoiceState::Playing; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /dd-subsynth/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dd_subsynth" 3 | version = "0.1.0" 4 | authors = ["Rob Saunders "] 5 | 6 | [dependencies] 7 | vst = { git = "https://github.com/rust-dsp/rust-vst" } 8 | log = "0.3" 9 | simplelog = "0.4.2" 10 | time = "0.1" 11 | dd_dsp = { path = "../dd-dsp" } 12 | 13 | [lib] 14 | name = "dd_subsynth" 15 | crate-type = ["cdylib"] 16 | -------------------------------------------------------------------------------- /dd-rvc1/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dd_rvc01" 3 | version = "0.1.0" 4 | authors = ["Rob Saunders "] 5 | 6 | [dependencies] 7 | vst2 = { git = "https://github.com/rust-dsp/rust-vst" } 8 | lanceverb = { git = "https://github.com/mitchmindtree/lanceverb" } 9 | dsp-chain = { version = "0.13.1" } 10 | 11 | [lib] 12 | name = "dd_rvc01" 13 | crate-type = ["cdylib"] 14 | -------------------------------------------------------------------------------- /dd-chunks/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dd_chunks" 3 | version = "0.1.0" 4 | authors = ["Rob Saunders "] 5 | 6 | [dependencies] 7 | vst2 = { git = "https://github.com/rust-dsp/rust-vst" } 8 | log = "0.3" 9 | simplelog = "0.4.2" 10 | time = "0.1" 11 | serde = "1.0" 12 | serde_derive = "1.0.11" 13 | bincode = "0.8" 14 | 15 | [lib] 16 | name = "dd_chunks" 17 | crate-type = ["cdylib"] 18 | -------------------------------------------------------------------------------- /dd-winit/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dd_winit" 3 | version = "0.1.0" 4 | authors = ["Rob Saunders "] 5 | 6 | [dependencies] 7 | vst2 = { git = "https://github.com/rust-dsp/rust-vst" } 8 | winit = { path = "../../_forks/winit" } 9 | cocoa = "0.9" 10 | log = "0.3" 11 | simplelog = "0.4.2" 12 | time = "0.1" 13 | serde = "1.0" 14 | serde_derive = "1.0.11" 15 | bincode = "0.8" 16 | 17 | [lib] 18 | name = "dd_winit" 19 | crate-type = ["cdylib"] 20 | -------------------------------------------------------------------------------- /dd-sampler/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dd_sampler" 3 | version = "0.1.0" 4 | authors = ["Rob Saunders "] 5 | 6 | [dependencies] 7 | vst2 = { git = "https://github.com/rust-dsp/rust-vst" } 8 | log = "0.3" 9 | simplelog = "0.4.2" 10 | time = "0.1" 11 | libc = "*" 12 | objc = "0.2" 13 | cocoa = "0.8.1" 14 | dd_dsp = { path = "../dd-dsp" } 15 | log-panics = "1.1.0" 16 | nfd = "0.0.4" 17 | 18 | [lib] 19 | name = "dd_sampler" 20 | crate-type = ["cdylib"] 21 | -------------------------------------------------------------------------------- /dd-dsp/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate hound; 2 | extern crate basic_dsp; 3 | #[macro_use] extern crate log; 4 | 5 | extern crate time; 6 | 7 | pub mod envelope; 8 | pub use envelope::*; 9 | 10 | pub mod oscillator; 11 | 12 | pub mod midi; 13 | pub use midi::*; 14 | 15 | //pub mod sampler; 16 | 17 | mod sample; 18 | 19 | pub mod sampler { 20 | // pub use sample::Sample; 21 | pub use sample::SampleFile; 22 | } 23 | 24 | mod instrument; 25 | pub use instrument::{Instrument, VoiceState }; 26 | // pub use voice_manager::PlayingVoice; 27 | 28 | mod voice; 29 | pub use voice::Voice; 30 | 31 | pub mod types { 32 | pub type MidiNote = u8; 33 | pub type Gain = f64; 34 | pub type Playhead = u64; 35 | pub type NoteFreq = f64; 36 | /// Size of samples. 37 | pub type Sample = f32; 38 | } 39 | -------------------------------------------------------------------------------- /contrib/mac-install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ ! -f "./contrib/vst-bundler.sh" ]; then 4 | echo "please run this script from the dd-plugs directory by typing ./contrib/mac-install.sh" 5 | exit 1 6 | fi 7 | 8 | cargo build --release --all 9 | 10 | INSTALL_DIR="$HOME/Library/Audio/Plug-Ins/VST/" 11 | plugins=$(find target/release/*.dylib -type f -exec basename {} \;) 12 | 13 | for plugin in $plugins; do 14 | DYLIB_FILE="target/release/$plugin" 15 | # strip .dylib suffix 16 | TMP_VST_NAME=${plugin%.dylib} 17 | # replace _ with - 18 | TMP_VST_NAME_2=${TMP_VST_NAME//_/-} 19 | # strip lib prefix 20 | VST_NAME=${TMP_VST_NAME_2#lib} 21 | 22 | TARGET="$INSTALL_DIR$VST_NAME.vst" 23 | 24 | # remove the file if it exists in the target directory. 25 | [ -d "$TARGET" ] && rm -rf "$TARGET" 26 | 27 | bash ./contrib/vst-bundler.sh $VST_NAME $DYLIB_FILE && 28 | mv -v ./$VST_NAME.vst $INSTALL_DIR 29 | done 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dd-plugs 2 | A set of minimalist VST plugins written in rust. 3 | 4 | - **dd-subsynth** a polyphonic subtractive synth, good for sub basses at the moment. 5 | - **dd-overdrive** a very simple overdrive / distortion. 6 | - **dd-sampler** a simple sample playback plugin. 7 | 8 | At the moment, these plugins are gui-less (though I'm building a vst gui lib to write gui based plugins which may end up here in future). 9 | 10 | I was largely inspired by @ferrisstreamsstuff and his approach to self-built tools - he largely works for demoscene projects but I wish to create a series of basic workhorse plugins for myself. 11 | 12 | Most plugins focus on flashy guis and silly unnecessary bloat, and coming with an army of presets so you don't have to do any thinking for yourself or understand how the plugin works. This suite is not for that kind of person. If you'd like to contribute your own plugins, please do so! Make a PR and lets learn DSP programming in rust together. 13 | 14 | ## Compiling 15 | ```shell 16 | cargo build --release --all 17 | ``` 18 | 19 | On MacOS, you will need to package these up. A provided script will help you here. 20 | ```shell 21 | chmod 777 ./contrib/mac-install.sh 22 | ./contrib/mac-install.sh 23 | ``` 24 | 25 | Alternatively I may release binary versions in the future. 26 | -------------------------------------------------------------------------------- /contrib/vst-bundler.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Make sure we have the arguments we need 4 | if [[ -z $1 || -z $2 ]]; then 5 | echo "Generates a macOS bundle from a compiled dylib file" 6 | echo "Example:" 7 | echo -e "\t$0 Plugin target/release/plugin.dylib" 8 | echo -e "\tCreates a Plugin.vst bundle" 9 | else 10 | # Make the bundle folder 11 | mkdir -p "$1.vst/Contents/MacOS" 12 | 13 | # Create the PkgInfo 14 | echo "BNDL????" > "$1.vst/Contents/PkgInfo" 15 | 16 | #build the Info.Plist 17 | echo " 18 | 19 | 20 | 21 | CFBundleDevelopmentRegion 22 | English 23 | 24 | CFBundleExecutable 25 | $1 26 | 27 | CFBundleGetInfoString 28 | vst 29 | 30 | CFBundleIconFile 31 | 32 | 33 | CFBundleIdentifier 34 | com.rust-vst2.$1 35 | 36 | CFBundleInfoDictionaryVersion 37 | 6.0 38 | 39 | CFBundleName 40 | $1 41 | 42 | CFBundlePackageType 43 | BNDL 44 | 45 | CFBundleVersion 46 | 1.0 47 | 48 | CFBundleSignature 49 | $((RANDOM % 9999)) 50 | 51 | CSResourcesFileMapped 52 | 53 | 54 | 55 | " > "$1.vst/Contents/Info.plist" 56 | 57 | # move the provided library to the correct location 58 | cp "$2" "$1.vst/Contents/MacOS/$1" 59 | 60 | echo "Created bundle $1.vst" 61 | fi -------------------------------------------------------------------------------- /dd-dsp/src/instrument.rs: -------------------------------------------------------------------------------- 1 | //use std; 2 | // use std::collections::HashMap; 3 | // use std::collections::hash_map::Entry; 4 | //use midi::midi_note_to_hz; 5 | use types::*; 6 | use Voice; 7 | use Envelope; 8 | 9 | pub struct Instrument where E:Envelope { 10 | pub voices: Vec<(MidiNote, Voice)>, 11 | pub envelope: E, 12 | } 13 | 14 | #[derive(Debug, Clone, Copy, PartialEq)] 15 | pub enum VoiceState { 16 | Playing, 17 | Released(Playhead), 18 | Retriggered(f64), 19 | } 20 | 21 | //use time; 22 | 23 | impl Instrument { 24 | 25 | pub fn note_on(&mut self, note: MidiNote, playhead: Playhead) { 26 | self.voices.push((note, Voice { 27 | started_at: playhead, 28 | state: VoiceState::Playing, 29 | })); 30 | } 31 | 32 | pub fn note_off(&mut self, note: MidiNote, playhead: Playhead) { 33 | // info!("note off {}", note); 34 | 35 | for &mut (n, ref mut voice) in self.voices.iter_mut() { 36 | if n == note && voice.state == VoiceState::Playing { 37 | voice.state = VoiceState::Released(playhead) 38 | } 39 | } 40 | } 41 | 42 | pub fn cleanup(&mut self, playhead: Playhead) { 43 | // let voices = self.voices.iter().filter(|(n,v) v == VoiceState::Released(_)|).collect::>(); 44 | 45 | // for (index, &mut (n, ref mut voice)) in self.voices.iter_mut().enumerate() { 46 | // match voice.state { 47 | // VoiceState::Released(release_time) => { 48 | // self.voices.remove(index); 49 | // }, 50 | // _ => (), 51 | // } 52 | // } 53 | 54 | let envelope = &self.envelope; 55 | 56 | self.voices.retain(|&(_, ref v)| 57 | // v.state == VoiceState::Playing 58 | match v.state { 59 | VoiceState::Released(release_time) => { 60 | envelope.expired(playhead, release_time) 61 | }, 62 | _ => true, 63 | } 64 | ); 65 | } 66 | 67 | // fn kill(&mut self, note: MidiNote) { 68 | // info!("note kill {}", note); 69 | // self.voices.remove(¬e); 70 | // } 71 | } 72 | -------------------------------------------------------------------------------- /dd-dsp/src/sample.rs: -------------------------------------------------------------------------------- 1 | use hound; 2 | use std::io::{BufReader, Read}; 3 | use types::*; 4 | 5 | #[derive(Clone)] 6 | pub struct SampleFile { 7 | pub sample_rate: f64, 8 | pub unity_pitch: f64, 9 | pub samples: Vec, 10 | } 11 | 12 | impl SampleFile { 13 | 14 | pub fn from_static_file(file: &'static [u8]) -> Result { 15 | // let samples: Vec = reader.samples::().map(|x|x.expect("Failed to read sample")).collect(); 16 | match hound::WavReader::new(BufReader::new(&file[..])) { 17 | Ok(samplefile) => SampleFile::from_wavreader(samplefile), 18 | Err(why) => Err(format!("{:?}", why)), 19 | } 20 | } 21 | 22 | pub fn from_path(file: String) -> Result { 23 | match hound::WavReader::open(file) { 24 | Ok(samplefile) => SampleFile::from_wavreader(samplefile), 25 | Err(why) => Err(format!("{:?}", why)), 26 | } 27 | } 28 | 29 | fn from_wavreader(mut reader: hound::WavReader>) -> Result { 30 | let samples: Vec = reader.samples::().map(|x|x.expect("Failed to read sample")).collect(); 31 | 32 | Ok(SampleFile { 33 | sample_rate: reader.spec().sample_rate as f64, 34 | unity_pitch: 440.0, 35 | samples: samples, 36 | }) 37 | } 38 | 39 | /// Gives a length of a resized sample at a specific pitch. 40 | pub fn len_for_freq(&self, freq: f64) -> usize { 41 | let unity_freq = self.unity_pitch; 42 | let scale_factor = freq / unity_freq; 43 | (self.samples.len() as f64 * scale_factor) as usize 44 | } 45 | 46 | /// Returns the relative sample for pos at a specific pitch. 47 | pub fn sample_at(&self, offset: Playhead, freq: f64) -> i16 { 48 | let unity_freq = self.unity_pitch; 49 | let scale_factor = freq / unity_freq; 50 | 51 | let new_samplerate_ratio = self.sample_rate / 10000.0 * scale_factor; 52 | let new_pos = ((offset as f64) * new_samplerate_ratio) as usize; 53 | 54 | if self.samples.len() > new_pos { 55 | // info!("{}", self.samples[new_pos]) 56 | self.samples[new_pos] 57 | } else { 0 } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /dd-dsp/src/sampler.rs: -------------------------------------------------------------------------------- 1 | use std::i16; 2 | use std; 3 | 4 | // use basic_dsp; 5 | // use basic_dsp::{ ToComplexVector, SingleBuffer, InterpolationOps }; 6 | // use basic_dsp::conv_types::{ SincFunction }; 7 | 8 | // #[derive(Clone)] 9 | // pub struct Mapping {} 10 | 11 | use types::*; 12 | use envelope; 13 | 14 | use SampleFile; 15 | use Instrument; 16 | use VoiceState; 17 | 18 | pub struct Sampler { 19 | sample_file: SampleFile, 20 | voice_manager: Instrument, 21 | output_channels: u16, 22 | output_sample_rate: f64, 23 | // envelope: envelope::ADSR, 24 | } 25 | 26 | // fn change_speed(samples: Vec) -> Vec { 27 | // // let sample_cache: Vec = samples.iter().map(|x| *x as f32).collect(); 28 | 29 | // info!("samples: {:?}", samples); 30 | // // let mut repitched_samples = samples.to_complex_time_vec(); 31 | // info!("repitched_samples: {:?}", repitched_samples); 32 | // let mut buffer = SingleBuffer::new(); 33 | // let function = SincFunction::new(); 34 | // repitched_samples.interpolatef(&mut buffer, &function, 1.5, 0.0, 10); 35 | 36 | // let mut converted_samples: Vec = Vec::new(); 37 | // for sample in &repitched_samples[..] { 38 | // let amplitude = i16::MAX as f32; 39 | // let sample = (sample * amplitude) as i16; 40 | // info!("{:?}", sample); 41 | // converted_samples.push(sample as i16); 42 | // } 43 | 44 | // info!("converted_samples: {:?}", converted_samples); 45 | // converted_samples 46 | // } 47 | 48 | impl Sampler { 49 | pub fn new(sample_rate: f64) -> Result { 50 | let sample = SampleFile::from_static_file(include_bytes!("../../dd-sampler/assets/bass.wav")).unwrap(); 51 | 52 | Ok(Sampler { 53 | output_sample_rate: sample_rate, 54 | sample_file: sample, 55 | output_channels: 2, 56 | voice_manager: Instrument::new(), 57 | // envelope: envelope::ADSR{ 58 | // attack_time: 90.0, 59 | // release_time: 90.0, 60 | // } 61 | }) 62 | } 63 | 64 | // pub fn note_on(&mut self, note: MidiNote) { self.voice_manager.note_on(note) } 65 | // pub fn note_off(&mut self, note: MidiNote) { self.voice_manager.note_off(note, self.play) } 66 | 67 | // pub fn process_buffer(&mut self, output_buffer: &mut Vec<&mut[f32]>) { 68 | // for output_channel in output_buffer.iter_mut() { 69 | // for output_sample in output_channel.iter_mut() { 70 | // *output_sample = self.process() 71 | // } 72 | // } 73 | // } 74 | 75 | // pub fn process(&mut self) -> f32 { 76 | // let mut output_sample: f64 = 0.0; 77 | // let voices = self.voice_manager.next(); 78 | // for playing_sample in voices { 79 | // let pos = playing_sample.samples_since_start as usize; 80 | // 81 | //// let envelope_gain = match playing_sample.state { 82 | //// VoiceState::Playing => { 83 | //// self.envelope.gain_ratio(std::time::Instant::now()) 84 | //// }, 85 | //// VoiceState::Released(release_time) => { 86 | //// self.envelope.release_gain_ratio(std::time::Instant::now(), release_time) 87 | //// } 88 | //// }; 89 | //// info!("{:?}", envelope_gain); 90 | // 91 | // if self.sample_file.samples.len() > pos { 92 | // output_sample += self.sample_file.sample_at(pos, playing_sample.pitch); // * envelope_gain; 93 | // output_sample += 94 | // } 95 | // } 96 | // let amplitude = i16::MAX as f32; 97 | // return output_sample as f32 / amplitude; 98 | // } 99 | } 100 | -------------------------------------------------------------------------------- /dd-overdrive/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] extern crate vst2; 2 | 3 | use vst2::buffer::AudioBuffer; 4 | use vst2::plugin::{Category, Plugin, Info}; 5 | 6 | struct DigiDist { 7 | threshold: f32, 8 | gain: f32, 9 | } 10 | 11 | impl Default for DigiDist { 12 | fn default() -> DigiDist { 13 | DigiDist { 14 | threshold: 1.0, // VST parameters are always 0.0 to 1.0 15 | gain: 1.0, 16 | } 17 | } 18 | } 19 | 20 | impl Plugin for DigiDist { 21 | fn get_info(&self) -> Info { 22 | Info { 23 | name: "DigiDist".to_string(), 24 | vendor: "DeathDisco".to_string(), 25 | unique_id: 25032022, 26 | category: Category::Effect, 27 | 28 | inputs: 2, 29 | outputs: 2, 30 | parameters: 2, 31 | 32 | preset_chunks: true, 33 | 34 | ..Info::default() 35 | } 36 | } 37 | 38 | fn get_preset_data(&mut self) -> Vec { Vec::new() } 39 | fn get_bank_data(&mut self) -> Vec { Vec::new() } 40 | fn load_preset_data(&mut self, data: &[u8]) { } 41 | fn load_bank_data(&mut self, data: &[u8]) { } 42 | 43 | fn get_parameter(&self, index: i32) -> f32 { 44 | match index { 45 | 0 => self.threshold, 46 | 1 => self.gain, 47 | _ => 0.0, 48 | } 49 | } 50 | 51 | fn set_parameter(&mut self, index: i32, value: f32) { 52 | match index { 53 | // We don't want to divide by zero, so we'll clamp the value 54 | 0 => self.threshold = value.max(0.01), 55 | 1 => self.gain = value, 56 | _ => (), 57 | } 58 | } 59 | 60 | fn get_parameter_name(&self, index: i32) -> String { 61 | match index { 62 | 0 => "Threshold".to_string(), 63 | 1 => "Gain".to_string(), 64 | _ => "".to_string(), 65 | } 66 | } 67 | 68 | fn get_parameter_text(&self, index: i32) -> String { 69 | match index { 70 | // Convert to a percentage 71 | 0 => format!("{}", self.threshold * 100.0), 72 | 1 => format!("{}", self.gain * 100.0), 73 | _ => "".to_string(), 74 | } 75 | } 76 | 77 | fn get_parameter_label(&self, index: i32) -> String { 78 | match index { 79 | 0 => "%".to_string(), 80 | 1 => "%".to_string(), 81 | _ => "".to_string(), 82 | } 83 | } 84 | 85 | fn process(&mut self, buffer: &mut AudioBuffer) { 86 | // Split out the input and output buffers into two vectors 87 | let (input_buffer, mut output_buffer) = buffer.split(); 88 | 89 | // Assume 2 channels 90 | if input_buffer.len() < 2 || output_buffer.len() < 2 { 91 | return; 92 | } 93 | 94 | // Iterate over inputs as (&f32, &f32) 95 | let (l, r) = input_buffer.split_at(1); 96 | let stereo_in = l[0].iter().zip(r[0].iter()); 97 | 98 | // Iterate over outputs as (&mut f32, &mut f32) 99 | let (mut l, mut r) = output_buffer.split_at_mut(1); 100 | let stereo_out = l[0].iter_mut().zip(r[0].iter_mut()); 101 | 102 | // Zip and process 103 | for ((left_in, right_in), (left_out, right_out)) in stereo_in.zip(stereo_out) { 104 | 105 | if *left_in >= 0.0 { 106 | *left_out = left_in.min(self.threshold) / self.threshold * self.gain; 107 | } 108 | else { 109 | *right_out = left_in.max(-self.threshold) / self.threshold * self.gain; 110 | } 111 | 112 | *left_out = *left_in + *left_out; 113 | *right_out = *right_in + *right_out; 114 | } 115 | } 116 | } 117 | 118 | plugin_main!(DigiDist); 119 | -------------------------------------------------------------------------------- /dd-chunks/src/lib.rs: -------------------------------------------------------------------------------- 1 | // dd-chunks: example of saving and loading binary chunks. 2 | 3 | #[macro_use] 4 | extern crate vst2; 5 | 6 | #[macro_use] 7 | extern crate log; 8 | extern crate simplelog; 9 | 10 | use simplelog::*; 11 | use std::fs::File; 12 | 13 | #[macro_use] 14 | extern crate serde_derive; 15 | extern crate bincode; 16 | 17 | use bincode::{serialize, deserialize, Infinite}; 18 | 19 | use vst2::buffer::AudioBuffer; 20 | use vst2::plugin::{Category, Plugin, Info}; 21 | use vst2::event::{Event}; 22 | 23 | /// Size of VST params. 24 | type Param = f32; 25 | 26 | struct Chunker { 27 | preset: ChunkerPreset, 28 | } 29 | 30 | #[derive(Serialize, Deserialize, Debug, Copy, Clone)] 31 | struct ChunkerPreset { 32 | volume: Param, 33 | } 34 | 35 | impl Default for Chunker { 36 | fn default() -> Self { 37 | let _ = CombinedLogger::init( 38 | vec![ 39 | WriteLogger::new(LogLevelFilter::Error, Config::default(), File::create("/tmp/simplesynth.log").unwrap()), 40 | ] 41 | ); 42 | info!("Loaded dd-chunks..."); 43 | Self { 44 | preset: ChunkerPreset{ volume: 0.5 } 45 | } 46 | } 47 | } 48 | use vst2::editor::*; 49 | 50 | impl Plugin for Chunker { 51 | 52 | fn get_preset_data(&mut self) -> Vec { 53 | error!("[SAVE TO PRESET] get_preset_data: {:?}", self.preset); 54 | let encoded: Vec = serialize(&self.preset, Infinite).unwrap(); 55 | 56 | encoded 57 | } 58 | 59 | fn get_bank_data(&mut self) -> Vec { 60 | error!("[SAVE TO PRESET] get_bank_data: {:?}", self.preset); 61 | self.get_preset_data() 62 | } 63 | 64 | fn load_preset_data(&mut self, data: &[u8]) { 65 | let decoded: ChunkerPreset = deserialize(&data[..]).unwrap(); 66 | error!("[LOAD FROM PRESET] load_preset_data called: {:?}", decoded); 67 | 68 | self.preset = decoded; 69 | } 70 | 71 | fn load_bank_data(&mut self, data: &[u8]) { 72 | self.load_preset_data(data); 73 | error!("[LOAD FROM PRESET] load_bank_data called: {:?}", self.preset); 74 | } 75 | 76 | fn get_preset_num(&self) -> i32 { 0 } 77 | fn change_preset(&mut self, preset: i32) { } 78 | 79 | fn get_preset_name(&self, preset: i32) -> String { 80 | match preset { 81 | 0 => "Preset 1".to_string(), 82 | 1 => "Preset 2".to_string(), 83 | 2 => "Preset 3".to_string(), 84 | _ => "Other Preset".to_string(), 85 | } 86 | } 87 | 88 | fn set_preset_name(&mut self, name: String) { } 89 | 90 | fn get_info(&self) -> Info { 91 | Info { 92 | name: "DD-Chunker".to_string(), 93 | vendor: "DeathDisco".to_string(), 94 | unique_id: 99887766, 95 | category: Category::Synth, 96 | inputs: 0, 97 | outputs: 1, 98 | parameters: 1, 99 | initial_delay: 0, 100 | preset_chunks: true, 101 | ..Info::default() 102 | } 103 | } 104 | 105 | fn get_parameter(&self, index: i32) -> f32 { self.preset.volume } 106 | fn set_parameter(&mut self, index: i32, value: f32) { self.preset.volume = value } 107 | 108 | fn can_be_automated(&self, index: i32) -> bool { error!("can_be_automated called"); true } 109 | 110 | // Some hosts, like Bitwig, break the VST2.4 spec and won't call getChunk/setChunk unless 111 | // a gui is present or params have been altered prior to saving a preset. 112 | fn get_editor(&mut self) -> Option<&mut Editor> { Some(self) } 113 | 114 | } 115 | 116 | impl Editor for Chunker { 117 | fn size(&self) -> (i32, i32) { (200, 100) } 118 | fn position(&self) -> (i32, i32) { (0, 0) } 119 | fn open(&mut self, window: *mut std::os::raw::c_void) {} 120 | fn is_open(&mut self) -> bool { true } 121 | } 122 | 123 | plugin_main!(Chunker); 124 | -------------------------------------------------------------------------------- /dd-winit/src/lib.rs: -------------------------------------------------------------------------------- 1 | // dd-chunks: example of saving and loading binary chunks. 2 | 3 | #[macro_use] 4 | extern crate vst2; 5 | 6 | #[macro_use] 7 | extern crate log; 8 | extern crate simplelog; 9 | 10 | extern crate winit; 11 | extern crate cocoa; 12 | use cocoa::base::id; 13 | 14 | use simplelog::*; 15 | use std::fs::File; 16 | 17 | #[macro_use] 18 | extern crate serde_derive; 19 | extern crate bincode; 20 | 21 | use bincode::{serialize, deserialize, Infinite}; 22 | 23 | // use vst2::buffer::AudioBuffer; 24 | use vst2::plugin::{Category, Plugin, Info}; 25 | // use vst2::event::{Event}; 26 | 27 | /// Size of VST params. 28 | type Param = f32; 29 | 30 | struct Chunker { 31 | preset: ChunkerPreset, 32 | } 33 | 34 | #[derive(Serialize, Deserialize, Debug, Copy, Clone)] 35 | struct ChunkerPreset { 36 | volume: Param, 37 | } 38 | 39 | impl Default for Chunker { 40 | fn default() -> Self { 41 | let _ = CombinedLogger::init( 42 | vec![ 43 | WriteLogger::new(LogLevelFilter::Error, Config::default(), File::create("/tmp/simplesynth.log").unwrap()), 44 | ] 45 | ); 46 | info!("Loaded dd-chunks..."); 47 | Self { 48 | preset: ChunkerPreset{ volume: 0.5 } 49 | } 50 | } 51 | } 52 | use vst2::editor::*; 53 | 54 | impl Plugin for Chunker { 55 | 56 | fn get_preset_data(&mut self) -> Vec { 57 | error!("[SAVE TO PRESET] get_preset_data: {:?}", self.preset); 58 | let encoded: Vec = serialize(&self.preset, Infinite).unwrap(); 59 | 60 | encoded 61 | } 62 | 63 | fn get_bank_data(&mut self) -> Vec { 64 | error!("[SAVE TO PRESET] get_bank_data: {:?}", self.preset); 65 | self.get_preset_data() 66 | } 67 | 68 | fn load_preset_data(&mut self, data: &[u8]) { 69 | let decoded: ChunkerPreset = deserialize(&data[..]).unwrap(); 70 | error!("[LOAD FROM PRESET] load_preset_data called: {:?}", decoded); 71 | 72 | self.preset = decoded; 73 | } 74 | 75 | fn load_bank_data(&mut self, data: &[u8]) { 76 | self.load_preset_data(data); 77 | error!("[LOAD FROM PRESET] load_bank_data called: {:?}", self.preset); 78 | } 79 | 80 | fn get_preset_num(&self) -> i32 { 0 } 81 | fn change_preset(&mut self, _: i32) { } 82 | 83 | fn get_preset_name(&self, preset: i32) -> String { 84 | match preset { 85 | 0 => "Preset 1".to_string(), 86 | 1 => "Preset 2".to_string(), 87 | 2 => "Preset 3".to_string(), 88 | _ => "Other Preset".to_string(), 89 | } 90 | } 91 | 92 | fn set_preset_name(&mut self, _: String) { } 93 | 94 | fn get_info(&self) -> Info { 95 | Info { 96 | name: "DD-Chunker".to_string(), 97 | vendor: "DeathDisco".to_string(), 98 | unique_id: 99887766, 99 | category: Category::Synth, 100 | inputs: 0, 101 | outputs: 1, 102 | parameters: 1, 103 | initial_delay: 0, 104 | preset_chunks: true, 105 | ..Info::default() 106 | } 107 | } 108 | 109 | fn get_parameter(&self, _: i32) -> f32 { self.preset.volume } 110 | fn set_parameter(&mut self, _: i32, value: f32) { self.preset.volume = value } 111 | 112 | fn can_be_automated(&self, _: i32) -> bool { error!("can_be_automated called"); true } 113 | 114 | // Some hosts, like Bitwig, break the VST2.4 spec and won't call getChunk/setChunk unless 115 | // a gui is present or params have been altered prior to saving a preset. 116 | fn get_editor(&mut self) -> Option<&mut Editor> { Some(self) } 117 | 118 | } 119 | 120 | impl Editor for Chunker { 121 | fn size(&self) -> (i32, i32) { (200, 100) } 122 | fn position(&self) -> (i32, i32) { (0, 0) } 123 | fn open(&mut self, window: *mut std::os::raw::c_void) { 124 | let mut events_loop = winit::EventsLoop::new(); 125 | 126 | use winit::os::macos::WindowBuilderExt; 127 | let w = winit::WindowBuilder::new().attach_to_existing_context(window); 128 | let win = w.build(&events_loop); 129 | } 130 | fn is_open(&mut self) -> bool { true } 131 | } 132 | 133 | plugin_main!(Chunker); 134 | -------------------------------------------------------------------------------- /dd-rvc1/src/lib.rs: -------------------------------------------------------------------------------- 1 | // based on mverb 2 | // https://github.com/martineastwood/mverb/blob/master/VstPlugin.cpp 3 | 4 | #[macro_use] extern crate vst2; 5 | 6 | use vst2::buffer::AudioBuffer; 7 | use vst2::plugin::{Category, Plugin, Info}; 8 | 9 | extern crate dsp; 10 | extern crate lanceverb; 11 | use lanceverb::Reverb; 12 | use dsp::{Frame, Node}; 13 | 14 | const SAMPLE_RATE: f32 = 44100.; 15 | const PRE_DELAY_TIME: f32 = 100. * (SAMPLE_RATE / 1000.); 16 | 17 | struct CombVerb { 18 | 19 | // params: 20 | dampening_freq: f32, 21 | density: f32, 22 | bandwidth_freq: f32, 23 | decay: f32, 24 | pre_delay: f32, 25 | size: f32, 26 | gain: f32, 27 | mix: f32, 28 | early_mix: f32, 29 | 30 | // state: 31 | prev_left_tank: f32, 32 | prev_right_tank: f32, 33 | sample_rate: f32, 34 | 35 | reverb: Reverb, 36 | 37 | } 38 | 39 | impl Default for CombVerb { 40 | fn default() -> CombVerb { 41 | CombVerb { 42 | // presets 43 | dampening_freq: 18000., 44 | density: 1., 45 | bandwidth_freq: 18000., 46 | decay: 0.5, 47 | pre_delay: 100. * (SAMPLE_RATE / 1000.), 48 | gain: 1., 49 | mix: 1., 50 | size: 1., 51 | early_mix: 1., 52 | // state 53 | prev_left_tank: 0., 54 | prev_right_tank: 0., 55 | sample_rate: 0., 56 | reverb: Reverb::new(), 57 | } 58 | } 59 | } 60 | 61 | impl Plugin for CombVerb { 62 | fn get_info(&self) -> Info { 63 | Info { 64 | name: "CombVerb".to_string(), 65 | vendor: "DeathDisco".to_string(), 66 | unique_id: 12356677, 67 | category: Category::Effect, 68 | 69 | inputs: 2, 70 | outputs: 2, 71 | parameters: 1, 72 | 73 | ..Info::default() 74 | } 75 | } 76 | 77 | // fn get_preset_data(&mut self) -> Vec { Vec::new() } 78 | // fn get_bank_data(&mut self) -> Vec { Vec::new() } 79 | // fn load_preset_data(&mut self, data: &[u8]) { } 80 | // fn load_bank_data(&mut self, data: &[u8]) { } 81 | 82 | fn get_parameter(&self, index: i32) -> f32 { 83 | match index { 84 | 0 => self.bandwidth_freq, 85 | 1 => self.dampening_freq, 86 | _ => 0.0, 87 | } 88 | } 89 | 90 | fn set_parameter(&mut self, index: i32, value: f32) { 91 | match index { 92 | // We don't want to divide by zero, so we'll clamp the value 93 | 0 => self.bandwidth_freq = value.max(0.01), 94 | 1 => self.dampening_freq = value, 95 | _ => (), 96 | } 97 | } 98 | 99 | fn get_parameter_name(&self, index: i32) -> String { 100 | match index { 101 | 0 => "Room Size".to_string(), 102 | 1 => "Dampening".to_string(), 103 | _ => "".to_string(), 104 | } 105 | } 106 | 107 | fn get_parameter_text(&self, index: i32) -> String { 108 | match index { 109 | // Convert to a percentage 110 | 0 => format!("{}", self.bandwidth_freq * 100.0), 111 | 1 => format!("{}", self.dampening_freq * 100.0), 112 | _ => "".to_string(), 113 | } 114 | } 115 | 116 | fn get_parameter_label(&self, index: i32) -> String { 117 | match index { 118 | 0 => "%".to_string(), 119 | 1 => "%".to_string(), 120 | _ => "".to_string(), 121 | } 122 | } 123 | 124 | fn set_sample_rate(&mut self, rate: f32) { 125 | self.sample_rate = rate; 126 | } 127 | 128 | fn process(&mut self, buffer: &mut AudioBuffer) { 129 | // Split out the input and output buffers into two vectors 130 | let (input_buffer, mut output_buffer) = buffer.split(); 131 | 132 | // Assume 2 channels 133 | if input_buffer.len() < 2 || output_buffer.len() < 2 { return; } 134 | 135 | // Iterate over inputs as (&f32, &f32) 136 | let (l, r) = input_buffer.split_at(1); 137 | let stereo_in = l[0].iter().zip(r[0].iter()); 138 | 139 | // Iterate over outputs as (&mut f32, &mut f32) 140 | let (mut l, mut r) = output_buffer.split_at_mut(1); 141 | let stereo_out = l[0].iter_mut().zip(r[0].iter_mut()); 142 | 143 | for ((left_in, right_in), (left_out, right_out)) in stereo_in.zip(stereo_out) { 144 | *left_out = *left_in; 145 | *right_out = *right_in; 146 | self.reverb.audio_requested(dsp::Frame::new(&mut [left_out]), SAMPLE_RATE as f64); 147 | } 148 | } 149 | } 150 | 151 | plugin_main!(CombVerb); 152 | -------------------------------------------------------------------------------- /dd-reverb/src/lib.rs: -------------------------------------------------------------------------------- 1 | // author: Rob Saunders 2 | 3 | #[macro_use] extern crate vst2; 4 | extern crate time; 5 | 6 | use vst2::plugin::{Category, Info, Plugin}; 7 | use vst2::buffer::AudioBuffer; 8 | 9 | use std::mem; 10 | use std::f64::consts::PI; 11 | use std::collections::VecDeque; 12 | 13 | /// Calculate the length in samples for a delay. Size ranges from 0.0 to 1.0. 14 | fn delay(index: usize, mut size: f32) -> isize { 15 | const SIZE_OFFSET: f32 = 0.06; 16 | const SIZE_MULT: f32 = 1_000.0; 17 | 18 | size += SIZE_OFFSET; 19 | 20 | // Spread ratio between delays 21 | const SPREAD: f32 = 0.3; 22 | 23 | let base = size * SIZE_MULT; 24 | let mult = (index as f32 * SPREAD) + 1.0; 25 | let offset = if index > 2 { base * SPREAD / 2.0 } else { 0.0 }; 26 | 27 | (base * mult + offset) as isize 28 | } 29 | 30 | /// A left channel and right channel sample. 31 | type SamplePair = (f32, f32); 32 | 33 | /// The Dimension Expander. 34 | struct DimensionExpander { 35 | buffers: Vec>, 36 | dry_wet: f32, 37 | size: f32, 38 | } 39 | 40 | impl Default for DimensionExpander { 41 | fn default() -> DimensionExpander { 42 | DimensionExpander::new(0.12, 0.66) 43 | } 44 | } 45 | 46 | impl DimensionExpander { 47 | fn new(size: f32, dry_wet: f32) -> DimensionExpander { 48 | const NUM_DELAYS: usize = 4; 49 | 50 | let mut buffers = Vec::new(); 51 | 52 | // Generate delay buffers 53 | for i in 0..NUM_DELAYS { 54 | let samples = delay(i, size); 55 | let mut buffer = VecDeque::with_capacity(samples as usize); 56 | 57 | // Fill in the delay buffers with empty samples 58 | for _ in 0..samples { 59 | buffer.push_back((0.0, 0.0)); 60 | } 61 | 62 | buffers.push(buffer); 63 | } 64 | 65 | DimensionExpander { 66 | buffers: buffers, 67 | dry_wet: dry_wet, 68 | size: size, 69 | } 70 | } 71 | 72 | /// Update the delay buffers with a new size value. 73 | fn resize(&mut self, n: f32) { 74 | let old_size = mem::replace(&mut self.size, n); 75 | 76 | for (i, buffer) in self.buffers.iter_mut().enumerate() { 77 | // Calculate the size difference between delays 78 | let old_delay = delay(i, old_size); 79 | let new_delay = delay(i, n); 80 | 81 | let diff = new_delay - old_delay; 82 | 83 | // Add empty samples if the delay was increased, remove if decreased 84 | if diff > 0 { 85 | for _ in 0..diff { 86 | buffer.push_back((0.0, 0.0)); 87 | } 88 | } else if diff < 0 { 89 | for _ in 0..-diff { 90 | let _ = buffer.pop_front(); 91 | } 92 | } 93 | } 94 | } 95 | } 96 | 97 | impl Plugin for DimensionExpander { 98 | fn get_info(&self) -> Info { 99 | Info { 100 | name: "Reverb".to_string(), 101 | vendor: "DeathDisco".to_string(), 102 | unique_id: 243723071, 103 | version: 0001, 104 | inputs: 2, 105 | outputs: 2, 106 | parameters: 2, 107 | category: Category::Effect, 108 | 109 | ..Default::default() 110 | } 111 | } 112 | 113 | fn get_parameter(&self, index: i32) -> f32 { 114 | match index { 115 | 0 => self.size, 116 | 1 => self.dry_wet, 117 | _ => 0.0, 118 | } 119 | } 120 | 121 | fn get_parameter_text(&self, index: i32) -> String { 122 | match index { 123 | 0 => format!("{}", (self.size * 1000.0) as isize), 124 | 1 => format!("{:.1}%", self.dry_wet * 100.0), 125 | _ => "".to_string() 126 | } 127 | } 128 | 129 | fn get_parameter_name(&self, index: i32) -> String { 130 | match index { 131 | 0 => "Size", 132 | 1 => "Dry/Wet", 133 | _ => "", 134 | }.to_string() 135 | } 136 | 137 | fn set_parameter(&mut self, index: i32, val: f32) { 138 | match index { 139 | 0 => self.resize(val), 140 | 1 => self.dry_wet = val, 141 | _ => (), 142 | } 143 | } 144 | fn process(&mut self, buffer: &mut AudioBuffer) { 145 | let (inputs, mut outputs) = buffer.split(); 146 | 147 | // Assume 2 channels 148 | if inputs.len() < 2 || outputs.len() < 2 { 149 | return; 150 | } 151 | 152 | // Iterate over inputs as (&f32, &f32) 153 | let (l, r) = inputs.split_at(1); 154 | let stereo_in = l[0].iter().zip(r[0].iter()); 155 | 156 | // Iterate over outputs as (&mut f32, &mut f32) 157 | let (mut l, mut r) = outputs.split_at_mut(1); 158 | let stereo_out = l[0].iter_mut().zip(r[0].iter_mut()); 159 | 160 | // Zip and process 161 | for ((left_in, right_in), (left_out, right_out)) in stereo_in.zip(stereo_out) { 162 | // Push the new samples into the delay buffers. 163 | for buffer in self.buffers.iter_mut() { 164 | buffer.push_back((*left_in, *right_in)); 165 | } 166 | 167 | let mut left_processed = 0.0; 168 | let mut right_processed = 0.0; 169 | 170 | // Recalculate time per sample 171 | let time_s = time::precise_time_ns() as f64 / 1_000_000_000.0; 172 | 173 | // Use buffer index to offset volume LFO 174 | for (n, buffer) in self.buffers.iter_mut().enumerate() { 175 | if let Some((left_old, right_old)) = buffer.pop_front() { 176 | const LFO_FREQ: f64 = 0.5; 177 | const WET_MULT: f32 = 0.66; 178 | 179 | let offset = 0.25 * (n % 4) as f64; 180 | 181 | // Sine wave volume LFO 182 | let lfo = ( 183 | (time_s * LFO_FREQ + offset) * PI * 2.0 184 | ).sin() as f32; 185 | 186 | let wet = self.dry_wet * WET_MULT; 187 | let mono = (left_old + right_old) / 2.0; 188 | 189 | // Flip right channel and keep left mono so that the result is 190 | // entirely stereo 191 | left_processed += mono * wet * lfo; 192 | right_processed += -mono * wet * lfo; 193 | } 194 | } 195 | 196 | // By only adding to the input, the output value always remains the same in mono 197 | *left_out = *left_in + left_processed; 198 | *right_out = *right_in + right_processed; 199 | } 200 | } 201 | } 202 | 203 | plugin_main!(DimensionExpander); 204 | -------------------------------------------------------------------------------- /dd-expander/src/lib.rs: -------------------------------------------------------------------------------- 1 | // author: Marko Mijalkovic 2 | 3 | #[macro_use] extern crate vst2; 4 | extern crate time; 5 | 6 | use vst2::plugin::{Category, Info, Plugin}; 7 | use vst2::buffer::AudioBuffer; 8 | 9 | use std::mem; 10 | use std::f64::consts::PI; 11 | use std::collections::VecDeque; 12 | 13 | const INITIAL_SPREAD: f32 = 0.3; 14 | 15 | /// Calculate the length in samples for a delay. Size ranges from 0.0 to 1.0. 16 | fn delay(index: usize, mut size: f32, spread: f32) -> isize { 17 | const SIZE_OFFSET: f32 = 0.06; 18 | const SIZE_MULT: f32 = 10_000.0; 19 | 20 | size += SIZE_OFFSET; 21 | 22 | let base = size * SIZE_MULT; 23 | let mult = (index as f32 * spread) + 1.0; 24 | let offset = if index > 2 { base * spread / 2.0 } else { 0.0 }; 25 | 26 | (base * mult + offset) as isize 27 | } 28 | 29 | /// A left channel and right channel sample. 30 | type SamplePair = (f32, f32); 31 | 32 | /// The Dimension Expander. 33 | struct DimensionExpander { 34 | buffers: Vec>, 35 | dry_wet: f32, 36 | size: f32, 37 | spread: f32, 38 | } 39 | 40 | impl Default for DimensionExpander { 41 | fn default() -> DimensionExpander { 42 | DimensionExpander::new(0.12, 0.66) 43 | } 44 | } 45 | 46 | impl DimensionExpander { 47 | fn new(size: f32, dry_wet: f32) -> DimensionExpander { 48 | const NUM_DELAYS: usize = 4; 49 | 50 | let mut buffers = Vec::new(); 51 | 52 | // Generate delay buffers 53 | for i in 0..NUM_DELAYS { 54 | let samples = delay(i, size, INITIAL_SPREAD); 55 | let mut buffer = VecDeque::with_capacity(samples as usize); 56 | 57 | // Fill in the delay buffers with empty samples 58 | for _ in 0..samples { 59 | buffer.push_back((0.0, 0.0)); 60 | } 61 | 62 | buffers.push(buffer); 63 | } 64 | 65 | DimensionExpander { 66 | buffers: buffers, 67 | dry_wet: dry_wet, 68 | size: size, 69 | spread: INITIAL_SPREAD, 70 | } 71 | } 72 | 73 | /// Update the delay buffers with a new size value. 74 | fn resize(&mut self, n: f32) { 75 | let old_size = mem::replace(&mut self.size, n); 76 | 77 | for (i, buffer) in self.buffers.iter_mut().enumerate() { 78 | // Calculate the size difference between delays 79 | let old_delay = delay(i, old_size, self.spread); 80 | let new_delay = delay(i, n, self.spread); 81 | 82 | let diff = new_delay - old_delay; 83 | 84 | // Add empty samples if the delay was increased, remove if decreased 85 | if diff > 0 { 86 | for _ in 0..diff { 87 | buffer.push_back((0.0, 0.0)); 88 | } 89 | } else if diff < 0 { 90 | for _ in 0..-diff { 91 | let _ = buffer.pop_front(); 92 | } 93 | } 94 | } 95 | } 96 | } 97 | 98 | impl Plugin for DimensionExpander { 99 | fn get_info(&self) -> Info { 100 | Info { 101 | name: "Expander".to_string(), 102 | vendor: "DeathDisco".to_string(), 103 | unique_id: 243723071, 104 | version: 0001, 105 | inputs: 2, 106 | outputs: 2, 107 | parameters: 3, 108 | category: Category::Effect, 109 | 110 | ..Default::default() 111 | } 112 | } 113 | 114 | fn get_parameter(&self, index: i32) -> f32 { 115 | match index { 116 | 0 => self.size, 117 | 1 => self.dry_wet, 118 | 2 => self.spread, 119 | _ => 0.0, 120 | } 121 | } 122 | 123 | fn get_parameter_text(&self, index: i32) -> String { 124 | match index { 125 | 0 => format!("{}", (self.size * 1000.0) as isize), 126 | 1 => format!("{:.1}%", self.dry_wet * 100.0), 127 | 2 => format!("{:.1}%", self.spread * 100.0), 128 | _ => "".to_string() 129 | } 130 | } 131 | 132 | fn get_parameter_name(&self, index: i32) -> String { 133 | match index { 134 | 0 => "Size", 135 | 1 => "Dry/Wet", 136 | 2 => "Spread", 137 | _ => "", 138 | }.to_string() 139 | } 140 | 141 | fn set_parameter(&mut self, index: i32, val: f32) { 142 | match index { 143 | 0 => self.resize(val), 144 | 1 => self.dry_wet = val, 145 | 2 => self.spread = val, 146 | _ => (), 147 | } 148 | } 149 | fn process(&mut self, buffer: &mut AudioBuffer) { 150 | let (inputs, mut outputs) = buffer.split(); 151 | 152 | // Assume 2 channels 153 | if inputs.len() < 2 || outputs.len() < 2 { 154 | return; 155 | } 156 | 157 | // Iterate over inputs as (&f32, &f32) 158 | let (l, r) = inputs.split_at(1); 159 | let stereo_in = l[0].iter().zip(r[0].iter()); 160 | 161 | // Iterate over outputs as (&mut f32, &mut f32) 162 | let (mut l, mut r) = outputs.split_at_mut(1); 163 | let stereo_out = l[0].iter_mut().zip(r[0].iter_mut()); 164 | 165 | // Zip and process 166 | for ((left_in, right_in), (left_out, right_out)) in stereo_in.zip(stereo_out) { 167 | // Push the new samples into the delay buffers. 168 | for buffer in self.buffers.iter_mut() { 169 | buffer.push_back((*left_in, *right_in)); 170 | } 171 | 172 | let mut left_processed = 0.0; 173 | let mut right_processed = 0.0; 174 | 175 | // Recalculate time per sample 176 | let time_s = time::precise_time_ns() as f64 / 1_000_000_000.0; 177 | 178 | // Use buffer index to offset volume LFO 179 | for (n, buffer) in self.buffers.iter_mut().enumerate() { 180 | if let Some((left_old, right_old)) = buffer.pop_front() { 181 | const LFO_FREQ: f64 = 0.5; 182 | const WET_MULT: f32 = 0.66; 183 | 184 | let offset = 0.25 * (n % 4) as f64; 185 | 186 | // Sine wave volume LFO 187 | let lfo = ( 188 | (time_s * LFO_FREQ + offset) * PI * 2.0 189 | ).sin() as f32; 190 | 191 | let wet = self.dry_wet * WET_MULT; 192 | let mono = (left_old + right_old) / 2.0; 193 | 194 | // Flip right channel and keep left mono so that the result is 195 | // entirely stereo 196 | left_processed += mono * wet * lfo; 197 | right_processed += -mono * wet * lfo; 198 | } 199 | } 200 | 201 | // By only adding to the input, the output value always remains the same in mono 202 | *left_out = *left_in + left_processed; 203 | *right_out = *right_in + right_processed; 204 | } 205 | } 206 | } 207 | 208 | plugin_main!(DimensionExpander); 209 | -------------------------------------------------------------------------------- /dd-dsp/src/envelope.rs: -------------------------------------------------------------------------------- 1 | use VoiceState; 2 | use Voice; 3 | use types::*; 4 | 5 | pub trait Envelope : Sized { 6 | fn expired(&self, playhead: Playhead, release_time: Playhead) -> bool; 7 | fn ratio(&self, playhead: Playhead, voice: &Voice, sample_rate: f64) -> f64; 8 | } 9 | 10 | /// Simple linear envelope - attack + release only (mainly used to prevent pops) 11 | pub struct SimpleEnvelope { 12 | pub attack: f64, // attack in ms 13 | pub release: f64, // release in ms 14 | } 15 | 16 | impl Envelope for SimpleEnvelope { 17 | 18 | fn expired(&self, playhead: Playhead, release_time: Playhead) -> bool { 19 | (playhead - release_time) < ((self.release * 44_100_f64) as u64) 20 | } 21 | 22 | /// returns a multiply ratio at a given time 23 | fn ratio(&self, playhead: Playhead, voice: &Voice, sample_rate: f64) -> f64 { 24 | let samples_since_triggered = playhead - voice.started_at; 25 | let time_since_triggered = samples_since_triggered as f64 / sample_rate; 26 | let mut attack_ratio = time_since_triggered / self.attack; 27 | 28 | match voice.state { 29 | VoiceState::Released(release) => { 30 | // guard in case envelope is finished 31 | // note: convert to std::mem::discriminant when it's stable. 32 | let samples_since_triggered = playhead - release; 33 | let time_since_triggered = samples_since_triggered as f64 / sample_rate; 34 | if time_since_triggered > self.release { return 0.0 } 35 | }, 36 | VoiceState::Retriggered(initial_attack_ratio) => { 37 | attack_ratio = initial_attack_ratio + (attack_ratio / initial_attack_ratio); 38 | }, 39 | _ => () 40 | } 41 | 42 | let release_ratio = match voice.state { 43 | VoiceState::Released(release_started_at) => { 44 | let samples_since_triggered = playhead - release_started_at; 45 | let time_since_triggered = samples_since_triggered as f64 / sample_rate; 46 | 1.0 - time_since_triggered / self.release 47 | }, 48 | _ => 1.0, 49 | }; 50 | 51 | attack_ratio.min(1.0) * release_ratio.max(0.0) 52 | } 53 | } 54 | 55 | 56 | 57 | 58 | 59 | // Recursive ADSR Envelope. 60 | 61 | //use std::ops::Neg; 62 | 63 | // Size of VST params. All incoming params are 0.0 - 1.0. 64 | //type VSTParam = f32; 65 | 66 | //pub trait Envelope { 67 | // fn new() -> Self; 68 | // fn process(&mut self) -> f32; 69 | //} 70 | 71 | // 72 | //use time; 73 | //use std; 74 | // 75 | //#[derive(Debug)] 76 | //pub struct ADSR { 77 | // pub attack_time: f64, 78 | // pub release_time: f64, 79 | //} 80 | // 81 | //impl ADSR { 82 | // pub fn gain_ratio(&self, started_at: std::time::Instant) -> f64 { 83 | // 1.0 84 | // } 85 | // 86 | // pub fn release_gain_ratio(&self, started_at: std::time::Instant, released_at: std::time::Instant) -> f64 { 87 | // let time_since_release_in_ms = time::Duration::from_std( std::time::Instant::now() - released_at).unwrap().num_milliseconds() as f64; 88 | // 89 | // if time_since_release_in_ms < self.attack_time { 90 | //// info!("{:?}", (time_since_release_in_ms, self.attack_time, (self.attack_time - time_since_release_in_ms) / self.attack_time)); 91 | // (self.attack_time - time_since_release_in_ms) / self.attack_time 92 | // } else { 93 | // 0.0 94 | // } 95 | // } 96 | //} 97 | // 98 | //#[test] 99 | //fn it_works() { 100 | // assert!(true); 101 | //} 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | // 115 | //#[derive(Clone)] 116 | //pub struct Envelope { 117 | // // attack_target_ratio: f32, // attack curve 118 | // attack_coef: f32, 119 | // attack_base: f32, 120 | // 121 | // release_coef: f32, 122 | // release_base: f32, 123 | // 124 | // pub state: State, 125 | // pub output: f32, 126 | //} 127 | // 128 | //#[derive(Clone, PartialEq)] 129 | //pub enum State { 130 | // Idle, 131 | // Attack, 132 | // Sustain, 133 | // Release, 134 | //} 135 | // 136 | //pub fn set_attack_rate(attack_rate: f32, attack_ratio: f32) -> (f32, f32) { 137 | // let attack_coef = calculate_coefficient(attack_rate, attack_ratio); 138 | // let attack_base = (1.0 + attack_ratio) * (1.0 - attack_coef); 139 | // (attack_coef, attack_base) 140 | //} 141 | // 142 | //pub fn set_target_ratio_a(attack_rate: f32, target_ratio: f32) -> (f32, f32) { 143 | // let attack_coef = calculate_coefficient(attack_rate, target_ratio.max(0.000000001)); 144 | // let attack_base = (1.0 + target_ratio) * (1.0 - attack_coef); 145 | // (attack_coef, attack_base) 146 | //} 147 | // 148 | //pub fn set_release_rate(release_rate: f32, release_ratio: f32) -> (f32, f32) { 149 | // let release_coef = calculate_coefficient(release_rate, release_ratio); 150 | // let release_base = release_ratio.neg() * (1.0 - release_coef); 151 | // (release_coef, release_base) 152 | //} 153 | // 154 | //fn calculate_coefficient(rate : f32, target_ratio: f32) -> f32 { 155 | // if rate <= 0.0 { 156 | // 0.0 157 | // } else { 158 | // (((1.0 + target_ratio) / target_ratio).ln().neg() / rate).exp() 159 | // } 160 | //} 161 | // 162 | //impl Envelope { 163 | // 164 | // /// Construct a new envelope. 165 | // pub fn new(sample_rate: f32, attack_rate: VSTParam, attack_ratio: VSTParam, release_rate: VSTParam, release_ratio: VSTParam) -> Envelope { 166 | // let (attack_coef, attack_base) = set_attack_rate(sample_rate * attack_rate, attack_ratio * 100.0); 167 | // let (release_coef, release_base) = set_release_rate(sample_rate * release_rate, release_ratio * 100.0); 168 | // 169 | // Envelope { 170 | // attack_coef: attack_coef, 171 | // attack_base: attack_base, 172 | // release_coef: release_coef, 173 | // release_base: release_base, 174 | // state: State::Attack, 175 | // output: 0.0, 176 | // } 177 | // } 178 | // 179 | // pub fn set_attack(&mut self, sample_rate: f32, attack_time: f32) { 180 | // let (attack_coef, attack_base) = set_attack_rate(sample_rate, attack_time); 181 | // 182 | // self.attack_coef = attack_coef; 183 | // self.attack_base = attack_base; 184 | // } 185 | // 186 | // /// Return a gain ratio to multiply your input sample against. 187 | // pub fn process(&mut self) -> f32 { 188 | // match self.state { 189 | // State::Attack => self.process_attack(), 190 | // State::Release => self.process_release(), 191 | // _ => (), 192 | // }; 193 | // 194 | // self.output 195 | // } 196 | // 197 | // /// Set envelope to release mode. 198 | // pub fn release(&mut self) { 199 | // self.state = State::Release; 200 | // } 201 | // 202 | // /// Retrigger the envelope (keeping the current output). 203 | // pub fn retrigger(&mut self) { 204 | // self.state = State::Attack; 205 | // } 206 | // 207 | // fn process_attack(&mut self) { 208 | // self.output = self.attack_base + self.output * self.attack_coef; 209 | // 210 | // if self.output >= 1.0 { 211 | // self.state = State::Sustain; 212 | // self.output = 1.0 213 | // } 214 | // } 215 | // 216 | // fn process_release(&mut self) { 217 | // self.output = self.release_base + self.output * self.release_coef; 218 | // if self.output <= 0.0 { 219 | // self.output = 0.0; 220 | // self.state = State::Idle; 221 | // } 222 | // } 223 | //} 224 | -------------------------------------------------------------------------------- /dd-subsynth/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate vst; 3 | 4 | #[macro_use] 5 | extern crate log; 6 | extern crate simplelog; 7 | 8 | use simplelog::*; 9 | use std::fs::File; 10 | 11 | use vst::buffer::AudioBuffer; 12 | use vst::plugin::{Category, Plugin, Info}; 13 | use vst::event::{Event}; 14 | use vst::api::Events; 15 | 16 | use std::collections::HashMap; 17 | 18 | extern crate dd_dsp; 19 | use dd_dsp::{ Instrument, oscillator, midi, Envelope, SimpleEnvelope }; 20 | use dd_dsp::types::*; 21 | 22 | /// Size of VST params. 23 | type VSTParam = f32; 24 | 25 | // struct Preset { 26 | // name: String, 27 | // attack: VSTParam, 28 | // release: VSTParam, 29 | // } 30 | 31 | struct SimpleSynth { 32 | playhead: Playhead, 33 | sample_rate: f64, 34 | instrument: Instrument, 35 | current_preset: i32, 36 | } 37 | 38 | impl Default for SimpleSynth { 39 | fn default() -> Self { 40 | let _ = CombinedLogger::init( 41 | vec![ 42 | // TermLogger::new( LevelFilter::Warn, Config::default()).unwrap(), 43 | WriteLogger::new(LogLevelFilter::Info, Config::default(), File::create("/tmp/simplesynth.log").unwrap()), 44 | ] 45 | ); 46 | Self { 47 | playhead: 0, 48 | sample_rate: 0.0, 49 | current_preset: 0, 50 | // attack_ratio: 0.75, 51 | // release_ratio: 0.0001, 52 | instrument: Instrument { 53 | voices: Vec::new(), 54 | envelope: SimpleEnvelope{ attack: 0.2, release: 0.4 } 55 | }, 56 | } 57 | } 58 | } 59 | 60 | impl SimpleSynth { 61 | 62 | fn process_sample(&mut self, playhead: Playhead) -> Sample { 63 | let mut output_sample = 0.0; 64 | 65 | self.instrument.cleanup(playhead); 66 | 67 | for &(note, ref voice) in self.instrument.voices.iter() { 68 | 69 | let envelope = self.instrument.envelope.ratio( 70 | playhead, 71 | voice, 72 | self.sample_rate 73 | ); 74 | 75 | let signal = oscillator::sine( 76 | self.sample_rate, 77 | midi::midi_note_to_hz(note), 78 | playhead 79 | ); 80 | 81 | // self.instrument.voices.remove(note); 82 | 83 | output_sample += signal * envelope; 84 | } 85 | 86 | (output_sample / 4.0) as Sample 87 | } 88 | 89 | fn process_midi_event(&mut self, data: [u8; 3]) { 90 | match data[0] { 91 | 128 => self.note_off(data[1]), 92 | 144 => self.note_on(data[1]), 93 | _ => info!("unsupported midi opcode: {}", data[0]) 94 | } 95 | } 96 | 97 | fn note_on(&mut self, note: u8) { self.instrument.note_on(note, self.playhead); } 98 | fn note_off(&mut self, note: u8) { self.instrument.note_off(note, self.playhead); } 99 | } 100 | 101 | impl Plugin for SimpleSynth { 102 | 103 | // fn get_preset_data(&mut self) -> Vec { info!("get_preset_data called"); Vec::new() } 104 | // fn get_bank_data(&mut self) -> Vec { info!("get_bank_data called"); Vec::new() } 105 | // fn load_preset_data(&mut self, data: &[u8]) { info!("load_preset_data called"); } 106 | // fn load_bank_data(&mut self, data: &[u8]) { info!("load_bank_data called"); } 107 | 108 | fn get_info(&self) -> Info { 109 | Info { 110 | name: "DD-SimpleSynth".to_string(), 111 | vendor: "DeathDisco".to_string(), 112 | unique_id: 6667, 113 | category: Category::Synth, 114 | inputs: 0, 115 | outputs: 1, 116 | parameters: 2, 117 | presets: 2, 118 | initial_delay: 0, 119 | ..Info::default() 120 | } 121 | } 122 | 123 | fn process_events(&mut self, events: &Events) { 124 | for event in events.events() { 125 | match event { 126 | Event::Midi(ev) => self.process_midi_event(ev.data), 127 | _ => (), 128 | } 129 | } 130 | } 131 | 132 | fn process(&mut self, buffer: &mut AudioBuffer) { 133 | let (_, output_buffer) = buffer.split(); 134 | let mut buffer_size:u64 = 0; 135 | 136 | for output_channel in output_buffer { 137 | buffer_size = output_channel.len() as u64; 138 | 139 | for time in 0..buffer_size { 140 | let current_playhead = self.playhead + time; 141 | output_channel[time as usize] = self.process_sample(current_playhead ); 142 | } 143 | } 144 | self.playhead += buffer_size; 145 | } 146 | 147 | fn set_sample_rate(&mut self, rate: f32) { 148 | info!("sample rate is assigned to {}", rate); 149 | self.sample_rate = rate as f64; 150 | } 151 | 152 | fn get_parameter(&self, index: i32) -> VSTParam { 153 | match index { 154 | 0 => self.instrument.envelope.attack as VSTParam, 155 | 1 => self.instrument.envelope.release as VSTParam, 156 | _ => 0.0, 157 | } 158 | } 159 | 160 | fn set_parameter(&mut self, index: i32, value: VSTParam) { 161 | match index { 162 | 0 => self.instrument.envelope.attack = (value.max(0.01)) as f64, // avoid pops by always having at least a tiny attack. 163 | 1 => self.instrument.envelope.release = (value.max(0.01)) as f64, // same with release. 164 | // 2 => self.attack_ratio = value.max(0.00001), // same with release. 165 | // 3 => self.release_ratio = value.max(0.00001), // same with release. 166 | _ => (), 167 | }; 168 | } 169 | 170 | fn get_parameter_name(&self, index: i32) -> String { 171 | match index { 172 | 0 => "Attack".to_string(), 173 | 1 => "Release".to_string(), 174 | // 2 => "Attack Curve".to_string(), 175 | // 3 => "Release Curve".to_string(), 176 | _ => "".to_string(), 177 | } 178 | } 179 | 180 | fn get_parameter_text(&self, index: i32) -> String { 181 | match index { 182 | 0 => format!("{:.0}ms", self.instrument.envelope.attack * 100.), 183 | 1 => format!("{:.0}ms", self.instrument.envelope.release * 100.), 184 | // 2 => format!("{}", (self.attack_ratio * 1000.0)), 185 | // 3 => format!("{}", (self.release_ratio * 1000.0)), 186 | _ => "".to_string(), 187 | } 188 | } 189 | 190 | fn get_parameter_label(&self, index: i32) -> String { 191 | match index { 192 | 0 => "ms".to_string(), 193 | 1 => "ms".to_string(), 194 | 2 => "%".to_string(), 195 | 3 => "%".to_string(), 196 | _ => "".to_string(), 197 | } 198 | } 199 | 200 | fn get_preset_name(&self, index: i32) -> String { 201 | match index { 202 | 0 => "Sub Bass".to_string(), 203 | 1 => "Pad".to_string(), 204 | _ => "".to_string(), 205 | } 206 | } 207 | 208 | fn set_preset_name(&mut self, name: String) { info!("set_preset_name called: {:?}", name); } 209 | 210 | fn get_preset_num(&self) -> i32 { self.current_preset } 211 | 212 | fn change_preset(&mut self, preset: i32) { 213 | info!("change_preset: {:?}", preset); 214 | self.current_preset = preset; 215 | match preset { 216 | 0 => { self.instrument.envelope.attack = 0.01; self.instrument.envelope.release = 0.05 }, 217 | 1 => { self.instrument.envelope.attack = 0.4; self.instrument.envelope.release = 0.6 }, 218 | _ => { self.instrument.envelope.attack = 0.0; self.instrument.envelope.release = 0.05 }, 219 | }; 220 | } 221 | } 222 | 223 | plugin_main!(SimpleSynth); 224 | -------------------------------------------------------------------------------- /dd-sampler/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate vst2; 3 | #[macro_use] 4 | extern crate log; 5 | extern crate simplelog; 6 | 7 | extern crate nfd; 8 | 9 | use simplelog::*; 10 | use std::fs::File; 11 | 12 | use vst2::buffer::AudioBuffer; 13 | use vst2::plugin::{ Category, Plugin, Info }; 14 | use vst2::event::{ Event }; 15 | use vst2::api::{ Events }; 16 | use vst2::editor::Editor; 17 | 18 | extern crate dd_dsp; 19 | use dd_dsp::sampler::SampleFile; 20 | use dd_dsp::{ Instrument, midi } ; 21 | use dd_dsp::types::*; 22 | 23 | extern crate log_panics; 24 | 25 | /// Size of VST params. 26 | type Param = f32; 27 | 28 | /// Used for timings of samples (eg position into voice) 29 | type SampleTiming = u64; 30 | 31 | struct SimpleSampler { 32 | playhead: Playhead, 33 | instrument: Instrument, 34 | sample: SampleFile, 35 | sample_rate: f64, 36 | attack_time: Param, 37 | release_time: Param, 38 | attack_ratio: Param, 39 | release_ratio: Param, 40 | preset: Vec, 41 | } 42 | 43 | #[derive(Clone)] 44 | struct Voice { 45 | samples_elapsed: u64, 46 | pitch_in_hz: f64, 47 | 48 | /// Time when note_off was fired. 49 | released_at: Option, 50 | } 51 | 52 | impl Default for SimpleSampler { 53 | fn default() -> Self { 54 | use log_panics; 55 | log_panics::init(); 56 | let _ = CombinedLogger::init( 57 | vec![ 58 | // TermLogger::new( LevelFilter::Warn, Config::default()).unwrap(), 59 | WriteLogger::new(LogLevelFilter::Error, Config::default(), File::create("/tmp/simplesynth.log").unwrap()), 60 | ] 61 | ); 62 | error!("Loaded dd-sampler."); 63 | 64 | // use nfd::Response; 65 | // let result = nfd::open_file_dialog(None, None).unwrap_or_else(|e| { 66 | // panic!(e); 67 | // }); 68 | 69 | SimpleSampler { 70 | playhead: 0, 71 | instrument: Instrument::new(), 72 | sample_rate: 0.0, 73 | attack_time: 0.02, 74 | release_time: 0.02, 75 | attack_ratio: 0.02, 76 | release_ratio: 0.0001, 77 | preset: vec![44_u8, 55_u8, 66_u8], 78 | sample: SampleFile::from_static_file( 79 | include_bytes!("../../dd-sampler/assets/snare.wav")).unwrap(), 80 | } 81 | } 82 | } 83 | 84 | impl SimpleSampler { 85 | fn process_sample(&self, playhead: Playhead) -> Sample { 86 | let mut output_sample: f64 = 0.0; 87 | let amplitude = std::u16::MAX as f64; 88 | 89 | for (note, voice) in self.instrument.voices.iter() { 90 | output_sample += self.sample.sample_at( 91 | (playhead - voice.started_at), 92 | midi::midi_note_to_hz(*note), 93 | ) as f64 / amplitude; 94 | } 95 | 96 | output_sample as Sample 97 | } 98 | 99 | fn process_midi_event(&mut self, data: [u8; 3]) { 100 | match data[0] { 101 | 128 => self.note_off(data[1]), 102 | 144 => self.note_on(data[1]), 103 | _ => info!("unsupported midi opcode: {}", data[0]) 104 | } 105 | } 106 | 107 | fn note_on(&mut self, note: u8) { 108 | if note == 0 { 109 | error!("changing file..."); 110 | self.sample = SampleFile::from_static_file( 111 | include_bytes!("../../dd-sampler/assets/bass.wav")).unwrap(); 112 | 113 | // use nfd::Response; 114 | // nfd::open_file_dialog(None, None); 115 | 116 | // let result = nfd::open_file_dialog(None, None); 117 | // .unwrap_or_else(|e| { 118 | // error!("{:?}", e); 119 | // }); 120 | // match result { 121 | // Response::Okay(file_path) => { 122 | // error!("File path = {:?}", file_path); 123 | // self.sample = SampleFile::from_path(file_path).unwrap(); 124 | // error!("loaded."); 125 | // }, 126 | // Response::OkayMultiple(files) => error!("Files {:?}", files), 127 | // Response::Cancel => error!("User canceled"), 128 | // } 129 | }; 130 | self.instrument.note_on(note, self.playhead); 131 | } 132 | 133 | fn note_off(&mut self, note: u8) { self.instrument.note_off(note, self.playhead); } 134 | } 135 | 136 | impl Plugin for SimpleSampler { 137 | 138 | fn get_info(&self) -> Info { 139 | Info { 140 | name: "DD-SimpleSynth".to_string(), 141 | vendor: "DeathDisco".to_string(), 142 | unique_id: 6600, 143 | category: Category::Synth, 144 | inputs: 0, 145 | outputs: 1, 146 | parameters: 4, 147 | initial_delay: 0, 148 | preset_chunks: true, 149 | f64_precision: true, 150 | silent_when_stopped: false, 151 | presets: 1, 152 | version: 0001, 153 | } 154 | 155 | } 156 | 157 | fn process_events(&mut self, events: &Events) { 158 | for &e in events.events_raw() { 159 | let event: Event = Event::from(unsafe { *e }); 160 | match event { 161 | Event::Midi(ev) => self.process_midi_event(ev.data), 162 | _ => () 163 | } 164 | } 165 | } 166 | 167 | fn process(&mut self, buffer: &mut AudioBuffer) { 168 | let (_, output_buffer) = buffer.split(); 169 | let mut buffer_size:u64 = 0; 170 | 171 | for output_channel in output_buffer { 172 | buffer_size = output_channel.len() as u64; 173 | 174 | for time in 0..buffer_size { 175 | output_channel[time as usize] = self.process_sample(self.playhead + time); 176 | } 177 | } 178 | self.playhead += buffer_size; 179 | } 180 | 181 | fn set_sample_rate(&mut self, rate: f32) { 182 | info!("sample rate is assigned to {}", rate); 183 | self.sample_rate = rate as f64; 184 | } 185 | 186 | fn get_parameter(&self, index: i32) -> f32 { 187 | match index { 188 | 0 => self.attack_time, 189 | 1 => self.release_time, 190 | 2 => self.attack_ratio, 191 | 3 => self.release_ratio, 192 | _ => 0.0, 193 | } 194 | } 195 | 196 | fn get_preset_data(&mut self) -> Vec { 197 | error!("[SAVE TO PRESET] get_preset_data"); 198 | self.preset.clone() 199 | } 200 | fn get_bank_data(&mut self) -> Vec { 201 | error!("[SAVE TO PRESET] get_bank_data"); 202 | self.preset.clone() 203 | } 204 | fn load_preset_data(&mut self, data: &[u8]) { error!("[LOAD FROM PRESET] load_preset_data called: {:?}", (data, data[0], data.len())); } 205 | fn load_bank_data(&mut self, data: &[u8]) { error!("[LOAD FROM PRESET] load_bank_data called: {:?}", (data, data[0], data.len())); } 206 | 207 | fn set_parameter(&mut self, index: i32, value: f32) { 208 | error!("set_parameter called."); 209 | match index { 210 | 0 => self.attack_time = value.max(0.001), // avoid pops by always having at least a tiny attack. 211 | 1 => self.release_time = value.max(0.001), // same with release. 212 | 2 => self.attack_ratio = value.max(0.00001), // same with release. 213 | 3 => self.release_ratio = value.max(0.00001), // same with release. 214 | _ => (), 215 | }; 216 | } 217 | 218 | fn get_parameter_name(&self, index: i32) -> String { 219 | match index { 220 | 0 => "Attack".to_string(), 221 | 1 => "Release".to_string(), 222 | 2 => "Attack Curve".to_string(), 223 | 3 => "Release Curve".to_string(), 224 | _ => "".to_string(), 225 | } 226 | } 227 | 228 | fn get_parameter_text(&self, index: i32) -> String { 229 | match index { 230 | 0 => format!("{}ms", (self.attack_time * 100.0)), 231 | 1 => format!("{}ms", (self.release_time * 100.0)), 232 | 2 => format!("{}", (self.attack_ratio * 100.0)), 233 | 3 => format!("{}", (self.release_ratio * 100.0)), 234 | _ => "".to_string(), 235 | } 236 | } 237 | 238 | fn get_parameter_label(&self, index: i32) -> String { 239 | match index { 240 | 0 => "ms".to_string(), 241 | 1 => "ms".to_string(), 242 | 2 => "%".to_string(), 243 | 3 => "%".to_string(), 244 | _ => "".to_string(), 245 | } 246 | } 247 | fn get_editor(&mut self) -> Option<&mut Editor> { None } 248 | } 249 | 250 | plugin_main!(SimpleSampler); 251 | --------------------------------------------------------------------------------