@devaloop/devalang 0.0.1-alpha.12 → 0.0.1-alpha.14
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 +8 -9
- package/Cargo.toml +8 -3
- package/README.md +36 -34
- package/docs/CHANGELOG.md +65 -1
- package/docs/CONTRIBUTING.md +1 -0
- package/docs/ROADMAP.md +2 -2
- package/docs/TODO.md +6 -5
- package/examples/bank.deva +2 -4
- package/examples/function.deva +15 -0
- package/examples/index.deva +25 -14
- package/out-tsc/bin/devalang.exe +0 -0
- package/package.json +6 -6
- package/project-version.json +3 -3
- package/rust/cli/bank.rs +2 -1
- package/rust/cli/build.rs +76 -14
- package/rust/cli/check.rs +71 -8
- 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 +45 -20
- package/rust/common/api.rs +8 -0
- package/rust/common/cdn.rs +2 -5
- package/rust/common/mod.rs +3 -1
- package/rust/common/sso.rs +8 -0
- package/rust/config/driver.rs +19 -1
- package/rust/config/loader.rs +56 -10
- package/rust/core/audio/engine.rs +254 -91
- package/rust/core/audio/interpreter/arrow_call.rs +34 -15
- package/rust/core/audio/interpreter/call.rs +72 -47
- package/rust/core/audio/interpreter/condition.rs +14 -12
- package/rust/core/audio/interpreter/driver.rs +90 -128
- package/rust/core/audio/interpreter/function.rs +21 -0
- package/rust/core/audio/interpreter/load.rs +1 -1
- package/rust/core/audio/interpreter/loop_.rs +24 -18
- package/rust/core/audio/interpreter/mod.rs +2 -1
- package/rust/core/audio/interpreter/sleep.rs +0 -6
- package/rust/core/audio/interpreter/spawn.rs +78 -60
- package/rust/core/audio/interpreter/trigger.rs +157 -70
- package/rust/core/audio/loader/trigger.rs +37 -4
- package/rust/core/audio/player.rs +20 -10
- package/rust/core/audio/renderer.rs +24 -25
- package/rust/core/builder/mod.rs +11 -6
- package/rust/core/debugger/mod.rs +2 -0
- package/rust/core/debugger/module.rs +47 -0
- package/rust/core/debugger/store.rs +25 -11
- package/rust/core/error/mod.rs +6 -0
- package/rust/core/lexer/handler/driver.rs +23 -1
- package/rust/core/lexer/handler/identifier.rs +1 -0
- package/rust/core/lexer/handler/indent.rs +16 -2
- package/rust/core/lexer/handler/mod.rs +1 -0
- package/rust/core/lexer/handler/parenthesis.rs +41 -0
- package/rust/core/lexer/token.rs +4 -0
- package/rust/core/mod.rs +2 -1
- package/rust/core/parser/driver.rs +47 -4
- package/rust/core/parser/handler/arrow_call.rs +78 -18
- package/rust/core/parser/handler/bank.rs +35 -7
- package/rust/core/parser/handler/dot.rs +81 -123
- package/rust/core/parser/handler/identifier/call.rs +69 -22
- package/rust/core/parser/handler/identifier/function.rs +92 -0
- package/rust/core/parser/handler/identifier/let_.rs +13 -19
- package/rust/core/parser/handler/identifier/mod.rs +1 -0
- package/rust/core/parser/handler/identifier/spawn.rs +74 -27
- package/rust/core/parser/statement.rs +16 -4
- package/rust/core/plugin/loader.rs +48 -0
- package/rust/core/plugin/mod.rs +1 -0
- package/rust/core/preprocessor/loader.rs +50 -32
- package/rust/core/preprocessor/module.rs +3 -1
- package/rust/core/preprocessor/processor.rs +26 -1
- package/rust/core/preprocessor/resolver/call.rs +61 -84
- package/rust/core/preprocessor/resolver/condition.rs +11 -6
- package/rust/core/preprocessor/resolver/driver.rs +52 -6
- package/rust/core/preprocessor/resolver/function.rs +78 -0
- package/rust/core/preprocessor/resolver/group.rs +43 -13
- package/rust/core/preprocessor/resolver/let_.rs +7 -10
- package/rust/core/preprocessor/resolver/mod.rs +2 -1
- package/rust/core/preprocessor/resolver/spawn.rs +64 -30
- package/rust/core/preprocessor/resolver/trigger.rs +7 -3
- package/rust/core/preprocessor/resolver/value.rs +10 -1
- package/rust/core/shared/value.rs +4 -1
- package/rust/core/store/function.rs +34 -0
- package/rust/core/store/global.rs +9 -10
- package/rust/core/store/mod.rs +2 -1
- package/rust/core/store/variable.rs +6 -0
- package/rust/installer/addon.rs +80 -0
- package/rust/installer/bank.rs +24 -14
- package/rust/installer/mod.rs +4 -1
- package/rust/installer/plugin.rs +55 -0
- package/rust/lib.rs +10 -7
- package/rust/main.rs +32 -9
- package/rust/utils/logger.rs +16 -0
- package/rust/utils/mod.rs +45 -1
- package/rust/utils/spinner.rs +2 -4
|
@@ -2,18 +2,20 @@ use crate::core::{
|
|
|
2
2
|
audio::{ engine::AudioEngine, interpreter::driver::execute_audio_block },
|
|
3
3
|
parser::statement::{ Statement, StatementKind },
|
|
4
4
|
shared::{ duration::Duration, value::Value },
|
|
5
|
-
store::variable::VariableTable,
|
|
5
|
+
store::{function::FunctionTable, global::GlobalStore, variable::VariableTable},
|
|
6
6
|
};
|
|
7
7
|
|
|
8
8
|
pub fn interprete_loop_statement(
|
|
9
9
|
stmt: &Statement,
|
|
10
|
-
audio_engine: AudioEngine,
|
|
11
|
-
|
|
10
|
+
audio_engine: &mut AudioEngine,
|
|
11
|
+
global_store: &GlobalStore,
|
|
12
|
+
variable_table: &VariableTable,
|
|
13
|
+
functions_table: &FunctionTable,
|
|
12
14
|
base_bpm: f32,
|
|
13
15
|
base_duration: f32,
|
|
14
16
|
max_end_time: f32,
|
|
15
17
|
cursor_time: f32
|
|
16
|
-
) -> (
|
|
18
|
+
) -> (f32, f32) {
|
|
17
19
|
if let Value::Map(loop_value) = &stmt.value {
|
|
18
20
|
let loop_count = match loop_value.get("iterator") {
|
|
19
21
|
Some(Value::Number(n)) => *n as usize,
|
|
@@ -22,12 +24,15 @@ pub fn interprete_loop_statement(
|
|
|
22
24
|
*n as usize
|
|
23
25
|
} else {
|
|
24
26
|
eprintln!("❌ Loop iterator must be a number, found: {:?}", ident);
|
|
25
|
-
return (
|
|
27
|
+
return (max_end_time, cursor_time);
|
|
26
28
|
}
|
|
27
29
|
}
|
|
28
30
|
_ => {
|
|
29
|
-
eprintln!(
|
|
30
|
-
|
|
31
|
+
eprintln!(
|
|
32
|
+
"❌ Loop iterator must be a number, found: {:?}",
|
|
33
|
+
loop_value.get("iterator")
|
|
34
|
+
);
|
|
35
|
+
return (max_end_time, cursor_time);
|
|
31
36
|
}
|
|
32
37
|
};
|
|
33
38
|
|
|
@@ -35,7 +40,7 @@ pub fn interprete_loop_statement(
|
|
|
35
40
|
Some(Value::Block(body)) => body.clone(),
|
|
36
41
|
_ => {
|
|
37
42
|
eprintln!("❌ Loop body must be a block, found: {:?}", loop_value.get("body"));
|
|
38
|
-
return (
|
|
43
|
+
return (max_end_time, cursor_time);
|
|
39
44
|
}
|
|
40
45
|
};
|
|
41
46
|
|
|
@@ -43,10 +48,12 @@ pub fn interprete_loop_statement(
|
|
|
43
48
|
let mut cur_time = cursor_time;
|
|
44
49
|
let mut max_time = max_end_time;
|
|
45
50
|
|
|
46
|
-
for
|
|
47
|
-
let (
|
|
48
|
-
engine,
|
|
51
|
+
for i in 0..loop_count {
|
|
52
|
+
let (block_end_time, cursor_time) = execute_audio_block(
|
|
53
|
+
&mut engine,
|
|
54
|
+
global_store,
|
|
49
55
|
variable_table.clone(),
|
|
56
|
+
functions_table.clone(),
|
|
50
57
|
loop_body.clone(),
|
|
51
58
|
base_bpm,
|
|
52
59
|
base_duration,
|
|
@@ -54,14 +61,13 @@ pub fn interprete_loop_statement(
|
|
|
54
61
|
cur_time
|
|
55
62
|
);
|
|
56
63
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
max_time = max_time.max(end_time);
|
|
64
|
+
cur_time = block_end_time;
|
|
65
|
+
max_time = max_time.max(cur_time);
|
|
60
66
|
}
|
|
61
67
|
|
|
62
|
-
(
|
|
63
|
-
} else {
|
|
64
|
-
eprintln!("❌ Loop statement value is not a map");
|
|
65
|
-
(audio_engine, max_end_time, cursor_time)
|
|
68
|
+
return (max_time, cur_time);
|
|
66
69
|
}
|
|
70
|
+
|
|
71
|
+
eprintln!("❌ Loop statement value is not a map");
|
|
72
|
+
(max_end_time, cursor_time)
|
|
67
73
|
}
|
|
@@ -18,12 +18,6 @@ pub fn interprete_sleep_statement(
|
|
|
18
18
|
0.0
|
|
19
19
|
})
|
|
20
20
|
}
|
|
21
|
-
Value::String(s) if s.ends_with("s") => {
|
|
22
|
-
s.trim_end_matches("s").parse::<f32>().unwrap_or_else(|_| {
|
|
23
|
-
eprintln!("❌ Invalid sleep value (s): {}", s);
|
|
24
|
-
0.0
|
|
25
|
-
})
|
|
26
|
-
}
|
|
27
21
|
other => {
|
|
28
22
|
eprintln!("❌ Invalid sleep value: {:?}", other);
|
|
29
23
|
0.0
|
|
@@ -1,84 +1,102 @@
|
|
|
1
1
|
use crate::core::{
|
|
2
|
-
audio::{
|
|
3
|
-
parser::statement::Statement,
|
|
2
|
+
audio::{engine::AudioEngine, interpreter::driver::execute_audio_block},
|
|
3
|
+
parser::statement::{Statement, StatementKind},
|
|
4
4
|
shared::value::Value,
|
|
5
|
-
store::variable::VariableTable,
|
|
5
|
+
store::{function::FunctionTable, global::GlobalStore, variable::VariableTable},
|
|
6
6
|
};
|
|
7
7
|
|
|
8
8
|
pub fn interprete_spawn_statement(
|
|
9
9
|
stmt: &Statement,
|
|
10
|
-
audio_engine: AudioEngine,
|
|
10
|
+
audio_engine: &mut AudioEngine,
|
|
11
11
|
variable_table: &VariableTable,
|
|
12
|
+
functions: &FunctionTable,
|
|
13
|
+
global_store: &GlobalStore,
|
|
12
14
|
base_bpm: f32,
|
|
13
15
|
base_duration: f32,
|
|
16
|
+
max_end_time: f32,
|
|
14
17
|
cursor_time: f32,
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
18
|
+
) -> (f32, f32) {
|
|
19
|
+
match &stmt.kind {
|
|
20
|
+
StatementKind::Spawn { name, args } => {
|
|
21
|
+
let mut local_engine = AudioEngine::new(audio_engine.module_name.clone());
|
|
22
|
+
|
|
23
|
+
// Function case
|
|
24
|
+
if let Some(func) = functions.functions.get(name) {
|
|
25
|
+
if func.parameters.len() != args.len() {
|
|
26
|
+
eprintln!(
|
|
27
|
+
"❌ Function '{}' expects {} args, got {}",
|
|
28
|
+
name,
|
|
29
|
+
func.parameters.len(),
|
|
30
|
+
args.len()
|
|
31
|
+
);
|
|
32
|
+
return (max_end_time, cursor_time);
|
|
33
|
+
}
|
|
29
34
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
let mut local_vars = VariableTable::with_parent(variable_table.clone());
|
|
36
|
+
for (param, arg) in func.parameters.iter().zip(args) {
|
|
37
|
+
local_vars.set(param.clone(), arg.clone());
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let (spawn_max, _) = execute_audio_block(
|
|
41
|
+
&mut local_engine,
|
|
42
|
+
global_store,
|
|
43
|
+
local_vars,
|
|
44
|
+
functions.clone(),
|
|
45
|
+
func.body.clone(),
|
|
36
46
|
base_bpm,
|
|
37
47
|
base_duration,
|
|
38
|
-
|
|
39
|
-
|
|
48
|
+
0.0,
|
|
49
|
+
0.0,
|
|
40
50
|
);
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
51
|
+
|
|
52
|
+
audio_engine.merge_with(local_engine);
|
|
53
|
+
return (spawn_max.max(max_end_time), cursor_time);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Group case
|
|
57
|
+
if let Some(group_stmt) = find_group(name, variable_table, global_store) {
|
|
58
|
+
if let Value::Map(map) = &group_stmt.value {
|
|
59
|
+
if let Some(Value::Block(body)) = map.get("body") {
|
|
60
|
+
let (spawn_max, _) = execute_audio_block(
|
|
61
|
+
&mut local_engine,
|
|
62
|
+
global_store,
|
|
63
|
+
variable_table.clone(),
|
|
64
|
+
functions.clone(),
|
|
65
|
+
body.clone(),
|
|
66
|
+
base_bpm,
|
|
67
|
+
base_duration,
|
|
68
|
+
0.0,
|
|
69
|
+
0.0,
|
|
70
|
+
);
|
|
71
|
+
audio_engine.merge_with(local_engine);
|
|
72
|
+
return (spawn_max.max(max_end_time), cursor_time);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
44
75
|
}
|
|
45
|
-
None
|
|
46
|
-
}
|
|
47
76
|
|
|
48
|
-
|
|
49
|
-
eprintln!("❌ Invalid spawn statement: expected identifier, found {:?}", stmt.value);
|
|
50
|
-
None
|
|
77
|
+
eprintln!("❌ Function or group '{}' not found", name);
|
|
51
78
|
}
|
|
79
|
+
|
|
80
|
+
_ => eprintln!("❌ interprete_spawn_statement expected Spawn, got {:?}", stmt.kind),
|
|
52
81
|
}
|
|
82
|
+
|
|
83
|
+
(max_end_time, cursor_time)
|
|
53
84
|
}
|
|
54
85
|
|
|
55
|
-
fn
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
variable_table.clone(),
|
|
69
|
-
block.clone(),
|
|
70
|
-
base_bpm,
|
|
71
|
-
base_duration,
|
|
72
|
-
max_end_time,
|
|
73
|
-
cursor_time
|
|
74
|
-
);
|
|
75
|
-
return Some((max_end_time.max(end_time), end_time, eng));
|
|
76
|
-
} else {
|
|
77
|
-
eprintln!("❌ Spawn group '{}' has no 'body' block", identifier);
|
|
86
|
+
fn find_group<'a>(
|
|
87
|
+
name: &str,
|
|
88
|
+
variable_table: &'a VariableTable,
|
|
89
|
+
global_store: &'a GlobalStore,
|
|
90
|
+
) -> Option<&'a Statement> {
|
|
91
|
+
if let Some(Value::Statement(stmt_box)) = variable_table.get(name) {
|
|
92
|
+
if let StatementKind::Group = stmt_box.kind {
|
|
93
|
+
return Some(stmt_box);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if let Some(Value::Statement(stmt_box)) = global_store.variables.variables.get(name) {
|
|
97
|
+
if let StatementKind::Group = stmt_box.kind {
|
|
98
|
+
return Some(stmt_box);
|
|
78
99
|
}
|
|
79
|
-
} else {
|
|
80
|
-
eprintln!("❌ Spawn group '{}' not found or not a map", identifier);
|
|
81
100
|
}
|
|
82
|
-
|
|
83
101
|
None
|
|
84
102
|
}
|
|
@@ -15,105 +15,192 @@ pub fn interprete_trigger_statement(
|
|
|
15
15
|
cursor_time: f32,
|
|
16
16
|
max_end_time: f32
|
|
17
17
|
) -> Option<(f32, f32, AudioEngine)> {
|
|
18
|
-
if let StatementKind::Trigger { entity, duration } = &stmt.kind {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
18
|
+
if let StatementKind::Trigger { entity, duration, effects } = &stmt.kind {
|
|
19
|
+
let mut trigger_val = Value::String(entity.clone());
|
|
20
|
+
let mut trigger_src = String::new();
|
|
21
|
+
|
|
22
|
+
match variable_table.variables.get(entity) {
|
|
23
|
+
Some(Value::Identifier(id)) => {
|
|
24
|
+
// Get real value from global variable table
|
|
25
|
+
if let Some(global_table) = &variable_table.parent {
|
|
26
|
+
if let Some(val) = global_table.get(id) {
|
|
27
|
+
trigger_val = val.clone();
|
|
26
28
|
} else {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
Some(Value::Identifier(other)) if other == "auto" => 1.0,
|
|
30
|
-
Some(other) => {
|
|
31
|
-
eprintln!(
|
|
32
|
-
"❌ Invalid duration reference '{}': expected number, got {:?}",
|
|
33
|
-
id,
|
|
34
|
-
other
|
|
35
|
-
);
|
|
36
|
-
return None;
|
|
37
|
-
}
|
|
38
|
-
None => {
|
|
39
|
-
eprintln!("❌ Duration identifier '{}' not found", id);
|
|
40
|
-
return None;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
29
|
+
eprintln!("❌ Trigger entity '{}' not found in global variable table", id);
|
|
30
|
+
return None;
|
|
43
31
|
}
|
|
32
|
+
} else if let Some(val) = variable_table.get(id) {
|
|
33
|
+
trigger_val = val.clone();
|
|
34
|
+
} else {
|
|
35
|
+
eprintln!("❌ Trigger entity '{}' not found in variable table", id);
|
|
36
|
+
return None;
|
|
44
37
|
}
|
|
38
|
+
}
|
|
39
|
+
Some(Value::Sample(sample_src)) => {
|
|
40
|
+
// If the entity is a sample, we use its path directly
|
|
41
|
+
trigger_src = sample_src.clone();
|
|
42
|
+
}
|
|
43
|
+
Some(Value::Map(map)) => {
|
|
44
|
+
// If the entity is a map, we assume it contains an "entity" key
|
|
45
|
+
if let Some(Value::String(src)) = map.get("entity") {
|
|
46
|
+
trigger_val = Value::String(src.clone());
|
|
47
|
+
} else if let Some(Value::Identifier(src)) = map.get("entity") {
|
|
48
|
+
trigger_val = Value::Identifier(src.clone());
|
|
49
|
+
} else {
|
|
50
|
+
eprintln!(
|
|
51
|
+
"❌ Trigger map must contain an 'entity' key with a string or identifier value."
|
|
52
|
+
);
|
|
53
|
+
return None;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
_ => {
|
|
57
|
+
trigger_val = Value::String(entity.clone());
|
|
58
|
+
}
|
|
59
|
+
}
|
|
45
60
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
61
|
+
let duration_secs = match duration {
|
|
62
|
+
Duration::Number(n) => *n,
|
|
63
|
+
|
|
64
|
+
Duration::Identifier(id) => {
|
|
65
|
+
if id == "auto" {
|
|
66
|
+
1.0
|
|
67
|
+
} else {
|
|
68
|
+
match variable_table.get(id) {
|
|
69
|
+
Some(Value::Number(n)) => *n,
|
|
70
|
+
Some(Value::Identifier(other)) if other == "auto" => 1.0,
|
|
71
|
+
Some(other) => {
|
|
72
|
+
eprintln!(
|
|
73
|
+
"❌ Invalid duration reference '{}': expected number, got {:?}",
|
|
74
|
+
id,
|
|
75
|
+
other
|
|
76
|
+
);
|
|
77
|
+
return None;
|
|
78
|
+
}
|
|
79
|
+
None => {
|
|
80
|
+
eprintln!("❌ Duration identifier '{}' not found", id);
|
|
81
|
+
return None;
|
|
82
|
+
}
|
|
53
83
|
}
|
|
54
|
-
|
|
55
|
-
let numerator: f32 = parts[0].parse().unwrap_or(1.0);
|
|
56
|
-
let denominator: f32 = parts[1].parse().unwrap_or(1.0);
|
|
57
|
-
numerator / denominator
|
|
58
84
|
}
|
|
85
|
+
}
|
|
59
86
|
|
|
60
|
-
|
|
61
|
-
|
|
87
|
+
Duration::Beat(beat_str) => {
|
|
88
|
+
let parts: Vec<&str> = beat_str.split('/').collect();
|
|
89
|
+
if parts.len() != 2 {
|
|
90
|
+
eprintln!("❌ Invalid beat duration format: {}", beat_str);
|
|
91
|
+
return None;
|
|
92
|
+
}
|
|
62
93
|
|
|
63
|
-
|
|
94
|
+
let numerator: f32 = parts[0].parse().unwrap_or(1.0);
|
|
95
|
+
let denominator: f32 = parts[1].parse().unwrap_or(4.0);
|
|
64
96
|
|
|
65
|
-
|
|
66
|
-
trigger_val,
|
|
67
|
-
duration,
|
|
68
|
-
base_duration,
|
|
69
|
-
variable_table.clone()
|
|
70
|
-
);
|
|
97
|
+
let beats = (numerator / denominator) * 4.0;
|
|
71
98
|
|
|
72
|
-
|
|
73
|
-
audio_engine.insert_sample(&src, cursor_time, duration_final, Some(effects));
|
|
74
|
-
} else {
|
|
75
|
-
audio_engine.insert_sample(&src, cursor_time, duration_final, None);
|
|
99
|
+
beats * base_duration
|
|
76
100
|
}
|
|
77
101
|
|
|
78
|
-
|
|
102
|
+
Duration::Auto => base_duration,
|
|
103
|
+
};
|
|
79
104
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
105
|
+
let final_variable_table = if let Some(parent) = &variable_table.parent {
|
|
106
|
+
VariableTable {
|
|
107
|
+
variables: parent.variables.clone(),
|
|
108
|
+
parent: None,
|
|
109
|
+
}
|
|
84
110
|
} else {
|
|
85
|
-
|
|
111
|
+
variable_table.clone()
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
let (src, sample_length) = load_trigger(
|
|
115
|
+
&trigger_val,
|
|
116
|
+
duration,
|
|
117
|
+
effects,
|
|
118
|
+
base_duration,
|
|
119
|
+
final_variable_table.clone()
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
if trigger_src.is_empty() {
|
|
123
|
+
trigger_src = src;
|
|
86
124
|
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
None
|
|
90
|
-
}
|
|
91
125
|
|
|
92
|
-
|
|
93
|
-
|
|
126
|
+
let effects = extract_effects(stmt.value.clone());
|
|
127
|
+
let one_shot = effects
|
|
128
|
+
.as_ref()
|
|
129
|
+
.and_then(|map| map.get("one_shot"))
|
|
130
|
+
.and_then(|v| {
|
|
131
|
+
match v {
|
|
132
|
+
Value::Identifier(id) if id == "true" => Some(true),
|
|
133
|
+
Value::String(s) if s == "true" => Some(true),
|
|
134
|
+
_ => None,
|
|
135
|
+
}
|
|
136
|
+
})
|
|
137
|
+
.unwrap_or(false);
|
|
94
138
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
current = variables.get(part);
|
|
139
|
+
let play_length = if one_shot {
|
|
140
|
+
sample_length // play entire sample
|
|
98
141
|
} else {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
142
|
+
duration_secs.min(sample_length)
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
let trigger_src = match trigger_val.get("entity") {
|
|
146
|
+
Some(Value::String(src)) => src.clone(),
|
|
147
|
+
Some(Value::Identifier(id)) => id.clone(),
|
|
148
|
+
Some(Value::Statement(stmt)) => {
|
|
149
|
+
if let StatementKind::Trigger { entity, .. } = &stmt.kind {
|
|
150
|
+
entity.clone()
|
|
151
|
+
} else {
|
|
152
|
+
eprintln!("❌ Invalid trigger statement in map: expected 'Trigger' kind");
|
|
102
153
|
return None;
|
|
103
154
|
}
|
|
104
|
-
}
|
|
155
|
+
}
|
|
156
|
+
_ => trigger_src,
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
if let Some(effects_map) = effects {
|
|
160
|
+
audio_engine.insert_sample(
|
|
161
|
+
&trigger_src,
|
|
162
|
+
cursor_time,
|
|
163
|
+
play_length,
|
|
164
|
+
Some(effects_map),
|
|
165
|
+
&final_variable_table
|
|
166
|
+
);
|
|
167
|
+
} else {
|
|
168
|
+
audio_engine.insert_sample(
|
|
169
|
+
&trigger_src,
|
|
170
|
+
cursor_time,
|
|
171
|
+
play_length,
|
|
172
|
+
None,
|
|
173
|
+
&final_variable_table
|
|
174
|
+
);
|
|
105
175
|
}
|
|
176
|
+
|
|
177
|
+
let new_cursor_time = cursor_time + duration_secs; // advance by beat duration
|
|
178
|
+
let new_max_end_time = (cursor_time + play_length).max(max_end_time);
|
|
179
|
+
|
|
180
|
+
let updated_engine = audio_engine.clone();
|
|
181
|
+
|
|
182
|
+
return Some((new_cursor_time, new_max_end_time, updated_engine));
|
|
106
183
|
}
|
|
107
184
|
|
|
108
|
-
|
|
185
|
+
None
|
|
109
186
|
}
|
|
110
187
|
|
|
111
188
|
fn extract_effects(value: Value) -> Option<HashMap<String, Value>> {
|
|
112
|
-
if let Value::Map(map) = value
|
|
189
|
+
if let Value::Map(map) = value {
|
|
113
190
|
let mut effects = HashMap::new();
|
|
114
191
|
|
|
115
192
|
for (key, val) in map {
|
|
116
|
-
|
|
193
|
+
if key == "effects" {
|
|
194
|
+
if let Value::Map(effect_map) = val {
|
|
195
|
+
for (effect_key, effect_val) in effect_map {
|
|
196
|
+
effects.insert(effect_key, effect_val);
|
|
197
|
+
}
|
|
198
|
+
} else {
|
|
199
|
+
return None; // effects must be a map
|
|
200
|
+
}
|
|
201
|
+
} else {
|
|
202
|
+
return Some(effects);
|
|
203
|
+
}
|
|
117
204
|
}
|
|
118
205
|
|
|
119
206
|
Some(effects)
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
use crate::core::{
|
|
1
|
+
use crate::core::{
|
|
2
|
+
parser::statement::StatementKind,
|
|
3
|
+
shared::{ duration::Duration, value::Value },
|
|
4
|
+
store::variable::VariableTable,
|
|
5
|
+
};
|
|
2
6
|
|
|
3
7
|
pub fn load_trigger(
|
|
4
8
|
trigger: &Value,
|
|
5
9
|
duration: &Duration,
|
|
10
|
+
effects: &Option<Value>,
|
|
6
11
|
base_duration: f32,
|
|
7
12
|
variable_table: VariableTable
|
|
8
13
|
) -> (String, f32) {
|
|
@@ -13,8 +18,36 @@ pub fn load_trigger(
|
|
|
13
18
|
Value::String(src) => {
|
|
14
19
|
trigger_path = src.to_string();
|
|
15
20
|
}
|
|
21
|
+
Value::Identifier(src) => {
|
|
22
|
+
trigger_path = src.to_string();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
Value::Map(map) => {
|
|
26
|
+
if let Some(Value::String(src)) = map.get("entity") {
|
|
27
|
+
trigger_path = format!("devalang://bank/{}", src.to_string());
|
|
28
|
+
} else if let Some(Value::Identifier(src)) = map.get("entity") {
|
|
29
|
+
trigger_path = format!("devalang://bank/{}", src.to_string());
|
|
30
|
+
} else {
|
|
31
|
+
eprintln!(
|
|
32
|
+
"❌ Trigger map must contain an 'entity' key with a string or identifier value."
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
Value::Sample(src) => {
|
|
37
|
+
trigger_path = src.to_string();
|
|
38
|
+
}
|
|
39
|
+
Value::Statement(stmt) => {
|
|
40
|
+
if let StatementKind::Trigger { entity, duration, effects } = &stmt.kind {
|
|
41
|
+
trigger_path = entity.clone();
|
|
42
|
+
} else {
|
|
43
|
+
eprintln!("❌ Trigger statement must be of type 'Trigger', found: {:?}", stmt.kind);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
16
46
|
_ => {
|
|
17
|
-
eprintln!(
|
|
47
|
+
eprintln!(
|
|
48
|
+
"❌ Invalid trigger type. Expected a string or identifier variable, found: {:?}",
|
|
49
|
+
trigger
|
|
50
|
+
);
|
|
18
51
|
}
|
|
19
52
|
}
|
|
20
53
|
|
|
@@ -45,11 +78,11 @@ pub fn load_trigger(
|
|
|
45
78
|
|
|
46
79
|
Duration::Beat(beat_str) => {
|
|
47
80
|
let parts: Vec<&str> = beat_str.split('/').collect();
|
|
48
|
-
|
|
81
|
+
|
|
49
82
|
if parts.len() == 2 {
|
|
50
83
|
let numerator: f32 = parts[0].parse().unwrap_or(1.0);
|
|
51
84
|
let denominator: f32 = parts[1].parse().unwrap_or(1.0);
|
|
52
|
-
duration_as_secs = numerator / denominator * base_duration;
|
|
85
|
+
duration_as_secs = (numerator / denominator) * base_duration;
|
|
53
86
|
} else {
|
|
54
87
|
eprintln!("❌ Invalid beat duration format: {}", beat_str);
|
|
55
88
|
}
|
|
@@ -21,23 +21,33 @@ impl AudioPlayer {
|
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
fn load_source(&self, path: &str) -> impl Source<Item = f32> + Send + 'static {
|
|
25
|
-
let file = File::open(path)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
fn load_source(&self, path: &str) -> Option<impl Source<Item = f32> + Send + 'static> {
|
|
25
|
+
if let Ok(file) = File::open(path) {
|
|
26
|
+
let reader = BufReader::new(file);
|
|
27
|
+
match Decoder::new(reader) {
|
|
28
|
+
Ok(decoder) => Some(decoder.convert_samples()),
|
|
29
|
+
Err(e) => {
|
|
30
|
+
eprintln!("❌ Failed to decode audio file '{}': {}", path, e);
|
|
31
|
+
None
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
} else {
|
|
35
|
+
eprintln!("❌ Could not open audio file: {}", path);
|
|
36
|
+
None
|
|
37
|
+
}
|
|
29
38
|
}
|
|
30
39
|
|
|
31
40
|
pub fn play_file_once(&mut self, path: &str) {
|
|
32
41
|
self.sink.stop();
|
|
33
42
|
self.sink = Sink::try_new(&self.handle).unwrap();
|
|
34
|
-
|
|
35
43
|
self.sink.set_volume(1.0);
|
|
36
44
|
|
|
37
|
-
let source = self.load_source(path)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
45
|
+
if let Some(source) = self.load_source(path) {
|
|
46
|
+
self.sink.append(source);
|
|
47
|
+
self.last_path = Some(path.to_string());
|
|
48
|
+
} else {
|
|
49
|
+
eprintln!("⚠️ Skipping playback: failed to load '{}'", path);
|
|
50
|
+
}
|
|
41
51
|
}
|
|
42
52
|
|
|
43
53
|
pub fn replay_last(&mut self) {
|