lib.rs (9333B)
1 use nih_plug::prelude::*; 2 use std::sync::Arc; 3 4 #[derive(PartialEq, Clone, Copy)] 5 pub enum Waveform { 6 Sine = 0, 7 Triangle = 1, 8 TriangleSawtooth = 2, 9 Sawtooth = 3, 10 ReverseSawtooth = 4, 11 Square = 5, 12 WidePulse = 6, 13 NarrowPulse = 7, 14 } 15 16 impl Waveform { 17 fn from_int(value: i32) -> Self { 18 match value { 19 0 => Waveform::Sine, 20 1 => Waveform::Triangle, 21 2 => Waveform::TriangleSawtooth, 22 3 => Waveform::Sawtooth, 23 4 => Waveform::ReverseSawtooth, 24 5 => Waveform::Square, 25 6 => Waveform::WidePulse, 26 7 => Waveform::NarrowPulse, 27 _ => Waveform::Sine, 28 } 29 } 30 } 31 32 struct GeedbackClap { 33 params: Arc<GeedbackClapParams>, 34 35 // Oscillator States 36 phases: [f64; 3], 37 phases_mod: [f64; 3], 38 mixes: [f64; 3], 39 40 sample_rate: f32, 41 } 42 43 #[derive(Params)] 44 struct GeedbackClapParams { 45 #[id = "accel_x"] 46 pub accel_x: FloatParam, 47 #[id = "accel_y"] 48 pub accel_y: FloatParam, 49 #[id = "accel_z"] 50 pub accel_z: FloatParam, 51 52 #[id = "quat_x"] 53 pub quat_x: FloatParam, 54 #[id = "quat_y"] 55 pub quat_y: FloatParam, 56 #[id = "quat_z"] 57 pub quat_z: FloatParam, 58 59 #[id = "osc1_wave"] 60 pub osc1_wave: IntParam, 61 #[id = "osc2_wave"] 62 pub osc2_wave: IntParam, 63 #[id = "osc3_wave"] 64 pub osc3_wave: IntParam, 65 } 66 67 impl Default for GeedbackClap { 68 fn default() -> Self { 69 Self { 70 params: Arc::new(GeedbackClapParams::default()), 71 phases: [0.0; 3], 72 phases_mod: [0.0; 3], 73 mixes: [0.3, 0.3, 0.3], 74 sample_rate: 44100.0, 75 } 76 } 77 } 78 79 impl Default for GeedbackClapParams { 80 fn default() -> Self { 81 Self { 82 accel_x: FloatParam::new("Accel X", 0.5, FloatRange::Linear { min: 0.0, max: 1.0 }) 83 .with_smoother(SmoothingStyle::Linear(50.0)), 84 accel_y: FloatParam::new("Accel Y", 0.5, FloatRange::Linear { min: 0.0, max: 1.0 }) 85 .with_smoother(SmoothingStyle::Linear(50.0)), 86 accel_z: FloatParam::new("Accel Z", 0.5, FloatRange::Linear { min: 0.0, max: 1.0 }) 87 .with_smoother(SmoothingStyle::Linear(50.0)), 88 quat_x: FloatParam::new( 89 "Quat X", 90 0.0, 91 FloatRange::Linear { 92 min: -1.0, 93 max: 1.0, 94 }, 95 ) 96 .with_smoother(SmoothingStyle::Linear(50.0)), 97 quat_y: FloatParam::new( 98 "Quat Y", 99 0.0, 100 FloatRange::Linear { 101 min: -1.0, 102 max: 1.0, 103 }, 104 ) 105 .with_smoother(SmoothingStyle::Linear(50.0)), 106 quat_z: FloatParam::new( 107 "Quat Z", 108 0.0, 109 FloatRange::Linear { 110 min: -1.0, 111 max: 1.0, 112 }, 113 ) 114 .with_smoother(SmoothingStyle::Linear(50.0)), 115 116 osc1_wave: IntParam::new("Osc 1 Wave", 0, IntRange::Linear { min: 0, max: 7 }) 117 .with_value_to_string(format_waveform_name()), 118 osc2_wave: IntParam::new("Osc 2 Wave", 0, IntRange::Linear { min: 0, max: 7 }) 119 .with_value_to_string(format_waveform_name()), 120 osc3_wave: IntParam::new("Osc 3 Wave", 0, IntRange::Linear { min: 0, max: 7 }) 121 .with_value_to_string(format_waveform_name()), 122 } 123 } 124 } 125 126 fn format_waveform_name() -> Arc<dyn Fn(i32) -> String + Send + Sync> { 127 Arc::new(|value| match value { 128 0 => "Sine".to_string(), 129 1 => "Triangle".to_string(), 130 2 => "Tri-Saw".to_string(), 131 3 => "Sawtooth".to_string(), 132 4 => "Rev-Saw".to_string(), 133 5 => "Square".to_string(), 134 6 => "Wide Pulse".to_string(), 135 7 => "Narrow Pulse".to_string(), 136 _ => "Unknown".to_string(), 137 }) 138 } 139 140 impl GeedbackClap { 141 fn calculate_wave(&self, phase: f64, wave: Waveform, morph: f64) -> f64 { 142 let m = (morph + 1.0) * 0.5; 143 let p = phase.fract(); 144 145 match wave { 146 Waveform::Sine => { 147 let raw = (p * 2.0 * std::f64::consts::PI).sin(); 148 if m > 0.5 { 149 let drive = 1.0 + (m - 0.5) * 2.0; 150 (raw * drive).tanh() 151 } else { 152 raw 153 } 154 } 155 Waveform::Triangle => 1.0 - 2.0 * (p + 0.75).fract().abs(), 156 Waveform::TriangleSawtooth => { 157 let tri = 1.0 - 2.0 * (p + 0.75).fract().abs(); 158 let saw = 2.0 * p - 1.0; 159 tri * (1.0 - m) + saw * m 160 } 161 Waveform::Sawtooth => 2.0 * p - 1.0, 162 Waveform::ReverseSawtooth => 1.0 - p * 2.0, 163 Waveform::Square | Waveform::WidePulse | Waveform::NarrowPulse => { 164 let width = 0.02 + m * 0.48; 165 if p < width { 1.0 } else { -1.0 } 166 } 167 } 168 } 169 } 170 171 impl Plugin for GeedbackClap { 172 const NAME: &'static str = "Geedback Clap"; 173 const VENDOR: &'static str = "Minerva_Juppiter"; 174 const URL: &'static str = env!("CARGO_PKG_HOMEPAGE"); 175 const EMAIL: &'static str = "geedback-clap@minervajuppiter.net"; 176 177 const VERSION: &'static str = env!("CARGO_PKG_VERSION"); 178 179 const AUDIO_IO_LAYOUTS: &'static [AudioIOLayout] = &[AudioIOLayout { 180 main_input_channels: NonZeroU32::new(2), 181 main_output_channels: NonZeroU32::new(2), 182 aux_input_ports: &[], 183 aux_output_ports: &[], 184 names: PortNames::const_default(), 185 }]; 186 187 const MIDI_INPUT: MidiConfig = MidiConfig::None; 188 const MIDI_OUTPUT: MidiConfig = MidiConfig::None; 189 190 const SAMPLE_ACCURATE_AUTOMATION: bool = true; 191 192 type SysExMessage = (); 193 type BackgroundTask = (); 194 195 fn params(&self) -> Arc<dyn Params> { 196 self.params.clone() 197 } 198 199 fn initialize( 200 &mut self, 201 _audio_io_layout: &AudioIOLayout, 202 buffer_config: &BufferConfig, 203 _context: &mut impl InitContext<Self>, 204 ) -> bool { 205 self.sample_rate = buffer_config.sample_rate; 206 true 207 } 208 209 fn reset(&mut self) { 210 self.phases = [0.0; 3]; 211 self.phases_mod = [0.0; 3]; 212 } 213 214 fn process( 215 &mut self, 216 buffer: &mut Buffer, 217 _aux: &mut AuxiliaryBuffers, 218 _context: &mut impl ProcessContext<Self>, 219 ) -> ProcessStatus { 220 for channel_samples in buffer.iter_samples() { 221 // Get smoothed parameters 222 let ax = self.params.accel_x.smoothed.next() as f64 * 2.0 - 1.0; 223 let ay = self.params.accel_y.smoothed.next() as f64 * 2.0 - 1.0; 224 let az = self.params.accel_z.smoothed.next() as f64 * 2.0 - 1.0; 225 226 let qx = self.params.quat_x.smoothed.next() as f64; 227 let qy = self.params.quat_y.smoothed.next() as f64; 228 let qz = self.params.quat_z.smoothed.next() as f64; 229 230 // Waveform selection (no smoothing needed for discrete enum) 231 let waves = [ 232 Waveform::from_int(self.params.osc1_wave.value()), 233 Waveform::from_int(self.params.osc2_wave.value()), 234 Waveform::from_int(self.params.osc3_wave.value()), 235 ]; 236 237 // Mapping Quaternions (X, Y, Z) to Oscillator frequencies and morphs 238 let freqs = [ 239 110.0 * 2.0_f64.powf(qy * 2.5), 240 164.8 * 2.0_f64.powf(qz * 4.0), 241 220.0 * 2.0_f64.powf(qx * 2.5), 242 ]; 243 244 let morphs = [qy, qz, qx]; 245 246 let fm_depths = [ 247 ay.abs().powi(3) * 1.0, 248 az.abs().powi(3) * 1.0, 249 ax.abs().powi(3) * 1.0, 250 ]; 251 252 let mut mixed_output = 0.0; 253 let sample_rate = self.sample_rate as f64; 254 255 for i in 0..3 { 256 self.phases_mod[i] = (self.phases_mod[i] + freqs[i] / sample_rate) % 1.0; 257 let modulation = 258 (self.phases_mod[i] * 2.0 * std::f64::consts::PI).sin() * fm_depths[i]; 259 let osc_out = self.calculate_wave(self.phases[i] + modulation, waves[i], morphs[i]); 260 mixed_output += osc_out * self.mixes[i]; 261 self.phases[i] = (self.phases[i] + freqs[i] / sample_rate) % 1.0; 262 } 263 264 // Master Gain / Saturation based on Accel only 265 let shake_intensity = (ax.abs() + ay.abs() + az.abs()) * 4.0; 266 let master_gain = 0.6 + shake_intensity; 267 268 let final_output = (mixed_output * master_gain).tanh() as f32; 269 270 for sample in channel_samples { 271 *sample = final_output; 272 } 273 } 274 275 ProcessStatus::Normal 276 } 277 } 278 279 impl ClapPlugin for GeedbackClap { 280 const CLAP_ID: &'static str = "net.minervajuppiter.geedback-clap"; 281 const CLAP_DESCRIPTION: Option<&'static str> = Some("clap for geedback the smartphone synth"); 282 const CLAP_MANUAL_URL: Option<&'static str> = Some(Self::URL); 283 const CLAP_SUPPORT_URL: Option<&'static str> = None; 284 285 const CLAP_FEATURES: &'static [ClapFeature] = &[ 286 ClapFeature::Instrument, 287 ClapFeature::Synthesizer, 288 ClapFeature::Stereo, 289 ]; 290 } 291 292 nih_export_clap!(GeedbackClap);