MIDI Documentation and optional mouse event support
Some checks failed
Deploy Website / deploy (push) Failing after 4m45s
Some checks failed
Deploy Website / deploy (push) Failing after 4m45s
This commit is contained in:
@@ -22,6 +22,7 @@ required-features = ["desktop"]
|
||||
[features]
|
||||
default = []
|
||||
desktop = [
|
||||
"cagire-forth/desktop",
|
||||
"egui",
|
||||
"eframe",
|
||||
"egui_ratatui",
|
||||
|
||||
@@ -3,5 +3,9 @@ name = "cagire-forth"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
desktop = []
|
||||
|
||||
[dependencies]
|
||||
rand = "0.8"
|
||||
|
||||
@@ -32,6 +32,12 @@ pub struct StepContext {
|
||||
pub fill: bool,
|
||||
pub nudge_secs: f64,
|
||||
pub cc_memory: Option<CcMemory>,
|
||||
#[cfg(feature = "desktop")]
|
||||
pub mouse_x: f64,
|
||||
#[cfg(feature = "desktop")]
|
||||
pub mouse_y: f64,
|
||||
#[cfg(feature = "desktop")]
|
||||
pub mouse_down: f64,
|
||||
}
|
||||
|
||||
impl StepContext {
|
||||
|
||||
@@ -427,6 +427,12 @@ impl Forth {
|
||||
"speed" => Value::Float(ctx.speed, None),
|
||||
"stepdur" => Value::Float(ctx.step_duration(), None),
|
||||
"fill" => Value::Int(if ctx.fill { 1 } else { 0 }, None),
|
||||
#[cfg(feature = "desktop")]
|
||||
"mx" => Value::Float(ctx.mouse_x, None),
|
||||
#[cfg(feature = "desktop")]
|
||||
"my" => Value::Float(ctx.mouse_y, None),
|
||||
#[cfg(feature = "desktop")]
|
||||
"mdown" => Value::Float(ctx.mouse_down, None),
|
||||
_ => Value::Int(0, None),
|
||||
};
|
||||
stack.push(val);
|
||||
|
||||
@@ -753,6 +753,39 @@ pub const WORDS: &[Word] = &[
|
||||
compile: Context("fill"),
|
||||
varargs: false,
|
||||
},
|
||||
#[cfg(feature = "desktop")]
|
||||
Word {
|
||||
name: "mx",
|
||||
aliases: &[],
|
||||
category: "Desktop",
|
||||
stack: "(-- x)",
|
||||
desc: "Normalized mouse X position (0-1)",
|
||||
example: "mx 440 880 range freq",
|
||||
compile: Context("mx"),
|
||||
varargs: false,
|
||||
},
|
||||
#[cfg(feature = "desktop")]
|
||||
Word {
|
||||
name: "my",
|
||||
aliases: &[],
|
||||
category: "Desktop",
|
||||
stack: "(-- y)",
|
||||
desc: "Normalized mouse Y position (0-1)",
|
||||
example: "my 0.1 0.9 range gain",
|
||||
compile: Context("my"),
|
||||
varargs: false,
|
||||
},
|
||||
#[cfg(feature = "desktop")]
|
||||
Word {
|
||||
name: "mdown",
|
||||
aliases: &[],
|
||||
category: "Desktop",
|
||||
stack: "(-- bool)",
|
||||
desc: "1 when mouse button held, 0 otherwise",
|
||||
example: "mdown { \"crash\" s . } ?",
|
||||
compile: Context("mdown"),
|
||||
varargs: false,
|
||||
},
|
||||
// Music
|
||||
Word {
|
||||
name: "mtof",
|
||||
|
||||
68
docs/midi_input.md
Normal file
68
docs/midi_input.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# MIDI Input
|
||||
|
||||
Read incoming MIDI control change values with the `ccval` word. This lets you use hardware controllers to modulate parameters in your scripts.
|
||||
|
||||
## Reading CC Values
|
||||
|
||||
The `ccval` word takes a CC number and channel from the stack, and returns the last received value:
|
||||
|
||||
```forth
|
||||
1 1 ccval ;; read CC 1 (mod wheel) on channel 1
|
||||
```
|
||||
|
||||
Stack effect: `(cc chan -- val)`
|
||||
|
||||
The returned value is `0`-`127`. If no message has been received for that CC/channel combination, the value is `0`.
|
||||
|
||||
## Device Selection
|
||||
|
||||
Use `dev` to select which input device slot to read from:
|
||||
|
||||
```forth
|
||||
1 dev 1 1 ccval ;; read from device slot 1
|
||||
```
|
||||
|
||||
Device defaults to `0` if not specified.
|
||||
|
||||
## Practical Examples
|
||||
|
||||
Map a controller knob to filter cutoff:
|
||||
|
||||
```forth
|
||||
74 1 ccval 127 / 200 2740 range lpf
|
||||
```
|
||||
|
||||
Use mod wheel for vibrato depth:
|
||||
|
||||
```forth
|
||||
1 1 ccval 127 / 0 0.5 range vibdepth
|
||||
```
|
||||
|
||||
Crossfade between two sounds:
|
||||
|
||||
```forth
|
||||
1 1 ccval 127 / ;; normalize to 0.0-1.0
|
||||
dup saw s swap gain .
|
||||
1 swap - tri s swap gain .
|
||||
```
|
||||
|
||||
## Scaling Values
|
||||
|
||||
CC values are integers `0`-`127`. Normalize to `0.0`-`1.0` first, then use `range` to scale:
|
||||
|
||||
```forth
|
||||
;; normalize to 0.0-1.0
|
||||
74 1 ccval 127 /
|
||||
|
||||
;; scale to custom range (e.g., 200-4000)
|
||||
74 1 ccval 127 / 200 4000 range
|
||||
|
||||
;; bipolar range (-1.0 to 1.0)
|
||||
74 1 ccval 127 / -1 1 range
|
||||
```
|
||||
|
||||
The `range` word takes a normalized value (`0.0`-`1.0`) and scales it to your target range: `(val min max -- scaled)`.
|
||||
|
||||
## Latency
|
||||
|
||||
CC values are sampled at the start of each step. Changes during a step take effect on the next step. For smoothest results, turn knobs slowly or use higher step rates.
|
||||
26
docs/midi_intro.md
Normal file
26
docs/midi_intro.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# MIDI
|
||||
|
||||
Cagire speaks MIDI. You can send notes, control changes, and other messages to external synthesizers, drum machines, and DAWs. You can also read incoming control change values from MIDI controllers and use them to modulate your scripts.
|
||||
|
||||
## Device Slots
|
||||
|
||||
Cagire provides four input slots and four output slots, numbered `0` through `3`. Each slot can connect to one MIDI device. By default, slot `0` is used for both input and output.
|
||||
|
||||
## Configuration
|
||||
|
||||
Configure your MIDI devices in the **Options** view. Select input and output devices for each slot. Changes take effect immediately.
|
||||
|
||||
## MIDI vs Audio
|
||||
|
||||
The audio engine (`Doux`) and MIDI are independent systems. Use `.` to emit audio commands, use `m.` to emit MIDI messages. You can use both in the same script:
|
||||
|
||||
```forth
|
||||
saw s c4 note 0.5 gain . ;; audio
|
||||
60 note 100 velocity m. ;; MIDI
|
||||
```
|
||||
|
||||
MIDI is useful when you want to sequence external gear, layer Cagire with hardware synths, or integrate into a larger studio setup. The audio engine is self-contained and needs no external equipment.
|
||||
|
||||
## Clock and Transport
|
||||
|
||||
Cagire can send MIDI clock and transport messages to synchronize external gear. Use `mclock` to send a single clock pulse, and `mstart`, `mstop`, `mcont` for transport control. MIDI clock requires 24 pulses per quarter note, so you need to call `mclock` at the appropriate rate for your tempo.
|
||||
92
docs/midi_output.md
Normal file
92
docs/midi_output.md
Normal file
@@ -0,0 +1,92 @@
|
||||
# MIDI Output
|
||||
|
||||
Send MIDI messages using the `m.` word. Build up parameters on the stack, then emit. The system determines message type based on which parameters you set.
|
||||
|
||||
## Note Messages
|
||||
|
||||
The default message type is a note. Set `note` and `velocity`, then emit:
|
||||
|
||||
```forth
|
||||
60 note 100 velocity m. ;; middle C, velocity 100
|
||||
c4 note 80 velocity m. ;; same pitch, lower velocity
|
||||
```
|
||||
|
||||
| Parameter | Stack | Range | Description |
|
||||
|-----------|-------|-------|-------------|
|
||||
| `note` | `(n --)` | 0-127 | MIDI note number |
|
||||
| `velocity` | `(n --)` | 0-127 | Note velocity |
|
||||
| `chan` | `(n --)` | 1-16 | MIDI channel |
|
||||
| `dur` | `(f --)` | steps | Note duration |
|
||||
| `dev` | `(n --)` | 0-3 | Output device slot |
|
||||
|
||||
Duration (`dur`) is measured in steps. If not set, the note plays until the next step. Channel defaults to `1`, device defaults to `0`.
|
||||
|
||||
## Control Change
|
||||
|
||||
Set both `ccnum` (controller number) and `ccout` (value) to send a CC message:
|
||||
|
||||
```forth
|
||||
74 ccnum 64 ccout m. ;; CC 74, value 64
|
||||
1 ccnum 127 ccout m. ;; mod wheel full
|
||||
```
|
||||
|
||||
| Parameter | Stack | Range | Description |
|
||||
|-----------|-------|-------|-------------|
|
||||
| `ccnum` | `(n --)` | 0-127 | Controller number |
|
||||
| `ccout` | `(n --)` | 0-127 | Controller value |
|
||||
|
||||
## Pitch Bend
|
||||
|
||||
Set `bend` to send pitch bend. The range is `-1.0` (full down) to `1.0` (full up), with `0.0` as center:
|
||||
|
||||
```forth
|
||||
0.5 bend m. ;; bend up halfway
|
||||
-1.0 bend m. ;; full bend down
|
||||
```
|
||||
|
||||
## Channel Pressure
|
||||
|
||||
Set `pressure` to send channel aftertouch:
|
||||
|
||||
```forth
|
||||
64 pressure m. ;; medium pressure
|
||||
```
|
||||
|
||||
## Program Change
|
||||
|
||||
Set `program` to send a program change message:
|
||||
|
||||
```forth
|
||||
0 program m. ;; select program 0
|
||||
127 program m. ;; select program 127
|
||||
```
|
||||
|
||||
## Message Priority
|
||||
|
||||
When multiple message types are set, only one is sent per emit. Priority order:
|
||||
|
||||
1. Control Change (if `ccnum` AND `ccout` set)
|
||||
2. Pitch Bend
|
||||
3. Channel Pressure
|
||||
4. Program Change
|
||||
5. Note (default)
|
||||
|
||||
To send multiple message types, use multiple emits:
|
||||
|
||||
```forth
|
||||
74 ccnum 100 ccout m. ;; CC first
|
||||
60 note 100 velocity m. ;; then note
|
||||
```
|
||||
|
||||
## Real-Time Messages
|
||||
|
||||
Transport and clock messages for external synchronization:
|
||||
|
||||
| Word | Description |
|
||||
|------|-------------|
|
||||
| `mclock` | Send MIDI clock pulse |
|
||||
| `mstart` | Send MIDI start |
|
||||
| `mstop` | Send MIDI stop |
|
||||
| `mcont` | Send MIDI continue |
|
||||
|
||||
These ignore all parameters and send immediately.
|
||||
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