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]]
|
||||
name = "doux"
|
||||
version = "0.0.5"
|
||||
source = "git+https://github.com/sova-org/doux#886702b4fe937d26ed681a2f6d7626d26d6890d0"
|
||||
source = "git+https://github.com/sova-org/doux#2b62f896b855dd3da84906c7085835974fb56c8c"
|
||||
dependencies = [
|
||||
"arc-swap",
|
||||
"clap",
|
||||
|
||||
@@ -118,6 +118,7 @@ pub enum Op {
|
||||
Euclid,
|
||||
EuclidRot,
|
||||
Times,
|
||||
Map,
|
||||
Chord(&'static [i64]),
|
||||
Transpose,
|
||||
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 => {
|
||||
let count = pop_int(stack)?;
|
||||
let ratio = pop_float(stack)?;
|
||||
|
||||
@@ -110,6 +110,7 @@ pub(super) fn simple_op(name: &str) -> Option<Op> {
|
||||
"euclid" => Op::Euclid,
|
||||
"euclidrot" => Op::EuclidRot,
|
||||
"times" => Op::Times,
|
||||
"map" => Op::Map,
|
||||
"m." => Op::MidiEmit,
|
||||
"ccval" => Op::GetMidiCC,
|
||||
"mclock" => Op::MidiClock,
|
||||
|
||||
@@ -567,6 +567,16 @@ pub(super) const WORDS: &[Word] = &[
|
||||
compile: Simple,
|
||||
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
|
||||
Word {
|
||||
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>,
|
||||
_analysis_handle: Option<AnalysisHandle>,
|
||||
midi_rx: Receiver<MidiCommand>,
|
||||
device_lost: Arc<AtomicBool>,
|
||||
stream_error_rx: crossbeam_channel::Receiver<String>,
|
||||
current_font: FontChoice,
|
||||
zoom_factor: f32,
|
||||
@@ -207,6 +208,7 @@ impl CagireDesktop {
|
||||
_input_stream: b.input_stream,
|
||||
_analysis_handle: b.analysis_handle,
|
||||
midi_rx: b.midi_rx,
|
||||
device_lost: b.device_lost,
|
||||
stream_error_rx: b.stream_error_rx,
|
||||
current_font,
|
||||
zoom_factor,
|
||||
@@ -277,6 +279,7 @@ impl CagireDesktop {
|
||||
Arc::clone(&self.audio_sample_pos),
|
||||
new_error_tx,
|
||||
&self.app.audio.config.sample_paths,
|
||||
Arc::clone(&self.device_lost),
|
||||
) {
|
||||
Ok((new_stream, new_input, info, new_analysis, registry)) => {
|
||||
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) {
|
||||
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() {
|
||||
self.app.ui.flash(&err, 3000, cagire::state::FlashKind::Error);
|
||||
}
|
||||
|
||||
@@ -309,6 +309,7 @@ pub fn build_stream(
|
||||
audio_sample_pos: Arc<AtomicU64>,
|
||||
error_tx: Sender<String>,
|
||||
sample_paths: &[std::path::PathBuf],
|
||||
device_lost: Arc<AtomicBool>,
|
||||
) -> Result<BuildStreamResult, String> {
|
||||
let device = match &config.output_device {
|
||||
Some(name) => doux::audio::find_output_device(name)
|
||||
@@ -410,7 +411,13 @@ pub fn build_stream(
|
||||
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,
|
||||
)
|
||||
.ok()?;
|
||||
@@ -521,7 +528,10 @@ pub fn build_stream(
|
||||
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,
|
||||
)
|
||||
.map_err(|e| format!("Failed to build stream: {e}"))?;
|
||||
|
||||
@@ -43,6 +43,7 @@ pub struct Init {
|
||||
pub input_stream: Option<cpal::Stream>,
|
||||
pub analysis_handle: Option<AnalysisHandle>,
|
||||
pub midi_rx: Receiver<MidiCommand>,
|
||||
pub device_lost: Arc<AtomicBool>,
|
||||
pub stream_error_rx: crossbeam_channel::Receiver<String>,
|
||||
#[cfg(feature = "desktop")]
|
||||
pub settings: Settings,
|
||||
@@ -202,6 +203,7 @@ pub fn init(args: InitArgs) -> Init {
|
||||
seq_config,
|
||||
);
|
||||
|
||||
let device_lost = Arc::new(AtomicBool::new(false));
|
||||
let (stream_error_tx, stream_error_rx) = crossbeam_channel::bounded(16);
|
||||
|
||||
let stream_config = AudioStreamConfig {
|
||||
@@ -222,6 +224,7 @@ pub fn init(args: InitArgs) -> Init {
|
||||
Arc::clone(&audio_sample_pos),
|
||||
stream_error_tx,
|
||||
&app.audio.config.sample_paths,
|
||||
Arc::clone(&device_lost),
|
||||
) {
|
||||
Ok((s, input, info, analysis, registry)) => {
|
||||
app.audio.config.sample_rate = info.sample_rate;
|
||||
@@ -267,6 +270,7 @@ pub fn init(args: InitArgs) -> Init {
|
||||
input_stream,
|
||||
analysis_handle,
|
||||
midi_rx,
|
||||
device_lost,
|
||||
stream_error_rx,
|
||||
#[cfg(feature = "desktop")]
|
||||
settings,
|
||||
|
||||
@@ -85,6 +85,7 @@ fn handle_live_keys(ctx: &mut InputContext, key: &KeyEvent) -> bool {
|
||||
match (key.code, key.kind) {
|
||||
_ if !matches!(ctx.app.ui.modal, Modal::None) => 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) => {
|
||||
ctx.dispatch(AppCommand::ToggleLiveKeysFill);
|
||||
true
|
||||
|
||||
@@ -103,6 +103,7 @@ fn main() -> io::Result<()> {
|
||||
let mut _input_stream = b.input_stream;
|
||||
let mut _analysis_handle = b.analysis_handle;
|
||||
let mut midi_rx = b.midi_rx;
|
||||
let device_lost = b.device_lost;
|
||||
let mut stream_error_rx = b.stream_error_rx;
|
||||
|
||||
enable_raw_mode()?;
|
||||
@@ -167,6 +168,7 @@ fn main() -> io::Result<()> {
|
||||
Arc::clone(&audio_sample_pos),
|
||||
new_error_tx,
|
||||
&app.audio.config.sample_paths,
|
||||
Arc::clone(&device_lost),
|
||||
) {
|
||||
Ok((new_stream, new_input, info, new_analysis, registry)) => {
|
||||
_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() {
|
||||
app.ui.flash(&err, 3000, state::FlashKind::Error);
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ impl Default for DisplaySettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
fps: 60,
|
||||
runtime_highlight: false,
|
||||
runtime_highlight: true,
|
||||
show_scope: true,
|
||||
show_spectrum: 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() {
|
||||
if body_area.width >= 120 {
|
||||
let panel_width = body_area.width * 35 / 100;
|
||||
let [main, side] =
|
||||
Layout::horizontal([Constraint::Fill(1), Constraint::Length(panel_width)])
|
||||
.areas(body_area);
|
||||
(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))
|
||||
}
|
||||
let panel_width = body_area.width * 35 / 100;
|
||||
let [main, side] =
|
||||
Layout::horizontal([Constraint::Fill(1), Constraint::Length(panel_width)])
|
||||
.areas(body_area);
|
||||
(main, Some(side))
|
||||
} else {
|
||||
(body_area, None)
|
||||
};
|
||||
|
||||
@@ -66,3 +66,6 @@ mod case_statement;
|
||||
|
||||
#[path = "forth/harmony.rs"]
|
||||
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