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(¶ms.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);