skeleton & stubs
This commit is contained in:
7
crates/engine/Cargo.toml
Normal file
7
crates/engine/Cargo.toml
Normal 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
29
crates/engine/src/lib.rs
Normal 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
4
crates/params/Cargo.toml
Normal file
@@ -0,0 +1,4 @@
|
||||
[package]
|
||||
name = "params"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
278
crates/params/src/lib.rs
Normal file
278
crates/params/src/lib.rs
Normal 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 1–4
|
||||
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,
|
||||
},
|
||||
}
|
||||
}
|
||||
13
crates/plugin-lv2/Cargo.toml
Normal file
13
crates/plugin-lv2/Cargo.toml
Normal 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 }
|
||||
7
crates/plugin-lv2/lv2/manifest.ttl
Normal file
7
crates/plugin-lv2/lv2/manifest.ttl
Normal 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> .
|
||||
21
crates/plugin-lv2/lv2/tenko.ttl
Normal file
21
crates/plugin-lv2/lv2/tenko.ttl
Normal 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"
|
||||
] .
|
||||
37
crates/plugin-lv2/src/lib.rs
Normal file
37
crates/plugin-lv2/src/lib.rs
Normal 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
8
crates/ui/Cargo.toml
Normal 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
9
crates/ui/src/lib.rs
Normal 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")
|
||||
}
|
||||
Reference in New Issue
Block a user