Feat: clean the codebase as much as possible
Some checks failed
Deploy Website / deploy (push) Failing after 4m51s
Some checks failed
Deploy Website / deploy (push) Failing after 4m51s
This commit is contained in:
17
Cargo.toml
17
Cargo.toml
@@ -34,12 +34,13 @@ path = "src/bin/desktop/main.rs"
|
|||||||
required-features = ["desktop"]
|
required-features = ["desktop"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = ["cli"]
|
||||||
block-renderer = ["dep:soft_ratatui", "dep:rustc-hash"]
|
cli = ["dep:cpal", "dep:midir", "dep:confy", "dep:clap", "dep:thread-priority"]
|
||||||
|
block-renderer = ["dep:soft_ratatui", "dep:rustc-hash", "dep:egui"]
|
||||||
desktop = [
|
desktop = [
|
||||||
|
"cli",
|
||||||
"block-renderer",
|
"block-renderer",
|
||||||
"cagire-forth/desktop",
|
"cagire-forth/desktop",
|
||||||
"dep:egui",
|
|
||||||
"dep:eframe",
|
"dep:eframe",
|
||||||
"dep:egui_ratatui",
|
"dep:egui_ratatui",
|
||||||
"dep:image",
|
"dep:image",
|
||||||
@@ -54,8 +55,8 @@ doux = { git = "https://github.com/Bubobubobubobubo/doux", features = ["native"]
|
|||||||
rusty_link = "0.4"
|
rusty_link = "0.4"
|
||||||
ratatui = "0.30"
|
ratatui = "0.30"
|
||||||
crossterm = "0.29"
|
crossterm = "0.29"
|
||||||
cpal = { version = "0.17", features = ["jack"] }
|
cpal = { version = "0.17", features = ["jack"], optional = true }
|
||||||
clap = { version = "4", features = ["derive"] }
|
clap = { version = "4", features = ["derive"], optional = true }
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
@@ -64,12 +65,12 @@ tui-big-text = "0.8"
|
|||||||
arboard = "3"
|
arboard = "3"
|
||||||
minimad = "0.13"
|
minimad = "0.13"
|
||||||
crossbeam-channel = "0.5"
|
crossbeam-channel = "0.5"
|
||||||
confy = "2"
|
confy = { version = "2", optional = true }
|
||||||
rustfft = "6"
|
rustfft = "6"
|
||||||
thread-priority = "1"
|
thread-priority = { version = "1", optional = true }
|
||||||
ringbuf = "0.4"
|
ringbuf = "0.4"
|
||||||
arc-swap = "1"
|
arc-swap = "1"
|
||||||
midir = "0.10"
|
midir = { version = "0.10", optional = true }
|
||||||
parking_lot = "0.12"
|
parking_lot = "0.12"
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ description = "Cagire as a CLAP/VST3 audio plugin"
|
|||||||
crate-type = ["cdylib", "lib"]
|
crate-type = ["cdylib", "lib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cagire = { path = "../..", features = ["block-renderer"] }
|
cagire = { path = "../..", default-features = false, features = ["block-renderer"] }
|
||||||
cagire-forth = { path = "../../crates/forth" }
|
cagire-forth = { path = "../../crates/forth" }
|
||||||
cagire-project = { path = "../../crates/project" }
|
cagire-project = { path = "../../crates/project" }
|
||||||
cagire-ratatui = { path = "../../crates/ratatui" }
|
cagire-ratatui = { path = "../../crates/ratatui" }
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ use cagire::model::{Dictionary, Rng, Variables};
|
|||||||
use cagire::theme;
|
use cagire::theme;
|
||||||
use cagire::views;
|
use cagire::views;
|
||||||
|
|
||||||
use crate::input_egui::{convert_egui_events, convert_egui_mouse};
|
use cagire::input_egui::{convert_egui_events, convert_egui_mouse};
|
||||||
use crate::params::CagireParams;
|
use crate::params::CagireParams;
|
||||||
use crate::PluginBridge;
|
use crate::PluginBridge;
|
||||||
|
|
||||||
|
|||||||
@@ -1,258 +0,0 @@
|
|||||||
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers, MouseButton, MouseEvent, MouseEventKind};
|
|
||||||
use nih_plug_egui::egui;
|
|
||||||
use ratatui::layout::Rect;
|
|
||||||
|
|
||||||
pub fn convert_egui_mouse(
|
|
||||||
ctx: &egui::Context,
|
|
||||||
widget_rect: egui::Rect,
|
|
||||||
term: Rect,
|
|
||||||
) -> Vec<MouseEvent> {
|
|
||||||
let mut events = Vec::new();
|
|
||||||
if widget_rect.width() < 1.0
|
|
||||||
|| widget_rect.height() < 1.0
|
|
||||||
|| term.width == 0
|
|
||||||
|| term.height == 0
|
|
||||||
{
|
|
||||||
return events;
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.input(|i| {
|
|
||||||
let Some(pos) = i.pointer.latest_pos() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
if !widget_rect.contains(pos) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let col =
|
|
||||||
((pos.x - widget_rect.left()) / widget_rect.width() * term.width as f32) as u16;
|
|
||||||
let row =
|
|
||||||
((pos.y - widget_rect.top()) / widget_rect.height() * term.height as f32) as u16;
|
|
||||||
let col = col.min(term.width.saturating_sub(1));
|
|
||||||
let row = row.min(term.height.saturating_sub(1));
|
|
||||||
|
|
||||||
if i.pointer.button_clicked(egui::PointerButton::Primary) {
|
|
||||||
events.push(MouseEvent {
|
|
||||||
kind: MouseEventKind::Down(MouseButton::Left),
|
|
||||||
column: col,
|
|
||||||
row,
|
|
||||||
modifiers: KeyModifiers::empty(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let scroll = i.raw_scroll_delta.y;
|
|
||||||
if scroll > 1.0 {
|
|
||||||
events.push(MouseEvent {
|
|
||||||
kind: MouseEventKind::ScrollUp,
|
|
||||||
column: col,
|
|
||||||
row,
|
|
||||||
modifiers: KeyModifiers::empty(),
|
|
||||||
});
|
|
||||||
} else if scroll < -1.0 {
|
|
||||||
events.push(MouseEvent {
|
|
||||||
kind: MouseEventKind::ScrollDown,
|
|
||||||
column: col,
|
|
||||||
row,
|
|
||||||
modifiers: KeyModifiers::empty(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
events
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn convert_egui_events(ctx: &egui::Context) -> Vec<KeyEvent> {
|
|
||||||
let mut events = Vec::new();
|
|
||||||
|
|
||||||
for event in &ctx.input(|i| i.events.clone()) {
|
|
||||||
if let Some(key_event) = convert_event(event) {
|
|
||||||
events.push(key_event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
events
|
|
||||||
}
|
|
||||||
|
|
||||||
fn convert_event(event: &egui::Event) -> Option<KeyEvent> {
|
|
||||||
match event {
|
|
||||||
egui::Event::Key {
|
|
||||||
key,
|
|
||||||
pressed,
|
|
||||||
modifiers,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
if !*pressed {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let mods = convert_modifiers(*modifiers);
|
|
||||||
if is_character_key(*key)
|
|
||||||
&& !mods.intersects(KeyModifiers::CONTROL | KeyModifiers::ALT)
|
|
||||||
{
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let code = convert_key(*key)?;
|
|
||||||
Some(KeyEvent::new(code, mods))
|
|
||||||
}
|
|
||||||
egui::Event::Text(text) => {
|
|
||||||
if text.len() == 1 {
|
|
||||||
let c = text.chars().next()?;
|
|
||||||
if !c.is_control() {
|
|
||||||
return Some(KeyEvent::new(KeyCode::Char(c), KeyModifiers::empty()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
egui::Event::Copy => Some(KeyEvent::new(KeyCode::Char('c'), KeyModifiers::CONTROL)),
|
|
||||||
egui::Event::Cut => Some(KeyEvent::new(KeyCode::Char('x'), KeyModifiers::CONTROL)),
|
|
||||||
egui::Event::Paste(_) => Some(KeyEvent::new(KeyCode::Char('v'), KeyModifiers::CONTROL)),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn convert_key(key: egui::Key) -> Option<KeyCode> {
|
|
||||||
Some(match key {
|
|
||||||
egui::Key::ArrowDown => KeyCode::Down,
|
|
||||||
egui::Key::ArrowLeft => KeyCode::Left,
|
|
||||||
egui::Key::ArrowRight => KeyCode::Right,
|
|
||||||
egui::Key::ArrowUp => KeyCode::Up,
|
|
||||||
egui::Key::Escape => KeyCode::Esc,
|
|
||||||
egui::Key::Tab => KeyCode::Tab,
|
|
||||||
egui::Key::Backspace => KeyCode::Backspace,
|
|
||||||
egui::Key::Enter => KeyCode::Enter,
|
|
||||||
egui::Key::Space => KeyCode::Char(' '),
|
|
||||||
egui::Key::Insert => KeyCode::Insert,
|
|
||||||
egui::Key::Delete => KeyCode::Delete,
|
|
||||||
egui::Key::Home => KeyCode::Home,
|
|
||||||
egui::Key::End => KeyCode::End,
|
|
||||||
egui::Key::PageUp => KeyCode::PageUp,
|
|
||||||
egui::Key::PageDown => KeyCode::PageDown,
|
|
||||||
egui::Key::F1 => KeyCode::F(1),
|
|
||||||
egui::Key::F2 => KeyCode::F(2),
|
|
||||||
egui::Key::F3 => KeyCode::F(3),
|
|
||||||
egui::Key::F4 => KeyCode::F(4),
|
|
||||||
egui::Key::F5 => KeyCode::F(5),
|
|
||||||
egui::Key::F6 => KeyCode::F(6),
|
|
||||||
egui::Key::F7 => KeyCode::F(7),
|
|
||||||
egui::Key::F8 => KeyCode::F(8),
|
|
||||||
egui::Key::F9 => KeyCode::F(9),
|
|
||||||
egui::Key::F10 => KeyCode::F(10),
|
|
||||||
egui::Key::F11 => KeyCode::F(11),
|
|
||||||
egui::Key::F12 => KeyCode::F(12),
|
|
||||||
egui::Key::A => KeyCode::Char('a'),
|
|
||||||
egui::Key::B => KeyCode::Char('b'),
|
|
||||||
egui::Key::C => KeyCode::Char('c'),
|
|
||||||
egui::Key::D => KeyCode::Char('d'),
|
|
||||||
egui::Key::E => KeyCode::Char('e'),
|
|
||||||
egui::Key::F => KeyCode::Char('f'),
|
|
||||||
egui::Key::G => KeyCode::Char('g'),
|
|
||||||
egui::Key::H => KeyCode::Char('h'),
|
|
||||||
egui::Key::I => KeyCode::Char('i'),
|
|
||||||
egui::Key::J => KeyCode::Char('j'),
|
|
||||||
egui::Key::K => KeyCode::Char('k'),
|
|
||||||
egui::Key::L => KeyCode::Char('l'),
|
|
||||||
egui::Key::M => KeyCode::Char('m'),
|
|
||||||
egui::Key::N => KeyCode::Char('n'),
|
|
||||||
egui::Key::O => KeyCode::Char('o'),
|
|
||||||
egui::Key::P => KeyCode::Char('p'),
|
|
||||||
egui::Key::Q => KeyCode::Char('q'),
|
|
||||||
egui::Key::R => KeyCode::Char('r'),
|
|
||||||
egui::Key::S => KeyCode::Char('s'),
|
|
||||||
egui::Key::T => KeyCode::Char('t'),
|
|
||||||
egui::Key::U => KeyCode::Char('u'),
|
|
||||||
egui::Key::V => KeyCode::Char('v'),
|
|
||||||
egui::Key::W => KeyCode::Char('w'),
|
|
||||||
egui::Key::X => KeyCode::Char('x'),
|
|
||||||
egui::Key::Y => KeyCode::Char('y'),
|
|
||||||
egui::Key::Z => KeyCode::Char('z'),
|
|
||||||
egui::Key::Num0 => KeyCode::Char('0'),
|
|
||||||
egui::Key::Num1 => KeyCode::Char('1'),
|
|
||||||
egui::Key::Num2 => KeyCode::Char('2'),
|
|
||||||
egui::Key::Num3 => KeyCode::Char('3'),
|
|
||||||
egui::Key::Num4 => KeyCode::Char('4'),
|
|
||||||
egui::Key::Num5 => KeyCode::Char('5'),
|
|
||||||
egui::Key::Num6 => KeyCode::Char('6'),
|
|
||||||
egui::Key::Num7 => KeyCode::Char('7'),
|
|
||||||
egui::Key::Num8 => KeyCode::Char('8'),
|
|
||||||
egui::Key::Num9 => KeyCode::Char('9'),
|
|
||||||
egui::Key::Minus => KeyCode::Char('-'),
|
|
||||||
egui::Key::Equals => KeyCode::Char('='),
|
|
||||||
egui::Key::OpenBracket => KeyCode::Char('['),
|
|
||||||
egui::Key::CloseBracket => KeyCode::Char(']'),
|
|
||||||
egui::Key::Semicolon => KeyCode::Char(';'),
|
|
||||||
egui::Key::Comma => KeyCode::Char(','),
|
|
||||||
egui::Key::Period => KeyCode::Char('.'),
|
|
||||||
egui::Key::Slash => KeyCode::Char('/'),
|
|
||||||
egui::Key::Backslash => KeyCode::Char('\\'),
|
|
||||||
egui::Key::Backtick => KeyCode::Char('`'),
|
|
||||||
egui::Key::Quote => KeyCode::Char('\''),
|
|
||||||
_ => return None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn convert_modifiers(mods: egui::Modifiers) -> KeyModifiers {
|
|
||||||
let mut result = KeyModifiers::empty();
|
|
||||||
if mods.shift {
|
|
||||||
result |= KeyModifiers::SHIFT;
|
|
||||||
}
|
|
||||||
if mods.ctrl || mods.command {
|
|
||||||
result |= KeyModifiers::CONTROL;
|
|
||||||
}
|
|
||||||
if mods.alt {
|
|
||||||
result |= KeyModifiers::ALT;
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_character_key(key: egui::Key) -> bool {
|
|
||||||
matches!(
|
|
||||||
key,
|
|
||||||
egui::Key::A
|
|
||||||
| egui::Key::B
|
|
||||||
| egui::Key::C
|
|
||||||
| egui::Key::D
|
|
||||||
| egui::Key::E
|
|
||||||
| egui::Key::F
|
|
||||||
| egui::Key::G
|
|
||||||
| egui::Key::H
|
|
||||||
| egui::Key::I
|
|
||||||
| egui::Key::J
|
|
||||||
| egui::Key::K
|
|
||||||
| egui::Key::L
|
|
||||||
| egui::Key::M
|
|
||||||
| egui::Key::N
|
|
||||||
| egui::Key::O
|
|
||||||
| egui::Key::P
|
|
||||||
| egui::Key::Q
|
|
||||||
| egui::Key::R
|
|
||||||
| egui::Key::S
|
|
||||||
| egui::Key::T
|
|
||||||
| egui::Key::U
|
|
||||||
| egui::Key::V
|
|
||||||
| egui::Key::W
|
|
||||||
| egui::Key::X
|
|
||||||
| egui::Key::Y
|
|
||||||
| egui::Key::Z
|
|
||||||
| egui::Key::Num0
|
|
||||||
| egui::Key::Num1
|
|
||||||
| egui::Key::Num2
|
|
||||||
| egui::Key::Num3
|
|
||||||
| egui::Key::Num4
|
|
||||||
| egui::Key::Num5
|
|
||||||
| egui::Key::Num6
|
|
||||||
| egui::Key::Num7
|
|
||||||
| egui::Key::Num8
|
|
||||||
| egui::Key::Num9
|
|
||||||
| egui::Key::Space
|
|
||||||
| egui::Key::Minus
|
|
||||||
| egui::Key::Equals
|
|
||||||
| egui::Key::OpenBracket
|
|
||||||
| egui::Key::CloseBracket
|
|
||||||
| egui::Key::Semicolon
|
|
||||||
| egui::Key::Comma
|
|
||||||
| egui::Key::Period
|
|
||||||
| egui::Key::Slash
|
|
||||||
| egui::Key::Backslash
|
|
||||||
| egui::Key::Backtick
|
|
||||||
| egui::Key::Quote
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
mod editor;
|
mod editor;
|
||||||
mod input_egui;
|
|
||||||
mod params;
|
mod params;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@@ -51,6 +50,8 @@ pub struct CagirePlugin {
|
|||||||
rng: Rng,
|
rng: Rng,
|
||||||
cmd_buffer: String,
|
cmd_buffer: String,
|
||||||
audio_buffer: Vec<f32>,
|
audio_buffer: Vec<f32>,
|
||||||
|
output_channels: usize,
|
||||||
|
scope_extract_buffer: Vec<f32>,
|
||||||
fft_producer: Option<ringbuf::HeapProd<f32>>,
|
fft_producer: Option<ringbuf::HeapProd<f32>>,
|
||||||
_analysis: Option<AnalysisHandle>,
|
_analysis: Option<AnalysisHandle>,
|
||||||
pending_note_offs: Vec<PendingNoteOff>,
|
pending_note_offs: Vec<PendingNoteOff>,
|
||||||
@@ -88,6 +89,8 @@ impl Default for CagirePlugin {
|
|||||||
rng,
|
rng,
|
||||||
cmd_buffer: String::with_capacity(256),
|
cmd_buffer: String::with_capacity(256),
|
||||||
audio_buffer: Vec::new(),
|
audio_buffer: Vec::new(),
|
||||||
|
output_channels: 2,
|
||||||
|
scope_extract_buffer: Vec::new(),
|
||||||
fft_producer: None,
|
fft_producer: None,
|
||||||
_analysis: None,
|
_analysis: None,
|
||||||
pending_note_offs: Vec::new(),
|
pending_note_offs: Vec::new(),
|
||||||
@@ -105,19 +108,36 @@ impl Plugin for CagirePlugin {
|
|||||||
const EMAIL: &'static str = "raphael.forment@gmail.com";
|
const EMAIL: &'static str = "raphael.forment@gmail.com";
|
||||||
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
||||||
|
|
||||||
const AUDIO_IO_LAYOUTS: &'static [AudioIOLayout] = &[AudioIOLayout {
|
const AUDIO_IO_LAYOUTS: &'static [AudioIOLayout] = &[
|
||||||
main_input_channels: None,
|
AudioIOLayout {
|
||||||
main_output_channels: Some(new_nonzero_u32(2)),
|
main_input_channels: None,
|
||||||
aux_input_ports: &[],
|
main_output_channels: Some(new_nonzero_u32(2)),
|
||||||
aux_output_ports: &[],
|
aux_input_ports: &[],
|
||||||
names: PortNames {
|
aux_output_ports: &[new_nonzero_u32(2); 7],
|
||||||
layout: Some("Stereo"),
|
names: PortNames {
|
||||||
main_input: None,
|
layout: Some("Multi-output"),
|
||||||
main_output: Some("Output"),
|
main_input: None,
|
||||||
aux_inputs: &[],
|
main_output: Some("Orbit 0"),
|
||||||
aux_outputs: &[],
|
aux_inputs: &[],
|
||||||
|
aux_outputs: &[
|
||||||
|
"Orbit 1", "Orbit 2", "Orbit 3", "Orbit 4", "Orbit 5", "Orbit 6", "Orbit 7",
|
||||||
|
],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}];
|
AudioIOLayout {
|
||||||
|
main_input_channels: None,
|
||||||
|
main_output_channels: Some(new_nonzero_u32(2)),
|
||||||
|
aux_input_ports: &[],
|
||||||
|
aux_output_ports: &[],
|
||||||
|
names: PortNames {
|
||||||
|
layout: Some("Stereo"),
|
||||||
|
main_input: None,
|
||||||
|
main_output: Some("Output"),
|
||||||
|
aux_inputs: &[],
|
||||||
|
aux_outputs: &[],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const MIDI_INPUT: MidiConfig = MidiConfig::MidiCCs;
|
const MIDI_INPUT: MidiConfig = MidiConfig::MidiCCs;
|
||||||
const MIDI_OUTPUT: MidiConfig = MidiConfig::MidiCCs;
|
const MIDI_OUTPUT: MidiConfig = MidiConfig::MidiCCs;
|
||||||
@@ -139,7 +159,7 @@ impl Plugin for CagirePlugin {
|
|||||||
|
|
||||||
fn initialize(
|
fn initialize(
|
||||||
&mut self,
|
&mut self,
|
||||||
_audio_io_layout: &AudioIOLayout,
|
audio_io_layout: &AudioIOLayout,
|
||||||
buffer_config: &BufferConfig,
|
buffer_config: &BufferConfig,
|
||||||
_context: &mut impl InitContext<Self>,
|
_context: &mut impl InitContext<Self>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
@@ -147,6 +167,9 @@ impl Plugin for CagirePlugin {
|
|||||||
self.sample_pos = 0;
|
self.sample_pos = 0;
|
||||||
self.prev_beat = -1.0;
|
self.prev_beat = -1.0;
|
||||||
|
|
||||||
|
let num_aux = audio_io_layout.aux_output_ports.len();
|
||||||
|
self.output_channels = 2 + num_aux * 2;
|
||||||
|
|
||||||
self.seq_state = Some(SequencerState::new(
|
self.seq_state = Some(SequencerState::new(
|
||||||
Arc::clone(&self.variables),
|
Arc::clone(&self.variables),
|
||||||
Arc::clone(&self.dict),
|
Arc::clone(&self.dict),
|
||||||
@@ -156,7 +179,7 @@ impl Plugin for CagirePlugin {
|
|||||||
|
|
||||||
let engine = doux::Engine::new_with_channels(
|
let engine = doux::Engine::new_with_channels(
|
||||||
self.sample_rate,
|
self.sample_rate,
|
||||||
2,
|
self.output_channels,
|
||||||
64,
|
64,
|
||||||
);
|
);
|
||||||
self.bridge
|
self.bridge
|
||||||
@@ -217,7 +240,7 @@ impl Plugin for CagirePlugin {
|
|||||||
fn process(
|
fn process(
|
||||||
&mut self,
|
&mut self,
|
||||||
buffer: &mut Buffer,
|
buffer: &mut Buffer,
|
||||||
_aux: &mut AuxiliaryBuffers,
|
aux: &mut AuxiliaryBuffers,
|
||||||
context: &mut impl ProcessContext<Self>,
|
context: &mut impl ProcessContext<Self>,
|
||||||
) -> ProcessStatus {
|
) -> ProcessStatus {
|
||||||
let Some(seq_state) = &mut self.seq_state else {
|
let Some(seq_state) = &mut self.seq_state else {
|
||||||
@@ -399,34 +422,45 @@ impl Plugin for CagirePlugin {
|
|||||||
engine.evaluate(cmd_ref);
|
engine.evaluate(cmd_ref);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process audio block — doux writes interleaved stereo into our buffer
|
// Process audio block — doux writes interleaved into our buffer
|
||||||
let num_samples = buffer_len * 2;
|
let total_samples = buffer_len * self.output_channels;
|
||||||
self.audio_buffer.resize(num_samples, 0.0);
|
self.audio_buffer.resize(total_samples, 0.0);
|
||||||
self.audio_buffer.fill(0.0);
|
self.audio_buffer.fill(0.0);
|
||||||
engine.process_block(&mut self.audio_buffer, &[], &[]);
|
engine.process_block(&mut self.audio_buffer, &[], &[]);
|
||||||
|
|
||||||
// Feed scope and spectrum analysis
|
// Feed scope and spectrum analysis (orbit 0 only)
|
||||||
self.bridge.scope_buffer.write(&self.audio_buffer);
|
if self.output_channels == 2 {
|
||||||
|
self.bridge.scope_buffer.write(&self.audio_buffer);
|
||||||
|
} else {
|
||||||
|
self.scope_extract_buffer.resize(buffer_len * 2, 0.0);
|
||||||
|
for i in 0..buffer_len {
|
||||||
|
self.scope_extract_buffer[i * 2] =
|
||||||
|
self.audio_buffer[i * self.output_channels];
|
||||||
|
self.scope_extract_buffer[i * 2 + 1] =
|
||||||
|
self.audio_buffer[i * self.output_channels + 1];
|
||||||
|
}
|
||||||
|
self.bridge.scope_buffer.write(&self.scope_extract_buffer);
|
||||||
|
}
|
||||||
if let Some(producer) = &mut self.fft_producer {
|
if let Some(producer) = &mut self.fft_producer {
|
||||||
for chunk in self.audio_buffer.chunks(2) {
|
let stride = self.output_channels;
|
||||||
let mono = (chunk[0] + chunk.get(1).copied().unwrap_or(0.0)) * 0.5;
|
for i in 0..buffer_len {
|
||||||
let _ = producer.try_push(mono);
|
let left = self.audio_buffer[i * stride];
|
||||||
|
let right = self.audio_buffer[i * stride + 1];
|
||||||
|
let _ = producer.try_push((left + right) * 0.5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy interleaved doux output → nih-plug channel slices
|
// De-interleave doux output into nih-plug channel buffers
|
||||||
let mut channel_iter = buffer.iter_samples();
|
let stride = self.output_channels;
|
||||||
for frame_idx in 0..buffer_len {
|
deinterleave_stereo(&self.audio_buffer, buffer.as_slice(), stride, 0, buffer_len);
|
||||||
if let Some(mut frame) = channel_iter.next() {
|
for (aux_idx, aux_buf) in aux.outputs.iter_mut().enumerate() {
|
||||||
let left = self.audio_buffer[frame_idx * 2];
|
deinterleave_stereo(
|
||||||
let right = self.audio_buffer[frame_idx * 2 + 1];
|
&self.audio_buffer,
|
||||||
if let Some(sample) = frame.get_mut(0) {
|
aux_buf.as_slice(),
|
||||||
*sample = left;
|
stride,
|
||||||
}
|
(aux_idx + 1) * 2,
|
||||||
if let Some(sample) = frame.get_mut(1) {
|
buffer_len,
|
||||||
*sample = right;
|
);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.sample_pos += buffer_len as u64;
|
self.sample_pos += buffer_len as u64;
|
||||||
@@ -445,6 +479,7 @@ impl ClapPlugin for CagirePlugin {
|
|||||||
ClapFeature::Instrument,
|
ClapFeature::Instrument,
|
||||||
ClapFeature::Synthesizer,
|
ClapFeature::Synthesizer,
|
||||||
ClapFeature::Stereo,
|
ClapFeature::Stereo,
|
||||||
|
ClapFeature::Surround,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -454,8 +489,23 @@ impl Vst3Plugin for CagirePlugin {
|
|||||||
Vst3SubCategory::Instrument,
|
Vst3SubCategory::Instrument,
|
||||||
Vst3SubCategory::Synth,
|
Vst3SubCategory::Synth,
|
||||||
Vst3SubCategory::Stereo,
|
Vst3SubCategory::Stereo,
|
||||||
|
Vst3SubCategory::Surround,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn deinterleave_stereo(
|
||||||
|
interleaved: &[f32],
|
||||||
|
out: &mut [&mut [f32]],
|
||||||
|
stride: usize,
|
||||||
|
ch_offset: usize,
|
||||||
|
len: usize,
|
||||||
|
) {
|
||||||
|
let (left, right) = out.split_at_mut(1);
|
||||||
|
for (i, (l, r)) in left[0][..len].iter_mut().zip(right[0][..len].iter_mut()).enumerate() {
|
||||||
|
*l = interleaved[i * stride + ch_offset];
|
||||||
|
*r = interleaved[i * stride + ch_offset + 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
nih_export_clap!(CagirePlugin);
|
nih_export_clap!(CagirePlugin);
|
||||||
nih_export_vst3!(CagirePlugin);
|
nih_export_vst3!(CagirePlugin);
|
||||||
|
|||||||
@@ -86,12 +86,7 @@ impl App {
|
|||||||
Self::build(variables, dict, rng, false)
|
Self::build(variables, dict, rng, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)] // used by plugin crate
|
||||||
pub fn with_shared(variables: Variables, dict: Dictionary, rng: Rng) -> Self {
|
|
||||||
Self::build(variables, dict, rng, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn new_plugin(variables: Variables, dict: Dictionary, rng: Rng) -> Self {
|
pub fn new_plugin(variables: Variables, dict: Dictionary, rng: Rng) -> Self {
|
||||||
Self::build(variables, dict, rng, true)
|
Self::build(variables, dict, rng, true)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -275,7 +275,7 @@ impl CagireDesktop {
|
|||||||
std::thread::Builder::new()
|
std::thread::Builder::new()
|
||||||
.name("sample-preload".into())
|
.name("sample-preload".into())
|
||||||
.spawn(move || {
|
.spawn(move || {
|
||||||
cagire::init::preload_sample_heads(preload_entries, sr, ®istry);
|
cagire::engine::preload_sample_heads(preload_entries, sr, ®istry);
|
||||||
})
|
})
|
||||||
.expect("failed to spawn preload thread");
|
.expect("failed to spawn preload thread");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,11 @@
|
|||||||
use cpal::traits::{DeviceTrait, StreamTrait};
|
|
||||||
use cpal::Stream;
|
|
||||||
use crossbeam_channel::Receiver;
|
|
||||||
use doux::{Engine, EngineMetrics};
|
|
||||||
use ringbuf::{traits::*, HeapRb};
|
use ringbuf::{traits::*, HeapRb};
|
||||||
use rustfft::{num_complex::Complex, FftPlanner};
|
use rustfft::{num_complex::Complex, FftPlanner};
|
||||||
use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering};
|
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::thread::{self, JoinHandle};
|
use std::thread::{self, JoinHandle};
|
||||||
|
|
||||||
use super::AudioCommand;
|
#[cfg(feature = "cli")]
|
||||||
|
use std::sync::atomic::AtomicU64;
|
||||||
|
|
||||||
pub struct ScopeBuffer {
|
pub struct ScopeBuffer {
|
||||||
pub samples: [AtomicU32; 256],
|
pub samples: [AtomicU32; 256],
|
||||||
@@ -230,6 +227,36 @@ fn analysis_loop(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn preload_sample_heads(
|
||||||
|
entries: Vec<(String, std::path::PathBuf)>,
|
||||||
|
target_sr: f32,
|
||||||
|
registry: &doux::SampleRegistry,
|
||||||
|
) {
|
||||||
|
let mut batch = Vec::with_capacity(entries.len());
|
||||||
|
for (name, path) in &entries {
|
||||||
|
match doux::sampling::decode_sample_head(path, target_sr) {
|
||||||
|
Ok(data) => batch.push((name.clone(), Arc::new(data))),
|
||||||
|
Err(e) => eprintln!("preload {name}: {e}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !batch.is_empty() {
|
||||||
|
registry.insert_batch(batch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "cli")]
|
||||||
|
use cpal::traits::{DeviceTrait, StreamTrait};
|
||||||
|
#[cfg(feature = "cli")]
|
||||||
|
use cpal::Stream;
|
||||||
|
#[cfg(feature = "cli")]
|
||||||
|
use crossbeam_channel::Receiver;
|
||||||
|
#[cfg(feature = "cli")]
|
||||||
|
use doux::{Engine, EngineMetrics};
|
||||||
|
|
||||||
|
#[cfg(feature = "cli")]
|
||||||
|
use super::AudioCommand;
|
||||||
|
|
||||||
|
#[cfg(feature = "cli")]
|
||||||
pub struct AudioStreamConfig {
|
pub struct AudioStreamConfig {
|
||||||
pub output_device: Option<String>,
|
pub output_device: Option<String>,
|
||||||
pub channels: u16,
|
pub channels: u16,
|
||||||
@@ -237,12 +264,14 @@ pub struct AudioStreamConfig {
|
|||||||
pub max_voices: usize,
|
pub max_voices: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "cli")]
|
||||||
pub struct AudioStreamInfo {
|
pub struct AudioStreamInfo {
|
||||||
pub sample_rate: f32,
|
pub sample_rate: f32,
|
||||||
pub host_name: String,
|
pub host_name: String,
|
||||||
pub channels: u16,
|
pub channels: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "cli")]
|
||||||
pub fn build_stream(
|
pub fn build_stream(
|
||||||
config: &AudioStreamConfig,
|
config: &AudioStreamConfig,
|
||||||
audio_rx: Receiver<AudioCommand>,
|
audio_rx: Receiver<AudioCommand>,
|
||||||
|
|||||||
@@ -7,16 +7,20 @@ mod timing;
|
|||||||
|
|
||||||
pub use timing::{substeps_in_window, StepTiming, SyncTime};
|
pub use timing::{substeps_in_window, StepTiming, SyncTime};
|
||||||
|
|
||||||
// AnalysisHandle and SequencerHandle are used by src/bin/desktop.rs
|
// Used by plugin and desktop crates via the lib; not by the terminal binary directly.
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
pub use audio::{
|
pub use audio::{
|
||||||
build_stream, spawn_analysis_thread, AnalysisHandle, AudioStreamConfig, ScopeBuffer,
|
preload_sample_heads, spawn_analysis_thread, AnalysisHandle, ScopeBuffer, SpectrumBuffer,
|
||||||
SpectrumBuffer,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "cli")]
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
pub use audio::{build_stream, AudioStreamConfig, AudioStreamInfo};
|
||||||
|
|
||||||
pub use link::LinkState;
|
pub use link::LinkState;
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
pub use sequencer::{
|
pub use sequencer::{
|
||||||
parse_midi_command, spawn_sequencer, AudioCommand, MidiCommand, PatternChange,
|
parse_midi_command, spawn_sequencer, AudioCommand, MidiCommand, PatternChange, PatternSnapshot,
|
||||||
PatternSnapshot, SeqCommand, SequencerConfig, SequencerHandle, SequencerSnapshot,
|
SeqCommand, SequencerConfig, SequencerHandle, SequencerSnapshot, SequencerState,
|
||||||
SequencerState, SharedSequencerState, StepSnapshot, TickInput, TickOutput, TimestampedCommand,
|
SharedSequencerState, StepSnapshot, TickInput, TickOutput, TimestampedCommand,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -28,27 +28,16 @@ mod memory {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn is_memory_locked() -> bool {
|
|
||||||
MLOCKALL_SUCCESS.load(Ordering::Relaxed)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
pub use memory::{is_memory_locked, lock_memory};
|
pub use memory::lock_memory;
|
||||||
|
|
||||||
#[cfg(not(target_os = "linux"))]
|
#[cfg(not(target_os = "linux"))]
|
||||||
pub fn lock_memory() -> bool {
|
pub fn lock_memory() -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_os = "linux"))]
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn is_memory_locked() -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Attempts to set realtime scheduling priority for the current thread.
|
/// Attempts to set realtime scheduling priority for the current thread.
|
||||||
/// Returns true if RT priority was successfully set, false otherwise.
|
/// Returns true if RT priority was successfully set, false otherwise.
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
@@ -105,7 +94,7 @@ pub fn set_realtime_priority() -> bool {
|
|||||||
/// - Configured rtprio limits in /etc/security/limits.conf:
|
/// - Configured rtprio limits in /etc/security/limits.conf:
|
||||||
/// @audio - rtprio 95
|
/// @audio - rtprio 95
|
||||||
/// @audio - memlock unlimited
|
/// @audio - memlock unlimited
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(all(target_os = "linux", feature = "cli"))]
|
||||||
pub fn set_realtime_priority() -> bool {
|
pub fn set_realtime_priority() -> bool {
|
||||||
use thread_priority::unix::{
|
use thread_priority::unix::{
|
||||||
set_thread_priority_and_policy, thread_native_id, NormalThreadSchedulePolicy,
|
set_thread_priority_and_policy, thread_native_id, NormalThreadSchedulePolicy,
|
||||||
@@ -138,17 +127,27 @@ pub fn set_realtime_priority() -> bool {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(all(target_os = "linux", not(feature = "cli")))]
|
||||||
|
pub fn set_realtime_priority() -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(any(unix, target_os = "windows")))]
|
#[cfg(not(any(unix, target_os = "windows")))]
|
||||||
pub fn set_realtime_priority() -> bool {
|
pub fn set_realtime_priority() -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(all(target_os = "windows", feature = "cli"))]
|
||||||
pub fn set_realtime_priority() -> bool {
|
pub fn set_realtime_priority() -> bool {
|
||||||
use thread_priority::{set_current_thread_priority, ThreadPriority};
|
use thread_priority::{set_current_thread_priority, ThreadPriority};
|
||||||
set_current_thread_priority(ThreadPriority::Max).is_ok()
|
set_current_thread_priority(ThreadPriority::Max).is_ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(all(target_os = "windows", not(feature = "cli")))]
|
||||||
|
pub fn set_realtime_priority() -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
/// High-precision sleep using clock_nanosleep on Linux.
|
/// High-precision sleep using clock_nanosleep on Linux.
|
||||||
/// Uses monotonic clock for jitter-free sleeping.
|
/// Uses monotonic clock for jitter-free sleeping.
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
|
|||||||
21
src/init.rs
21
src/init.rs
@@ -7,8 +7,9 @@ use doux::EngineMetrics;
|
|||||||
|
|
||||||
use crate::app::App;
|
use crate::app::App;
|
||||||
use crate::engine::{
|
use crate::engine::{
|
||||||
build_stream, spawn_sequencer, AnalysisHandle, AudioStreamConfig, LinkState, MidiCommand,
|
build_stream, preload_sample_heads, spawn_sequencer, AnalysisHandle, AudioStreamConfig,
|
||||||
PatternChange, ScopeBuffer, SequencerConfig, SequencerHandle, SpectrumBuffer,
|
LinkState, MidiCommand, PatternChange, ScopeBuffer, SequencerConfig, SequencerHandle,
|
||||||
|
SpectrumBuffer,
|
||||||
};
|
};
|
||||||
use crate::midi;
|
use crate::midi;
|
||||||
use crate::model;
|
use crate::model;
|
||||||
@@ -230,19 +231,3 @@ pub fn init(args: InitArgs) -> Init {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn preload_sample_heads(
|
|
||||||
entries: Vec<(String, std::path::PathBuf)>,
|
|
||||||
target_sr: f32,
|
|
||||||
registry: &doux::SampleRegistry,
|
|
||||||
) {
|
|
||||||
let mut batch = Vec::with_capacity(entries.len());
|
|
||||||
for (name, path) in &entries {
|
|
||||||
match doux::sampling::decode_sample_head(path, target_sr) {
|
|
||||||
Ok(data) => batch.push((name.clone(), Arc::new(data))),
|
|
||||||
Err(e) => eprintln!("preload {name}: {e}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !batch.is_empty() {
|
|
||||||
registry.insert_batch(batch);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -209,7 +209,7 @@ fn load_project_samples(ctx: &mut InputContext) {
|
|||||||
std::thread::Builder::new()
|
std::thread::Builder::new()
|
||||||
.name("sample-preload".into())
|
.name("sample-preload".into())
|
||||||
.spawn(move || {
|
.spawn(move || {
|
||||||
crate::init::preload_sample_heads(all_preload_entries, sr, ®istry);
|
crate::engine::preload_sample_heads(all_preload_entries, sr, ®istry);
|
||||||
})
|
})
|
||||||
.expect("failed to spawn preload thread");
|
.expect("failed to spawn preload thread");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -208,7 +208,7 @@ pub(super) fn handle_modal_input(ctx: &mut InputContext, key: KeyEvent) -> Input
|
|||||||
std::thread::Builder::new()
|
std::thread::Builder::new()
|
||||||
.name("sample-preload".into())
|
.name("sample-preload".into())
|
||||||
.spawn(move || {
|
.spawn(move || {
|
||||||
crate::init::preload_sample_heads(preload_entries, sr, ®istry);
|
crate::engine::preload_sample_heads(preload_entries, sr, ®istry);
|
||||||
})
|
})
|
||||||
.expect("failed to spawn preload thread");
|
.expect("failed to spawn preload thread");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
pub use cagire_forth as forth;
|
pub use cagire_forth as forth;
|
||||||
|
|
||||||
pub mod app;
|
pub mod app;
|
||||||
|
#[cfg(feature = "cli")]
|
||||||
pub mod init;
|
pub mod init;
|
||||||
pub mod commands;
|
pub mod commands;
|
||||||
pub mod engine;
|
pub mod engine;
|
||||||
@@ -18,5 +19,5 @@ pub mod widgets;
|
|||||||
#[cfg(feature = "block-renderer")]
|
#[cfg(feature = "block-renderer")]
|
||||||
pub mod block_renderer;
|
pub mod block_renderer;
|
||||||
|
|
||||||
#[cfg(feature = "desktop")]
|
#[cfg(feature = "block-renderer")]
|
||||||
pub mod input_egui;
|
pub mod input_egui;
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ fn main() -> io::Result<()> {
|
|||||||
std::thread::Builder::new()
|
std::thread::Builder::new()
|
||||||
.name("sample-preload".into())
|
.name("sample-preload".into())
|
||||||
.spawn(move || {
|
.spawn(move || {
|
||||||
init::preload_sample_heads(preload_entries, sr, ®istry);
|
engine::preload_sample_heads(preload_entries, sr, ®istry);
|
||||||
})
|
})
|
||||||
.expect("failed to spawn preload thread");
|
.expect("failed to spawn preload thread");
|
||||||
}
|
}
|
||||||
|
|||||||
64
src/midi.rs
64
src/midi.rs
@@ -1,8 +1,6 @@
|
|||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use midir::{MidiInput, MidiOutput};
|
|
||||||
|
|
||||||
use crate::model::CcAccess;
|
use crate::model::CcAccess;
|
||||||
|
|
||||||
pub const MAX_MIDI_OUTPUTS: usize = 4;
|
pub const MAX_MIDI_OUTPUTS: usize = 4;
|
||||||
@@ -22,12 +20,12 @@ impl CcMemory {
|
|||||||
Self(Arc::new(Mutex::new([[[0u8; 128]; 16]; MAX_MIDI_DEVICES])))
|
Self(Arc::new(Mutex::new([[[0u8; 128]; 16]; MAX_MIDI_DEVICES])))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "cli")]
|
||||||
fn inner(&self) -> &CcMemoryInner {
|
fn inner(&self) -> &CcMemoryInner {
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a CC value (for testing)
|
#[allow(dead_code)] // used by integration tests
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn set_cc(&self, device: usize, channel: usize, cc: usize, value: u8) {
|
pub fn set_cc(&self, device: usize, channel: usize, cc: usize, value: u8) {
|
||||||
let mut mem = self.0.lock();
|
let mut mem = self.0.lock();
|
||||||
mem[device.min(MAX_MIDI_DEVICES - 1)][channel.min(15)][cc.min(127)] = value;
|
mem[device.min(MAX_MIDI_DEVICES - 1)][channel.min(15)][cc.min(127)] = value;
|
||||||
@@ -52,8 +50,9 @@ pub struct MidiDeviceInfo {
|
|||||||
pub name: String,
|
pub name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "cli")]
|
||||||
pub fn list_midi_outputs() -> Vec<MidiDeviceInfo> {
|
pub fn list_midi_outputs() -> Vec<MidiDeviceInfo> {
|
||||||
let Ok(midi_out) = MidiOutput::new("cagire-probe") else {
|
let Ok(midi_out) = midir::MidiOutput::new("cagire-probe") else {
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
};
|
};
|
||||||
midi_out
|
midi_out
|
||||||
@@ -68,8 +67,14 @@ pub fn list_midi_outputs() -> Vec<MidiDeviceInfo> {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "cli"))]
|
||||||
|
pub fn list_midi_outputs() -> Vec<MidiDeviceInfo> {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "cli")]
|
||||||
pub fn list_midi_inputs() -> Vec<MidiDeviceInfo> {
|
pub fn list_midi_inputs() -> Vec<MidiDeviceInfo> {
|
||||||
let Ok(midi_in) = MidiInput::new("cagire-probe") else {
|
let Ok(midi_in) = midir::MidiInput::new("cagire-probe") else {
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
};
|
};
|
||||||
midi_in
|
midi_in
|
||||||
@@ -84,8 +89,15 @@ pub fn list_midi_inputs() -> Vec<MidiDeviceInfo> {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "cli"))]
|
||||||
|
pub fn list_midi_inputs() -> Vec<MidiDeviceInfo> {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
|
||||||
pub struct MidiState {
|
pub struct MidiState {
|
||||||
|
#[cfg(feature = "cli")]
|
||||||
output_conns: [Option<midir::MidiOutputConnection>; MAX_MIDI_OUTPUTS],
|
output_conns: [Option<midir::MidiOutputConnection>; MAX_MIDI_OUTPUTS],
|
||||||
|
#[cfg(feature = "cli")]
|
||||||
input_conns: [Option<midir::MidiInputConnection<(CcMemoryInner, usize)>>; MAX_MIDI_INPUTS],
|
input_conns: [Option<midir::MidiInputConnection<(CcMemoryInner, usize)>>; MAX_MIDI_INPUTS],
|
||||||
pub selected_outputs: [Option<usize>; MAX_MIDI_OUTPUTS],
|
pub selected_outputs: [Option<usize>; MAX_MIDI_OUTPUTS],
|
||||||
pub selected_inputs: [Option<usize>; MAX_MIDI_INPUTS],
|
pub selected_inputs: [Option<usize>; MAX_MIDI_INPUTS],
|
||||||
@@ -101,7 +113,9 @@ impl Default for MidiState {
|
|||||||
impl MidiState {
|
impl MidiState {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
#[cfg(feature = "cli")]
|
||||||
output_conns: [None, None, None, None],
|
output_conns: [None, None, None, None],
|
||||||
|
#[cfg(feature = "cli")]
|
||||||
input_conns: [None, None, None, None],
|
input_conns: [None, None, None, None],
|
||||||
selected_outputs: [None; MAX_MIDI_OUTPUTS],
|
selected_outputs: [None; MAX_MIDI_OUTPUTS],
|
||||||
selected_inputs: [None; MAX_MIDI_INPUTS],
|
selected_inputs: [None; MAX_MIDI_INPUTS],
|
||||||
@@ -109,11 +123,13 @@ impl MidiState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "cli")]
|
||||||
pub fn connect_output(&mut self, slot: usize, port_index: usize) -> Result<(), String> {
|
pub fn connect_output(&mut self, slot: usize, port_index: usize) -> Result<(), String> {
|
||||||
if slot >= MAX_MIDI_OUTPUTS {
|
if slot >= MAX_MIDI_OUTPUTS {
|
||||||
return Err("Invalid output slot".to_string());
|
return Err("Invalid output slot".to_string());
|
||||||
}
|
}
|
||||||
let midi_out = MidiOutput::new(&format!("cagire-out-{slot}")).map_err(|e| e.to_string())?;
|
let midi_out =
|
||||||
|
midir::MidiOutput::new(&format!("cagire-out-{slot}")).map_err(|e| e.to_string())?;
|
||||||
let ports = midi_out.ports();
|
let ports = midi_out.ports();
|
||||||
let port = ports.get(port_index).ok_or("MIDI output port not found")?;
|
let port = ports.get(port_index).ok_or("MIDI output port not found")?;
|
||||||
let conn = midi_out
|
let conn = midi_out
|
||||||
@@ -124,6 +140,12 @@ impl MidiState {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "cli"))]
|
||||||
|
pub fn connect_output(&mut self, _slot: usize, _port_index: usize) -> Result<(), String> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "cli")]
|
||||||
pub fn disconnect_output(&mut self, slot: usize) {
|
pub fn disconnect_output(&mut self, slot: usize) {
|
||||||
if slot < MAX_MIDI_OUTPUTS {
|
if slot < MAX_MIDI_OUTPUTS {
|
||||||
self.output_conns[slot] = None;
|
self.output_conns[slot] = None;
|
||||||
@@ -131,11 +153,20 @@ impl MidiState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "cli"))]
|
||||||
|
pub fn disconnect_output(&mut self, slot: usize) {
|
||||||
|
if slot < MAX_MIDI_OUTPUTS {
|
||||||
|
self.selected_outputs[slot] = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "cli")]
|
||||||
pub fn connect_input(&mut self, slot: usize, port_index: usize) -> Result<(), String> {
|
pub fn connect_input(&mut self, slot: usize, port_index: usize) -> Result<(), String> {
|
||||||
if slot >= MAX_MIDI_INPUTS {
|
if slot >= MAX_MIDI_INPUTS {
|
||||||
return Err("Invalid input slot".to_string());
|
return Err("Invalid input slot".to_string());
|
||||||
}
|
}
|
||||||
let midi_in = MidiInput::new(&format!("cagire-in-{slot}")).map_err(|e| e.to_string())?;
|
let midi_in =
|
||||||
|
midir::MidiInput::new(&format!("cagire-in-{slot}")).map_err(|e| e.to_string())?;
|
||||||
let ports = midi_in.ports();
|
let ports = midi_in.ports();
|
||||||
let port = ports.get(port_index).ok_or("MIDI input port not found")?;
|
let port = ports.get(port_index).ok_or("MIDI input port not found")?;
|
||||||
|
|
||||||
@@ -165,6 +196,12 @@ impl MidiState {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "cli"))]
|
||||||
|
pub fn connect_input(&mut self, _slot: usize, _port_index: usize) -> Result<(), String> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "cli")]
|
||||||
pub fn disconnect_input(&mut self, slot: usize) {
|
pub fn disconnect_input(&mut self, slot: usize) {
|
||||||
if slot < MAX_MIDI_INPUTS {
|
if slot < MAX_MIDI_INPUTS {
|
||||||
self.input_conns[slot] = None;
|
self.input_conns[slot] = None;
|
||||||
@@ -172,6 +209,14 @@ impl MidiState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "cli"))]
|
||||||
|
pub fn disconnect_input(&mut self, slot: usize) {
|
||||||
|
if slot < MAX_MIDI_INPUTS {
|
||||||
|
self.selected_inputs[slot] = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "cli")]
|
||||||
fn send_message(&mut self, device: u8, message: &[u8]) {
|
fn send_message(&mut self, device: u8, message: &[u8]) {
|
||||||
let slot = (device as usize).min(MAX_MIDI_OUTPUTS - 1);
|
let slot = (device as usize).min(MAX_MIDI_OUTPUTS - 1);
|
||||||
if let Some(conn) = &mut self.output_conns[slot] {
|
if let Some(conn) = &mut self.output_conns[slot] {
|
||||||
@@ -179,6 +224,9 @@ impl MidiState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "cli"))]
|
||||||
|
fn send_message(&mut self, _device: u8, _message: &[u8]) {}
|
||||||
|
|
||||||
pub fn send_note_on(&mut self, device: u8, channel: u8, note: u8, velocity: u8) {
|
pub fn send_note_on(&mut self, device: u8, channel: u8, note: u8, velocity: u8) {
|
||||||
let status = 0x90 | (channel & 0x0F);
|
let status = 0x90 | (channel & 0x0F);
|
||||||
self.send_message(device, &[status, note & 0x7F, velocity & 0x7F]);
|
self.send_message(device, &[status, note & 0x7F, velocity & 0x7F]);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
use crate::state::{ColorScheme, MainLayout};
|
use crate::state::{ColorScheme, MainLayout};
|
||||||
|
|
||||||
|
#[cfg(feature = "cli")]
|
||||||
const APP_NAME: &str = "cagire";
|
const APP_NAME: &str = "cagire";
|
||||||
|
|
||||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||||
@@ -116,6 +117,7 @@ impl Default for LinkSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Settings {
|
impl Settings {
|
||||||
|
#[cfg(feature = "cli")]
|
||||||
pub fn load() -> Self {
|
pub fn load() -> Self {
|
||||||
let mut settings: Self = confy::load(APP_NAME, None).unwrap_or_default();
|
let mut settings: Self = confy::load(APP_NAME, None).unwrap_or_default();
|
||||||
if settings.audio.channels == 0 {
|
if settings.audio.channels == 0 {
|
||||||
@@ -127,10 +129,19 @@ impl Settings {
|
|||||||
settings
|
settings
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "cli"))]
|
||||||
|
pub fn load() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "cli")]
|
||||||
pub fn save(&self) {
|
pub fn save(&self) {
|
||||||
if let Err(e) = confy::store(APP_NAME, None, self) {
|
if let Err(e) = confy::store(APP_NAME, None, self) {
|
||||||
eprintln!("Failed to save settings: {e}");
|
eprintln!("Failed to save settings: {e}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "cli"))]
|
||||||
|
pub fn save(&self) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user