some sort of ui

This commit is contained in:
2026-04-19 16:20:33 +03:00
parent 4f14980610
commit cdff703f7e
30 changed files with 1504 additions and 184 deletions

View File

@@ -1,114 +1,120 @@
use std::sync::Arc;
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::atomic::Ordering;
use atomic_float::AtomicF32;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(usize)]
#[allow(dead_code)]
pub enum ParamId {
// Master
MasterVolume = 0,
MasterPan,
// Oscillator 1
Osc1Volume,
// OSC 1-3
Osc1Gain = 0,
Osc1Pan,
Osc1Pitch,
Osc1Semitone,
Osc1Fine,
Osc1WavePos,
Osc1UnisonVoices,
Osc1UnisonCount,
Osc1UnisonDetune,
Osc1UnisonSpread,
Osc1WavePos,
// Oscillator 2
Osc2Volume,
Osc2Gain,
Osc2Pan,
Osc2Pitch,
Osc2Semitone,
Osc2Fine,
Osc2WavePos,
Osc2UnisonVoices,
Osc2UnisonCount,
Osc2UnisonDetune,
Osc2UnisonSpread,
Osc2WavePos,
// Oscillator 3
Osc3Volume,
Osc3Gain,
Osc3Pan,
Osc3Pitch,
Osc3Semitone,
Osc3Fine,
Osc3WavePos,
Osc3UnisonVoices,
Osc3UnisonCount,
Osc3UnisonDetune,
Osc3UnisonSpread,
Osc3WavePos,
// Envelope 1 (amplitude)
// ENV 1-3
Env1Delay,
Env1Attack,
Env1Hold,
Env1Decay,
Env1Sustain,
Env1Release,
Env1AttackCurve,
Env1DecayCurve,
Env1ReleaseCurve,
// Envelope 2
Env2Delay,
Env2Attack,
Env2Hold,
Env2Decay,
Env2Sustain,
Env2Release,
Env2AttackCurve,
Env2DecayCurve,
Env2ReleaseCurve,
// Envelope 3
Env3Delay,
Env3Attack,
Env3Hold,
Env3Decay,
Env3Sustain,
Env3Release,
Env3AttackCurve,
Env3DecayCurve,
Env3ReleaseCurve,
// LFO 14
// LFO 1-4
Lfo1Rate,
Lfo1Depth,
Lfo1Phase,
Lfo1Depth,
Lfo1WavePos,
Lfo1Sync,
Lfo2Rate,
Lfo2Depth,
Lfo2Phase,
Lfo2Depth,
Lfo2WavePos,
Lfo2Sync,
Lfo3Rate,
Lfo3Depth,
Lfo3Phase,
Lfo3Depth,
Lfo3WavePos,
Lfo3Sync,
Lfo4Rate,
Lfo4Depth,
Lfo4Phase,
Lfo4Depth,
Lfo4WavePos,
Lfo4Sync,
// Filter 1
// Filter 1-2
Filter1Cutoff,
Filter1Resonance,
Filter1Drive,
Filter1Keytrack,
// Filter 2
Filter1Type,
Filter2Cutoff,
Filter2Resonance,
Filter2Drive,
Filter2Keytrack,
Filter2Type,
// FX — Chorus
// FX
DistDrive,
DistType,
ChorusRate,
ChorusDepth,
ChorusMix,
// FX — Reverb
PhaserRate,
PhaserDepth,
PhaserMix,
ReverbSize,
ReverbDamping,
ReverbMix,
// FX — Delay
DelayTime,
DelayFeedback,
DelayMix,
// FX — Distortion
DistortionDrive,
DistortionMix,
// FX — EQ
EqLowGain,
EqMidFreq,
EqMidGain,
@@ -124,83 +130,96 @@ pub enum ParamId {
Macro7,
Macro8,
// Master
MasterVolume,
MasterPan,
Polyphony,
PortamentoTime,
PortamentoMode,
Count,
}
pub const PARAM_COUNT: usize = ParamId::Count as usize;
impl ParamId {
pub const COUNT: usize = ParamId::Count as usize;
pub fn default_value(id: ParamId) -> f32 {
use ParamId::*;
match id {
MasterVolume => 0.8,
MasterPan => 0.5,
pub fn default_value(self) -> f32 {
match self {
Self::Osc1Gain | Self::Osc2Gain | Self::Osc3Gain => 1.0,
Self::MasterVolume => 1.0,
Self::Filter1Cutoff | Self::Filter2Cutoff => 1.0,
Self::Filter1Resonance | Self::Filter2Resonance => 0.0,
Self::Env1Sustain | Self::Env2Sustain | Self::Env3Sustain => 1.0,
Self::Env1Attack | Self::Env2Attack | Self::Env3Attack => 0.01,
Self::Env1Decay | Self::Env2Decay | Self::Env3Decay => 0.3,
Self::Env1Release | Self::Env2Release | Self::Env3Release => 0.3,
Self::Polyphony => 1.0, // normalized: 1.0 = 16 voices
Self::ReverbMix => 0.15,
Self::ChorusMix => 0.0,
_ => 0.0,
}
}
Osc1Volume => 0.8,
Osc2Volume | Osc3Volume => 0.0, // off by default
Osc1Pan | Osc2Pan | Osc3Pan => 0.5,
Osc1Pitch | Osc2Pitch | Osc3Pitch => 0.5,
Osc1Fine | Osc2Fine | Osc3Fine => 0.5,
Env1Attack | Env2Attack | Env3Attack => 0.02,
Env1Decay | Env2Decay | Env3Decay => 0.30,
Env1Sustain | Env2Sustain | Env3Sustain => 0.70,
Env1Release | Env2Release | Env3Release => 0.35,
Filter1Cutoff | Filter2Cutoff => 1.0,
Filter1Keytrack | Filter2Keytrack => 0.0,
Lfo1Rate | Lfo2Rate | Lfo3Rate | Lfo4Rate => 0.3,
_ => 0.0,
pub fn label(self) -> &'static str {
match self {
Self::Osc1Gain => "Gain",
Self::Osc1Pan => "Pan",
Self::Osc1Semitone => "Semi",
Self::Osc1Fine => "Fine",
Self::Osc1UnisonCount => "Voices",
Self::Osc1UnisonDetune => "Detune",
Self::Osc1UnisonSpread => "Spread",
Self::Osc1WavePos => "Wave",
Self::Filter1Cutoff => "Cutoff",
Self::Filter1Resonance => "Res",
Self::Filter1Drive => "Drive",
Self::Filter1Keytrack => "Key",
Self::Env1Attack => "A",
Self::Env1Decay => "D",
Self::Env1Sustain => "S",
Self::Env1Release => "R",
Self::Lfo1Rate => "Rate",
Self::Lfo1Depth => "Depth",
Self::Macro1 => "M1",
Self::Macro2 => "M2",
Self::Macro3 => "M3",
Self::Macro4 => "M4",
Self::Macro5 => "M5",
Self::Macro6 => "M6",
Self::Macro7 => "M7",
Self::Macro8 => "M8",
Self::MasterVolume => "Vol",
_ => "",
}
}
}
#[derive(Clone)]
pub struct ParamStore {
data: Arc<[AtomicU32]>,
params: Arc<Box<[AtomicF32]>>,
}
impl ParamStore {
pub fn new() -> Self {
let data: Arc<[AtomicU32]> = (0..PARAM_COUNT)
let params = (0..ParamId::COUNT)
.map(|i| {
let id: ParamId = unsafe { std::mem::transmute(i) };
AtomicU32::new(default_value(id).to_bits())
AtomicF32::new(id.default_value())
})
.collect::<Vec<_>>()
.into();
Self { data }
.into_boxed_slice();
Self {
params: Arc::new(params),
}
}
#[inline(always)]
#[inline]
pub fn get(&self, id: ParamId) -> f32 {
f32::from_bits(self.data[id as usize].load(Ordering::Relaxed))
self.params[id as usize].load(Ordering::Relaxed)
}
#[inline(always)]
pub fn set(&self, id: ParamId, value: f32) {
debug_assert!((0.0..=1.0).contains(&value), "param out of range: {value}");
self.data[id as usize].store(value.to_bits(), Ordering::Relaxed);
}
pub fn reset_to_defaults(&self) {
for i in 0..PARAM_COUNT {
let id: ParamId = unsafe { std::mem::transmute(i) };
self.data[i].store(default_value(id).to_bits(), Ordering::Relaxed);
}
}
pub fn snapshot(&self) -> Vec<f32> {
(0..PARAM_COUNT)
.map(|i| f32::from_bits(self.data[i].load(Ordering::Relaxed)))
.collect()
}
pub fn restore(&self, values: &[f32]) {
assert_eq!(values.len(), PARAM_COUNT, "snapshot length mismatch");
for (i, &v) in values.iter().enumerate() {
self.data[i].store(v.to_bits(), Ordering::Relaxed);
}
#[inline]
pub fn set(&self, id: ParamId, v: f32) {
self.params[id as usize].store(v, Ordering::Relaxed);
}
}
@@ -209,70 +228,3 @@ impl Default for ParamStore {
Self::new()
}
}
pub struct ParamMeta {
pub name: &'static str,
pub label: &'static str,
pub steps: Option<u32>,
}
pub fn param_meta(id: ParamId) -> ParamMeta {
use ParamId::*;
match id {
MasterVolume => ParamMeta {
name: "Master Volume",
label: "%",
steps: None,
},
MasterPan => ParamMeta {
name: "Master Pan",
label: "",
steps: None,
},
Osc1Pitch => ParamMeta {
name: "Osc1 Pitch",
label: "st",
steps: Some(96),
},
Filter1Cutoff => ParamMeta {
name: "Filter1 Cutoff",
label: "Hz",
steps: None,
},
Filter1Resonance => ParamMeta {
name: "Filter1 Res",
label: "",
steps: None,
},
Env1Attack => ParamMeta {
name: "Env1 Attack",
label: "s",
steps: None,
},
Env1Decay => ParamMeta {
name: "Env1 Decay",
label: "s",
steps: None,
},
Env1Sustain => ParamMeta {
name: "Env1 Sustain",
label: "%",
steps: None,
},
Env1Release => ParamMeta {
name: "Env1 Release",
label: "s",
steps: None,
},
Macro1 => ParamMeta {
name: "Macro 1",
label: "",
steps: None,
},
_ => ParamMeta {
name: "?",
label: "",
steps: None,
},
}
}