Molyxide

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

lib.rs (28270B)


      1 use nih_plug::prelude::*;
      2 use std::sync::Arc;
      3 
      4 // --- Enums ---
      5 
      6 #[derive(Enum, PartialEq, Clone, Copy)]
      7 pub enum WaveformSelection {
      8     #[name = "Sawtooth"]
      9     Saw,
     10     #[name = "Square"]
     11     Square,
     12     #[name = "Saw + Square"]
     13     Both,
     14 }
     15 
     16 #[derive(Enum, PartialEq, Clone, Copy)]
     17 pub enum ResonatorMode {
     18     #[name = "Low Pass"]
     19     LowPass,
     20     #[name = "Band Pass"]
     21     BandPass,
     22     #[name = "High Pass"]
     23     HighPass,
     24 }
     25 
     26 #[derive(Enum, PartialEq, Clone, Copy)]
     27 pub enum FilterAlgorithm {
     28     #[name = "Biquad (Standard)"]
     29     Biquad,
     30     #[name = "ZDF SVF (Analog-style)"]
     31     ZdfSvf,
     32 }
     33 
     34 #[derive(Enum, PartialEq, Clone, Copy)]
     35 pub enum FinalDecayMode {
     36     #[name = "Lock (No Release)"]
     37     Lock,
     38     #[name = "Manual (Release On)"]
     39     Manual,
     40 }
     41 
     42 // --- DSP Modules ---
     43 
     44 /// ZDF SVF (Topology Preserving Transform)
     45 struct ZdfSvf {
     46     s1: f32,
     47     s2: f32,
     48 }
     49 
     50 impl ZdfSvf {
     51     fn new() -> Self {
     52         Self { s1: 0.0, s2: 0.0 }
     53     }
     54     fn process(&mut self, input: f32, mode: ResonatorMode, freq: f32, emph: f32, sr: f32) -> f32 {
     55         let q = (0.5 + emph * 9.5).max(0.01);
     56         let g = (std::f32::consts::PI * freq / sr).tan();
     57         let k = 1.0 / q;
     58         let a1 = 1.0 / (1.0 + g * (g + k));
     59         let a2 = g * a1;
     60         let a3 = g * a2;
     61         let m_v3 = input - self.s2;
     62         let v1 = a1 * self.s1 + a2 * m_v3;
     63         let v2 = self.s2 + a2 * self.s1 + a3 * m_v3;
     64         self.s1 = 2.0 * v1 - self.s1;
     65         self.s2 = 2.0 * v2 - self.s2;
     66         match mode {
     67             ResonatorMode::LowPass => v2,
     68             ResonatorMode::BandPass => v1,
     69             ResonatorMode::HighPass => input - k * v1 - v2,
     70         }
     71     }
     72 }
     73 
     74 /// ZDF Ladder Filter (24dB/oct)
     75 /// Moog スタイルの 4-pole ローパスフィルター
     76 struct LadderFilter {
     77     s: [f32; 4],
     78 }
     79 
     80 impl LadderFilter {
     81     fn new() -> Self {
     82         Self { s: [0.0; 4] }
     83     }
     84     fn process(&mut self, input: f32, cutoff: f32, resonance: f32, sr: f32) -> f32 {
     85         let g = (std::f32::consts::PI * cutoff / sr).tan();
     86         let k = 4.0 * resonance.clamp(0.0, 0.99); // 自励振を避けるため微調整
     87 
     88         // 1-pole 係数
     89         let a = 1.0 / (1.0 + g);
     90 
     91         // フィードバック補正
     92         let g_pow4 = g * g * g * g;
     93         let sigma = g_pow4 * a * a * a * a;
     94         let gamma = 1.0 / (1.0 + k * sigma);
     95 
     96         // 内部状態からのフィードバック
     97         let input_fb = (input
     98             - k * (self.s[0] * a
     99                 + self.s[1] * a * a
    100                 + self.s[2] * a * a * a
    101                 + self.s[3] * a * a * a * a))
    102             * gamma;
    103 
    104         // 4段の 1-pole フィルターの直列処理
    105         let mut vin = input_fb;
    106         for i in 0..4 {
    107             let v = (vin - self.s[i]) * g * a;
    108             let out = v + self.s[i];
    109             self.s[i] = out + v;
    110             vin = out;
    111         }
    112 
    113         vin // 4段目の出力
    114     }
    115 }
    116 
    117 // --- Parameters ---
    118 
    119 #[derive(Params)]
    120 pub struct MixerParams {
    121     #[id = "m_tune"]
    122     pub master_tune: FloatParam,
    123     #[id = "mix_dir"]
    124     pub direct: FloatParam,
    125     #[id = "mix_mode"]
    126     pub mode: FloatParam,
    127     #[id = "mix_res"]
    128     pub resonator: FloatParam,
    129     #[id = "mix_vcf"]
    130     pub vcf: FloatParam,
    131 }
    132 
    133 #[derive(Params)]
    134 pub struct OscParams {
    135     #[id = "l_wave"]
    136     pub lower_waveshape: EnumParam<WaveformSelection>,
    137     #[id = "u_wave"]
    138     pub upper_waveshape: EnumParam<WaveformSelection>,
    139     #[id = "l_pw"]
    140     pub lower_pw: FloatParam,
    141     #[id = "u_pw"]
    142     pub upper_pw: FloatParam,
    143     #[id = "l_mix"]
    144     pub lower_mix: FloatParam,
    145     #[id = "u_mix"]
    146     pub upper_mix: FloatParam,
    147     #[id = "rank_a_tune"]
    148     pub rank_a_tune: FloatParam,
    149     #[id = "rank_b_beat"]
    150     pub rank_b_beat: FloatParam,
    151     #[id = "pwm_rate"]
    152     pub pwm_rate: FloatParam,
    153     #[id = "pwm_amt"]
    154     pub pwm_amt: FloatParam,
    155     #[id = "fm_rate"]
    156     pub fm_rate: FloatParam,
    157     #[id = "fm_amt"]
    158     pub fm_amt: FloatParam,
    159 }
    160 
    161 #[derive(Params)]
    162 pub struct ResonatorParams {
    163     #[id = "res_on"]
    164     pub enabled: BoolParam,
    165     #[id = "res_mode"]
    166     pub mode: EnumParam<ResonatorMode>,
    167     #[id = "res_l_cf"]
    168     pub low_cf: FloatParam,
    169     #[id = "res_l_emph"]
    170     pub low_emph: FloatParam,
    171     #[id = "res_l_gain"]
    172     pub low_gain: FloatParam,
    173     #[id = "res_m_cf"]
    174     pub mid_cf: FloatParam,
    175     #[id = "res_m_emph"]
    176     pub mid_emph: FloatParam,
    177     #[id = "res_m_gain"]
    178     pub mid_gain: FloatParam,
    179     #[id = "res_h_cf"]
    180     pub high_cf: FloatParam,
    181     #[id = "res_h_emph"]
    182     pub high_emph: FloatParam,
    183     #[id = "res_h_gain"]
    184     pub high_gain: FloatParam,
    185 }
    186 
    187 #[derive(Params)]
    188 pub struct VcfParams {
    189     #[id = "vcf_on"]
    190     pub enabled: BoolParam,
    191     #[id = "vcf_cutoff"]
    192     pub cutoff: FloatParam,
    193     #[id = "vcf_emph"]
    194     pub emphasis: FloatParam,
    195     #[id = "vcf_kb"]
    196     pub kb_track: FloatParam,
    197     #[id = "vcf_glide"]
    198     pub kb_glide: FloatParam,
    199     #[id = "vcf_lfo_r"]
    200     pub lfo_rate: FloatParam,
    201     #[id = "vcf_lfo_a"]
    202     pub lfo_amt: FloatParam,
    203     #[id = "vcf_lfo_sh"]
    204     pub lfo_sh: FloatParam,
    205     #[id = "vcf_env_a"]
    206     pub attack: FloatParam,
    207     #[id = "vcf_env_d"]
    208     pub decay: FloatParam,
    209     #[id = "vcf_env_s"]
    210     pub sustain: FloatParam,
    211     #[id = "vcf_env_amt"]
    212     pub env_amt: FloatParam,
    213 }
    214 
    215 #[derive(Params)]
    216 pub struct AmpParams {
    217     #[id = "amp_a"]
    218     pub attack: FloatParam,
    219     #[id = "amp_s"]
    220     pub sustain: FloatParam,
    221     #[id = "amp_dr"]
    222     pub decay_release: FloatParam,
    223     #[id = "amp_vel"]
    224     pub velocity: FloatParam,
    225     #[id = "amp_f_dec"]
    226     pub final_decay: EnumParam<FinalDecayMode>,
    227 }
    228 
    229 #[derive(Params)]
    230 struct MolyxideParams {
    231     #[nested(group = "1. Mixer & Tune")]
    232     pub mixer: MixerParams,
    233     #[nested(group = "2. Oscillators")]
    234     pub osc: OscParams,
    235     #[nested(group = "3. Resonator")]
    236     pub res: ResonatorParams,
    237     #[nested(group = "4. VCF (Master Filter)")]
    238     pub vcf: VcfParams,
    239     #[nested(group = "5. Loudness (Per-Voice)")]
    240     pub amp: AmpParams,
    241     #[id = "gain"]
    242     pub gain: FloatParam,
    243 }
    244 
    245 impl Default for MolyxideParams {
    246     fn default() -> Self {
    247         Self {
    248             mixer: MixerParams {
    249                 master_tune: FloatParam::new(
    250                     "Master Tune",
    251                     0.0,
    252                     FloatRange::Linear {
    253                         min: -200.0,
    254                         max: 200.0,
    255                     },
    256                 ),
    257                 direct: FloatParam::new(
    258                     "Direct Level",
    259                     0.5,
    260                     FloatRange::Linear { min: 0.0, max: 1.0 },
    261                 ),
    262                 mode: FloatParam::new("Mode Level", 0.0, FloatRange::Linear { min: 0.0, max: 1.0 }),
    263                 resonator: FloatParam::new(
    264                     "Resonator Level",
    265                     0.5,
    266                     FloatRange::Linear { min: 0.0, max: 1.0 },
    267                 ),
    268                 vcf: FloatParam::new("VCF Level", 0.5, FloatRange::Linear { min: 0.0, max: 1.0 }),
    269             },
    270             osc: OscParams {
    271                 lower_waveshape: EnumParam::new("Lower Waveshape", WaveformSelection::Saw),
    272                 upper_waveshape: EnumParam::new("Upper Waveshape", WaveformSelection::Square),
    273                 lower_pw: FloatParam::new(
    274                     "Lower PW",
    275                     0.5,
    276                     FloatRange::Linear {
    277                         min: 0.05,
    278                         max: 0.5,
    279                     },
    280                 ),
    281                 upper_pw: FloatParam::new(
    282                     "Upper PW",
    283                     0.2,
    284                     FloatRange::Linear {
    285                         min: 0.05,
    286                         max: 0.5,
    287                     },
    288                 ),
    289                 lower_mix: FloatParam::new(
    290                     "Lower Rank Mix",
    291                     0.5,
    292                     FloatRange::Linear { min: 0.0, max: 1.0 },
    293                 ),
    294                 upper_mix: FloatParam::new(
    295                     "Upper Rank Mix",
    296                     0.5,
    297                     FloatRange::Linear { min: 0.0, max: 1.0 },
    298                 ),
    299                 rank_a_tune: FloatParam::new(
    300                     "Rank A Tune",
    301                     0.0,
    302                     FloatRange::Linear {
    303                         min: -600.0,
    304                         max: 600.0,
    305                     },
    306                 ),
    307                 rank_b_beat: FloatParam::new(
    308                     "Rank B Beat",
    309                     0.0,
    310                     FloatRange::Linear {
    311                         min: -50.0,
    312                         max: 50.0,
    313                     },
    314                 ),
    315                 pwm_rate: FloatParam::new(
    316                     "PWM Rate",
    317                     0.5,
    318                     FloatRange::Skewed {
    319                         min: 0.05,
    320                         max: 25.0,
    321                         factor: FloatRange::skew_factor(-0.5),
    322                     },
    323                 ),
    324                 pwm_amt: FloatParam::new(
    325                     "PWM Amount",
    326                     0.0,
    327                     FloatRange::Linear { min: 0.0, max: 1.0 },
    328                 ),
    329                 fm_rate: FloatParam::new(
    330                     "FM Rate",
    331                     6.0,
    332                     FloatRange::Skewed {
    333                         min: 0.05,
    334                         max: 25.0,
    335                         factor: FloatRange::skew_factor(-0.5),
    336                     },
    337                 ),
    338                 fm_amt: FloatParam::new(
    339                     "FM Amount",
    340                     0.0,
    341                     FloatRange::Linear { min: 0.0, max: 1.0 },
    342                 ),
    343             },
    344             res: ResonatorParams {
    345                 enabled: BoolParam::new("Resonator Enabled", true),
    346                 mode: EnumParam::new("Pass Mode", ResonatorMode::LowPass),
    347                 low_cf: FloatParam::new(
    348                     "Low CF",
    349                     200.0,
    350                     FloatRange::Skewed {
    351                         min: 20.0,
    352                         max: 20000.0,
    353                         factor: FloatRange::skew_factor(-0.9),
    354                     },
    355                 ),
    356                 low_emph: FloatParam::new(
    357                     "Low Emphasis",
    358                     0.0,
    359                     FloatRange::Linear { min: 0.0, max: 1.0 },
    360                 ),
    361                 low_gain: FloatParam::new(
    362                     "Low Gain",
    363                     0.5,
    364                     FloatRange::Linear { min: 0.0, max: 1.0 },
    365                 ),
    366                 mid_cf: FloatParam::new(
    367                     "Mid CF",
    368                     1000.0,
    369                     FloatRange::Skewed {
    370                         min: 20.0,
    371                         max: 20000.0,
    372                         factor: FloatRange::skew_factor(-0.9),
    373                     },
    374                 ),
    375                 mid_emph: FloatParam::new(
    376                     "Mid Emphasis",
    377                     0.0,
    378                     FloatRange::Linear { min: 0.0, max: 1.0 },
    379                 ),
    380                 mid_gain: FloatParam::new(
    381                     "Mid Gain",
    382                     0.5,
    383                     FloatRange::Linear { min: 0.0, max: 1.0 },
    384                 ),
    385                 high_cf: FloatParam::new(
    386                     "High CF",
    387                     5000.0,
    388                     FloatRange::Skewed {
    389                         min: 20.0,
    390                         max: 20000.0,
    391                         factor: FloatRange::skew_factor(-0.9),
    392                     },
    393                 ),
    394                 high_emph: FloatParam::new(
    395                     "High Emphasis",
    396                     0.0,
    397                     FloatRange::Linear { min: 0.0, max: 1.0 },
    398                 ),
    399                 high_gain: FloatParam::new(
    400                     "High Gain",
    401                     0.5,
    402                     FloatRange::Linear { min: 0.0, max: 1.0 },
    403                 ),
    404             },
    405             vcf: VcfParams {
    406                 enabled: BoolParam::new("VCF Enabled", true),
    407                 cutoff: FloatParam::new(
    408                     "VCF Cutoff",
    409                     20000.0,
    410                     FloatRange::Skewed {
    411                         min: 20.0,
    412                         max: 20000.0,
    413                         factor: FloatRange::skew_factor(-0.9),
    414                     },
    415                 ),
    416                 emphasis: FloatParam::new(
    417                     "VCF Emphasis",
    418                     0.0,
    419                     FloatRange::Linear { min: 0.0, max: 1.0 },
    420                 ),
    421                 kb_track: FloatParam::new(
    422                     "Keyboard Track",
    423                     1.0,
    424                     FloatRange::Linear { min: 0.0, max: 1.0 },
    425                 ),
    426                 kb_glide: FloatParam::new(
    427                     "Keyboard Glide",
    428                     0.0,
    429                     FloatRange::Linear { min: 0.0, max: 1.0 },
    430                 ),
    431                 lfo_rate: FloatParam::new(
    432                     "VCF LFO Rate",
    433                     6.0,
    434                     FloatRange::Skewed {
    435                         min: 0.05,
    436                         max: 25.0,
    437                         factor: FloatRange::skew_factor(-0.5),
    438                     },
    439                 ),
    440                 lfo_amt: FloatParam::new(
    441                     "VCF LFO Amount",
    442                     0.0,
    443                     FloatRange::Linear { min: 0.0, max: 1.0 },
    444                 ),
    445                 lfo_sh: FloatParam::new(
    446                     "VCF S&H Amount",
    447                     0.0,
    448                     FloatRange::Linear { min: 0.0, max: 1.0 },
    449                 ),
    450                 attack: FloatParam::new(
    451                     "VCF Attack",
    452                     0.01,
    453                     FloatRange::Skewed {
    454                         min: 0.01,
    455                         max: 10.0,
    456                         factor: FloatRange::skew_factor(-0.8),
    457                     },
    458                 ),
    459                 decay: FloatParam::new(
    460                     "VCF Decay",
    461                     0.1,
    462                     FloatRange::Skewed {
    463                         min: 0.01,
    464                         max: 10.0,
    465                         factor: FloatRange::skew_factor(-0.8),
    466                     },
    467                 ),
    468                 sustain: FloatParam::new(
    469                     "VCF Sustain",
    470                     1.0,
    471                     FloatRange::Linear { min: 0.0, max: 1.0 },
    472                 ),
    473                 env_amt: FloatParam::new(
    474                     "VCF Envelope Amount",
    475                     0.0,
    476                     FloatRange::Linear {
    477                         min: -1.0,
    478                         max: 1.0,
    479                     },
    480                 ),
    481             },
    482             amp: AmpParams {
    483                 attack: FloatParam::new(
    484                     "Amp Attack",
    485                     0.01,
    486                     FloatRange::Skewed {
    487                         min: 0.01,
    488                         max: 10.0,
    489                         factor: FloatRange::skew_factor(-0.8),
    490                     },
    491                 ),
    492                 sustain: FloatParam::new(
    493                     "Amp Sustain",
    494                     1.0,
    495                     FloatRange::Linear { min: 0.0, max: 1.0 },
    496                 ),
    497                 decay_release: FloatParam::new(
    498                     "Amp Decay/Release",
    499                     0.1,
    500                     FloatRange::Skewed {
    501                         min: 0.01,
    502                         max: 10.0,
    503                         factor: FloatRange::skew_factor(-0.8),
    504                     },
    505                 ),
    506                 velocity: FloatParam::new(
    507                     "Keyboard Dynamics",
    508                     0.5,
    509                     FloatRange::Linear { min: 0.0, max: 1.0 },
    510                 ),
    511                 final_decay: EnumParam::new("Final Decay", FinalDecayMode::Manual),
    512             },
    513             gain: FloatParam::new(
    514                 "Gain",
    515                 util::db_to_gain(0.0),
    516                 FloatRange::Linear { min: 0.0, max: 1.0 },
    517             ),
    518         }
    519     }
    520 }
    521 
    522 // --- TOG (Top Octave Generator) ---
    523 
    524 struct TopOctaveGenerator {
    525     phases: [f64; 12],
    526     wrap_counts: [u64; 12],
    527 }
    528 
    529 impl TopOctaveGenerator {
    530     fn new() -> Self {
    531         Self {
    532             phases: [0.0; 12],
    533             wrap_counts: [0; 12],
    534         }
    535     }
    536     fn process(&mut self, sample_rate: f32, frequencies: &[f32; 12]) {
    537         for i in 0..12 {
    538             self.phases[i] += frequencies[i] as f64 / sample_rate as f64;
    539             if self.phases[i] >= 1.0 {
    540                 self.phases[i] -= 1.0;
    541                 self.wrap_counts[i] = self.wrap_counts[i].wrapping_add(1);
    542             }
    543         }
    544     }
    545 }
    546 
    547 // --- Poly Chip ---
    548 
    549 const NUM_CHIPS: usize = 71;
    550 const BASE_NOTE: u8 = 29; // F1
    551 
    552 struct PolyChip {
    553     note: u8,
    554     tog_index: usize,
    555     division: f64,
    556     gate: bool,
    557     velocity: f32,
    558     samples_since_on: u32,
    559     samples_since_off: u32,
    560     release_start_level: f32,
    561 }
    562 
    563 impl PolyChip {
    564     fn new(note: u8) -> Self {
    565         let tog_index = (note % 12) as usize;
    566         let division = 2.0f64.powi((108 + (tog_index as i32) - (note as i32)) / 12);
    567         Self {
    568             note,
    569             tog_index,
    570             division,
    571             gate: false,
    572             velocity: 0.0,
    573             samples_since_on: 0,
    574             samples_since_off: 0,
    575             release_start_level: 0.0,
    576         }
    577     }
    578     fn get_envelope(&self, params: &AmpParams, sr: f32) -> f32 {
    579         let a = params.attack.value();
    580         let dr = params.decay_release.value();
    581         let s = params.sustain.value();
    582         if self.gate {
    583             let t = self.samples_since_on as f32 / sr;
    584             if t < a {
    585                 t / a.max(0.0001)
    586             } else {
    587                 1.0 - ((t - a) / dr.max(0.0001)).clamp(0.0, 1.0) * (1.0 - s)
    588             }
    589         } else {
    590             let t = self.samples_since_off as f32 / sr;
    591             if params.final_decay.value() == FinalDecayMode::Manual {
    592                 self.release_start_level * (1.0 - (t / dr.max(0.0001)).clamp(0.0, 1.0))
    593             } else {
    594                 0.0
    595             }
    596         }
    597     }
    598     fn process(
    599         &mut self,
    600         t_a: f64,
    601         w_a: u64,
    602         t_b: f64,
    603         w_b: u64,
    604         params: &MolyxideParams,
    605         sr: f32,
    606         pwm: f32,
    607     ) -> f32 {
    608         let env = self.get_envelope(&params.amp, sr);
    609         if !self.gate && env <= 0.0 {
    610             return 0.0;
    611         }
    612         let (ws, base_pw, mix) = if self.note < 49 {
    613             (
    614                 params.osc.lower_waveshape.value(),
    615                 params.osc.lower_pw.value(),
    616                 params.osc.lower_mix.value(),
    617             )
    618         } else {
    619             (
    620                 params.osc.upper_waveshape.value(),
    621                 params.osc.upper_pw.value(),
    622                 params.osc.upper_mix.value(),
    623             )
    624         };
    625         let p_a = ((w_a as f64 + t_a) / self.division).fract();
    626         let sig_a = (p_a * 2.0 - 1.0) as f32;
    627         let p_b = ((w_b as f64 + t_b) / self.division).fract();
    628         let sig_b = if p_b
    629             < (base_pw as f64 + pwm as f64 * params.osc.pwm_amt.value() as f64 * 0.4)
    630                 .clamp(0.05, 0.95)
    631         {
    632             1.0
    633         } else {
    634             -1.0
    635         };
    636         let raw = match ws {
    637             WaveformSelection::Saw => sig_a,
    638             WaveformSelection::Square => sig_b,
    639             WaveformSelection::Both => sig_a * mix + sig_b * (1.0 - mix),
    640         };
    641         if self.gate {
    642             self.samples_since_on = self.samples_since_on.wrapping_add(1);
    643         } else {
    644             self.samples_since_off = self.samples_since_off.wrapping_add(1);
    645         }
    646         raw * env * (1.0 - (1.0 - self.velocity) * params.amp.velocity.value())
    647     }
    648 }
    649 
    650 // --- Main Plugin ---
    651 
    652 struct Molyxide {
    653     params: Arc<MolyxideParams>,
    654     sample_rate: f32,
    655     tog_a: TopOctaveGenerator,
    656     tog_b: TopOctaveGenerator,
    657     chips: Vec<PolyChip>,
    658     fm_phase: f32,
    659     pwm_phase: f32,
    660     res_low: ZdfSvf,
    661     res_mid: ZdfSvf,
    662     res_high: ZdfSvf,
    663     vcf: LadderFilter,
    664     vcf_samples_on: u32,
    665     vcf_last_note_id: u8,
    666 }
    667 
    668 impl Default for Molyxide {
    669     fn default() -> Self {
    670         let mut chips = Vec::with_capacity(NUM_CHIPS);
    671         for i in 0..NUM_CHIPS {
    672             chips.push(PolyChip::new(BASE_NOTE + i as u8));
    673         }
    674         Self {
    675             params: Arc::new(MolyxideParams::default()),
    676             sample_rate: 44100.0,
    677             tog_a: TopOctaveGenerator::new(),
    678             tog_b: TopOctaveGenerator::new(),
    679             chips,
    680             fm_phase: 0.0,
    681             pwm_phase: 0.0,
    682             res_low: ZdfSvf::new(),
    683             res_mid: ZdfSvf::new(),
    684             res_high: ZdfSvf::new(),
    685             vcf: LadderFilter::new(),
    686             vcf_samples_on: 0,
    687             vcf_last_note_id: 0,
    688         }
    689     }
    690 }
    691 
    692 impl Plugin for Molyxide {
    693     const NAME: &'static str = "Molyxide";
    694     const VENDOR: &'static str = "Minerva_Juppiter";
    695     const URL: &'static str = env!("CARGO_PKG_HOMEPAGE");
    696     const EMAIL: &'static str = "contact@minervajuppiter.net";
    697     const VERSION: &'static str = env!("CARGO_PKG_VERSION");
    698     const AUDIO_IO_LAYOUTS: &'static [AudioIOLayout] = &[AudioIOLayout {
    699         main_input_channels: NonZeroU32::new(2),
    700         main_output_channels: NonZeroU32::new(2),
    701         aux_input_ports: &[],
    702         aux_output_ports: &[],
    703         names: PortNames::const_default(),
    704     }];
    705     const MIDI_INPUT: MidiConfig = MidiConfig::Basic;
    706     const MIDI_OUTPUT: MidiConfig = MidiConfig::None;
    707     const SAMPLE_ACCURATE_AUTOMATION: bool = true;
    708     type SysExMessage = ();
    709     type BackgroundTask = ();
    710 
    711     fn params(&self) -> Arc<dyn Params> {
    712         self.params.clone()
    713     }
    714     fn initialize(
    715         &mut self,
    716         _: &AudioIOLayout,
    717         bc: &BufferConfig,
    718         _: &mut impl InitContext<Self>,
    719     ) -> bool {
    720         self.sample_rate = bc.sample_rate;
    721         true
    722     }
    723     fn process(
    724         &mut self,
    725         buffer: &mut Buffer,
    726         _: &mut AuxiliaryBuffers,
    727         context: &mut impl ProcessContext<Self>,
    728     ) -> ProcessStatus {
    729         let mut next_event = context.next_event();
    730         let sr = self.sample_rate;
    731         for (sample_id, channel_samples) in buffer.iter_samples().enumerate() {
    732             while let Some(event) = next_event {
    733                 if event.timing() > sample_id as u32 {
    734                     break;
    735                 }
    736                 match event {
    737                     NoteEvent::NoteOn { note, velocity, .. } => {
    738                         if let Some(c) = self.chips.iter_mut().find(|c| c.note == note) {
    739                             c.gate = true;
    740                             c.velocity = velocity;
    741                             c.samples_since_on = 0;
    742                             c.samples_since_off = 0;
    743                             // VCF Contour リセット
    744                             self.vcf_samples_on = 0;
    745                             self.vcf_last_note_id = note;
    746                         }
    747                     }
    748                     NoteEvent::NoteOff { note, .. } => {
    749                         if let Some(c) = self.chips.iter_mut().find(|c| c.note == note) {
    750                             if c.gate {
    751                                 c.gate = false;
    752                                 c.samples_since_off = 0;
    753                                 c.release_start_level = c.get_envelope(&self.params.amp, sr);
    754                             }
    755                         }
    756                     }
    757                     _ => (),
    758                 }
    759                 next_event = context.next_event();
    760             }
    761 
    762             self.fm_phase += self.params.osc.fm_rate.value() / sr;
    763             if self.fm_phase >= 1.0 {
    764                 self.fm_phase -= 1.0;
    765             }
    766             let fm_lfo = (self.fm_phase * 2.0 * std::f32::consts::PI).sin();
    767             self.pwm_phase += self.params.osc.pwm_rate.value() / sr;
    768             if self.pwm_phase >= 1.0 {
    769                 self.pwm_phase -= 1.0;
    770             }
    771             let pwm_lfo = (self.pwm_phase * 2.0 * std::f32::consts::PI).sin();
    772 
    773             let tune = self.params.mixer.master_tune.value()
    774                 + fm_lfo * self.params.osc.fm_amt.value() * 50.0;
    775             let mut freqs_a = [0.0f32; 12];
    776             let mut freqs_b = [0.0f32; 12];
    777             for i in 0..12 {
    778                 let base = util::midi_note_to_freq(108 + i as u8);
    779                 freqs_a[i] = base * 2.0f32.powf(tune / 1200.0);
    780                 freqs_b[i] = freqs_a[i] * 2.0f32.powf(self.params.osc.rank_b_beat.value() / 1200.0);
    781             }
    782             self.tog_a.process(sr, &freqs_a);
    783             self.tog_b.process(sr, &freqs_b);
    784 
    785             let mut mixed = 0.0;
    786             for c in &mut self.chips {
    787                 mixed += c.process(
    788                     self.tog_a.phases[c.tog_index],
    789                     self.tog_a.wrap_counts[c.tog_index],
    790                     self.tog_b.phases[c.tog_index],
    791                     self.tog_b.wrap_counts[c.tog_index],
    792                     &self.params,
    793                     sr,
    794                     pwm_lfo,
    795                 );
    796             }
    797 
    798             // Resonator Path
    799             let mut res_out = 0.0;
    800             if self.params.res.enabled.value() {
    801                 let mode = self.params.res.mode.value();
    802                 res_out = self.res_low.process(
    803                     mixed,
    804                     mode,
    805                     self.params.res.low_cf.value(),
    806                     self.params.res.low_emph.value(),
    807                     sr,
    808                 ) * self.params.res.low_gain.value()
    809                     + self.res_mid.process(
    810                         mixed,
    811                         mode,
    812                         self.params.res.mid_cf.value(),
    813                         self.params.res.mid_emph.value(),
    814                         sr,
    815                     ) * self.params.res.mid_gain.value()
    816                     + self.res_high.process(
    817                         mixed,
    818                         mode,
    819                         self.params.res.high_cf.value(),
    820                         self.params.res.high_emph.value(),
    821                         sr,
    822                     ) * self.params.res.high_gain.value();
    823             }
    824 
    825             // VCF Path
    826             let mut vcf_out = 0.0;
    827             if self.params.vcf.enabled.value() {
    828                 // VCF Envelope (Contour)
    829                 let t = self.vcf_samples_on as f32 / sr;
    830                 let v_a = self.params.vcf.attack.value();
    831                 let v_d = self.params.vcf.decay.value();
    832                 let v_s = self.params.vcf.sustain.value();
    833                 let v_env = if t < v_a {
    834                     t / v_a.max(0.0001)
    835                 } else {
    836                     1.0 - ((t - v_a) / v_d.max(0.0001)).clamp(0.0, 1.0) * (1.0 - v_s)
    837                 };
    838 
    839                 // Cutoff calculation with Envelope and Keyboard Tracking
    840                 let kb_offset =
    841                     (self.vcf_last_note_id as f32 - 60.0) / 12.0 * self.params.vcf.kb_track.value();
    842                 let cutoff = self.params.vcf.cutoff.value()
    843                     * 2.0f32.powf(kb_offset + v_env * self.params.vcf.env_amt.value() * 4.0);
    844 
    845                 vcf_out = self.vcf.process(
    846                     mixed,
    847                     cutoff.clamp(20.0, 20000.0),
    848                     self.params.vcf.emphasis.value(),
    849                     sr,
    850                 );
    851                 self.vcf_samples_on = self.vcf_samples_on.wrapping_add(1);
    852             }
    853 
    854             let direct = mixed * self.params.mixer.direct.value();
    855             let res_path = res_out * self.params.mixer.resonator.value();
    856             let vcf_path = vcf_out * self.params.mixer.vcf.value();
    857 
    858             let output = (direct + res_path + vcf_path) * 0.02 * self.params.gain.value();
    859             for sample in channel_samples {
    860                 *sample = output;
    861             }
    862         }
    863         ProcessStatus::Normal
    864     }
    865     fn reset(&mut self) {}
    866 }
    867 
    868 impl ClapPlugin for Molyxide {
    869     const CLAP_ID: &'static str = "net.minervajuppiter.Molyxide";
    870     const CLAP_DESCRIPTION: Option<&'static str> = Some("A Simple Polyphonic Synthesizer");
    871     const CLAP_MANUAL_URL: Option<&'static str> = Some(Self::URL);
    872     const CLAP_SUPPORT_URL: Option<&'static str> = None;
    873     const CLAP_FEATURES: &'static [ClapFeature] = &[ClapFeature::Synthesizer, ClapFeature::Stereo];
    874 }
    875 
    876 nih_export_clap!(Molyxide);