expand MDK with missing types and trait methods

This commit is contained in:
jess 2026-03-30 21:41:05 -07:00
parent cf29a6d26b
commit c9443d5877
8 changed files with 218 additions and 5 deletions

View File

@ -52,6 +52,11 @@ impl ModuleHost {
} }
} }
pub fn load_builtin<T: OxideModule + 'static>(&mut self, name: &str, config: &GlobalConfig) -> u32 {
let instance = T::new(config);
self.load_builtin_boxed(Box::new(instance), name)
}
pub fn load_builtin_boxed(&mut self, instance: Box<dyn OxideModule>, name: &str) -> u32 { pub fn load_builtin_boxed(&mut self, instance: Box<dyn OxideModule>, name: &str) -> u32 {
let id = self.next_id; let id = self.next_id;
self.next_id += 1; self.next_id += 1;

View File

@ -15,6 +15,7 @@ path = "src/main.rs"
[dependencies] [dependencies]
clap = { version = "4.5.48", features = ["derive"] } clap = { version = "4.5.48", features = ["derive"] }
crossbeam-channel = "0.5.12"
serde = { version = "1.0.228", features = ["derive"] } serde = { version = "1.0.228", features = ["derive"] }
toml = "0.9.7" toml = "0.9.7"
uuid = { version = "1.18.1", features = ["v4", "serde"] } uuid = { version = "1.18.1", features = ["v4", "serde"] }

43
oxforge/src/mdk/gui.rs Normal file
View File

@ -0,0 +1,43 @@
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
#[derive(Clone)]
pub struct AudioFenceHandle {
gui_to_audio: Arc<Mutex<Vec<(String, f32)>>>,
audio_to_gui: Arc<Mutex<HashMap<String, f32>>>,
}
impl AudioFenceHandle {
pub fn new() -> Self {
Self {
gui_to_audio: Arc::new(Mutex::new(Vec::new())),
audio_to_gui: Arc::new(Mutex::new(HashMap::new())),
}
}
pub fn drain_changes(&mut self) -> Vec<(String, f32)> {
let mut lock = self.gui_to_audio.lock().unwrap();
lock.drain(..).collect()
}
pub fn write_readback(&self, key: &str, value: f32) {
let mut lock = self.audio_to_gui.lock().unwrap();
lock.insert(key.to_string(), value);
}
pub fn push_change(&self, key: String, value: f32) {
let mut lock = self.gui_to_audio.lock().unwrap();
lock.push((key, value));
}
pub fn read_current(&self) -> HashMap<String, f32> {
let lock = self.audio_to_gui.lock().unwrap();
lock.clone()
}
}
impl Default for AudioFenceHandle {
fn default() -> Self {
Self::new()
}
}

View File

@ -1,3 +1,4 @@
pub mod gui;
pub mod types; pub mod types;
pub mod recording; pub mod recording;
@ -7,6 +8,7 @@ pub use recording::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::any::Any; use std::any::Any;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use uuid::Uuid; use uuid::Uuid;
pub use serde; pub use serde;
@ -80,6 +82,8 @@ pub struct GuiElement {
pub controls: String, pub controls: String,
} }
// --- MIDI types ---
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct MidiEvent { pub struct MidiEvent {
pub timing: u32, pub timing: u32,
@ -92,12 +96,45 @@ pub enum MidiMessage {
NoteOff { key: u8, velocity: u8 }, NoteOff { key: u8, velocity: u8 },
} }
pub struct MidiInput<'a> {
pub events: &'a [MidiEvent],
}
impl<'a> MidiInput<'a> {
pub fn new(events: &'a [MidiEvent]) -> Self {
Self { events }
}
pub fn events(&self) -> &[MidiEvent] {
self.events
}
}
pub struct MidiOutput {
pub port_name: String,
pub buffer: Arc<Mutex<Vec<MidiEvent>>>,
}
impl MidiOutput {
pub fn new(port_name: String, buffer: Arc<Mutex<Vec<MidiEvent>>>) -> Self {
Self { port_name, buffer }
}
pub fn send(&self, event: MidiEvent) {
let mut lock = self.buffer.lock().unwrap();
lock.push(event);
}
}
// --- Transport / timing ---
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum TransportState { pub enum TransportState {
Playing, Playing,
#[default] #[default]
Stopped, Stopped,
} }
#[derive(Debug, Clone, Copy, Default)] #[derive(Debug, Clone, Copy, Default)]
pub struct MusicalTime { pub struct MusicalTime {
pub sample_pos: u64, pub sample_pos: u64,
@ -106,8 +143,13 @@ pub struct MusicalTime {
pub time_signature_numerator: u8, pub time_signature_numerator: u8,
pub time_signature_denominator: u8, pub time_signature_denominator: u8,
pub state: TransportState, pub state: TransportState,
pub cycle_active: bool,
pub cycle_start_sample: u64,
pub cycle_end_sample: u64,
} }
// --- GUI messages ---
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum ToGuiMessage { pub enum ToGuiMessage {
Log(String), Log(String),
@ -162,6 +204,8 @@ impl ToGuiQueue {
} }
} }
// --- Global config ---
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct GlobalConfig { pub struct GlobalConfig {
pub instance_id: Uuid, pub instance_id: Uuid,
@ -169,6 +213,8 @@ pub struct GlobalConfig {
pub buffer_size: u32, pub buffer_size: u32,
} }
// --- Audio I/O types ---
pub struct MainAudioInput<'a> { pub buffer: &'a [f32] } pub struct MainAudioInput<'a> { pub buffer: &'a [f32] }
impl<'a> MainAudioInput<'a> { impl<'a> MainAudioInput<'a> {
pub fn iter(&self) -> impl Iterator<Item = &f32> { self.buffer.iter() } pub fn iter(&self) -> impl Iterator<Item = &f32> { self.buffer.iter() }
@ -181,6 +227,8 @@ impl<'a> MainAudioOutput<'a> {
pub fn buffer_mut(&mut self) -> &mut [f32] { self.buffer } pub fn buffer_mut(&mut self) -> &mut [f32] { self.buffer }
} }
// --- Chain types ---
pub struct ChainInput<'a> { pub struct ChainInput<'a> {
pub data: &'a (dyn Any + Send), pub data: &'a (dyn Any + Send),
} }
@ -199,7 +247,7 @@ impl<'a> ChainOutput<'a> {
} }
} }
// --- Lane/Bus view types for module port access --- // --- Lane/Bus view types ---
pub struct LaneRef<'a> { pub struct LaneRef<'a> {
real: &'a [f32], real: &'a [f32],
@ -311,15 +359,16 @@ pub struct PortDeclaration {
pub struct ModuleContract { pub struct ModuleContract {
pub realtime: bool, pub realtime: bool,
pub min_buffer_samples: Option<usize>, pub min_buffer_samples: Option<usize>,
pub latency_samples: u32,
} }
impl Default for ModuleContract { impl Default for ModuleContract {
fn default() -> Self { fn default() -> Self {
Self { realtime: true, min_buffer_samples: None } Self { realtime: true, min_buffer_samples: None, latency_samples: 0 }
} }
} }
// --- Port view (runtime data passed to process) --- // --- Port view ---
pub struct PortView<'a> { pub struct PortView<'a> {
buses_in: HashMap<String, BusRef<'a>>, buses_in: HashMap<String, BusRef<'a>>,
@ -375,6 +424,8 @@ pub struct Ports<'a> {
pub chain_in: Option<ChainInput<'a>>, pub chain_in: Option<ChainInput<'a>>,
pub chain_out: Option<ChainOutput<'a>>, pub chain_out: Option<ChainOutput<'a>>,
pub port: PortView<'a>, pub port: PortView<'a>,
pub midi_out: Option<MidiOutput>,
pub midi_in: Option<MidiInput<'a>>,
} }
impl<'a> Default for Ports<'a> { impl<'a> Default for Ports<'a> {
@ -385,20 +436,110 @@ impl<'a> Default for Ports<'a> {
chain_in: None, chain_in: None,
chain_out: None, chain_out: None,
port: PortView::new(), port: PortView::new(),
midi_out: None,
midi_in: None,
} }
} }
} }
// --- Process context ---
pub struct ProcessContext { pub struct ProcessContext {
pub time: MusicalTime, pub time: MusicalTime,
pub params: HashMap<String, f32>, pub params: HashMap<String, f32>,
pub to_gui: ToGuiQueue, pub to_gui: ToGuiQueue,
pub sample_rate: u32,
pub error_log: ErrorChannel,
pub error_report: ErrorChannel,
} }
// --- Parameter descriptors ---
#[derive(Debug, Clone)]
pub struct ParameterDescriptor {
pub key: String,
pub label: String,
pub kind: ParamKind,
}
#[derive(Debug, Clone)]
pub enum ParamKind {
Float { min: f32, max: f32, default: f32, step: Option<f32> },
Bool { default: bool },
Choice { options: Vec<String>, default: usize },
Int { min: i32, max: i32, default: i32 },
}
// --- Module GUI descriptor ---
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GuiToolkit {
Framebuffer,
Native,
}
impl Default for GuiToolkit {
fn default() -> Self {
Self::Native
}
}
#[derive(Debug, Clone)]
pub struct ModuleGuiDescriptor {
pub width: u32,
pub height: u32,
pub title: String,
pub toolkit: GuiToolkit,
}
// --- Error types ---
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorKind {
Panic,
ProcessError,
PortError,
ContractViolation,
}
#[derive(Debug, Clone)]
pub struct ModuleError {
pub module_id: u32,
pub module_name: String,
pub error_kind: ErrorKind,
pub message: String,
}
impl ModuleError {
pub fn new(module_id: u32, module_name: String, error_kind: ErrorKind, message: String) -> Self {
Self { module_id, module_name, error_kind, message }
}
}
#[derive(Clone)]
pub struct ErrorChannel {
tx: crossbeam_channel::Sender<ModuleError>,
}
impl ErrorChannel {
pub fn new(tx: crossbeam_channel::Sender<ModuleError>) -> Self {
Self { tx }
}
pub fn send(&self, error: ModuleError) {
let _ = self.tx.send(error);
}
}
// --- OxideModule trait ---
pub trait OxideModule: Send + Sync { pub trait OxideModule: Send + Sync {
fn new(config: &GlobalConfig) -> Self where Self: Sized; fn new(config: &GlobalConfig) -> Self where Self: Sized;
fn process(&mut self, ports: Ports, context: &ProcessContext); fn process(&mut self, ports: Ports, context: &ProcessContext);
fn contract(&self) -> ModuleContract { ModuleContract::default() } fn contract(&self) -> ModuleContract { ModuleContract::default() }
fn port_declarations(&self) -> Vec<PortDeclaration> { Vec::new() } fn port_declarations(&self) -> Vec<PortDeclaration> { Vec::new() }
fn receive_data(&mut self, _key: &str, _data: Box<dyn Any + Send>) {} fn receive_data(&mut self, _key: &str, _data: Box<dyn Any + Send>) {}
fn param_descriptors(&self) -> Vec<ParameterDescriptor> { Vec::new() }
fn gui_descriptor(&self) -> Option<ModuleGuiDescriptor> { None }
fn has_gui(&self) -> bool { false }
} }

View File

@ -18,6 +18,9 @@ pub enum RecorderMessage {
tempo: f32, tempo: f32,
time_sig_num: u8, time_sig_num: u8,
}, },
CycleBoundary {
boundary_sample: u64,
},
} }
pub struct PlaybackRegion { pub struct PlaybackRegion {
@ -26,4 +29,23 @@ pub struct PlaybackRegion {
pub start_sample: u64, pub start_sample: u64,
pub audio_l: Vec<f32>, pub audio_l: Vec<f32>,
pub audio_r: Vec<f32>, pub audio_r: Vec<f32>,
pub fade_in_samples: u64,
pub fade_out_samples: u64,
}
#[derive(Debug, Clone)]
pub struct MidiPlaybackNote {
pub start_tick: u64,
pub duration_ticks: u64,
pub note: u8,
pub velocity: u8,
pub channel: u8,
}
#[derive(Debug, Clone)]
pub struct MidiPlaybackRegion {
pub bus_name: String,
pub region_id: Uuid,
pub start_beat: f64,
pub notes: Vec<MidiPlaybackNote>,
} }

View File

@ -40,6 +40,7 @@ impl OxideModule for HilbertModule {
ModuleContract { ModuleContract {
realtime: true, realtime: true,
min_buffer_samples: Some(self.fft_size), min_buffer_samples: Some(self.fft_size),
latency_samples: 0,
} }
} }

View File

@ -49,7 +49,7 @@ impl OxideModule for RegionPlayerModule {
} }
fn contract(&self) -> ModuleContract { fn contract(&self) -> ModuleContract {
ModuleContract { realtime: true, min_buffer_samples: None } ModuleContract { realtime: true, min_buffer_samples: None, latency_samples: 0 }
} }
fn receive_data(&mut self, key: &str, data: Box<dyn Any + Send>) { fn receive_data(&mut self, key: &str, data: Box<dyn Any + Send>) {

View File

@ -110,6 +110,6 @@ impl OxideModule for SpiralVisualizer {
} }
fn contract(&self) -> ModuleContract { fn contract(&self) -> ModuleContract {
ModuleContract { realtime: true, min_buffer_samples: None } ModuleContract { realtime: true, min_buffer_samples: None, latency_samples: 0 }
} }
} }