Feat: integrating workshop fixes
All checks were successful
Deploy Website / deploy (push) Has been skipped
All checks were successful
Deploy Website / deploy (push) Has been skipped
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -1810,7 +1810,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "doux"
|
name = "doux"
|
||||||
version = "0.0.5"
|
version = "0.0.5"
|
||||||
source = "git+https://github.com/sova-org/doux#886702b4fe937d26ed681a2f6d7626d26d6890d0"
|
source = "git+https://github.com/sova-org/doux#2b62f896b855dd3da84906c7085835974fb56c8c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arc-swap",
|
"arc-swap",
|
||||||
"clap",
|
"clap",
|
||||||
|
|||||||
@@ -118,6 +118,7 @@ pub enum Op {
|
|||||||
Euclid,
|
Euclid,
|
||||||
EuclidRot,
|
EuclidRot,
|
||||||
Times,
|
Times,
|
||||||
|
Map,
|
||||||
Chord(&'static [i64]),
|
Chord(&'static [i64]),
|
||||||
Transpose,
|
Transpose,
|
||||||
Invert,
|
Invert,
|
||||||
|
|||||||
@@ -1374,6 +1374,15 @@ impl Forth {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Op::Map => {
|
||||||
|
let quot = pop(stack)?;
|
||||||
|
let items = std::mem::take(stack);
|
||||||
|
for item in items {
|
||||||
|
stack.push(item);
|
||||||
|
run_quotation(quot.clone(), stack, outputs, cmd)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Op::GeomRange => {
|
Op::GeomRange => {
|
||||||
let count = pop_int(stack)?;
|
let count = pop_int(stack)?;
|
||||||
let ratio = pop_float(stack)?;
|
let ratio = pop_float(stack)?;
|
||||||
|
|||||||
@@ -110,6 +110,7 @@ pub(super) fn simple_op(name: &str) -> Option<Op> {
|
|||||||
"euclid" => Op::Euclid,
|
"euclid" => Op::Euclid,
|
||||||
"euclidrot" => Op::EuclidRot,
|
"euclidrot" => Op::EuclidRot,
|
||||||
"times" => Op::Times,
|
"times" => Op::Times,
|
||||||
|
"map" => Op::Map,
|
||||||
"m." => Op::MidiEmit,
|
"m." => Op::MidiEmit,
|
||||||
"ccval" => Op::GetMidiCC,
|
"ccval" => Op::GetMidiCC,
|
||||||
"mclock" => Op::MidiClock,
|
"mclock" => Op::MidiClock,
|
||||||
|
|||||||
@@ -567,6 +567,16 @@ pub(super) const WORDS: &[Word] = &[
|
|||||||
compile: Simple,
|
compile: Simple,
|
||||||
varargs: false,
|
varargs: false,
|
||||||
},
|
},
|
||||||
|
Word {
|
||||||
|
name: "map",
|
||||||
|
aliases: &[],
|
||||||
|
category: "Control",
|
||||||
|
stack: "(..vals quot -- ..results)",
|
||||||
|
desc: "Apply quotation to each stack element",
|
||||||
|
example: "1 2 3 ( 10 * ) map => 10 20 30",
|
||||||
|
compile: Simple,
|
||||||
|
varargs: false,
|
||||||
|
},
|
||||||
// Variables
|
// Variables
|
||||||
Word {
|
Word {
|
||||||
name: "@<var>",
|
name: "@<var>",
|
||||||
|
|||||||
8371
demos/03.cagire
8371
demos/03.cagire
File diff suppressed because it is too large
Load Diff
@@ -161,6 +161,7 @@ struct CagireDesktop {
|
|||||||
_input_stream: Option<cpal::Stream>,
|
_input_stream: Option<cpal::Stream>,
|
||||||
_analysis_handle: Option<AnalysisHandle>,
|
_analysis_handle: Option<AnalysisHandle>,
|
||||||
midi_rx: Receiver<MidiCommand>,
|
midi_rx: Receiver<MidiCommand>,
|
||||||
|
device_lost: Arc<AtomicBool>,
|
||||||
stream_error_rx: crossbeam_channel::Receiver<String>,
|
stream_error_rx: crossbeam_channel::Receiver<String>,
|
||||||
current_font: FontChoice,
|
current_font: FontChoice,
|
||||||
zoom_factor: f32,
|
zoom_factor: f32,
|
||||||
@@ -207,6 +208,7 @@ impl CagireDesktop {
|
|||||||
_input_stream: b.input_stream,
|
_input_stream: b.input_stream,
|
||||||
_analysis_handle: b.analysis_handle,
|
_analysis_handle: b.analysis_handle,
|
||||||
midi_rx: b.midi_rx,
|
midi_rx: b.midi_rx,
|
||||||
|
device_lost: b.device_lost,
|
||||||
stream_error_rx: b.stream_error_rx,
|
stream_error_rx: b.stream_error_rx,
|
||||||
current_font,
|
current_font,
|
||||||
zoom_factor,
|
zoom_factor,
|
||||||
@@ -277,6 +279,7 @@ impl CagireDesktop {
|
|||||||
Arc::clone(&self.audio_sample_pos),
|
Arc::clone(&self.audio_sample_pos),
|
||||||
new_error_tx,
|
new_error_tx,
|
||||||
&self.app.audio.config.sample_paths,
|
&self.app.audio.config.sample_paths,
|
||||||
|
Arc::clone(&self.device_lost),
|
||||||
) {
|
) {
|
||||||
Ok((new_stream, new_input, info, new_analysis, registry)) => {
|
Ok((new_stream, new_input, info, new_analysis, registry)) => {
|
||||||
self._stream = Some(new_stream);
|
self._stream = Some(new_stream);
|
||||||
@@ -373,6 +376,11 @@ impl eframe::App for CagireDesktop {
|
|||||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||||
self.handle_audio_restart();
|
self.handle_audio_restart();
|
||||||
|
|
||||||
|
if self.device_lost.load(Ordering::Acquire) {
|
||||||
|
self.device_lost.store(false, Ordering::Release);
|
||||||
|
self.app.audio.restart_pending = true;
|
||||||
|
}
|
||||||
|
|
||||||
while let Ok(err) = self.stream_error_rx.try_recv() {
|
while let Ok(err) = self.stream_error_rx.try_recv() {
|
||||||
self.app.ui.flash(&err, 3000, cagire::state::FlashKind::Error);
|
self.app.ui.flash(&err, 3000, cagire::state::FlashKind::Error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -309,6 +309,7 @@ pub fn build_stream(
|
|||||||
audio_sample_pos: Arc<AtomicU64>,
|
audio_sample_pos: Arc<AtomicU64>,
|
||||||
error_tx: Sender<String>,
|
error_tx: Sender<String>,
|
||||||
sample_paths: &[std::path::PathBuf],
|
sample_paths: &[std::path::PathBuf],
|
||||||
|
device_lost: Arc<AtomicBool>,
|
||||||
) -> Result<BuildStreamResult, String> {
|
) -> Result<BuildStreamResult, String> {
|
||||||
let device = match &config.output_device {
|
let device = match &config.output_device {
|
||||||
Some(name) => doux::audio::find_output_device(name)
|
Some(name) => doux::audio::find_output_device(name)
|
||||||
@@ -410,7 +411,13 @@ pub fn build_stream(
|
|||||||
drop(b.drain(..excess));
|
drop(b.drain(..excess));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|err| eprintln!("input stream error: {err}"),
|
{
|
||||||
|
let device_lost = Arc::clone(&device_lost);
|
||||||
|
move |err| {
|
||||||
|
eprintln!("input stream error: {err}");
|
||||||
|
device_lost.store(true, Ordering::Release);
|
||||||
|
}
|
||||||
|
},
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.ok()?;
|
.ok()?;
|
||||||
@@ -521,7 +528,10 @@ pub fn build_stream(
|
|||||||
let _ = fft_producer.try_push(mono);
|
let _ = fft_producer.try_push(mono);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
move |err| { let _ = error_tx.try_send(format!("stream error: {err}")); },
|
move |err| {
|
||||||
|
let _ = error_tx.try_send(format!("stream error: {err}"));
|
||||||
|
device_lost.store(true, Ordering::Release);
|
||||||
|
},
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.map_err(|e| format!("Failed to build stream: {e}"))?;
|
.map_err(|e| format!("Failed to build stream: {e}"))?;
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ pub struct Init {
|
|||||||
pub input_stream: Option<cpal::Stream>,
|
pub input_stream: Option<cpal::Stream>,
|
||||||
pub analysis_handle: Option<AnalysisHandle>,
|
pub analysis_handle: Option<AnalysisHandle>,
|
||||||
pub midi_rx: Receiver<MidiCommand>,
|
pub midi_rx: Receiver<MidiCommand>,
|
||||||
|
pub device_lost: Arc<AtomicBool>,
|
||||||
pub stream_error_rx: crossbeam_channel::Receiver<String>,
|
pub stream_error_rx: crossbeam_channel::Receiver<String>,
|
||||||
#[cfg(feature = "desktop")]
|
#[cfg(feature = "desktop")]
|
||||||
pub settings: Settings,
|
pub settings: Settings,
|
||||||
@@ -202,6 +203,7 @@ pub fn init(args: InitArgs) -> Init {
|
|||||||
seq_config,
|
seq_config,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let device_lost = Arc::new(AtomicBool::new(false));
|
||||||
let (stream_error_tx, stream_error_rx) = crossbeam_channel::bounded(16);
|
let (stream_error_tx, stream_error_rx) = crossbeam_channel::bounded(16);
|
||||||
|
|
||||||
let stream_config = AudioStreamConfig {
|
let stream_config = AudioStreamConfig {
|
||||||
@@ -222,6 +224,7 @@ pub fn init(args: InitArgs) -> Init {
|
|||||||
Arc::clone(&audio_sample_pos),
|
Arc::clone(&audio_sample_pos),
|
||||||
stream_error_tx,
|
stream_error_tx,
|
||||||
&app.audio.config.sample_paths,
|
&app.audio.config.sample_paths,
|
||||||
|
Arc::clone(&device_lost),
|
||||||
) {
|
) {
|
||||||
Ok((s, input, info, analysis, registry)) => {
|
Ok((s, input, info, analysis, registry)) => {
|
||||||
app.audio.config.sample_rate = info.sample_rate;
|
app.audio.config.sample_rate = info.sample_rate;
|
||||||
@@ -267,6 +270,7 @@ pub fn init(args: InitArgs) -> Init {
|
|||||||
input_stream,
|
input_stream,
|
||||||
analysis_handle,
|
analysis_handle,
|
||||||
midi_rx,
|
midi_rx,
|
||||||
|
device_lost,
|
||||||
stream_error_rx,
|
stream_error_rx,
|
||||||
#[cfg(feature = "desktop")]
|
#[cfg(feature = "desktop")]
|
||||||
settings,
|
settings,
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ fn handle_live_keys(ctx: &mut InputContext, key: &KeyEvent) -> bool {
|
|||||||
match (key.code, key.kind) {
|
match (key.code, key.kind) {
|
||||||
_ if !matches!(ctx.app.ui.modal, Modal::None) => false,
|
_ if !matches!(ctx.app.ui.modal, Modal::None) => false,
|
||||||
_ if ctx.app.page == Page::Script && ctx.app.script_editor.focused => false,
|
_ if ctx.app.page == Page::Script && ctx.app.script_editor.focused => false,
|
||||||
|
_ if ctx.app.ui.dict_search_active || ctx.app.ui.help_search_active => false,
|
||||||
(KeyCode::Char('f'), KeyEventKind::Press) if !key.modifiers.contains(KeyModifiers::ALT) => {
|
(KeyCode::Char('f'), KeyEventKind::Press) if !key.modifiers.contains(KeyModifiers::ALT) => {
|
||||||
ctx.dispatch(AppCommand::ToggleLiveKeysFill);
|
ctx.dispatch(AppCommand::ToggleLiveKeysFill);
|
||||||
true
|
true
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ fn main() -> io::Result<()> {
|
|||||||
let mut _input_stream = b.input_stream;
|
let mut _input_stream = b.input_stream;
|
||||||
let mut _analysis_handle = b.analysis_handle;
|
let mut _analysis_handle = b.analysis_handle;
|
||||||
let mut midi_rx = b.midi_rx;
|
let mut midi_rx = b.midi_rx;
|
||||||
|
let device_lost = b.device_lost;
|
||||||
let mut stream_error_rx = b.stream_error_rx;
|
let mut stream_error_rx = b.stream_error_rx;
|
||||||
|
|
||||||
enable_raw_mode()?;
|
enable_raw_mode()?;
|
||||||
@@ -167,6 +168,7 @@ fn main() -> io::Result<()> {
|
|||||||
Arc::clone(&audio_sample_pos),
|
Arc::clone(&audio_sample_pos),
|
||||||
new_error_tx,
|
new_error_tx,
|
||||||
&app.audio.config.sample_paths,
|
&app.audio.config.sample_paths,
|
||||||
|
Arc::clone(&device_lost),
|
||||||
) {
|
) {
|
||||||
Ok((new_stream, new_input, info, new_analysis, registry)) => {
|
Ok((new_stream, new_input, info, new_analysis, registry)) => {
|
||||||
_stream = Some(new_stream);
|
_stream = Some(new_stream);
|
||||||
@@ -197,6 +199,11 @@ fn main() -> io::Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if device_lost.load(Ordering::Acquire) {
|
||||||
|
device_lost.store(false, Ordering::Release);
|
||||||
|
app.audio.restart_pending = true;
|
||||||
|
}
|
||||||
|
|
||||||
while let Ok(err) = stream_error_rx.try_recv() {
|
while let Ok(err) = stream_error_rx.try_recv() {
|
||||||
app.ui.flash(&err, 3000, state::FlashKind::Error);
|
app.ui.flash(&err, 3000, state::FlashKind::Error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ impl Default for DisplaySettings {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
fps: 60,
|
fps: 60,
|
||||||
runtime_highlight: false,
|
runtime_highlight: true,
|
||||||
show_scope: true,
|
show_scope: true,
|
||||||
show_spectrum: true,
|
show_spectrum: true,
|
||||||
show_lissajous: true,
|
show_lissajous: true,
|
||||||
|
|||||||
@@ -165,19 +165,11 @@ pub fn render(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let (page_area, panel_area) = if app.panel.visible && app.panel.side.is_some() {
|
let (page_area, panel_area) = if app.panel.visible && app.panel.side.is_some() {
|
||||||
if body_area.width >= 120 {
|
|
||||||
let panel_width = body_area.width * 35 / 100;
|
let panel_width = body_area.width * 35 / 100;
|
||||||
let [main, side] =
|
let [main, side] =
|
||||||
Layout::horizontal([Constraint::Fill(1), Constraint::Length(panel_width)])
|
Layout::horizontal([Constraint::Fill(1), Constraint::Length(panel_width)])
|
||||||
.areas(body_area);
|
.areas(body_area);
|
||||||
(main, Some(side))
|
(main, Some(side))
|
||||||
} else {
|
|
||||||
let panel_height = body_area.height * 40 / 100;
|
|
||||||
let [main, side] =
|
|
||||||
Layout::vertical([Constraint::Fill(1), Constraint::Length(panel_height)])
|
|
||||||
.areas(body_area);
|
|
||||||
(main, Some(side))
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
(body_area, None)
|
(body_area, None)
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -66,3 +66,6 @@ mod case_statement;
|
|||||||
|
|
||||||
#[path = "forth/harmony.rs"]
|
#[path = "forth/harmony.rs"]
|
||||||
mod harmony;
|
mod harmony;
|
||||||
|
|
||||||
|
#[path = "forth/map.rs"]
|
||||||
|
mod map;
|
||||||
|
|||||||
55
tests/forth/map.rs
Normal file
55
tests/forth/map.rs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
use crate::harness::{expect_error, expect_int, expect_stack, run};
|
||||||
|
use cagire::forth::Value;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn map_add() {
|
||||||
|
expect_stack(
|
||||||
|
"1 2 3 4 5 ( 2 + ) map",
|
||||||
|
&[
|
||||||
|
Value::Int(3, None),
|
||||||
|
Value::Int(4, None),
|
||||||
|
Value::Int(5, None),
|
||||||
|
Value::Int(6, None),
|
||||||
|
Value::Int(7, None),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn map_multiply() {
|
||||||
|
expect_stack(
|
||||||
|
"1 2 3 ( 10 * ) map",
|
||||||
|
&[
|
||||||
|
Value::Int(10, None),
|
||||||
|
Value::Int(20, None),
|
||||||
|
Value::Int(30, None),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn map_single_element() {
|
||||||
|
expect_int("42 ( 1 + ) map", 43);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn map_empty_stack() {
|
||||||
|
run("( 1 + ) map");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn map_identity() {
|
||||||
|
expect_stack(
|
||||||
|
"1 2 3 ( ) map",
|
||||||
|
&[
|
||||||
|
Value::Int(1, None),
|
||||||
|
Value::Int(2, None),
|
||||||
|
Value::Int(3, None),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn map_missing_quotation() {
|
||||||
|
expect_error("1 2 3 map", "expected quotation");
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user