skeleton & stubs

This commit is contained in:
2026-04-19 02:13:56 +03:00
parent e3f641d646
commit 4f14980610
13 changed files with 468 additions and 609 deletions

7
crates/engine/Cargo.toml Normal file
View File

@@ -0,0 +1,7 @@
[package]
name = "engine"
version.workspace = true
edition.workspace = true
[dependencies]
params = { workspace = true }

29
crates/engine/src/lib.rs Normal file
View File

@@ -0,0 +1,29 @@
use params::{ParamId, ParamStore};
pub struct Engine {
params: ParamStore,
sample_rate: f32,
}
impl Engine {
pub fn new(params: ParamStore, sample_rate: f32) -> Self {
Self {
params,
sample_rate,
}
}
pub fn set_sample_rate(&mut self, rate: f32) {
self.sample_rate = rate;
}
pub fn process(&mut self, out_l: &mut [f32], out_r: &mut [f32]) {
debug_assert_eq!(out_l.len(), out_r.len());
out_l.fill(0.0);
out_r.fill(0.0);
let _vol = self.params.get(ParamId::MasterVolume);
}
pub fn midi_event(&mut self, _data: [u8; 3]) {}
}

4
crates/params/Cargo.toml Normal file
View File

@@ -0,0 +1,4 @@
[package]
name = "params"
version.workspace = true
edition.workspace = true

278
crates/params/src/lib.rs Normal file
View File

@@ -0,0 +1,278 @@
use std::sync::Arc;
use std::sync::atomic::{AtomicU32, Ordering};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(usize)]
#[allow(dead_code)]
pub enum ParamId {
// Master
MasterVolume = 0,
MasterPan,
// Oscillator 1
Osc1Volume,
Osc1Pan,
Osc1Pitch,
Osc1Fine,
Osc1WavePos,
Osc1UnisonVoices,
Osc1UnisonDetune,
Osc1UnisonSpread,
// Oscillator 2
Osc2Volume,
Osc2Pan,
Osc2Pitch,
Osc2Fine,
Osc2WavePos,
Osc2UnisonVoices,
Osc2UnisonDetune,
Osc2UnisonSpread,
// Oscillator 3
Osc3Volume,
Osc3Pan,
Osc3Pitch,
Osc3Fine,
Osc3WavePos,
Osc3UnisonVoices,
Osc3UnisonDetune,
Osc3UnisonSpread,
// Envelope 1 (amplitude)
Env1Delay,
Env1Attack,
Env1Hold,
Env1Decay,
Env1Sustain,
Env1Release,
// Envelope 2
Env2Delay,
Env2Attack,
Env2Hold,
Env2Decay,
Env2Sustain,
Env2Release,
// Envelope 3
Env3Delay,
Env3Attack,
Env3Hold,
Env3Decay,
Env3Sustain,
Env3Release,
// LFO 14
Lfo1Rate,
Lfo1Depth,
Lfo1Phase,
Lfo2Rate,
Lfo2Depth,
Lfo2Phase,
Lfo3Rate,
Lfo3Depth,
Lfo3Phase,
Lfo4Rate,
Lfo4Depth,
Lfo4Phase,
// Filter 1
Filter1Cutoff,
Filter1Resonance,
Filter1Drive,
Filter1Keytrack,
// Filter 2
Filter2Cutoff,
Filter2Resonance,
Filter2Drive,
Filter2Keytrack,
// FX — Chorus
ChorusRate,
ChorusDepth,
ChorusMix,
// FX — Reverb
ReverbSize,
ReverbDamping,
ReverbMix,
// FX — Delay
DelayTime,
DelayFeedback,
DelayMix,
// FX — Distortion
DistortionDrive,
DistortionMix,
// FX — EQ
EqLowGain,
EqMidFreq,
EqMidGain,
EqHighGain,
// Macros
Macro1,
Macro2,
Macro3,
Macro4,
Macro5,
Macro6,
Macro7,
Macro8,
Count,
}
pub const PARAM_COUNT: usize = ParamId::Count as usize;
pub fn default_value(id: ParamId) -> f32 {
use ParamId::*;
match id {
MasterVolume => 0.8,
MasterPan => 0.5,
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,
}
}
#[derive(Clone)]
pub struct ParamStore {
data: Arc<[AtomicU32]>,
}
impl ParamStore {
pub fn new() -> Self {
let data: Arc<[AtomicU32]> = (0..PARAM_COUNT)
.map(|i| {
let id: ParamId = unsafe { std::mem::transmute(i) };
AtomicU32::new(default_value(id).to_bits())
})
.collect::<Vec<_>>()
.into();
Self { data }
}
#[inline(always)]
pub fn get(&self, id: ParamId) -> f32 {
f32::from_bits(self.data[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);
}
}
}
impl Default for ParamStore {
fn default() -> Self {
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,
},
}
}

View File

@@ -0,0 +1,13 @@
[package]
name = "plugin-lv2"
version.workspace = true
edition.workspace = true
[lib]
name = "tenko_lv2"
crate-type = ["cdylib"]
[dependencies]
params = { workspace = true }
engine = { workspace = true }
lv2 = { workspace = true }

View File

@@ -0,0 +1,7 @@
@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
<https://git.yokai.digital/deadYokai/tenko>
a lv2:Plugin ;
lv2:binary <libtenko_lv2.so> ;
rdfs:seeAlso <tenko.ttl> .

View File

@@ -0,0 +1,21 @@
@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
@prefix doap: <http://usefulinc.com/ns/doap#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
<https://git.yokai.digital/deadYokai/tenko>
a lv2:Plugin, lv2:InstrumentPlugin ;
doap:name "Tenko" ;
doap:license <https://spdx.org/licenses/MIT> ;
lv2:port [
a lv2:OutputPort, lv2:AudioPort ;
lv2:index 0 ;
lv2:symbol "out_l" ;
rdfs:label "Left Out"
] , [
a lv2:OutputPort, lv2:AudioPort ;
lv2:index 1 ;
lv2:symbol "out_r" ;
rdfs:label "Right Out"
] .

View File

@@ -0,0 +1,37 @@
use engine::Engine;
use lv2::prelude::*;
use params::ParamStore;
#[derive(PortCollection)]
struct Ports {
out_l: OutputPort<Audio>,
out_r: OutputPort<Audio>,
}
#[uri("https://git.yokai.digital/deadYokai/tenko")]
struct TenkoLv2 {
engine: Engine,
}
impl Plugin for TenkoLv2 {
type Ports = Ports;
type InitFeatures = ();
type AudioFeatures = ();
fn new(info: &PluginInfo, _features: &mut ()) -> Option<Self> {
let params = ParamStore::new();
let engine = Engine::new(params, info.sample_rate() as f32);
Some(Self { engine })
}
fn run(&mut self, ports: &mut Ports, _: &mut (), _: u32) {
for s in ports.out_l.iter_mut() {
*s = 0.0;
}
for s in ports.out_r.iter_mut() {
*s = 0.0;
}
}
}
lv2_descriptors!(TenkoLv2);

8
crates/ui/Cargo.toml Normal file
View File

@@ -0,0 +1,8 @@
[package]
name = "ui"
version.workspace = true
edition.workspace = true
[dependencies]
params = { workspace = true }
vizia = { workspace = true }

9
crates/ui/src/lib.rs Normal file
View File

@@ -0,0 +1,9 @@
pub mod knob {}
pub mod env_disp {}
pub mod wave_disp {}
pub mod mod_matrix {}
pub mod spectrum {}
pub fn run_ui(_params: params::ParamStore) {
unimplemented!("stub")
}