@devaloop/devalang 0.0.1-alpha.13 → 0.0.1-alpha.15
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/.devalang +2 -3
- package/Cargo.toml +58 -54
- package/README.md +59 -27
- package/docs/CHANGELOG.md +99 -2
- package/docs/CONTRIBUTING.md +1 -0
- package/docs/ROADMAP.md +3 -3
- package/docs/TODO.md +5 -4
- package/examples/automation.deva +44 -0
- package/examples/bank.deva +2 -4
- package/examples/function.deva +15 -0
- package/examples/index.deva +41 -11
- package/examples/plugin.deva +15 -0
- package/out-tsc/bin/devalang.exe +0 -0
- package/package.json +6 -6
- package/project-version.json +3 -3
- package/rust/cli/bank.rs +16 -16
- package/rust/cli/build.rs +69 -30
- package/rust/cli/check.rs +46 -6
- package/rust/cli/driver.rs +40 -28
- package/rust/cli/install.rs +22 -7
- package/rust/cli/login.rs +134 -0
- package/rust/cli/mod.rs +2 -1
- package/rust/cli/play.rs +44 -19
- package/rust/cli/update.rs +1 -1
- package/rust/common/api.rs +5 -0
- package/rust/common/cdn.rs +3 -9
- package/rust/common/mod.rs +3 -1
- package/rust/common/sso.rs +5 -0
- package/rust/config/driver.rs +19 -1
- package/rust/config/loader.rs +56 -10
- package/rust/core/audio/engine.rs +314 -63
- package/rust/core/audio/evaluator.rs +101 -0
- package/rust/core/audio/interpreter/arrow_call.rs +60 -15
- package/rust/core/audio/interpreter/automate.rs +18 -0
- package/rust/core/audio/interpreter/call.rs +4 -4
- package/rust/core/audio/interpreter/condition.rs +3 -3
- package/rust/core/audio/interpreter/driver.rs +68 -30
- package/rust/core/audio/interpreter/let_.rs +14 -7
- package/rust/core/audio/interpreter/loop_.rs +39 -6
- package/rust/core/audio/interpreter/mod.rs +2 -1
- package/rust/core/audio/interpreter/sleep.rs +2 -4
- package/rust/core/audio/interpreter/spawn.rs +4 -4
- package/rust/core/audio/loader/trigger.rs +2 -5
- package/rust/core/audio/mod.rs +2 -1
- package/rust/core/audio/renderer.rs +1 -1
- package/rust/core/audio/special/easing.rs +120 -0
- package/rust/core/audio/special/env.rs +41 -0
- package/rust/core/audio/special/math.rs +92 -0
- package/rust/core/audio/special/mod.rs +9 -0
- package/rust/core/audio/special/modulator.rs +120 -0
- package/rust/core/builder/mod.rs +11 -6
- package/rust/core/debugger/store.rs +1 -1
- package/rust/core/error/mod.rs +4 -1
- package/rust/core/lexer/handler/arrow.rs +60 -9
- package/rust/core/lexer/handler/at.rs +4 -4
- package/rust/core/lexer/handler/brace.rs +8 -8
- package/rust/core/lexer/handler/colon.rs +4 -4
- package/rust/core/lexer/handler/comment.rs +2 -2
- package/rust/core/lexer/handler/dot.rs +4 -4
- package/rust/core/lexer/handler/driver.rs +42 -13
- package/rust/core/lexer/handler/identifier.rs +5 -4
- package/rust/core/lexer/handler/indent.rs +16 -2
- package/rust/core/lexer/handler/newline.rs +1 -1
- package/rust/core/lexer/handler/number.rs +3 -3
- package/rust/core/lexer/handler/operator.rs +3 -1
- package/rust/core/lexer/handler/parenthesis.rs +8 -8
- package/rust/core/lexer/handler/slash.rs +5 -5
- package/rust/core/lexer/handler/string.rs +1 -1
- package/rust/core/lexer/mod.rs +1 -1
- package/rust/core/lexer/token.rs +4 -0
- package/rust/core/mod.rs +2 -1
- package/rust/core/parser/driver.rs +134 -11
- package/rust/core/parser/handler/arrow_call.rs +141 -65
- package/rust/core/parser/handler/at.rs +1 -1
- package/rust/core/parser/handler/bank.rs +35 -7
- package/rust/core/parser/handler/dot.rs +43 -22
- package/rust/core/parser/handler/identifier/automate.rs +194 -0
- package/rust/core/parser/handler/identifier/function.rs +2 -3
- package/rust/core/parser/handler/identifier/let_.rs +16 -0
- package/rust/core/parser/handler/identifier/mod.rs +14 -10
- package/rust/core/parser/handler/identifier/print.rs +29 -0
- package/rust/core/parser/handler/identifier/sleep.rs +1 -1
- package/rust/core/parser/handler/identifier/synth.rs +7 -9
- package/rust/core/parser/handler/loop_.rs +60 -43
- package/rust/core/parser/statement.rs +5 -0
- package/rust/core/plugin/loader.rs +48 -0
- package/rust/core/plugin/mod.rs +1 -0
- package/rust/core/preprocessor/loader.rs +7 -5
- package/rust/core/preprocessor/processor.rs +4 -4
- package/rust/core/preprocessor/resolver/bank.rs +1 -2
- package/rust/core/preprocessor/resolver/call.rs +19 -18
- package/rust/core/preprocessor/resolver/driver.rs +7 -5
- package/rust/core/preprocessor/resolver/function.rs +3 -13
- package/rust/core/preprocessor/resolver/loop_.rs +31 -1
- package/rust/core/preprocessor/resolver/spawn.rs +3 -22
- package/rust/core/preprocessor/resolver/tempo.rs +1 -1
- package/rust/core/preprocessor/resolver/trigger.rs +2 -3
- package/rust/core/preprocessor/resolver/value.rs +6 -12
- package/rust/core/shared/bank.rs +1 -1
- package/rust/core/utils/path.rs +1 -1
- package/rust/core/utils/validation.rs +0 -1
- package/rust/installer/addon.rs +80 -0
- package/rust/installer/bank.rs +25 -15
- package/rust/installer/mod.rs +4 -1
- package/rust/installer/plugin.rs +55 -0
- package/rust/main.rs +32 -10
- package/rust/utils/error.rs +51 -0
- package/rust/utils/logger.rs +20 -0
- package/rust/utils/mod.rs +1 -44
- package/rust/utils/spinner.rs +3 -5
|
@@ -18,7 +18,7 @@ pub fn interprete_call_statement(
|
|
|
18
18
|
) -> (f32, f32) {
|
|
19
19
|
match &stmt.kind {
|
|
20
20
|
StatementKind::Call { name, args } => {
|
|
21
|
-
//
|
|
21
|
+
// Classic function call case
|
|
22
22
|
if let Some(func) = functions.functions.get(name) {
|
|
23
23
|
if func.parameters.len() != args.len() {
|
|
24
24
|
eprintln!(
|
|
@@ -40,7 +40,7 @@ pub fn interprete_call_statement(
|
|
|
40
40
|
global_store,
|
|
41
41
|
local_vars,
|
|
42
42
|
functions.clone(),
|
|
43
|
-
func.body
|
|
43
|
+
&func.body,
|
|
44
44
|
base_bpm,
|
|
45
45
|
base_duration,
|
|
46
46
|
max_end_time,
|
|
@@ -48,7 +48,7 @@ pub fn interprete_call_statement(
|
|
|
48
48
|
);
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
//
|
|
51
|
+
// Group case
|
|
52
52
|
if let Some(group_stmt) = find_group(name, variable_table, global_store) {
|
|
53
53
|
if let Value::Map(map) = &group_stmt.value {
|
|
54
54
|
if let Some(Value::Block(body)) = map.get("body") {
|
|
@@ -57,7 +57,7 @@ pub fn interprete_call_statement(
|
|
|
57
57
|
global_store,
|
|
58
58
|
variable_table.clone(),
|
|
59
59
|
functions.clone(),
|
|
60
|
-
body
|
|
60
|
+
&body,
|
|
61
61
|
base_bpm,
|
|
62
62
|
base_duration,
|
|
63
63
|
max_end_time,
|
|
@@ -20,8 +20,8 @@ pub fn interprete_condition_statement(
|
|
|
20
20
|
max_end_time: f32,
|
|
21
21
|
cursor_time: f32
|
|
22
22
|
) -> (f32, f32) {
|
|
23
|
-
let
|
|
24
|
-
let
|
|
23
|
+
let cur_time = cursor_time;
|
|
24
|
+
let max_time = max_end_time;
|
|
25
25
|
|
|
26
26
|
let mut current = stmt.value.clone();
|
|
27
27
|
|
|
@@ -44,7 +44,7 @@ pub fn interprete_condition_statement(
|
|
|
44
44
|
global_store,
|
|
45
45
|
variable_table.clone(),
|
|
46
46
|
functions_table.clone(),
|
|
47
|
-
block
|
|
47
|
+
&block,
|
|
48
48
|
base_bpm,
|
|
49
49
|
base_duration,
|
|
50
50
|
max_time,
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
use rayon::prelude::*;
|
|
2
|
-
use std::sync::{ Arc, Mutex };
|
|
3
2
|
|
|
4
|
-
use crate::core::{
|
|
3
|
+
use crate::{core::{
|
|
5
4
|
audio::{
|
|
6
5
|
engine::AudioEngine,
|
|
7
6
|
interpreter::{
|
|
8
7
|
arrow_call::interprete_call_arrow_statement,
|
|
9
8
|
call::interprete_call_statement,
|
|
10
|
-
condition::interprete_condition_statement,
|
|
11
9
|
function::interprete_function_statement,
|
|
12
10
|
let_::interprete_let_statement,
|
|
13
11
|
load::interprete_load_statement,
|
|
@@ -17,29 +15,27 @@ use crate::core::{
|
|
|
17
15
|
tempo::interprete_tempo_statement,
|
|
18
16
|
trigger::interprete_trigger_statement,
|
|
19
17
|
},
|
|
20
|
-
},
|
|
21
|
-
|
|
22
|
-
store::{ function::FunctionTable, global::GlobalStore, variable::VariableTable },
|
|
23
|
-
};
|
|
18
|
+
}, parser::statement::{ Statement, StatementKind }, shared::value::Value, store::{ function::FunctionTable, global::GlobalStore, variable::VariableTable }
|
|
19
|
+
}, utils::logger::{LogLevel, Logger}};
|
|
24
20
|
|
|
25
21
|
pub fn run_audio_program(
|
|
26
22
|
statements: &Vec<Statement>,
|
|
27
23
|
audio_engine: &mut AudioEngine,
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
24
|
+
_entry: String,
|
|
25
|
+
_output: String,
|
|
26
|
+
_module_variables: VariableTable,
|
|
27
|
+
_module_functions: FunctionTable,
|
|
32
28
|
global_store: &mut GlobalStore
|
|
33
29
|
) -> (f32, f32) {
|
|
34
|
-
let
|
|
35
|
-
let
|
|
30
|
+
let base_bpm = 120.0;
|
|
31
|
+
let base_duration = 60.0 / base_bpm;
|
|
36
32
|
|
|
37
33
|
let (max_end_time, cursor_time) = execute_audio_block(
|
|
38
34
|
audio_engine,
|
|
39
35
|
global_store,
|
|
40
36
|
global_store.variables.clone(),
|
|
41
37
|
global_store.functions.clone(),
|
|
42
|
-
statements
|
|
38
|
+
&statements,
|
|
43
39
|
base_bpm,
|
|
44
40
|
base_duration,
|
|
45
41
|
0.0,
|
|
@@ -52,36 +48,37 @@ pub fn run_audio_program(
|
|
|
52
48
|
pub fn execute_audio_block(
|
|
53
49
|
audio_engine: &mut AudioEngine,
|
|
54
50
|
global_store: &GlobalStore,
|
|
55
|
-
variable_table: VariableTable,
|
|
56
|
-
functions_table: FunctionTable,
|
|
57
|
-
statements:
|
|
51
|
+
mut variable_table: VariableTable,
|
|
52
|
+
mut functions_table: FunctionTable,
|
|
53
|
+
statements: &[Statement],
|
|
58
54
|
mut base_bpm: f32,
|
|
59
55
|
mut base_duration: f32,
|
|
60
56
|
mut max_end_time: f32,
|
|
61
57
|
mut cursor_time: f32
|
|
62
58
|
) -> (f32, f32) {
|
|
63
59
|
let (spawns, others): (Vec<_>, Vec<_>) = statements
|
|
64
|
-
.
|
|
60
|
+
.iter()
|
|
65
61
|
.partition(|stmt| matches!(stmt.kind, StatementKind::Spawn { .. }));
|
|
66
62
|
|
|
67
63
|
// Execute sequential statements first
|
|
68
64
|
for stmt in others {
|
|
69
65
|
match &stmt.kind {
|
|
70
66
|
StatementKind::Load { .. } => {
|
|
71
|
-
if
|
|
72
|
-
|
|
73
|
-
&stmt,
|
|
74
|
-
&mut variable_table.clone()
|
|
75
|
-
)
|
|
76
|
-
{
|
|
77
|
-
// Extend the variable_table if necessary
|
|
67
|
+
if let Some(new_table) = interprete_load_statement(&stmt, &mut variable_table) {
|
|
68
|
+
variable_table = new_table;
|
|
78
69
|
}
|
|
79
70
|
}
|
|
80
71
|
StatementKind::Let { .. } => {
|
|
81
|
-
interprete_let_statement(&stmt, &mut variable_table
|
|
72
|
+
if let Some(new_table) = interprete_let_statement(&stmt, &mut variable_table) {
|
|
73
|
+
variable_table = new_table;
|
|
74
|
+
}
|
|
82
75
|
}
|
|
83
76
|
StatementKind::Function { .. } => {
|
|
84
|
-
|
|
77
|
+
if let Some(new_functions) =
|
|
78
|
+
interprete_function_statement(&stmt, &mut functions_table)
|
|
79
|
+
{
|
|
80
|
+
functions_table = new_functions;
|
|
81
|
+
}
|
|
85
82
|
}
|
|
86
83
|
StatementKind::Tempo => {
|
|
87
84
|
if let Some((new_bpm, new_duration)) = interprete_tempo_statement(&stmt) {
|
|
@@ -129,7 +126,7 @@ pub fn execute_audio_block(
|
|
|
129
126
|
max_end_time = new_max;
|
|
130
127
|
}
|
|
131
128
|
StatementKind::Call { .. } => {
|
|
132
|
-
let (new_max,
|
|
129
|
+
let (new_max, _) = interprete_call_statement(
|
|
133
130
|
&stmt,
|
|
134
131
|
audio_engine,
|
|
135
132
|
&variable_table,
|
|
@@ -140,7 +137,7 @@ pub fn execute_audio_block(
|
|
|
140
137
|
max_end_time,
|
|
141
138
|
cursor_time,
|
|
142
139
|
);
|
|
143
|
-
cursor_time =
|
|
140
|
+
cursor_time = new_max;
|
|
144
141
|
max_end_time = new_max;
|
|
145
142
|
}
|
|
146
143
|
StatementKind::ArrowCall { .. } => {
|
|
@@ -154,8 +151,49 @@ pub fn execute_audio_block(
|
|
|
154
151
|
Some(&mut cursor_time),
|
|
155
152
|
true
|
|
156
153
|
);
|
|
154
|
+
|
|
157
155
|
cursor_time = new_cursor;
|
|
158
|
-
|
|
156
|
+
|
|
157
|
+
if new_max > max_end_time {
|
|
158
|
+
max_end_time = new_max;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
StatementKind::Automate { .. } => {
|
|
162
|
+
if let Some(new_table) = crate::core::audio::interpreter::automate::interprete_automate_statement(&stmt, &mut variable_table) {
|
|
163
|
+
variable_table = new_table;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
StatementKind::Print => {
|
|
167
|
+
// Print debug output; if the string contains special expressions, evaluate them.
|
|
168
|
+
let logger = Logger::new();
|
|
169
|
+
match &stmt.value {
|
|
170
|
+
Value::String(s) => {
|
|
171
|
+
let bpm = if let Some(Value::Number(n)) = variable_table.get("bpm") { *n } else { 120.0 };
|
|
172
|
+
let beat = if let Some(Value::Number(n)) = variable_table.get("beat") { *n } else { 0.0 };
|
|
173
|
+
// If the string is exactly a variable name, print its value
|
|
174
|
+
if let Some(val) = variable_table.get(&s) {
|
|
175
|
+
logger.log_message(LogLevel::Print, &format!("{:?}", val));
|
|
176
|
+
} else if s.contains("$env") || s.contains("$math") || s.parse::<f32>().is_ok() {
|
|
177
|
+
let v = crate::core::audio::evaluator::evaluate_rhs_into_value(s, &variable_table, bpm, beat);
|
|
178
|
+
match v { Value::Number(n) => logger.log_message(LogLevel::Print, &format!("{}", n)), _ => logger.log_message(LogLevel::Print, s) }
|
|
179
|
+
} else {
|
|
180
|
+
logger.log_message(LogLevel::Print, s)
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
Value::Identifier(name) => {
|
|
184
|
+
if let Some(val) = variable_table.get(name) {
|
|
185
|
+
match val {
|
|
186
|
+
Value::Number(n) => logger.log_message(LogLevel::Print, &format!("{}", n)),
|
|
187
|
+
Value::String(s) => logger.log_message(LogLevel::Print, s),
|
|
188
|
+
Value::Boolean(b) => logger.log_message(LogLevel::Print, &format!("{}", b)),
|
|
189
|
+
other => logger.log_message(LogLevel::Print, &format!("{:?}", other)),
|
|
190
|
+
}
|
|
191
|
+
} else {
|
|
192
|
+
logger.log_message(LogLevel::Print, name)
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
v => logger.log_message(LogLevel::Print, &format!("{:?}", v)),
|
|
196
|
+
}
|
|
159
197
|
}
|
|
160
198
|
_ => {}
|
|
161
199
|
}
|
|
@@ -1,16 +1,23 @@
|
|
|
1
|
-
use crate::core::{
|
|
2
|
-
audio::engine::AudioEngine,
|
|
3
|
-
parser::statement::{ Statement, StatementKind },
|
|
4
|
-
shared::value::Value,
|
|
5
|
-
store::variable::VariableTable,
|
|
6
|
-
};
|
|
1
|
+
use crate::core::{ parser::statement::{ Statement, StatementKind }, shared::value::Value, store::variable::VariableTable };
|
|
7
2
|
|
|
8
3
|
pub fn interprete_let_statement(
|
|
9
4
|
stmt: &Statement,
|
|
10
5
|
variable_table: &mut VariableTable
|
|
11
6
|
) -> Option<VariableTable> {
|
|
12
7
|
if let StatementKind::Let { name } = &stmt.kind {
|
|
13
|
-
|
|
8
|
+
// If RHS is a string and looks like an expression, evaluate it
|
|
9
|
+
let evaluated = match &stmt.value {
|
|
10
|
+
Value::String(s) if s.contains("$env") || s.contains("$math") => {
|
|
11
|
+
// We don't have direct env here; use defaults or infer from table
|
|
12
|
+
let bpm = if let Some(Value::Number(n)) = variable_table.get("bpm") { *n } else { 120.0 };
|
|
13
|
+
// Try to infer beat from time-based variables if any, else 0.0
|
|
14
|
+
let beat = if let Some(Value::Number(n)) = variable_table.get("beat") { *n } else { 0.0 };
|
|
15
|
+
crate::core::audio::evaluator::evaluate_rhs_into_value(s, variable_table, bpm, beat)
|
|
16
|
+
}
|
|
17
|
+
other => other.clone(),
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
variable_table.set(name.to_string(), evaluated);
|
|
14
21
|
|
|
15
22
|
return Some(variable_table.clone())
|
|
16
23
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
use crate::core::{
|
|
2
2
|
audio::{ engine::AudioEngine, interpreter::driver::execute_audio_block },
|
|
3
|
-
parser::statement::
|
|
4
|
-
shared::
|
|
3
|
+
parser::statement::Statement,
|
|
4
|
+
shared::value::Value,
|
|
5
5
|
store::{function::FunctionTable, global::GlobalStore, variable::VariableTable},
|
|
6
6
|
};
|
|
7
7
|
|
|
@@ -17,6 +17,39 @@ pub fn interprete_loop_statement(
|
|
|
17
17
|
cursor_time: f32
|
|
18
18
|
) -> (f32, f32) {
|
|
19
19
|
if let Value::Map(loop_value) = &stmt.value {
|
|
20
|
+
// Foreach form: { foreach: Identifier(name), array: Array([...]), body: Block }
|
|
21
|
+
if let (Some(Value::Identifier(var_name)), Some(Value::Array(items)), Some(Value::Block(loop_body))) = (
|
|
22
|
+
loop_value.get("foreach"),
|
|
23
|
+
loop_value.get("array"),
|
|
24
|
+
loop_value.get("body"),
|
|
25
|
+
) {
|
|
26
|
+
let mut engine = audio_engine;
|
|
27
|
+
let mut cur_time = cursor_time;
|
|
28
|
+
let mut max_time = max_end_time;
|
|
29
|
+
|
|
30
|
+
for item in items {
|
|
31
|
+
let mut scoped_vars = variable_table.clone();
|
|
32
|
+
scoped_vars.set(var_name.clone(), item.clone());
|
|
33
|
+
|
|
34
|
+
let (block_end_time, new_cursor) = execute_audio_block(
|
|
35
|
+
&mut engine,
|
|
36
|
+
global_store,
|
|
37
|
+
scoped_vars,
|
|
38
|
+
functions_table.clone(),
|
|
39
|
+
&loop_body,
|
|
40
|
+
base_bpm,
|
|
41
|
+
base_duration,
|
|
42
|
+
max_time,
|
|
43
|
+
cur_time,
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
cur_time = new_cursor.max(block_end_time);
|
|
47
|
+
max_time = max_time.max(cur_time);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return (max_time, cur_time);
|
|
51
|
+
}
|
|
52
|
+
|
|
20
53
|
let loop_count = match loop_value.get("iterator") {
|
|
21
54
|
Some(Value::Number(n)) => *n as usize,
|
|
22
55
|
Some(Value::Identifier(ident)) => {
|
|
@@ -48,20 +81,20 @@ pub fn interprete_loop_statement(
|
|
|
48
81
|
let mut cur_time = cursor_time;
|
|
49
82
|
let mut max_time = max_end_time;
|
|
50
83
|
|
|
51
|
-
for
|
|
52
|
-
let (block_end_time,
|
|
84
|
+
for _ in 0..loop_count {
|
|
85
|
+
let (block_end_time, new_cursor) = execute_audio_block(
|
|
53
86
|
&mut engine,
|
|
54
87
|
global_store,
|
|
55
88
|
variable_table.clone(),
|
|
56
89
|
functions_table.clone(),
|
|
57
|
-
loop_body
|
|
90
|
+
&loop_body,
|
|
58
91
|
base_bpm,
|
|
59
92
|
base_duration,
|
|
60
93
|
max_time,
|
|
61
94
|
cur_time
|
|
62
95
|
);
|
|
63
96
|
|
|
64
|
-
cur_time = block_end_time;
|
|
97
|
+
cur_time = new_cursor.max(block_end_time);
|
|
65
98
|
max_time = max_time.max(cur_time);
|
|
66
99
|
}
|
|
67
100
|
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
use crate::core::{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
shared::{ duration::Duration, value::Value },
|
|
5
|
-
store::variable::VariableTable,
|
|
2
|
+
parser::statement::Statement,
|
|
3
|
+
shared::value::Value,
|
|
6
4
|
};
|
|
7
5
|
|
|
8
6
|
pub fn interprete_sleep_statement(
|
|
@@ -20,7 +20,7 @@ pub fn interprete_spawn_statement(
|
|
|
20
20
|
StatementKind::Spawn { name, args } => {
|
|
21
21
|
let mut local_engine = AudioEngine::new(audio_engine.module_name.clone());
|
|
22
22
|
|
|
23
|
-
//
|
|
23
|
+
// Function case
|
|
24
24
|
if let Some(func) = functions.functions.get(name) {
|
|
25
25
|
if func.parameters.len() != args.len() {
|
|
26
26
|
eprintln!(
|
|
@@ -42,7 +42,7 @@ pub fn interprete_spawn_statement(
|
|
|
42
42
|
global_store,
|
|
43
43
|
local_vars,
|
|
44
44
|
functions.clone(),
|
|
45
|
-
func.body
|
|
45
|
+
&func.body,
|
|
46
46
|
base_bpm,
|
|
47
47
|
base_duration,
|
|
48
48
|
0.0,
|
|
@@ -53,7 +53,7 @@ pub fn interprete_spawn_statement(
|
|
|
53
53
|
return (spawn_max.max(max_end_time), cursor_time);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
//
|
|
56
|
+
// Group case
|
|
57
57
|
if let Some(group_stmt) = find_group(name, variable_table, global_store) {
|
|
58
58
|
if let Value::Map(map) = &group_stmt.value {
|
|
59
59
|
if let Some(Value::Block(body)) = map.get("body") {
|
|
@@ -62,7 +62,7 @@ pub fn interprete_spawn_statement(
|
|
|
62
62
|
global_store,
|
|
63
63
|
variable_table.clone(),
|
|
64
64
|
functions.clone(),
|
|
65
|
-
body
|
|
65
|
+
&body,
|
|
66
66
|
base_bpm,
|
|
67
67
|
base_duration,
|
|
68
68
|
0.0,
|
|
@@ -7,7 +7,7 @@ use crate::core::{
|
|
|
7
7
|
pub fn load_trigger(
|
|
8
8
|
trigger: &Value,
|
|
9
9
|
duration: &Duration,
|
|
10
|
-
|
|
10
|
+
_effects: &Option<Value>,
|
|
11
11
|
base_duration: f32,
|
|
12
12
|
variable_table: VariableTable
|
|
13
13
|
) -> (String, f32) {
|
|
@@ -37,7 +37,7 @@ pub fn load_trigger(
|
|
|
37
37
|
trigger_path = src.to_string();
|
|
38
38
|
}
|
|
39
39
|
Value::Statement(stmt) => {
|
|
40
|
-
if let StatementKind::Trigger { entity, duration, effects } = &stmt.kind {
|
|
40
|
+
if let StatementKind::Trigger { entity, duration: _, effects: _ } = &stmt.kind {
|
|
41
41
|
trigger_path = entity.clone();
|
|
42
42
|
} else {
|
|
43
43
|
eprintln!("❌ Trigger statement must be of type 'Trigger', found: {:?}", stmt.kind);
|
|
@@ -88,9 +88,6 @@ pub fn load_trigger(
|
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
_ => {
|
|
92
|
-
eprintln!("❌ Invalid duration type. Expected an identifier.");
|
|
93
|
-
}
|
|
94
91
|
}
|
|
95
92
|
|
|
96
93
|
(trigger_path, duration_as_secs)
|
package/rust/core/audio/mod.rs
CHANGED
|
@@ -22,7 +22,7 @@ pub fn render_audio_with_modules(
|
|
|
22
22
|
// Apply global variables to the initial engine
|
|
23
23
|
if let Some(module) = global_store.get_module(&module_name) {
|
|
24
24
|
// interprete statements to fill the audio buffer
|
|
25
|
-
let (module_max_end_time,
|
|
25
|
+
let (module_max_end_time, _cursor_time) = run_audio_program(
|
|
26
26
|
&statements,
|
|
27
27
|
&mut audio_engine,
|
|
28
28
|
module_name.clone(),
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
use crate::core::store::variable::VariableTable;
|
|
2
|
+
|
|
3
|
+
// Basic easing functions operating on t in [0,1]
|
|
4
|
+
fn easing_value(func: &str, t: f32) -> Option<f32> {
|
|
5
|
+
let x = t.clamp(0.0, 1.0);
|
|
6
|
+
match func {
|
|
7
|
+
"linear" => Some(x),
|
|
8
|
+
"easeInQuad" => Some(x * x),
|
|
9
|
+
"easeOutQuad" => Some(x * (2.0 - x)),
|
|
10
|
+
"easeInOutQuad" => {
|
|
11
|
+
if x < 0.5 { Some(2.0 * x * x) } else { Some(-1.0 + (4.0 - 2.0 * x) * x) }
|
|
12
|
+
}
|
|
13
|
+
// Cubic
|
|
14
|
+
"easeInCubic" => Some(x * x * x),
|
|
15
|
+
"easeOutCubic" => Some(1.0 - (1.0 - x).powi(3)),
|
|
16
|
+
"easeInOutCubic" => {
|
|
17
|
+
if x < 0.5 { Some(4.0 * x * x * x) } else { Some(1.0 - (-2.0 * x + 2.0).powi(3) / 2.0) }
|
|
18
|
+
}
|
|
19
|
+
// Quartic
|
|
20
|
+
"easeInQuart" => Some(x.powi(4)),
|
|
21
|
+
"easeOutQuart" => Some(1.0 - (1.0 - x).powi(4)),
|
|
22
|
+
"easeInOutQuart" => {
|
|
23
|
+
if x < 0.5 { Some(8.0 * x.powi(4)) } else { Some(1.0 - (-2.0 * x + 2.0).powi(4) / 2.0) }
|
|
24
|
+
}
|
|
25
|
+
// Exponential
|
|
26
|
+
"easeInExpo" => Some(if x <= 0.0 { 0.0 } else { 2.0_f32.powf(10.0 * x - 10.0) }),
|
|
27
|
+
"easeOutExpo" => Some(if x >= 1.0 { 1.0 } else { 1.0 - 2.0_f32.powf(-10.0 * x) }),
|
|
28
|
+
"easeInOutExpo" => Some(if x <= 0.0 { 0.0 } else if x >= 1.0 { 1.0 } else if x < 0.5 { 2.0_f32.powf(20.0 * x - 10.0) / 2.0 } else { (2.0 - 2.0_f32.powf(-20.0 * x + 10.0)) / 2.0 }),
|
|
29
|
+
// Back (overshoot c ~ 1.70158)
|
|
30
|
+
"easeInBack" => { let c = 1.70158; Some((c + 1.0) * x * x * x - c * x * x) }
|
|
31
|
+
"easeOutBack" => { let c = 1.70158; let y = 1.0 - x; Some(1.0 - ((c + 1.0) * y * y * y - c * y * y)) }
|
|
32
|
+
"easeInOutBack" => {
|
|
33
|
+
let c1 = 1.70158; let c2 = c1 * 1.525; let x2 = x * 2.0;
|
|
34
|
+
if x2 < 1.0 { Some((x2 * x2 * ((c2 + 1.0) * x2 - c2)) / 2.0) } else {
|
|
35
|
+
let x2 = x2 - 2.0; Some((x2 * x2 * ((c2 + 1.0) * x2 + c2)) / 2.0 + 1.0)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// Elastic
|
|
39
|
+
"easeInElastic" => {
|
|
40
|
+
if x == 0.0 { Some(0.0) } else if x == 1.0 { Some(1.0) } else {
|
|
41
|
+
let c = 2.0 * std::f32::consts::PI / 3.0;
|
|
42
|
+
Some(- (2.0_f32.powf(10.0 * x - 10.0)) * ((x * 10.0 - 10.75) * c).sin())
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
"easeOutElastic" => {
|
|
46
|
+
if x == 0.0 { Some(0.0) } else if x == 1.0 { Some(1.0) } else {
|
|
47
|
+
let c = 2.0 * std::f32::consts::PI / 3.0;
|
|
48
|
+
Some(2.0_f32.powf(-10.0 * x) * ((x * 10.0 - 0.75) * c).sin() + 1.0)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
"easeInOutElastic" => {
|
|
52
|
+
if x == 0.0 { Some(0.0) } else if x == 1.0 { Some(1.0) } else {
|
|
53
|
+
let c = 2.0 * std::f32::consts::PI / 4.5;
|
|
54
|
+
if x < 0.5 {
|
|
55
|
+
Some(-(2.0_f32.powf(20.0 * x - 10.0)) * ((20.0 * x - 11.125) * c).sin() / 2.0)
|
|
56
|
+
} else {
|
|
57
|
+
Some(2.0_f32.powf(-20.0 * x + 10.0) * ((20.0 * x - 11.125) * c).sin() / 2.0 + 1.0)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// Bounce helpers
|
|
62
|
+
"easeInBounce" => Some(1.0 - bounce_out(1.0 - x)),
|
|
63
|
+
"easeOutBounce" => Some(bounce_out(x)),
|
|
64
|
+
"easeInOutBounce" => Some(if x < 0.5 { (1.0 - bounce_out(1.0 - 2.0 * x)) / 2.0 } else { (1.0 + bounce_out(2.0 * x - 1.0)) / 2.0 }),
|
|
65
|
+
_ => None,
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
fn bounce_out(x: f32) -> f32 {
|
|
70
|
+
let n1 = 7.5625; let d1 = 2.75;
|
|
71
|
+
if x < 1.0 / d1 {
|
|
72
|
+
n1 * x * x
|
|
73
|
+
} else if x < 2.0 / d1 {
|
|
74
|
+
let x = x - 1.5 / d1; n1 * x * x + 0.75
|
|
75
|
+
} else if x < 2.5 / d1 {
|
|
76
|
+
let x = x - 2.25 / d1; n1 * x * x + 0.9375
|
|
77
|
+
} else {
|
|
78
|
+
let x = x - 2.625 / d1; n1 * x * x + 0.984375
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Find and evaluate the first $easing.<fn>(...) occurrence in the string.
|
|
83
|
+
// Accepts a single argument expression producing t in [0,1].
|
|
84
|
+
pub fn find_and_eval_first_easing_call<EvalFn>(
|
|
85
|
+
s: &str,
|
|
86
|
+
eval: EvalFn,
|
|
87
|
+
vars: &VariableTable,
|
|
88
|
+
bpm: f32,
|
|
89
|
+
beat: f32,
|
|
90
|
+
) -> Option<String>
|
|
91
|
+
where
|
|
92
|
+
EvalFn: Fn(&str, &VariableTable, f32, f32) -> Option<f32>,
|
|
93
|
+
{
|
|
94
|
+
let start = s.find("$easing.")?;
|
|
95
|
+
let open_rel = s[start..].find('(')?;
|
|
96
|
+
let open = start + open_rel;
|
|
97
|
+
let func = &s[start + 9..open];
|
|
98
|
+
|
|
99
|
+
// Find matching close parenthesis
|
|
100
|
+
let mut depth: i32 = 0;
|
|
101
|
+
let mut close_abs: Option<usize> = None;
|
|
102
|
+
for (i, ch) in s[open..].char_indices() {
|
|
103
|
+
match ch {
|
|
104
|
+
'(' => depth += 1,
|
|
105
|
+
')' => { depth -= 1; if depth == 0 { close_abs = Some(open + i); break; } }
|
|
106
|
+
_ => {}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
let close = close_abs?;
|
|
110
|
+
|
|
111
|
+
let inner = &s[open + 1..close];
|
|
112
|
+
let t = eval(inner, vars, bpm, beat)?;
|
|
113
|
+
let result = easing_value(func, t)?;
|
|
114
|
+
|
|
115
|
+
let mut replaced = String::new();
|
|
116
|
+
replaced.push_str(&s[..start]);
|
|
117
|
+
replaced.push_str(&result.to_string());
|
|
118
|
+
replaced.push_str(&s[close + 1..]);
|
|
119
|
+
Some(replaced)
|
|
120
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
use crate::core::store::variable::VariableTable;
|
|
2
|
+
use std::sync::OnceLock;
|
|
3
|
+
use std::time::{ SystemTime, UNIX_EPOCH };
|
|
4
|
+
|
|
5
|
+
static SESSION_SEED: OnceLock<f32> = OnceLock::new();
|
|
6
|
+
|
|
7
|
+
pub fn get_session_seed() -> f32 {
|
|
8
|
+
*SESSION_SEED.get_or_init(|| {
|
|
9
|
+
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default();
|
|
10
|
+
// Build a stable 0..1 seed from nanos
|
|
11
|
+
let nanos = now.subsec_nanos();
|
|
12
|
+
((nanos as f32) / 1_000_000_000.0).clamp(0.0, 1.0)
|
|
13
|
+
})
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Resolve special environment variables like $env.bpm, $env.beat, $env.position
|
|
17
|
+
// For now, $env.position is treated as an alias of beat.
|
|
18
|
+
pub fn resolve_env_atom(atom: &str, bpm: f32, beat: f32) -> Option<f32> {
|
|
19
|
+
match atom {
|
|
20
|
+
"$env.bpm" => Some(bpm),
|
|
21
|
+
"$env.beat" => Some(beat),
|
|
22
|
+
"$env.position" => Some(beat),
|
|
23
|
+
// Optional seed for deterministic randomness
|
|
24
|
+
"$env.seed" => Some(get_session_seed()),
|
|
25
|
+
_ => None,
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Utility: resolve an identifier or numeric literal to f32 using the variable table
|
|
30
|
+
pub fn resolve_atom_or_var(atom: &str, vars: &VariableTable, bpm: f32, beat: f32) -> Option<f32> {
|
|
31
|
+
if let Some(v) = resolve_env_atom(atom, bpm, beat) {
|
|
32
|
+
return Some(v);
|
|
33
|
+
}
|
|
34
|
+
if let Ok(n) = atom.parse::<f32>() {
|
|
35
|
+
return Some(n);
|
|
36
|
+
}
|
|
37
|
+
if let Some(crate::core::shared::value::Value::Number(n)) = vars.get(atom) {
|
|
38
|
+
return Some(*n);
|
|
39
|
+
}
|
|
40
|
+
None
|
|
41
|
+
}
|