geedback_clap

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

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);