MIDI Documentation and optional mouse event support
This commit is contained in:
18
src/app.rs
18
src/app.rs
@@ -333,6 +333,12 @@ impl App {
|
||||
fill: false,
|
||||
nudge_secs: 0.0,
|
||||
cc_memory: None,
|
||||
#[cfg(feature = "desktop")]
|
||||
mouse_x: 0.5,
|
||||
#[cfg(feature = "desktop")]
|
||||
mouse_y: 0.5,
|
||||
#[cfg(feature = "desktop")]
|
||||
mouse_down: 0.0,
|
||||
};
|
||||
|
||||
let cmds = self.script_engine.evaluate(script, &ctx)?;
|
||||
@@ -384,6 +390,12 @@ impl App {
|
||||
fill: false,
|
||||
nudge_secs: 0.0,
|
||||
cc_memory: None,
|
||||
#[cfg(feature = "desktop")]
|
||||
mouse_x: 0.5,
|
||||
#[cfg(feature = "desktop")]
|
||||
mouse_y: 0.5,
|
||||
#[cfg(feature = "desktop")]
|
||||
mouse_down: 0.0,
|
||||
};
|
||||
|
||||
match self.script_engine.evaluate(&script, &ctx) {
|
||||
@@ -462,6 +474,12 @@ impl App {
|
||||
fill: false,
|
||||
nudge_secs: 0.0,
|
||||
cc_memory: None,
|
||||
#[cfg(feature = "desktop")]
|
||||
mouse_x: 0.5,
|
||||
#[cfg(feature = "desktop")]
|
||||
mouse_y: 0.5,
|
||||
#[cfg(feature = "desktop")]
|
||||
mouse_down: 0.0,
|
||||
};
|
||||
|
||||
if let Ok(cmds) = self.script_engine.evaluate(&script, &ctx) {
|
||||
|
||||
@@ -19,9 +19,10 @@ use soft_ratatui::{EmbeddedGraphics, SoftBackend};
|
||||
|
||||
use cagire::app::App;
|
||||
use cagire::engine::{
|
||||
build_stream, spawn_sequencer, AnalysisHandle, AudioStreamConfig, LinkState, ScopeBuffer,
|
||||
SequencerConfig, SequencerHandle, SpectrumBuffer,
|
||||
build_stream, spawn_sequencer, AnalysisHandle, AudioStreamConfig, LinkState, MidiCommand,
|
||||
ScopeBuffer, SequencerConfig, SequencerHandle, SpectrumBuffer,
|
||||
};
|
||||
use crossbeam_channel::Receiver;
|
||||
use cagire::input::{handle_key, InputContext, InputResult};
|
||||
use cagire::input_egui::convert_egui_events;
|
||||
use cagire::settings::Settings;
|
||||
@@ -144,7 +145,11 @@ struct CagireDesktop {
|
||||
sample_rate_shared: Arc<AtomicU32>,
|
||||
_stream: Option<cpal::Stream>,
|
||||
_analysis_handle: Option<AnalysisHandle>,
|
||||
midi_rx: Receiver<MidiCommand>,
|
||||
current_font: FontChoice,
|
||||
mouse_x: Arc<AtomicU32>,
|
||||
mouse_y: Arc<AtomicU32>,
|
||||
mouse_down: Arc<AtomicU32>,
|
||||
}
|
||||
|
||||
impl CagireDesktop {
|
||||
@@ -201,13 +206,21 @@ impl CagireDesktop {
|
||||
initial_samples.extend(index);
|
||||
}
|
||||
|
||||
let mouse_x = Arc::new(AtomicU32::new(0.5_f32.to_bits()));
|
||||
let mouse_y = Arc::new(AtomicU32::new(0.5_f32.to_bits()));
|
||||
let mouse_down = Arc::new(AtomicU32::new(0.0_f32.to_bits()));
|
||||
|
||||
let seq_config = SequencerConfig {
|
||||
audio_sample_pos: Arc::clone(&audio_sample_pos),
|
||||
sample_rate: Arc::clone(&sample_rate_shared),
|
||||
lookahead_ms: Arc::clone(&lookahead_ms),
|
||||
cc_memory: Some(Arc::clone(&app.midi.cc_memory)),
|
||||
mouse_x: Arc::clone(&mouse_x),
|
||||
mouse_y: Arc::clone(&mouse_y),
|
||||
mouse_down: Arc::clone(&mouse_down),
|
||||
};
|
||||
|
||||
let (sequencer, initial_audio_rx) = spawn_sequencer(
|
||||
let (sequencer, initial_audio_rx, midi_rx) = spawn_sequencer(
|
||||
Arc::clone(&link),
|
||||
Arc::clone(&playing),
|
||||
Arc::clone(&app.variables),
|
||||
@@ -268,7 +281,11 @@ impl CagireDesktop {
|
||||
sample_rate_shared,
|
||||
_stream: stream,
|
||||
_analysis_handle: analysis_handle,
|
||||
midi_rx,
|
||||
current_font,
|
||||
mouse_x,
|
||||
mouse_y,
|
||||
mouse_down,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,6 +302,7 @@ impl CagireDesktop {
|
||||
return;
|
||||
};
|
||||
let new_audio_rx = sequencer.swap_audio_channel();
|
||||
self.midi_rx = sequencer.swap_midi_channel();
|
||||
|
||||
let new_config = AudioStreamConfig {
|
||||
output_device: self.app.audio.config.output_device.clone(),
|
||||
@@ -373,6 +391,18 @@ impl eframe::App for CagireDesktop {
|
||||
self.handle_audio_restart();
|
||||
self.update_metrics();
|
||||
|
||||
ctx.input(|i| {
|
||||
if let Some(pos) = i.pointer.latest_pos() {
|
||||
let screen = i.viewport_rect();
|
||||
let nx = (pos.x / screen.width()).clamp(0.0, 1.0);
|
||||
let ny = (pos.y / screen.height()).clamp(0.0, 1.0);
|
||||
self.mouse_x.store(nx.to_bits(), Ordering::Relaxed);
|
||||
self.mouse_y.store(ny.to_bits(), Ordering::Relaxed);
|
||||
}
|
||||
let down = if i.pointer.primary_down() { 1.0_f32 } else { 0.0_f32 };
|
||||
self.mouse_down.store(down.to_bits(), Ordering::Relaxed);
|
||||
});
|
||||
|
||||
let Some(ref sequencer) = self.sequencer else {
|
||||
return;
|
||||
};
|
||||
@@ -395,6 +425,33 @@ impl eframe::App for CagireDesktop {
|
||||
self.app.flush_queued_changes(&sequencer.cmd_tx);
|
||||
self.app.flush_dirty_patterns(&sequencer.cmd_tx);
|
||||
|
||||
while let Ok(midi_cmd) = self.midi_rx.try_recv() {
|
||||
match midi_cmd {
|
||||
MidiCommand::NoteOn { device, channel, note, velocity } => {
|
||||
self.app.midi.send_note_on(device, channel, note, velocity);
|
||||
}
|
||||
MidiCommand::NoteOff { device, channel, note } => {
|
||||
self.app.midi.send_note_off(device, channel, note);
|
||||
}
|
||||
MidiCommand::CC { device, channel, cc, value } => {
|
||||
self.app.midi.send_cc(device, channel, cc, value);
|
||||
}
|
||||
MidiCommand::PitchBend { device, channel, value } => {
|
||||
self.app.midi.send_pitch_bend(device, channel, value);
|
||||
}
|
||||
MidiCommand::Pressure { device, channel, value } => {
|
||||
self.app.midi.send_pressure(device, channel, value);
|
||||
}
|
||||
MidiCommand::ProgramChange { device, channel, program } => {
|
||||
self.app.midi.send_program_change(device, channel, program);
|
||||
}
|
||||
MidiCommand::Clock { device } => self.app.midi.send_realtime(device, 0xF8),
|
||||
MidiCommand::Start { device } => self.app.midi.send_realtime(device, 0xFA),
|
||||
MidiCommand::Stop { device } => self.app.midi.send_realtime(device, 0xFC),
|
||||
MidiCommand::Continue { device } => self.app.midi.send_realtime(device, 0xFB),
|
||||
}
|
||||
}
|
||||
|
||||
let should_quit = self.handle_input(ctx);
|
||||
if should_quit {
|
||||
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use arc_swap::ArcSwap;
|
||||
use crossbeam_channel::{bounded, Receiver, Sender, TrySendError};
|
||||
use std::collections::HashMap;
|
||||
#[cfg(feature = "desktop")]
|
||||
use std::sync::atomic::AtomicU32;
|
||||
use std::sync::atomic::{AtomicI64, AtomicU64};
|
||||
use std::sync::Arc;
|
||||
use std::thread::{self, JoinHandle};
|
||||
@@ -232,6 +234,12 @@ pub struct SequencerConfig {
|
||||
pub sample_rate: Arc<std::sync::atomic::AtomicU32>,
|
||||
pub lookahead_ms: Arc<std::sync::atomic::AtomicU32>,
|
||||
pub cc_memory: Option<CcMemory>,
|
||||
#[cfg(feature = "desktop")]
|
||||
pub mouse_x: Arc<AtomicU32>,
|
||||
#[cfg(feature = "desktop")]
|
||||
pub mouse_y: Arc<AtomicU32>,
|
||||
#[cfg(feature = "desktop")]
|
||||
pub mouse_down: Arc<AtomicU32>,
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@@ -257,6 +265,13 @@ pub fn spawn_sequencer(
|
||||
let audio_tx_for_thread = Arc::clone(&audio_tx);
|
||||
let midi_tx_for_thread = Arc::clone(&midi_tx);
|
||||
|
||||
#[cfg(feature = "desktop")]
|
||||
let mouse_x = config.mouse_x;
|
||||
#[cfg(feature = "desktop")]
|
||||
let mouse_y = config.mouse_y;
|
||||
#[cfg(feature = "desktop")]
|
||||
let mouse_down = config.mouse_down;
|
||||
|
||||
let thread = thread::Builder::new()
|
||||
.name("sequencer".into())
|
||||
.spawn(move || {
|
||||
@@ -277,6 +292,12 @@ pub fn spawn_sequencer(
|
||||
config.sample_rate,
|
||||
config.lookahead_ms,
|
||||
config.cc_memory,
|
||||
#[cfg(feature = "desktop")]
|
||||
mouse_x,
|
||||
#[cfg(feature = "desktop")]
|
||||
mouse_y,
|
||||
#[cfg(feature = "desktop")]
|
||||
mouse_down,
|
||||
);
|
||||
})
|
||||
.expect("Failed to spawn sequencer thread");
|
||||
@@ -407,6 +428,12 @@ pub(crate) struct TickInput {
|
||||
pub current_time_us: i64,
|
||||
pub engine_time: f64,
|
||||
pub lookahead_secs: f64,
|
||||
#[cfg(feature = "desktop")]
|
||||
pub mouse_x: f64,
|
||||
#[cfg(feature = "desktop")]
|
||||
pub mouse_y: f64,
|
||||
#[cfg(feature = "desktop")]
|
||||
pub mouse_down: f64,
|
||||
}
|
||||
|
||||
pub struct TimestampedCommand {
|
||||
@@ -591,6 +618,12 @@ impl SequencerState {
|
||||
input.current_time_us,
|
||||
input.engine_time,
|
||||
input.lookahead_secs,
|
||||
#[cfg(feature = "desktop")]
|
||||
input.mouse_x,
|
||||
#[cfg(feature = "desktop")]
|
||||
input.mouse_y,
|
||||
#[cfg(feature = "desktop")]
|
||||
input.mouse_down,
|
||||
);
|
||||
|
||||
let vars = self.read_variables(&steps.completed_iterations, &stopped, steps.any_step_fired);
|
||||
@@ -684,6 +717,9 @@ impl SequencerState {
|
||||
_current_time_us: i64,
|
||||
engine_time: f64,
|
||||
lookahead_secs: f64,
|
||||
#[cfg(feature = "desktop")] mouse_x: f64,
|
||||
#[cfg(feature = "desktop")] mouse_y: f64,
|
||||
#[cfg(feature = "desktop")] mouse_down: f64,
|
||||
) -> StepResult {
|
||||
self.buf_audio_commands.clear();
|
||||
let mut result = StepResult {
|
||||
@@ -746,6 +782,12 @@ impl SequencerState {
|
||||
fill,
|
||||
nudge_secs,
|
||||
cc_memory: self.cc_memory.clone(),
|
||||
#[cfg(feature = "desktop")]
|
||||
mouse_x,
|
||||
#[cfg(feature = "desktop")]
|
||||
mouse_y,
|
||||
#[cfg(feature = "desktop")]
|
||||
mouse_down,
|
||||
};
|
||||
if let Some(script) = resolved_script {
|
||||
let mut trace = ExecutionTrace::default();
|
||||
@@ -902,6 +944,9 @@ fn sequencer_loop(
|
||||
sample_rate: Arc<std::sync::atomic::AtomicU32>,
|
||||
lookahead_ms: Arc<std::sync::atomic::AtomicU32>,
|
||||
cc_memory: Option<CcMemory>,
|
||||
#[cfg(feature = "desktop")] mouse_x: Arc<AtomicU32>,
|
||||
#[cfg(feature = "desktop")] mouse_y: Arc<AtomicU32>,
|
||||
#[cfg(feature = "desktop")] mouse_down: Arc<AtomicU32>,
|
||||
) {
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
@@ -943,6 +988,12 @@ fn sequencer_loop(
|
||||
current_time_us,
|
||||
engine_time,
|
||||
lookahead_secs,
|
||||
#[cfg(feature = "desktop")]
|
||||
mouse_x: f32::from_bits(mouse_x.load(Ordering::Relaxed)) as f64,
|
||||
#[cfg(feature = "desktop")]
|
||||
mouse_y: f32::from_bits(mouse_y.load(Ordering::Relaxed)) as f64,
|
||||
#[cfg(feature = "desktop")]
|
||||
mouse_down: f32::from_bits(mouse_down.load(Ordering::Relaxed)) as f64,
|
||||
};
|
||||
|
||||
let output = seq_state.tick(input);
|
||||
@@ -1141,6 +1192,12 @@ mod tests {
|
||||
current_time_us: 0,
|
||||
engine_time: 0.0,
|
||||
lookahead_secs: 0.0,
|
||||
#[cfg(feature = "desktop")]
|
||||
mouse_x: 0.5,
|
||||
#[cfg(feature = "desktop")]
|
||||
mouse_y: 0.5,
|
||||
#[cfg(feature = "desktop")]
|
||||
mouse_down: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1156,6 +1213,12 @@ mod tests {
|
||||
current_time_us: 0,
|
||||
engine_time: 0.0,
|
||||
lookahead_secs: 0.0,
|
||||
#[cfg(feature = "desktop")]
|
||||
mouse_x: 0.5,
|
||||
#[cfg(feature = "desktop")]
|
||||
mouse_y: 0.5,
|
||||
#[cfg(feature = "desktop")]
|
||||
mouse_down: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
13
src/main.rs
13
src/main.rs
@@ -135,11 +135,24 @@ fn main() -> io::Result<()> {
|
||||
initial_samples.extend(index);
|
||||
}
|
||||
|
||||
#[cfg(feature = "desktop")]
|
||||
let mouse_x = Arc::new(AtomicU32::new(0.5_f32.to_bits()));
|
||||
#[cfg(feature = "desktop")]
|
||||
let mouse_y = Arc::new(AtomicU32::new(0.5_f32.to_bits()));
|
||||
#[cfg(feature = "desktop")]
|
||||
let mouse_down = Arc::new(AtomicU32::new(0.0_f32.to_bits()));
|
||||
|
||||
let seq_config = SequencerConfig {
|
||||
audio_sample_pos: Arc::clone(&audio_sample_pos),
|
||||
sample_rate: Arc::clone(&sample_rate_shared),
|
||||
lookahead_ms: Arc::clone(&lookahead_ms),
|
||||
cc_memory: Some(Arc::clone(&app.midi.cc_memory)),
|
||||
#[cfg(feature = "desktop")]
|
||||
mouse_x: Arc::clone(&mouse_x),
|
||||
#[cfg(feature = "desktop")]
|
||||
mouse_y: Arc::clone(&mouse_y),
|
||||
#[cfg(feature = "desktop")]
|
||||
mouse_down: Arc::clone(&mouse_down),
|
||||
};
|
||||
|
||||
let (sequencer, initial_audio_rx, mut midi_rx) = spawn_sequencer(
|
||||
|
||||
@@ -59,6 +59,11 @@ const DOCS: &[DocEntry] = &[
|
||||
),
|
||||
Topic("Space & Time", include_str!("../../docs/engine_space.md")),
|
||||
Topic("Words & Sounds", include_str!("../../docs/engine_words.md")),
|
||||
// MIDI
|
||||
Section("MIDI"),
|
||||
Topic("Introduction", include_str!("../../docs/midi_intro.md")),
|
||||
Topic("MIDI Output", include_str!("../../docs/midi_output.md")),
|
||||
Topic("MIDI Input", include_str!("../../docs/midi_input.md")),
|
||||
];
|
||||
|
||||
pub fn topic_count() -> usize {
|
||||
|
||||
@@ -71,6 +71,12 @@ fn compute_stack_display(lines: &[String], editor: &cagire_ratatui::Editor, cach
|
||||
fill: false,
|
||||
nudge_secs: 0.0,
|
||||
cc_memory: None,
|
||||
#[cfg(feature = "desktop")]
|
||||
mouse_x: 0.5,
|
||||
#[cfg(feature = "desktop")]
|
||||
mouse_y: 0.5,
|
||||
#[cfg(feature = "desktop")]
|
||||
mouse_down: 0.0,
|
||||
};
|
||||
|
||||
match forth.evaluate(&script, &ctx) {
|
||||
|
||||
Reference in New Issue
Block a user