@devaloop/devalang 0.0.1-alpha.16-hotfix.1 → 0.0.1-alpha.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cargo/config.toml +2 -0
- package/.devalang +6 -10
- package/.github/workflows/ci.yml +19 -8
- package/Cargo.toml +18 -2
- package/README.md +80 -33
- package/docs/CHANGELOG.md +56 -0
- package/docs/ROADMAP.md +6 -3
- package/examples/index.deva +52 -35
- package/out-tsc/bin/index.d.ts +2 -0
- package/out-tsc/core/functions/index.d.ts +37 -0
- package/out-tsc/core/functions/index.js +76 -0
- package/out-tsc/core/index.d.ts +6 -0
- package/out-tsc/core/index.js +22 -0
- package/out-tsc/core/types/index.d.ts +4 -0
- package/out-tsc/core/types/index.js +20 -0
- package/out-tsc/core/types/plugin.d.ts +18 -0
- package/out-tsc/core/types/plugin.js +2 -0
- package/out-tsc/core/types/result.d.ts +27 -0
- package/out-tsc/core/types/result.js +2 -0
- package/out-tsc/core/types/statement.d.ts +106 -0
- package/out-tsc/core/types/statement.js +2 -0
- package/out-tsc/core/types/value.d.ts +43 -0
- package/out-tsc/core/types/value.js +2 -0
- package/out-tsc/index.d.ts +7 -0
- package/out-tsc/index.js +41 -2
- package/out-tsc/pkg/devalang_core.d.ts +7 -0
- package/out-tsc/pkg/devalang_core_bg.wasm.d.ts +33 -0
- package/out-tsc/scripts/copy-wasm-dts.d.ts +1 -0
- package/out-tsc/scripts/copy-wasm-dts.js +73 -0
- package/out-tsc/scripts/postinstall.d.ts +1 -0
- package/out-tsc/scripts/postinstall.js +33 -23
- package/out-tsc/scripts/version/bump.d.ts +1 -0
- package/out-tsc/scripts/version/fetch.d.ts +1 -0
- package/out-tsc/scripts/version/index.d.ts +1 -0
- package/out-tsc/scripts/version/sync.d.ts +1 -0
- package/package.json +16 -4
- package/project-version.json +3 -3
- package/rust/cli/bank/api.rs +122 -0
- package/rust/cli/bank/commands.rs +275 -0
- package/rust/cli/bank/mod.rs +29 -0
- package/rust/cli/build/commands.rs +97 -0
- package/rust/cli/build/mod.rs +2 -0
- package/rust/cli/build/process.rs +146 -0
- package/rust/cli/{check.rs → check/mod.rs} +18 -31
- package/rust/cli/discover/commands.rs +253 -0
- package/rust/cli/discover/config.rs +111 -0
- package/rust/cli/discover/fs.rs +19 -0
- package/rust/cli/discover/install.rs +103 -0
- package/rust/cli/discover/metadata.rs +48 -0
- package/rust/cli/discover/mod.rs +5 -0
- package/rust/cli/{init.rs → init/commands.rs} +88 -87
- package/rust/cli/init/mod.rs +1 -0
- package/rust/{installer → cli/install}/addon.rs +5 -9
- package/rust/cli/install/bank.rs +53 -0
- package/rust/cli/{install.rs → install/commands.rs} +9 -9
- package/rust/{installer → cli/install}/mod.rs +2 -3
- package/rust/cli/install/plugin.rs +61 -0
- package/rust/cli/{login.rs → login/commands.rs} +8 -11
- package/rust/cli/login/mod.rs +1 -0
- package/rust/cli/mod.rs +2 -3
- package/rust/cli/{driver.rs → parser.rs} +19 -2
- package/rust/cli/play/commands.rs +324 -0
- package/rust/cli/play/io.rs +17 -0
- package/rust/cli/play/mod.rs +5 -0
- package/rust/cli/play/process.rs +150 -0
- package/rust/cli/play/realtime.rs +91 -0
- package/rust/cli/play/utils.rs +23 -0
- package/rust/cli/telemetry/commands.rs +22 -0
- package/rust/cli/telemetry/event_creator.rs +80 -0
- package/rust/cli/telemetry/mod.rs +3 -0
- package/rust/cli/telemetry/send.rs +51 -0
- package/rust/cli/{template.rs → template/commands.rs} +1 -1
- package/rust/cli/template/mod.rs +1 -0
- package/rust/cli/{update.rs → update/commands.rs} +6 -6
- package/rust/cli/update/mod.rs +1 -0
- package/rust/config/driver.rs +57 -72
- package/rust/config/mod.rs +1 -2
- package/rust/config/ops.rs +26 -0
- package/rust/config/settings.rs +60 -50
- package/rust/core/audio/engine/helpers.rs +146 -0
- package/rust/core/audio/engine/mod.rs +7 -0
- package/rust/core/audio/engine/sample.rs +298 -0
- package/rust/core/audio/engine/synth.rs +310 -0
- package/rust/core/audio/evaluator.rs +15 -12
- package/rust/core/audio/interpreter/arrow_call.rs +99 -24
- package/rust/core/audio/interpreter/call.rs +81 -60
- package/rust/core/audio/interpreter/condition.rs +3 -2
- package/rust/core/audio/interpreter/driver.rs +206 -151
- package/rust/core/audio/interpreter/let_.rs +1 -1
- package/rust/core/audio/interpreter/load.rs +2 -1
- package/rust/core/audio/interpreter/loop_.rs +7 -6
- package/rust/core/audio/interpreter/sleep.rs +2 -1
- package/rust/core/audio/interpreter/spawn.rs +45 -57
- package/rust/core/audio/interpreter/tempo.rs +31 -10
- package/rust/core/audio/interpreter/trigger.rs +2 -2
- package/rust/core/audio/loader/trigger.rs +4 -7
- package/rust/core/audio/player.rs +6 -0
- package/rust/core/audio/renderer.rs +5 -7
- package/rust/core/audio/special/env.rs +3 -1
- package/rust/core/audio/special/math.rs +4 -4
- package/rust/core/audio/special/modulator.rs +2 -2
- package/rust/core/builder/mod.rs +9 -3
- package/rust/core/debugger/lexer.rs +1 -1
- package/rust/core/debugger/mod.rs +6 -0
- package/rust/core/debugger/module.rs +4 -4
- package/rust/core/debugger/preprocessor.rs +1 -1
- package/rust/core/debugger/store.rs +2 -2
- package/rust/core/error/mod.rs +189 -0
- package/rust/core/lexer/handler/arrow.rs +1 -1
- package/rust/core/lexer/handler/at.rs +1 -1
- package/rust/core/lexer/handler/brace.rs +2 -2
- package/rust/core/lexer/handler/colon.rs +1 -1
- package/rust/core/lexer/handler/comment.rs +1 -1
- package/rust/core/lexer/handler/dot.rs +1 -1
- package/rust/core/lexer/handler/driver.rs +1 -1
- package/rust/core/lexer/handler/identifier.rs +1 -1
- package/rust/core/lexer/handler/mod.rs +1 -2
- package/rust/core/lexer/handler/number.rs +1 -1
- package/rust/core/lexer/handler/operator.rs +1 -1
- package/rust/core/lexer/handler/parenthesis.rs +2 -2
- package/rust/core/lexer/handler/slash.rs +1 -1
- package/rust/core/lexer/handler/string.rs +1 -1
- package/rust/core/lexer/mod.rs +22 -12
- package/rust/core/lexer/token.rs +90 -97
- package/rust/core/mod.rs +0 -1
- package/rust/core/parser/driver.rs +66 -13
- package/rust/core/parser/handler/arrow_call.rs +28 -8
- package/rust/core/parser/handler/at.rs +55 -21
- package/rust/core/parser/handler/bank.rs +14 -4
- package/rust/core/parser/handler/condition.rs +6 -3
- package/rust/core/parser/handler/dot.rs +2 -1
- package/rust/core/parser/handler/identifier/automate.rs +13 -16
- package/rust/core/parser/handler/identifier/call.rs +4 -4
- package/rust/core/parser/handler/identifier/emit.rs +9 -5
- package/rust/core/parser/handler/identifier/function.rs +20 -7
- package/rust/core/parser/handler/identifier/group.rs +11 -7
- package/rust/core/parser/handler/identifier/let_.rs +24 -9
- package/rust/core/parser/handler/identifier/mod.rs +6 -5
- package/rust/core/parser/handler/identifier/on.rs +16 -7
- package/rust/core/parser/handler/identifier/print.rs +6 -9
- package/rust/core/parser/handler/identifier/sleep.rs +12 -5
- package/rust/core/parser/handler/identifier/spawn.rs +4 -4
- package/rust/core/parser/handler/identifier/synth.rs +79 -9
- package/rust/core/parser/handler/loop_.rs +39 -14
- package/rust/core/parser/handler/tempo.rs +9 -5
- package/rust/core/parser/mod.rs +0 -1
- package/rust/core/parser/statement.rs +6 -137
- package/rust/core/plugin/loader.rs +41 -27
- package/rust/core/plugin/runner.rs +68 -17
- package/rust/core/preprocessor/loader.rs +155 -33
- package/rust/core/preprocessor/processor.rs +2 -2
- package/rust/core/preprocessor/resolver/bank.rs +6 -8
- package/rust/core/preprocessor/resolver/call.rs +20 -24
- package/rust/core/preprocessor/resolver/condition.rs +6 -8
- package/rust/core/preprocessor/resolver/driver.rs +14 -16
- package/rust/core/preprocessor/resolver/function.rs +6 -6
- package/rust/core/preprocessor/resolver/group.rs +6 -8
- package/rust/core/preprocessor/resolver/loop_.rs +8 -10
- package/rust/core/preprocessor/resolver/spawn.rs +19 -23
- package/rust/core/preprocessor/resolver/synth.rs +6 -8
- package/rust/core/preprocessor/resolver/tempo.rs +6 -8
- package/rust/core/preprocessor/resolver/trigger.rs +22 -19
- package/rust/core/preprocessor/resolver/value.rs +99 -4
- package/rust/core/store/export.rs +28 -28
- package/rust/core/store/function.rs +6 -0
- package/rust/core/store/global.rs +7 -1
- package/rust/core/store/import.rs +28 -28
- package/rust/core/store/variable.rs +1 -1
- package/rust/core/utils/mod.rs +0 -1
- package/rust/lib.rs +102 -9
- package/rust/main.rs +156 -45
- package/rust/types/Cargo.toml +8 -0
- package/rust/types/src/addons.rs +55 -0
- package/rust/types/src/ast.rs +198 -0
- package/rust/types/src/config.rs +74 -0
- package/rust/types/src/lib.rs +12 -0
- package/rust/types/src/telemetry.rs +85 -0
- package/rust/utils/Cargo.toml +23 -0
- package/rust/utils/{error.rs → src/error.rs} +186 -200
- package/rust/utils/src/file.rs +94 -0
- package/rust/utils/src/first_usage.rs +97 -0
- package/rust/utils/{mod.rs → src/lib.rs} +1 -1
- package/rust/utils/{logger.rs → src/logger.rs} +17 -12
- package/rust/utils/src/path.rs +88 -0
- package/rust/utils/src/signature.rs +41 -0
- package/rust/utils/{spinner.rs → src/spinner.rs} +3 -5
- package/rust/utils/src/version.rs +27 -0
- package/rust/utils/{watcher.rs → src/watcher.rs} +13 -1
- package/rust/web/api.rs +5 -0
- package/rust/web/cdn.rs +34 -0
- package/templates/minimal/README.md +98 -54
- package/templates/welcome/README.md +98 -54
- package/templates/welcome/src/index.deva +56 -8
- package/templates/welcome/src/variables.deva +2 -4
- package/tests/rust/TODO.md +0 -0
- package/tests/typescript/index.spec.ts +136 -0
- package/tests/typescript/playhead.spec.ts +36 -0
- package/tests/typescript/render_e2e.spec.ts +77 -0
- package/tsconfig.json +1 -1
- package/typescript/core/functions/index.ts +83 -0
- package/typescript/core/index.ts +6 -0
- package/typescript/core/types/index.ts +4 -0
- package/typescript/core/types/plugin.ts +19 -0
- package/typescript/core/types/result.ts +29 -0
- package/typescript/core/types/statement.ts +47 -0
- package/typescript/core/types/value.ts +29 -0
- package/typescript/index.ts +7 -2
- package/typescript/pkg/devalang_core.d.ts +4 -0
- package/typescript/scripts/copy-wasm-dts.ts +41 -0
- package/typescript/scripts/postinstall.ts +45 -32
- package/rust/cli/bank.rs +0 -462
- package/rust/cli/build.rs +0 -252
- package/rust/cli/generator.rs +0 -1
- package/rust/cli/play.rs +0 -1123
- package/rust/cli/telemetry.rs +0 -19
- package/rust/common/api.rs +0 -5
- package/rust/common/cdn.rs +0 -5
- package/rust/config/loader.rs +0 -165
- package/rust/config/stats.rs +0 -257
- package/rust/core/audio/engine.rs +0 -696
- package/rust/core/shared/bank.rs +0 -21
- package/rust/core/shared/duration.rs +0 -9
- package/rust/core/shared/mod.rs +0 -3
- package/rust/core/shared/value.rs +0 -35
- package/rust/core/utils/validation.rs +0 -35
- package/rust/installer/bank.rs +0 -62
- package/rust/installer/plugin.rs +0 -54
- package/rust/installer/utils.rs +0 -56
- package/rust/utils/file.rs +0 -38
- package/rust/utils/first_usage.rs +0 -76
- package/rust/utils/signature.rs +0 -19
- package/rust/utils/telemetry.rs +0 -292
- package/rust/utils/version.rs +0 -15
- /package/rust/{common → web}/mod.rs +0 -0
- /package/rust/{common → web}/sso.rs +0 -0
package/rust/cli/play.rs
DELETED
|
@@ -1,1123 +0,0 @@
|
|
|
1
|
-
use crate::{
|
|
2
|
-
config::driver::ProjectConfig,
|
|
3
|
-
core::{
|
|
4
|
-
builder::Builder,
|
|
5
|
-
debugger::{
|
|
6
|
-
lexer::write_lexer_log_file,
|
|
7
|
-
module::{write_module_function_log_file, write_module_variable_log_file},
|
|
8
|
-
preprocessor::write_preprocessor_log_file,
|
|
9
|
-
store::{write_function_log_file, write_variables_log_file},
|
|
10
|
-
},
|
|
11
|
-
preprocessor::loader::ModuleLoader,
|
|
12
|
-
store::global::GlobalStore,
|
|
13
|
-
utils::path::{find_entry_file, normalize_path},
|
|
14
|
-
},
|
|
15
|
-
utils::{
|
|
16
|
-
logger::{LogLevel, Logger},
|
|
17
|
-
spinner::with_spinner,
|
|
18
|
-
watcher::watch_directory,
|
|
19
|
-
},
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
use std::collections::HashMap;
|
|
23
|
-
use std::fs;
|
|
24
|
-
use std::sync::atomic::{AtomicBool, Ordering};
|
|
25
|
-
use std::{
|
|
26
|
-
path::Path,
|
|
27
|
-
sync::{Arc, mpsc::channel},
|
|
28
|
-
thread,
|
|
29
|
-
time::Duration,
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
#[cfg(feature = "cli")]
|
|
33
|
-
pub fn handle_play_command(
|
|
34
|
-
config: Option<ProjectConfig>,
|
|
35
|
-
entry: Option<String>,
|
|
36
|
-
output: Option<String>,
|
|
37
|
-
watch: bool,
|
|
38
|
-
repeat: bool,
|
|
39
|
-
debug: bool,
|
|
40
|
-
) -> Result<(), String> {
|
|
41
|
-
use crate::core::audio::player::AudioPlayer;
|
|
42
|
-
|
|
43
|
-
let logger = Logger::new();
|
|
44
|
-
|
|
45
|
-
let entry_path = entry
|
|
46
|
-
.or_else(|| config.as_ref().and_then(|c| c.defaults.entry.clone()))
|
|
47
|
-
.unwrap_or_else(|| "".to_string());
|
|
48
|
-
|
|
49
|
-
let output_path = output
|
|
50
|
-
.or_else(|| config.as_ref().and_then(|c| c.defaults.output.clone()))
|
|
51
|
-
.unwrap_or_else(|| "".to_string());
|
|
52
|
-
|
|
53
|
-
let fetched_repeat = if repeat {
|
|
54
|
-
true
|
|
55
|
-
} else {
|
|
56
|
-
config
|
|
57
|
-
.as_ref()
|
|
58
|
-
.and_then(|c| c.defaults.repeat)
|
|
59
|
-
.unwrap_or(false)
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
if entry_path.is_empty() || output_path.is_empty() {
|
|
63
|
-
logger.log_message(LogLevel::Error, "Entry or output path not specified.");
|
|
64
|
-
return Err("missing entry or output".to_string());
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
let entry_file = match find_entry_file(&entry_path) {
|
|
68
|
-
Some(p) => p,
|
|
69
|
-
None => {
|
|
70
|
-
logger.log_message(LogLevel::Error, "index.deva not found");
|
|
71
|
-
return Err("index.deva not found".to_string());
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
let audio_file = format!("{}/audio/index.wav", normalize_path(&output_path));
|
|
76
|
-
// Persist basic stats at start of play (reflects current output dir)
|
|
77
|
-
if let Err(e) = super::super::config::stats::save_to_file(&compute_basic_stats(&output_path)) {
|
|
78
|
-
eprintln!("[stats] failed to save: {}", e);
|
|
79
|
-
}
|
|
80
|
-
let mut audio_player = AudioPlayer::new();
|
|
81
|
-
|
|
82
|
-
// Helper: compute WAV duration in seconds
|
|
83
|
-
fn wav_duration_seconds(path: &str) -> Option<f32> {
|
|
84
|
-
if let Ok(reader) = hound::WavReader::open(path) {
|
|
85
|
-
let spec = reader.spec();
|
|
86
|
-
let len = reader.len(); // total samples (per channel)
|
|
87
|
-
if spec.sample_rate == 0 {
|
|
88
|
-
return None;
|
|
89
|
-
}
|
|
90
|
-
// len is total samples across channels; duration(seconds) = frames / sample_rate
|
|
91
|
-
// frames = len / channels
|
|
92
|
-
let channels = spec.channels.max(1) as u32;
|
|
93
|
-
let frames = (len as u32) / channels;
|
|
94
|
-
let dur = (frames as f32) / (spec.sample_rate as f32);
|
|
95
|
-
Some(dur)
|
|
96
|
-
} else {
|
|
97
|
-
None
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Real-time executor following tempo; executes one statement per beat; spawns run in parallel.
|
|
102
|
-
struct RtContext {
|
|
103
|
-
bpm: f32,
|
|
104
|
-
entry_stmts: Vec<crate::core::parser::statement::Statement>,
|
|
105
|
-
variables: crate::core::store::variable::VariableTable,
|
|
106
|
-
functions: crate::core::store::function::FunctionTable,
|
|
107
|
-
global_store: crate::core::store::global::GlobalStore,
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
struct RtRunner {
|
|
111
|
-
stop: Arc<AtomicBool>,
|
|
112
|
-
handle: std::thread::JoinHandle<()>,
|
|
113
|
-
}
|
|
114
|
-
fn start_realtime_runner(ctx: RtContext, total_secs: f32) -> RtRunner {
|
|
115
|
-
use crate::core::audio::engine::AudioEngine;
|
|
116
|
-
use crate::core::audio::interpreter::{
|
|
117
|
-
arrow_call::interprete_call_arrow_statement, call::interprete_call_statement,
|
|
118
|
-
driver::execute_audio_block, function::interprete_function_statement,
|
|
119
|
-
let_::interprete_let_statement, loop_::interprete_loop_statement,
|
|
120
|
-
sleep::interprete_sleep_statement, spawn::interprete_spawn_statement,
|
|
121
|
-
tempo::interprete_tempo_statement,
|
|
122
|
-
};
|
|
123
|
-
use crate::core::parser::statement::Statement as AstStatement;
|
|
124
|
-
use crate::core::parser::statement::StatementKind;
|
|
125
|
-
use crate::core::shared::value::Value;
|
|
126
|
-
use crate::utils::logger::{LogLevel, Logger};
|
|
127
|
-
|
|
128
|
-
let stop = Arc::new(AtomicBool::new(false));
|
|
129
|
-
let stop_clone = stop.clone();
|
|
130
|
-
// State to pace a loop across beats
|
|
131
|
-
struct CurrentLoopState {
|
|
132
|
-
var_name: Option<String>,
|
|
133
|
-
items: Vec<Value>,
|
|
134
|
-
body: Vec<AstStatement>,
|
|
135
|
-
idx: usize,
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
let handle = std::thread::spawn(move || {
|
|
139
|
-
let logger = Logger::new();
|
|
140
|
-
let mut bpm = if ctx.bpm > 0.0 { ctx.bpm } else { 120.0 };
|
|
141
|
-
let mut beat_secs = 60.0f32 / bpm;
|
|
142
|
-
let mut elapsed = 0.0f32;
|
|
143
|
-
|
|
144
|
-
let mut variables = ctx.variables.clone();
|
|
145
|
-
// mark realtime mode so prints/events only log during playback
|
|
146
|
-
variables.set("__rt".to_string(), Value::Boolean(true));
|
|
147
|
-
let mut functions = ctx.functions.clone();
|
|
148
|
-
let global_store = ctx.global_store.clone();
|
|
149
|
-
let mut audio_engine = AudioEngine::new("rt".to_string()); // dummy engine
|
|
150
|
-
|
|
151
|
-
let mut i: usize = 0;
|
|
152
|
-
let mut current_loop: Option<CurrentLoopState> = None;
|
|
153
|
-
let mut beat_index: u64 = 0;
|
|
154
|
-
while elapsed + 1e-3 < total_secs && i < ctx.entry_stmts.len() {
|
|
155
|
-
if stop_clone.load(Ordering::Relaxed) {
|
|
156
|
-
break;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// Wait until next beat
|
|
160
|
-
std::thread::sleep(Duration::from_secs_f32(beat_secs));
|
|
161
|
-
elapsed += beat_secs;
|
|
162
|
-
beat_index += 1;
|
|
163
|
-
if stop_clone.load(Ordering::Relaxed) {
|
|
164
|
-
break;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// If in the middle of a loop or if the next statement is a Loop, skip periodic events this beat to avoid interleaving
|
|
168
|
-
let next_is_loop = current_loop.is_some()
|
|
169
|
-
|| ctx
|
|
170
|
-
.entry_stmts
|
|
171
|
-
.get(i)
|
|
172
|
-
.map(|s| matches!(s.kind, StatementKind::Loop))
|
|
173
|
-
.unwrap_or(false);
|
|
174
|
-
// Fire real-time periodic events (beat/bar)
|
|
175
|
-
if !next_is_loop {
|
|
176
|
-
if let Some(handlers) = ctx.global_store.get_event_handlers("beat") {
|
|
177
|
-
for h in handlers {
|
|
178
|
-
if let StatementKind::On { event, args, body } = &h.kind {
|
|
179
|
-
let every: f32 = args
|
|
180
|
-
.as_ref()
|
|
181
|
-
.and_then(|v| v.get(0))
|
|
182
|
-
.and_then(|x| match x {
|
|
183
|
-
Value::Number(n) => Some(*n),
|
|
184
|
-
Value::Identifier(s) => match variables.get(s) {
|
|
185
|
-
Some(Value::Number(n)) => Some(*n),
|
|
186
|
-
_ => s.parse::<f32>().ok(),
|
|
187
|
-
},
|
|
188
|
-
_ => None,
|
|
189
|
-
})
|
|
190
|
-
.unwrap_or(1.0)
|
|
191
|
-
.max(0.0001);
|
|
192
|
-
let period = every.round().max(1.0) as u64;
|
|
193
|
-
if beat_index % period == 0 {
|
|
194
|
-
let mut vt = variables.clone();
|
|
195
|
-
let mut ctx_map = std::collections::HashMap::new();
|
|
196
|
-
ctx_map
|
|
197
|
-
.insert("name".to_string(), Value::String(event.clone()));
|
|
198
|
-
if let Some(a) = args.clone() {
|
|
199
|
-
ctx_map.insert("args".to_string(), Value::Array(a));
|
|
200
|
-
}
|
|
201
|
-
vt.set("event".to_string(), Value::Map(ctx_map));
|
|
202
|
-
vt.set("beat".to_string(), Value::Number(beat_index as f32));
|
|
203
|
-
vt.set("__in_event".to_string(), Value::Boolean(true));
|
|
204
|
-
vt.set("__rt".to_string(), Value::Boolean(true));
|
|
205
|
-
let _ = execute_audio_block(
|
|
206
|
-
&mut audio_engine,
|
|
207
|
-
&ctx.global_store,
|
|
208
|
-
vt,
|
|
209
|
-
functions.clone(),
|
|
210
|
-
body,
|
|
211
|
-
bpm,
|
|
212
|
-
60.0 / bpm,
|
|
213
|
-
0.0,
|
|
214
|
-
0.0,
|
|
215
|
-
);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
if !next_is_loop {
|
|
222
|
-
if let Some(handlers) = ctx.global_store.get_event_handlers("$beat") {
|
|
223
|
-
for h in handlers {
|
|
224
|
-
if let StatementKind::On { event, args, body } = &h.kind {
|
|
225
|
-
let every: f32 = args
|
|
226
|
-
.as_ref()
|
|
227
|
-
.and_then(|v| v.get(0))
|
|
228
|
-
.and_then(|x| match x {
|
|
229
|
-
Value::Number(n) => Some(*n),
|
|
230
|
-
Value::Identifier(s) => match variables.get(s) {
|
|
231
|
-
Some(Value::Number(n)) => Some(*n),
|
|
232
|
-
_ => s.parse::<f32>().ok(),
|
|
233
|
-
},
|
|
234
|
-
_ => None,
|
|
235
|
-
})
|
|
236
|
-
.unwrap_or(1.0)
|
|
237
|
-
.max(0.0001);
|
|
238
|
-
let period = every.round().max(1.0) as u64;
|
|
239
|
-
if beat_index % period == 0 {
|
|
240
|
-
let mut vt = variables.clone();
|
|
241
|
-
let mut ctx_map = std::collections::HashMap::new();
|
|
242
|
-
ctx_map
|
|
243
|
-
.insert("name".to_string(), Value::String(event.clone()));
|
|
244
|
-
if let Some(a) = args.clone() {
|
|
245
|
-
ctx_map.insert("args".to_string(), Value::Array(a));
|
|
246
|
-
}
|
|
247
|
-
vt.set("event".to_string(), Value::Map(ctx_map));
|
|
248
|
-
vt.set("beat".to_string(), Value::Number(beat_index as f32));
|
|
249
|
-
vt.set("__in_event".to_string(), Value::Boolean(true));
|
|
250
|
-
vt.set("__rt".to_string(), Value::Boolean(true));
|
|
251
|
-
let _ = execute_audio_block(
|
|
252
|
-
&mut audio_engine,
|
|
253
|
-
&ctx.global_store,
|
|
254
|
-
vt,
|
|
255
|
-
functions.clone(),
|
|
256
|
-
body,
|
|
257
|
-
bpm,
|
|
258
|
-
60.0 / bpm,
|
|
259
|
-
0.0,
|
|
260
|
-
0.0,
|
|
261
|
-
);
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
// Bars (4/4)
|
|
268
|
-
let bar_beats = 4u64;
|
|
269
|
-
if !next_is_loop {
|
|
270
|
-
if let Some(handlers) = ctx.global_store.get_event_handlers("bar") {
|
|
271
|
-
for h in handlers {
|
|
272
|
-
if let StatementKind::On { event, args, body } = &h.kind {
|
|
273
|
-
let first_only = args.as_ref().and_then(|v| v.get(0)).is_none();
|
|
274
|
-
let every_bar: f32 = if first_only {
|
|
275
|
-
1.0
|
|
276
|
-
} else {
|
|
277
|
-
args.as_ref()
|
|
278
|
-
.and_then(|v| v.get(0))
|
|
279
|
-
.and_then(|x| match x {
|
|
280
|
-
Value::Number(n) => Some(*n),
|
|
281
|
-
Value::Identifier(s) => match variables.get(s) {
|
|
282
|
-
Some(Value::Number(n)) => Some(*n),
|
|
283
|
-
_ => s.parse::<f32>().ok(),
|
|
284
|
-
},
|
|
285
|
-
_ => None,
|
|
286
|
-
})
|
|
287
|
-
.unwrap_or(1.0)
|
|
288
|
-
.max(0.0001)
|
|
289
|
-
};
|
|
290
|
-
let step_beats =
|
|
291
|
-
((bar_beats as f32) * every_bar).round().max(1.0) as u64;
|
|
292
|
-
let should_fire = if first_only {
|
|
293
|
-
beat_index == step_beats
|
|
294
|
-
} else {
|
|
295
|
-
beat_index % step_beats == 0
|
|
296
|
-
};
|
|
297
|
-
if should_fire {
|
|
298
|
-
let mut vt = variables.clone();
|
|
299
|
-
let mut ctx_map = std::collections::HashMap::new();
|
|
300
|
-
ctx_map
|
|
301
|
-
.insert("name".to_string(), Value::String(event.clone()));
|
|
302
|
-
if let Some(a) = args.clone() {
|
|
303
|
-
ctx_map.insert("args".to_string(), Value::Array(a));
|
|
304
|
-
}
|
|
305
|
-
vt.set("event".to_string(), Value::Map(ctx_map));
|
|
306
|
-
vt.set("beat".to_string(), Value::Number(beat_index as f32));
|
|
307
|
-
vt.set("__in_event".to_string(), Value::Boolean(true));
|
|
308
|
-
vt.set("__rt".to_string(), Value::Boolean(true));
|
|
309
|
-
let _ = execute_audio_block(
|
|
310
|
-
&mut audio_engine,
|
|
311
|
-
&ctx.global_store,
|
|
312
|
-
vt,
|
|
313
|
-
functions.clone(),
|
|
314
|
-
body,
|
|
315
|
-
bpm,
|
|
316
|
-
60.0 / bpm,
|
|
317
|
-
0.0,
|
|
318
|
-
0.0,
|
|
319
|
-
);
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
if !next_is_loop {
|
|
326
|
-
if let Some(handlers) = ctx.global_store.get_event_handlers("$bar") {
|
|
327
|
-
for h in handlers {
|
|
328
|
-
if let StatementKind::On { event, args, body } = &h.kind {
|
|
329
|
-
let first_only = args.as_ref().and_then(|v| v.get(0)).is_none();
|
|
330
|
-
let every_bar: f32 = if first_only {
|
|
331
|
-
1.0
|
|
332
|
-
} else {
|
|
333
|
-
args.as_ref()
|
|
334
|
-
.and_then(|v| v.get(0))
|
|
335
|
-
.and_then(|x| match x {
|
|
336
|
-
Value::Number(n) => Some(*n),
|
|
337
|
-
Value::Identifier(s) => match variables.get(s) {
|
|
338
|
-
Some(Value::Number(n)) => Some(*n),
|
|
339
|
-
_ => s.parse::<f32>().ok(),
|
|
340
|
-
},
|
|
341
|
-
_ => None,
|
|
342
|
-
})
|
|
343
|
-
.unwrap_or(1.0)
|
|
344
|
-
.max(0.0001)
|
|
345
|
-
};
|
|
346
|
-
let step_beats =
|
|
347
|
-
((bar_beats as f32) * every_bar).round().max(1.0) as u64;
|
|
348
|
-
let should_fire = if first_only {
|
|
349
|
-
beat_index == step_beats
|
|
350
|
-
} else {
|
|
351
|
-
beat_index % step_beats == 0
|
|
352
|
-
};
|
|
353
|
-
if should_fire {
|
|
354
|
-
let mut vt = variables.clone();
|
|
355
|
-
let mut ctx_map = std::collections::HashMap::new();
|
|
356
|
-
ctx_map
|
|
357
|
-
.insert("name".to_string(), Value::String(event.clone()));
|
|
358
|
-
if let Some(a) = args.clone() {
|
|
359
|
-
ctx_map.insert("args".to_string(), Value::Array(a));
|
|
360
|
-
}
|
|
361
|
-
vt.set("event".to_string(), Value::Map(ctx_map));
|
|
362
|
-
vt.set("beat".to_string(), Value::Number(beat_index as f32));
|
|
363
|
-
vt.set("__in_event".to_string(), Value::Boolean(true));
|
|
364
|
-
vt.set("__rt".to_string(), Value::Boolean(true));
|
|
365
|
-
let _ = execute_audio_block(
|
|
366
|
-
&mut audio_engine,
|
|
367
|
-
&ctx.global_store,
|
|
368
|
-
vt,
|
|
369
|
-
functions.clone(),
|
|
370
|
-
body,
|
|
371
|
-
bpm,
|
|
372
|
-
60.0 / bpm,
|
|
373
|
-
0.0,
|
|
374
|
-
0.0,
|
|
375
|
-
);
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
// If we are in the middle of a loop, execute one iteration per beat
|
|
383
|
-
if let Some(state) = &mut current_loop {
|
|
384
|
-
if state.idx < state.items.len() {
|
|
385
|
-
let mut vt = variables.clone();
|
|
386
|
-
if let Some(name) = &state.var_name {
|
|
387
|
-
vt.set(name.clone(), state.items[state.idx].clone());
|
|
388
|
-
}
|
|
389
|
-
let _ = execute_audio_block(
|
|
390
|
-
&mut audio_engine,
|
|
391
|
-
&global_store,
|
|
392
|
-
vt,
|
|
393
|
-
functions.clone(),
|
|
394
|
-
&state.body,
|
|
395
|
-
bpm,
|
|
396
|
-
60.0 / bpm,
|
|
397
|
-
0.0,
|
|
398
|
-
0.0,
|
|
399
|
-
);
|
|
400
|
-
state.idx += 1;
|
|
401
|
-
// If finished, clear and advance to next statement
|
|
402
|
-
if state.idx >= state.items.len() {
|
|
403
|
-
current_loop = None;
|
|
404
|
-
i += 1;
|
|
405
|
-
}
|
|
406
|
-
continue; // consume this beat
|
|
407
|
-
} else {
|
|
408
|
-
current_loop = None; // safety
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
let stmt = ctx.entry_stmts[i].clone();
|
|
413
|
-
|
|
414
|
-
match &stmt.kind {
|
|
415
|
-
StatementKind::Tempo => {
|
|
416
|
-
if let Some((new_bpm, _dur)) = interprete_tempo_statement(&stmt) {
|
|
417
|
-
bpm = new_bpm;
|
|
418
|
-
beat_secs = 60.0 / bpm.max(0.0001);
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
StatementKind::Sleep => {
|
|
422
|
-
let (new_cursor, _max) = interprete_sleep_statement(&stmt, 0.0, 0.0);
|
|
423
|
-
if new_cursor > 0.0 {
|
|
424
|
-
std::thread::sleep(Duration::from_secs_f32(new_cursor));
|
|
425
|
-
elapsed += new_cursor;
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
StatementKind::Let { .. } => {
|
|
429
|
-
if let Some(new_vars) = interprete_let_statement(&stmt, &mut variables) {
|
|
430
|
-
variables = new_vars;
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
StatementKind::Function { .. } => {
|
|
434
|
-
if let Some(new_funcs) =
|
|
435
|
-
interprete_function_statement(&stmt, &mut functions)
|
|
436
|
-
{
|
|
437
|
-
functions = new_funcs;
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
StatementKind::Call { .. } => {
|
|
441
|
-
let (_new_max, _cursor) = interprete_call_statement(
|
|
442
|
-
&stmt,
|
|
443
|
-
&mut audio_engine,
|
|
444
|
-
&variables,
|
|
445
|
-
&functions,
|
|
446
|
-
&global_store,
|
|
447
|
-
bpm,
|
|
448
|
-
60.0 / bpm,
|
|
449
|
-
0.0,
|
|
450
|
-
0.0,
|
|
451
|
-
);
|
|
452
|
-
}
|
|
453
|
-
StatementKind::ArrowCall { .. } => {
|
|
454
|
-
let mut max_end_time = 0.0;
|
|
455
|
-
let (_max, _cursor) = interprete_call_arrow_statement(
|
|
456
|
-
&stmt,
|
|
457
|
-
&mut audio_engine,
|
|
458
|
-
&variables,
|
|
459
|
-
bpm,
|
|
460
|
-
60.0 / bpm,
|
|
461
|
-
&mut max_end_time,
|
|
462
|
-
None,
|
|
463
|
-
true,
|
|
464
|
-
);
|
|
465
|
-
}
|
|
466
|
-
StatementKind::Loop => {
|
|
467
|
-
// Initialize pacing state for the loop and execute first iteration this beat
|
|
468
|
-
if let Value::Map(loop_map) = &stmt.value {
|
|
469
|
-
let body: Vec<AstStatement> = match loop_map.get("body") {
|
|
470
|
-
Some(Value::Block(b)) => b.clone(),
|
|
471
|
-
_ => Vec::new(),
|
|
472
|
-
};
|
|
473
|
-
|
|
474
|
-
if let (Some(Value::Identifier(var_name)), Some(Value::Array(items))) =
|
|
475
|
-
(loop_map.get("foreach"), loop_map.get("array"))
|
|
476
|
-
{
|
|
477
|
-
// foreach form
|
|
478
|
-
current_loop = Some(CurrentLoopState {
|
|
479
|
-
var_name: Some(var_name.clone()),
|
|
480
|
-
items: items.clone(),
|
|
481
|
-
body,
|
|
482
|
-
idx: 0,
|
|
483
|
-
});
|
|
484
|
-
} else if let Some(Value::Number(n)) = loop_map.get("iterator") {
|
|
485
|
-
// count form -> iterate 0..n-1
|
|
486
|
-
let count = (*n).max(0.0) as usize;
|
|
487
|
-
let items: Vec<Value> =
|
|
488
|
-
(0..count).map(|i| Value::Number(i as f32)).collect();
|
|
489
|
-
current_loop = Some(CurrentLoopState {
|
|
490
|
-
var_name: None,
|
|
491
|
-
items,
|
|
492
|
-
body,
|
|
493
|
-
idx: 0,
|
|
494
|
-
});
|
|
495
|
-
} else {
|
|
496
|
-
// Fallback: execute immediately if malformed
|
|
497
|
-
let (_max, _cursor) = interprete_loop_statement(
|
|
498
|
-
&stmt,
|
|
499
|
-
&mut audio_engine,
|
|
500
|
-
&global_store,
|
|
501
|
-
&variables,
|
|
502
|
-
&functions,
|
|
503
|
-
bpm,
|
|
504
|
-
60.0 / bpm,
|
|
505
|
-
0.0,
|
|
506
|
-
0.0,
|
|
507
|
-
);
|
|
508
|
-
i += 1;
|
|
509
|
-
continue;
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
// Execute first iteration now
|
|
513
|
-
if let Some(state) = &mut current_loop {
|
|
514
|
-
if state.idx < state.items.len() {
|
|
515
|
-
let mut vt = variables.clone();
|
|
516
|
-
if let Some(name) = &state.var_name {
|
|
517
|
-
vt.set(name.clone(), state.items[state.idx].clone());
|
|
518
|
-
}
|
|
519
|
-
let _ = execute_audio_block(
|
|
520
|
-
&mut audio_engine,
|
|
521
|
-
&global_store,
|
|
522
|
-
vt,
|
|
523
|
-
functions.clone(),
|
|
524
|
-
&state.body,
|
|
525
|
-
bpm,
|
|
526
|
-
60.0 / bpm,
|
|
527
|
-
0.0,
|
|
528
|
-
0.0,
|
|
529
|
-
);
|
|
530
|
-
state.idx += 1;
|
|
531
|
-
if state.idx >= state.items.len() {
|
|
532
|
-
current_loop = None;
|
|
533
|
-
i += 1;
|
|
534
|
-
}
|
|
535
|
-
continue; // consume this beat
|
|
536
|
-
} else {
|
|
537
|
-
current_loop = None; // no items, advance
|
|
538
|
-
i += 1;
|
|
539
|
-
continue;
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
} else {
|
|
543
|
-
// Not a map, fallback to immediate interpreter
|
|
544
|
-
let (_max, _cursor) = interprete_loop_statement(
|
|
545
|
-
&stmt,
|
|
546
|
-
&mut audio_engine,
|
|
547
|
-
&global_store,
|
|
548
|
-
&variables,
|
|
549
|
-
&functions,
|
|
550
|
-
bpm,
|
|
551
|
-
60.0 / bpm,
|
|
552
|
-
0.0,
|
|
553
|
-
0.0,
|
|
554
|
-
);
|
|
555
|
-
i += 1;
|
|
556
|
-
continue;
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
StatementKind::Spawn { .. } => {
|
|
560
|
-
// Run in parallel
|
|
561
|
-
let stmt_clone = stmt.clone();
|
|
562
|
-
let mut local_engine = AudioEngine::new("rt_spawn".to_string());
|
|
563
|
-
let vars = variables.clone();
|
|
564
|
-
let funcs = functions.clone();
|
|
565
|
-
let store = global_store.clone();
|
|
566
|
-
let local_bpm = bpm;
|
|
567
|
-
std::thread::spawn(move || {
|
|
568
|
-
let _ = interprete_spawn_statement(
|
|
569
|
-
&stmt_clone,
|
|
570
|
-
&mut local_engine,
|
|
571
|
-
&vars,
|
|
572
|
-
&funcs,
|
|
573
|
-
&store,
|
|
574
|
-
local_bpm,
|
|
575
|
-
60.0 / local_bpm,
|
|
576
|
-
0.0,
|
|
577
|
-
0.0,
|
|
578
|
-
);
|
|
579
|
-
});
|
|
580
|
-
}
|
|
581
|
-
StatementKind::Print => {
|
|
582
|
-
// Reuse print behavior from audio driver
|
|
583
|
-
match &stmt.value {
|
|
584
|
-
Value::String(s) => {
|
|
585
|
-
let env_bpm = bpm;
|
|
586
|
-
let env_beat = (elapsed / beat_secs).floor();
|
|
587
|
-
if let Some(res) =
|
|
588
|
-
crate::core::audio::evaluator::evaluate_string_expression(
|
|
589
|
-
s, &variables, env_bpm, env_beat,
|
|
590
|
-
)
|
|
591
|
-
{
|
|
592
|
-
logger.log_message(LogLevel::Print, &res);
|
|
593
|
-
} else if let Some(val) = variables.get(&s) {
|
|
594
|
-
logger.log_message(LogLevel::Print, &format!("{:?}", val));
|
|
595
|
-
} else if s.contains("$env")
|
|
596
|
-
|| s.contains("$math")
|
|
597
|
-
|| s.parse::<f32>().is_ok()
|
|
598
|
-
{
|
|
599
|
-
let v = crate::core::audio::evaluator::evaluate_rhs_into_value(
|
|
600
|
-
s, &variables, env_bpm, env_beat,
|
|
601
|
-
);
|
|
602
|
-
match v {
|
|
603
|
-
Value::Number(n) => {
|
|
604
|
-
logger.log_message(LogLevel::Print, &format!("{}", n))
|
|
605
|
-
}
|
|
606
|
-
_ => logger.log_message(LogLevel::Print, s),
|
|
607
|
-
}
|
|
608
|
-
} else {
|
|
609
|
-
logger.log_message(LogLevel::Print, s);
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
Value::Number(n) => {
|
|
613
|
-
logger.log_message(LogLevel::Print, &format!("{}", n));
|
|
614
|
-
}
|
|
615
|
-
Value::Identifier(name) => {
|
|
616
|
-
if let Some(val) = variables.get(name) {
|
|
617
|
-
match val {
|
|
618
|
-
Value::Number(n) => {
|
|
619
|
-
logger.log_message(LogLevel::Print, &format!("{}", n))
|
|
620
|
-
}
|
|
621
|
-
Value::String(s) => logger.log_message(LogLevel::Print, s),
|
|
622
|
-
Value::Boolean(b) => {
|
|
623
|
-
logger.log_message(LogLevel::Print, &format!("{}", b))
|
|
624
|
-
}
|
|
625
|
-
other => logger
|
|
626
|
-
.log_message(LogLevel::Print, &format!("{:?}", other)),
|
|
627
|
-
}
|
|
628
|
-
} else {
|
|
629
|
-
logger.log_message(LogLevel::Print, name);
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
v => logger.log_message(LogLevel::Print, &format!("{:?}", v)),
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
StatementKind::On { .. } => { /* handlers already registered by preprocessor */
|
|
636
|
-
}
|
|
637
|
-
StatementKind::Emit { .. } => { /* could log or handle later */ }
|
|
638
|
-
_ => { /* ignore others in RT runner */ }
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
i += 1;
|
|
642
|
-
}
|
|
643
|
-
});
|
|
644
|
-
RtRunner { stop, handle }
|
|
645
|
-
}
|
|
646
|
-
fn stop_realtime_runner(runner_opt: &mut Option<RtRunner>) {
|
|
647
|
-
if let Some(r) = runner_opt.take() {
|
|
648
|
-
r.stop.store(true, Ordering::Relaxed);
|
|
649
|
-
let _ = r.handle.join();
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
fn join_realtime_runner(runner_opt: &mut Option<RtRunner>) {
|
|
654
|
-
if let Some(r) = runner_opt.take() {
|
|
655
|
-
let _ = r.handle.join();
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
if watch && fetched_repeat {
|
|
660
|
-
logger.log_message(
|
|
661
|
-
LogLevel::Error,
|
|
662
|
-
"Watch and repeat cannot be used together. Use repeat instead.",
|
|
663
|
-
);
|
|
664
|
-
return Err("invalid options: watch and repeat cannot be combined".to_string());
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
if watch {
|
|
668
|
-
let (tx, rx) = channel::<()>();
|
|
669
|
-
|
|
670
|
-
// Thread 1 : Watcher sending changes
|
|
671
|
-
let entry_clone = entry_path.clone();
|
|
672
|
-
thread::spawn(move || {
|
|
673
|
-
let _ = watch_directory(entry_clone, move || {
|
|
674
|
-
let _ = tx.send(()); // signal a change
|
|
675
|
-
});
|
|
676
|
-
});
|
|
677
|
-
|
|
678
|
-
// Main thread: build + play in a loop
|
|
679
|
-
let (bpm, entry_stmts, variables, functions, global_store) =
|
|
680
|
-
begin_play(&config, &entry_file, &output_path, debug)?;
|
|
681
|
-
audio_player.play_file_once(&audio_file);
|
|
682
|
-
// Estimate duration: base on statement count plus extra for loop iterations (1 beat per iter)
|
|
683
|
-
let loop_iters: usize = entry_stmts
|
|
684
|
-
.iter()
|
|
685
|
-
.map(|s| match &s.kind {
|
|
686
|
-
crate::core::parser::statement::StatementKind::Loop => {
|
|
687
|
-
if let crate::core::shared::value::Value::Map(m) = &s.value {
|
|
688
|
-
if let Some(crate::core::shared::value::Value::Array(items)) =
|
|
689
|
-
m.get("array")
|
|
690
|
-
{
|
|
691
|
-
items.len()
|
|
692
|
-
} else if let Some(crate::core::shared::value::Value::Number(n)) =
|
|
693
|
-
m.get("iterator")
|
|
694
|
-
{
|
|
695
|
-
(*n).max(0.0) as usize
|
|
696
|
-
} else {
|
|
697
|
-
0
|
|
698
|
-
}
|
|
699
|
-
} else {
|
|
700
|
-
0
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
_ => 0,
|
|
704
|
-
})
|
|
705
|
-
.sum();
|
|
706
|
-
let est_beats = entry_stmts.len() as f32 + loop_iters as f32;
|
|
707
|
-
let est_by_len = ((60.0 / bpm).max(0.01) * est_beats).max(1.0);
|
|
708
|
-
let total_secs = wav_duration_seconds(&audio_file)
|
|
709
|
-
.unwrap_or(0.0)
|
|
710
|
-
.max(est_by_len);
|
|
711
|
-
let mut rt_runner = Some(start_realtime_runner(
|
|
712
|
-
RtContext {
|
|
713
|
-
bpm,
|
|
714
|
-
entry_stmts,
|
|
715
|
-
variables,
|
|
716
|
-
functions,
|
|
717
|
-
global_store,
|
|
718
|
-
},
|
|
719
|
-
total_secs,
|
|
720
|
-
));
|
|
721
|
-
|
|
722
|
-
logger.log_message(
|
|
723
|
-
LogLevel::Watcher,
|
|
724
|
-
"Watching for changes... Press Ctrl+C to exit.",
|
|
725
|
-
);
|
|
726
|
-
|
|
727
|
-
while let Ok(_) = rx.recv() {
|
|
728
|
-
logger.log_message(LogLevel::Watcher, "Change detected, rebuilding...");
|
|
729
|
-
|
|
730
|
-
// Stop previous real-time runner before restarting playback
|
|
731
|
-
stop_realtime_runner(&mut rt_runner);
|
|
732
|
-
|
|
733
|
-
let (bpm, entry_stmts, variables, functions, global_store) =
|
|
734
|
-
match begin_play(&config, &entry_file, &output_path, debug) {
|
|
735
|
-
Ok(v) => v,
|
|
736
|
-
Err(e) => {
|
|
737
|
-
logger.log_message(LogLevel::Error, &format!("Rebuild failed: {}", e));
|
|
738
|
-
continue;
|
|
739
|
-
}
|
|
740
|
-
};
|
|
741
|
-
|
|
742
|
-
logger.log_message(LogLevel::Info, "🎵 Playback started (once mode)...");
|
|
743
|
-
|
|
744
|
-
audio_player.play_file_once(&audio_file);
|
|
745
|
-
let loop_iters: usize = entry_stmts
|
|
746
|
-
.iter()
|
|
747
|
-
.map(|s| match &s.kind {
|
|
748
|
-
crate::core::parser::statement::StatementKind::Loop => {
|
|
749
|
-
if let crate::core::shared::value::Value::Map(m) = &s.value {
|
|
750
|
-
if let Some(crate::core::shared::value::Value::Array(items)) =
|
|
751
|
-
m.get("array")
|
|
752
|
-
{
|
|
753
|
-
items.len()
|
|
754
|
-
} else if let Some(crate::core::shared::value::Value::Number(n)) =
|
|
755
|
-
m.get("iterator")
|
|
756
|
-
{
|
|
757
|
-
(*n).max(0.0) as usize
|
|
758
|
-
} else {
|
|
759
|
-
0
|
|
760
|
-
}
|
|
761
|
-
} else {
|
|
762
|
-
0
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
_ => 0,
|
|
766
|
-
})
|
|
767
|
-
.sum();
|
|
768
|
-
let est_beats = entry_stmts.len() as f32 + loop_iters as f32;
|
|
769
|
-
let est_by_len = ((60.0 / bpm).max(0.01) * est_beats).max(1.0);
|
|
770
|
-
let total_secs = wav_duration_seconds(&audio_file)
|
|
771
|
-
.unwrap_or(0.0)
|
|
772
|
-
.max(est_by_len);
|
|
773
|
-
rt_runner = Some(start_realtime_runner(
|
|
774
|
-
RtContext {
|
|
775
|
-
bpm,
|
|
776
|
-
entry_stmts,
|
|
777
|
-
variables,
|
|
778
|
-
functions,
|
|
779
|
-
global_store,
|
|
780
|
-
},
|
|
781
|
-
total_secs,
|
|
782
|
-
));
|
|
783
|
-
}
|
|
784
|
-
} else if fetched_repeat {
|
|
785
|
-
// Initial build to start from a clean slate
|
|
786
|
-
let (bpm, entry_stmts, variables, functions, global_store) =
|
|
787
|
-
begin_play(&config, &entry_file, &output_path, debug)?;
|
|
788
|
-
|
|
789
|
-
logger.log_message(LogLevel::Info, "🎵 Playback started (repeat mode)...");
|
|
790
|
-
|
|
791
|
-
let mut last_snapshot = snapshot_files(&entry_path);
|
|
792
|
-
let mut audio_player = AudioPlayer::new();
|
|
793
|
-
audio_player.play_file_once(&audio_file);
|
|
794
|
-
|
|
795
|
-
let loop_iters: usize = entry_stmts
|
|
796
|
-
.iter()
|
|
797
|
-
.map(|s| match &s.kind {
|
|
798
|
-
crate::core::parser::statement::StatementKind::Loop => {
|
|
799
|
-
if let crate::core::shared::value::Value::Map(m) = &s.value {
|
|
800
|
-
if let Some(crate::core::shared::value::Value::Array(items)) =
|
|
801
|
-
m.get("array")
|
|
802
|
-
{
|
|
803
|
-
items.len()
|
|
804
|
-
} else if let Some(crate::core::shared::value::Value::Number(n)) =
|
|
805
|
-
m.get("iterator")
|
|
806
|
-
{
|
|
807
|
-
(*n).max(0.0) as usize
|
|
808
|
-
} else {
|
|
809
|
-
0
|
|
810
|
-
}
|
|
811
|
-
} else {
|
|
812
|
-
0
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
_ => 0,
|
|
816
|
-
})
|
|
817
|
-
.sum();
|
|
818
|
-
let est_beats = entry_stmts.len() as f32 + loop_iters as f32;
|
|
819
|
-
let est_by_len = ((60.0 / bpm).max(0.01) * est_beats).max(1.0);
|
|
820
|
-
let total_secs = wav_duration_seconds(&audio_file)
|
|
821
|
-
.unwrap_or(0.0)
|
|
822
|
-
.max(est_by_len);
|
|
823
|
-
let mut rt_runner = Some(start_realtime_runner(
|
|
824
|
-
RtContext {
|
|
825
|
-
bpm,
|
|
826
|
-
entry_stmts: entry_stmts.clone(),
|
|
827
|
-
variables: variables.clone(),
|
|
828
|
-
functions: functions.clone(),
|
|
829
|
-
global_store: global_store.clone(),
|
|
830
|
-
},
|
|
831
|
-
total_secs,
|
|
832
|
-
));
|
|
833
|
-
|
|
834
|
-
loop {
|
|
835
|
-
let current_snapshot = snapshot_files(&entry_path);
|
|
836
|
-
let has_changed = files_changed(&last_snapshot, ¤t_snapshot);
|
|
837
|
-
|
|
838
|
-
if has_changed {
|
|
839
|
-
logger.log_message(
|
|
840
|
-
LogLevel::Info,
|
|
841
|
-
"Change detected, rebuilding in background...",
|
|
842
|
-
);
|
|
843
|
-
let entry_file = entry_file.clone();
|
|
844
|
-
let output_path = output_path.clone();
|
|
845
|
-
let config_clone = config.clone();
|
|
846
|
-
|
|
847
|
-
// Rebuild in a separate thread
|
|
848
|
-
std::thread::spawn(move || {
|
|
849
|
-
if let Err(e) = begin_play(&config_clone, &entry_file, &output_path, debug) {
|
|
850
|
-
eprintln!("Rebuild failed in background: {}", e);
|
|
851
|
-
}
|
|
852
|
-
});
|
|
853
|
-
|
|
854
|
-
last_snapshot = current_snapshot;
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
// Wait for the audio to finish
|
|
858
|
-
audio_player.wait_until_end();
|
|
859
|
-
// Stop the current real-time runner
|
|
860
|
-
stop_realtime_runner(&mut rt_runner);
|
|
861
|
-
|
|
862
|
-
// Then replay the audio (rebuilt or not)
|
|
863
|
-
audio_player.play_file_once(&audio_file);
|
|
864
|
-
let loop_iters: usize = entry_stmts
|
|
865
|
-
.iter()
|
|
866
|
-
.map(|s| match &s.kind {
|
|
867
|
-
crate::core::parser::statement::StatementKind::Loop => {
|
|
868
|
-
if let crate::core::shared::value::Value::Map(m) = &s.value {
|
|
869
|
-
if let Some(crate::core::shared::value::Value::Array(items)) =
|
|
870
|
-
m.get("array")
|
|
871
|
-
{
|
|
872
|
-
items.len()
|
|
873
|
-
} else if let Some(crate::core::shared::value::Value::Number(n)) =
|
|
874
|
-
m.get("iterator")
|
|
875
|
-
{
|
|
876
|
-
(*n).max(0.0) as usize
|
|
877
|
-
} else {
|
|
878
|
-
0
|
|
879
|
-
}
|
|
880
|
-
} else {
|
|
881
|
-
0
|
|
882
|
-
}
|
|
883
|
-
}
|
|
884
|
-
_ => 0,
|
|
885
|
-
})
|
|
886
|
-
.sum();
|
|
887
|
-
let est_beats = entry_stmts.len() as f32 + loop_iters as f32;
|
|
888
|
-
let est_by_len = ((60.0 / bpm).max(0.01) * est_beats).max(1.0);
|
|
889
|
-
let total_secs = wav_duration_seconds(&audio_file)
|
|
890
|
-
.unwrap_or(0.0)
|
|
891
|
-
.max(est_by_len);
|
|
892
|
-
rt_runner = Some(start_realtime_runner(
|
|
893
|
-
RtContext {
|
|
894
|
-
bpm,
|
|
895
|
-
entry_stmts: entry_stmts.clone(),
|
|
896
|
-
variables: variables.clone(),
|
|
897
|
-
functions: functions.clone(),
|
|
898
|
-
global_store: global_store.clone(),
|
|
899
|
-
},
|
|
900
|
-
total_secs,
|
|
901
|
-
));
|
|
902
|
-
}
|
|
903
|
-
} else {
|
|
904
|
-
// Single execution
|
|
905
|
-
let (bpm, entry_stmts, variables, functions, global_store) =
|
|
906
|
-
begin_play(&config, &entry_file, &output_path, debug)?;
|
|
907
|
-
|
|
908
|
-
logger.log_message(LogLevel::Info, "🎵 Playback started (once mode)...");
|
|
909
|
-
|
|
910
|
-
audio_player.play_file_once(&audio_file);
|
|
911
|
-
|
|
912
|
-
let est_by_len = ((60.0 / bpm).max(0.01) * (entry_stmts.len() as f32)).max(1.0);
|
|
913
|
-
let total_secs = wav_duration_seconds(&audio_file)
|
|
914
|
-
.unwrap_or(0.0)
|
|
915
|
-
.max(est_by_len);
|
|
916
|
-
let mut rt_runner = Some(start_realtime_runner(
|
|
917
|
-
RtContext {
|
|
918
|
-
bpm,
|
|
919
|
-
entry_stmts,
|
|
920
|
-
variables,
|
|
921
|
-
functions,
|
|
922
|
-
global_store,
|
|
923
|
-
},
|
|
924
|
-
total_secs,
|
|
925
|
-
));
|
|
926
|
-
|
|
927
|
-
audio_player.wait_until_end();
|
|
928
|
-
// Let the runner finish naturally to execute all remaining statements (e.g., loop prints)
|
|
929
|
-
join_realtime_runner(&mut rt_runner);
|
|
930
|
-
}
|
|
931
|
-
// ... existing implementation continues unchanged until the end of the file ...
|
|
932
|
-
// The original function didn’t return a Result; ensure Ok(()) on normal paths and Err(...) on failures.
|
|
933
|
-
Ok(())
|
|
934
|
-
}
|
|
935
|
-
|
|
936
|
-
fn compute_basic_stats(output_dir: &str) -> crate::config::stats::ProjectStats {
|
|
937
|
-
use crate::config::stats::ProjectStats;
|
|
938
|
-
use std::{fs, path::Path};
|
|
939
|
-
let mut stats = ProjectStats::new();
|
|
940
|
-
let out = Path::new(output_dir);
|
|
941
|
-
let mut nb_files = 0usize;
|
|
942
|
-
let mut nb_lines = 0usize;
|
|
943
|
-
if out.exists() {
|
|
944
|
-
if let Ok(entries) = fs::read_dir(out) {
|
|
945
|
-
for entry in entries.flatten() {
|
|
946
|
-
if let Ok(ft) = entry.file_type() {
|
|
947
|
-
if ft.is_file() {
|
|
948
|
-
nb_files += 1;
|
|
949
|
-
if let Ok(content) = fs::read_to_string(entry.path()) {
|
|
950
|
-
nb_lines += content.lines().count();
|
|
951
|
-
}
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
}
|
|
957
|
-
stats.counts.nb_files = nb_files;
|
|
958
|
-
stats.counts.nb_lines = nb_lines;
|
|
959
|
-
stats
|
|
960
|
-
}
|
|
961
|
-
|
|
962
|
-
fn begin_play(
|
|
963
|
-
_config: &Option<ProjectConfig>,
|
|
964
|
-
entry_file: &str,
|
|
965
|
-
output: &str,
|
|
966
|
-
debug: bool,
|
|
967
|
-
) -> Result<
|
|
968
|
-
(
|
|
969
|
-
f32,
|
|
970
|
-
Vec<crate::core::parser::statement::Statement>,
|
|
971
|
-
crate::core::store::variable::VariableTable,
|
|
972
|
-
crate::core::store::function::FunctionTable,
|
|
973
|
-
crate::core::store::global::GlobalStore,
|
|
974
|
-
),
|
|
975
|
-
String,
|
|
976
|
-
> {
|
|
977
|
-
let spinner = with_spinner("Building...", || {
|
|
978
|
-
thread::sleep(Duration::from_millis(800));
|
|
979
|
-
});
|
|
980
|
-
|
|
981
|
-
let normalized_entry = normalize_path(entry_file);
|
|
982
|
-
let normalized_output_dir = normalize_path(&output);
|
|
983
|
-
|
|
984
|
-
let duration = std::time::Instant::now();
|
|
985
|
-
let mut global_store = GlobalStore::new();
|
|
986
|
-
let loader = ModuleLoader::new(&normalized_entry, &normalized_output_dir);
|
|
987
|
-
let (modules_tokens, modules_statements) = loader.load_all_modules(&mut global_store);
|
|
988
|
-
|
|
989
|
-
// Try to detect initial BPM from statements (fallback to 120.0)
|
|
990
|
-
let mut detected_bpm: f32 = 120.0;
|
|
991
|
-
let mut entry_statements: Vec<crate::core::parser::statement::Statement> = Vec::new();
|
|
992
|
-
// Prefer the entry module if present
|
|
993
|
-
if let Some(entry_stmts) = modules_statements.get(&normalized_entry) {
|
|
994
|
-
entry_statements = entry_stmts.clone();
|
|
995
|
-
for stmt in entry_stmts {
|
|
996
|
-
if let crate::core::parser::statement::StatementKind::Tempo = &stmt.kind {
|
|
997
|
-
if let crate::core::shared::value::Value::Number(n) = &stmt.value {
|
|
998
|
-
detected_bpm = *n;
|
|
999
|
-
break;
|
|
1000
|
-
}
|
|
1001
|
-
}
|
|
1002
|
-
}
|
|
1003
|
-
}
|
|
1004
|
-
// If still default, scan other modules for a tempo directive
|
|
1005
|
-
if (detected_bpm - 120.0).abs() < f32::EPSILON {
|
|
1006
|
-
'outer: for (_name, stmts) in modules_statements.iter() {
|
|
1007
|
-
for stmt in stmts {
|
|
1008
|
-
if let crate::core::parser::statement::StatementKind::Tempo = &stmt.kind {
|
|
1009
|
-
if let crate::core::shared::value::Value::Number(n) = &stmt.value {
|
|
1010
|
-
detected_bpm = *n;
|
|
1011
|
-
break 'outer;
|
|
1012
|
-
}
|
|
1013
|
-
}
|
|
1014
|
-
}
|
|
1015
|
-
}
|
|
1016
|
-
}
|
|
1017
|
-
|
|
1018
|
-
// SECTION Write logs
|
|
1019
|
-
if debug {
|
|
1020
|
-
for (module_path, module) in global_store.modules.clone() {
|
|
1021
|
-
write_module_variable_log_file(
|
|
1022
|
-
&normalized_output_dir,
|
|
1023
|
-
&module_path,
|
|
1024
|
-
&module.variable_table,
|
|
1025
|
-
);
|
|
1026
|
-
write_module_function_log_file(
|
|
1027
|
-
&normalized_output_dir,
|
|
1028
|
-
&module_path,
|
|
1029
|
-
&module.function_table,
|
|
1030
|
-
);
|
|
1031
|
-
}
|
|
1032
|
-
|
|
1033
|
-
write_lexer_log_file(
|
|
1034
|
-
&normalized_output_dir,
|
|
1035
|
-
"lexer_tokens.log",
|
|
1036
|
-
modules_tokens.clone(),
|
|
1037
|
-
);
|
|
1038
|
-
write_preprocessor_log_file(
|
|
1039
|
-
&normalized_output_dir,
|
|
1040
|
-
"resolved_statements.log",
|
|
1041
|
-
modules_statements.clone(),
|
|
1042
|
-
);
|
|
1043
|
-
write_variables_log_file(
|
|
1044
|
-
&normalized_output_dir,
|
|
1045
|
-
"global_variables.log",
|
|
1046
|
-
global_store.variables.clone(),
|
|
1047
|
-
);
|
|
1048
|
-
write_function_log_file(
|
|
1049
|
-
&normalized_output_dir,
|
|
1050
|
-
"global_functions.log",
|
|
1051
|
-
global_store.functions.clone(),
|
|
1052
|
-
);
|
|
1053
|
-
}
|
|
1054
|
-
|
|
1055
|
-
// SECTION Detect errors before building (like build.rs)
|
|
1056
|
-
let all_errors = crate::utils::error::collect_all_errors_with_modules(&modules_statements);
|
|
1057
|
-
let (warnings, criticals) = crate::utils::error::partition_errors(all_errors);
|
|
1058
|
-
crate::utils::error::log_errors_with_stack("Play", &warnings, &criticals);
|
|
1059
|
-
if !criticals.is_empty() {
|
|
1060
|
-
spinner.finish_and_clear();
|
|
1061
|
-
return Err(format!(
|
|
1062
|
-
"play failed with {} critical error(s): {}",
|
|
1063
|
-
criticals.len(),
|
|
1064
|
-
criticals[0].message
|
|
1065
|
-
));
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
// SECTION Building AST and Audio
|
|
1069
|
-
let builder = Builder::new();
|
|
1070
|
-
builder.build_ast(&modules_statements, &output, false);
|
|
1071
|
-
builder.build_audio(&modules_statements, &output, &mut global_store);
|
|
1072
|
-
|
|
1073
|
-
// SECTION Logging
|
|
1074
|
-
let logger = Logger::new();
|
|
1075
|
-
let success_message = format!(
|
|
1076
|
-
"Build completed successfully in {:.2?}. Output files written to: '{}'",
|
|
1077
|
-
duration.elapsed(),
|
|
1078
|
-
normalized_output_dir
|
|
1079
|
-
);
|
|
1080
|
-
|
|
1081
|
-
// Compute and persist rich stats (align with build/check)
|
|
1082
|
-
let stats = crate::config::stats::compute_from(
|
|
1083
|
-
&modules_statements,
|
|
1084
|
-
&global_store,
|
|
1085
|
-
&_config,
|
|
1086
|
-
Some(&normalized_output_dir),
|
|
1087
|
-
);
|
|
1088
|
-
crate::config::stats::set_memory_stats(stats.clone());
|
|
1089
|
-
if let Err(e) = crate::config::stats::save_to_file(&stats) {
|
|
1090
|
-
eprintln!("[stats] failed to save: {}", e);
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1093
|
-
spinner.finish_and_clear();
|
|
1094
|
-
logger.log_message(LogLevel::Success, &success_message);
|
|
1095
|
-
|
|
1096
|
-
Ok((
|
|
1097
|
-
detected_bpm,
|
|
1098
|
-
entry_statements,
|
|
1099
|
-
global_store.variables.clone(),
|
|
1100
|
-
global_store.functions.clone(),
|
|
1101
|
-
global_store,
|
|
1102
|
-
))
|
|
1103
|
-
}
|
|
1104
|
-
|
|
1105
|
-
fn snapshot_files<P: AsRef<Path>>(dir: P) -> HashMap<String, u64> {
|
|
1106
|
-
let mut map = HashMap::new();
|
|
1107
|
-
if let Ok(entries) = fs::read_dir(dir) {
|
|
1108
|
-
for entry in entries.flatten() {
|
|
1109
|
-
if let Ok(meta) = entry.metadata() {
|
|
1110
|
-
if let Ok(mtime) = meta.modified() {
|
|
1111
|
-
if let Ok(duration) = mtime.duration_since(std::time::UNIX_EPOCH) {
|
|
1112
|
-
map.insert(entry.path().display().to_string(), duration.as_secs());
|
|
1113
|
-
}
|
|
1114
|
-
}
|
|
1115
|
-
}
|
|
1116
|
-
}
|
|
1117
|
-
}
|
|
1118
|
-
map
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
fn files_changed(old: &HashMap<String, u64>, new: &HashMap<String, u64>) -> bool {
|
|
1122
|
-
old != new
|
|
1123
|
-
}
|