@devaloop/devalang 0.0.1-alpha.11 → 0.0.1-alpha.13
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 -8
- package/Cargo.toml +8 -8
- package/README.md +1 -14
- package/docs/CHANGELOG.md +44 -0
- package/docs/TODO.md +1 -1
- package/examples/index.deva +10 -11
- package/out-tsc/bin/devalang.exe +0 -0
- package/package.json +2 -1
- package/project-version.json +3 -3
- package/rust/cli/build.rs +25 -2
- package/rust/cli/check.rs +26 -3
- package/rust/cli/play.rs +1 -1
- package/rust/core/audio/engine.rs +207 -41
- 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 +84 -127
- 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 +169 -61
- 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/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/mod.rs +1 -0
- package/rust/core/lexer/handler/parenthesis.rs +41 -0
- package/rust/core/lexer/token.rs +3 -0
- package/rust/core/parser/driver.rs +31 -3
- package/rust/core/parser/handler/dot.rs +65 -129
- 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/preprocessor/loader.rs +45 -29
- 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/bank.rs +1 -1
- package/rust/installer/mod.rs +2 -1
- package/rust/lib.rs +10 -8
- package/rust/utils/mod.rs +44 -1
- /package/rust/{utils/installer.rs → installer/utils.rs} +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
|
+
// ✅ 1. Cas : fonction
|
|
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
|
+
// ✅ 2. Cas : group dans variable_table ou global_store
|
|
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
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
use std::collections::HashMap;
|
|
2
|
+
|
|
1
3
|
use crate::core::{
|
|
2
4
|
audio::{ engine::AudioEngine, loader::trigger::load_trigger },
|
|
3
5
|
parser::statement::{ Statement, StatementKind },
|
|
@@ -13,90 +15,196 @@ pub fn interprete_trigger_statement(
|
|
|
13
15
|
cursor_time: f32,
|
|
14
16
|
max_end_time: f32
|
|
15
17
|
) -> Option<(f32, f32, AudioEngine)> {
|
|
16
|
-
if let StatementKind::Trigger { entity, duration } = &stmt.kind {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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();
|
|
24
28
|
} else {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
Some(Value::Identifier(other)) if other == "auto" => 1.0,
|
|
28
|
-
Some(other) => {
|
|
29
|
-
eprintln!(
|
|
30
|
-
"❌ Invalid duration reference '{}': expected number, got {:?}",
|
|
31
|
-
id,
|
|
32
|
-
other
|
|
33
|
-
);
|
|
34
|
-
return None;
|
|
35
|
-
}
|
|
36
|
-
None => {
|
|
37
|
-
eprintln!("❌ Duration identifier '{}' not found", id);
|
|
38
|
-
return None;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
29
|
+
eprintln!("❌ Trigger entity '{}' not found in global variable table", id);
|
|
30
|
+
return None;
|
|
41
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;
|
|
42
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
|
+
}
|
|
43
60
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
let parts: Vec<&str> = beat_str.split('/').collect();
|
|
61
|
+
let duration_secs = match duration {
|
|
62
|
+
Duration::Number(n) => *n,
|
|
47
63
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
+
}
|
|
51
83
|
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
52
86
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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;
|
|
56
92
|
}
|
|
57
93
|
|
|
58
|
-
|
|
59
|
-
|
|
94
|
+
let numerator: f32 = parts[0].parse().unwrap_or(1.0);
|
|
95
|
+
let denominator: f32 = parts[1].parse().unwrap_or(4.0);
|
|
60
96
|
|
|
61
|
-
|
|
97
|
+
let beats = (numerator / denominator) * 4.0;
|
|
62
98
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
duration,
|
|
66
|
-
base_duration,
|
|
67
|
-
variable_table.clone()
|
|
68
|
-
);
|
|
99
|
+
beats * base_duration
|
|
100
|
+
}
|
|
69
101
|
|
|
70
|
-
|
|
71
|
-
|
|
102
|
+
Duration::Auto => base_duration,
|
|
103
|
+
};
|
|
72
104
|
|
|
73
|
-
|
|
74
|
-
|
|
105
|
+
let final_variable_table = if let Some(parent) = &variable_table.parent {
|
|
106
|
+
VariableTable {
|
|
107
|
+
variables: parent.variables.clone(),
|
|
108
|
+
parent: None,
|
|
109
|
+
}
|
|
110
|
+
} else {
|
|
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;
|
|
124
|
+
}
|
|
75
125
|
|
|
76
|
-
|
|
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);
|
|
138
|
+
|
|
139
|
+
let play_length = if one_shot {
|
|
140
|
+
sample_length // play entire sample
|
|
77
141
|
} else {
|
|
78
|
-
|
|
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");
|
|
153
|
+
return None;
|
|
154
|
+
}
|
|
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
|
+
);
|
|
79
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));
|
|
80
183
|
}
|
|
81
184
|
|
|
82
185
|
None
|
|
83
186
|
}
|
|
84
187
|
|
|
85
|
-
fn
|
|
86
|
-
let
|
|
188
|
+
fn extract_effects(value: Value) -> Option<HashMap<String, Value>> {
|
|
189
|
+
if let Value::Map(map) = value {
|
|
190
|
+
let mut effects = HashMap::new();
|
|
87
191
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
return None;
|
|
192
|
+
for (key, val) in map {
|
|
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
|
|
96
200
|
}
|
|
97
|
-
}
|
|
201
|
+
} else {
|
|
202
|
+
return Some(effects);
|
|
203
|
+
}
|
|
98
204
|
}
|
|
99
|
-
}
|
|
100
205
|
|
|
101
|
-
|
|
206
|
+
Some(effects)
|
|
207
|
+
} else {
|
|
208
|
+
None
|
|
209
|
+
}
|
|
102
210
|
}
|
|
@@ -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) {
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
use std::collections::HashMap;
|
|
2
|
-
|
|
3
2
|
use crate::{
|
|
4
3
|
core::{
|
|
5
4
|
audio::{ engine::AudioEngine, interpreter::driver::run_audio_program },
|
|
@@ -18,36 +17,36 @@ pub fn render_audio_with_modules(
|
|
|
18
17
|
|
|
19
18
|
for (module_name, statements) in modules {
|
|
20
19
|
let mut global_max_end_time: f32 = 0.0;
|
|
21
|
-
let mut
|
|
20
|
+
let mut audio_engine = AudioEngine::new(module_name.clone());
|
|
22
21
|
|
|
23
22
|
// Apply global variables to the initial engine
|
|
24
23
|
if let Some(module) = global_store.get_module(&module_name) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
);
|
|
35
|
-
|
|
36
|
-
// Verify if the buffer is silent (all samples are zero)
|
|
37
|
-
if updated_engine.buffer.iter().all(|&s| s == 0) {
|
|
38
|
-
let logger = Logger::new();
|
|
39
|
-
logger.log_message(
|
|
40
|
-
LogLevel::Warning,
|
|
41
|
-
&format!("Module '{}' ignored: silent buffer (no non-zero samples)", module_name)
|
|
24
|
+
// interprete statements to fill the audio buffer
|
|
25
|
+
let (module_max_end_time, cursor_time) = run_audio_program(
|
|
26
|
+
&statements,
|
|
27
|
+
&mut audio_engine,
|
|
28
|
+
module_name.clone(),
|
|
29
|
+
output_dir.to_string(),
|
|
30
|
+
module.variable_table.clone(),
|
|
31
|
+
module.function_table.clone(),
|
|
32
|
+
global_store
|
|
42
33
|
);
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
34
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
35
|
+
// Verify if the buffer is silent (all samples are zero)
|
|
36
|
+
if audio_engine.buffer.iter().all(|&s| s == 0) {
|
|
37
|
+
let logger = Logger::new();
|
|
38
|
+
logger.log_message(
|
|
39
|
+
LogLevel::Warning,
|
|
40
|
+
&format!("Module '{}' ignored: silent buffer (no non-zero samples)", module_name)
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Determines the maximum end time for the module
|
|
45
|
+
global_max_end_time = global_max_end_time.max(module_max_end_time);
|
|
46
|
+
audio_engine.set_duration(global_max_end_time);
|
|
49
47
|
|
|
50
|
-
|
|
48
|
+
result.insert(module_name, audio_engine);
|
|
49
|
+
}
|
|
51
50
|
}
|
|
52
51
|
|
|
53
52
|
result
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
pub mod preprocessor;
|
|
2
2
|
pub mod lexer;
|
|
3
3
|
pub mod store;
|
|
4
|
+
pub mod module;
|
|
4
5
|
|
|
5
6
|
use std::io::Write;
|
|
6
7
|
|
|
@@ -13,6 +14,7 @@ impl Debugger {
|
|
|
13
14
|
|
|
14
15
|
pub fn write_log_file(&self, path: &str, filename: &str, content: &str) {
|
|
15
16
|
std::fs::create_dir_all(path).expect("Failed to create directory");
|
|
17
|
+
|
|
16
18
|
let file_path = format!("{}/{}", path, filename);
|
|
17
19
|
let mut file = std::fs::File::create(file_path).expect("Failed to create file");
|
|
18
20
|
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
use std::{ fs::create_dir_all };
|
|
2
|
+
use crate::core::{
|
|
3
|
+
debugger::Debugger,
|
|
4
|
+
store::{ function::FunctionTable, variable::VariableTable },
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
pub fn write_module_variable_log_file(
|
|
8
|
+
output_dir: &str,
|
|
9
|
+
module_path: &str,
|
|
10
|
+
variable_table: &VariableTable
|
|
11
|
+
) {
|
|
12
|
+
let debugger = Debugger::new();
|
|
13
|
+
let mut content = String::new();
|
|
14
|
+
let module_name = module_path.split('/').last().unwrap_or("index").replace(".deva", "");
|
|
15
|
+
|
|
16
|
+
let log_directory = format!("{}/logs/modules/{}", output_dir, module_name);
|
|
17
|
+
create_dir_all(&log_directory).expect("Failed to create log directory");
|
|
18
|
+
|
|
19
|
+
for (var_name, var_data) in &variable_table.variables {
|
|
20
|
+
content.push_str(&format!("{:?} = {:?}\n", var_name, var_data));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
content.push_str("\n");
|
|
24
|
+
|
|
25
|
+
debugger.write_log_file(&log_directory, "variables.log", &content);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
pub fn write_module_function_log_file(
|
|
29
|
+
output_dir: &str,
|
|
30
|
+
module_path: &str,
|
|
31
|
+
function_table: &FunctionTable
|
|
32
|
+
) {
|
|
33
|
+
let debugger = Debugger::new();
|
|
34
|
+
let mut content = String::new();
|
|
35
|
+
let module_name = module_path.split('/').last().unwrap_or("index").replace(".deva", "");
|
|
36
|
+
|
|
37
|
+
let log_directory = format!("{}/logs/modules/{}", output_dir, module_name);
|
|
38
|
+
create_dir_all(&log_directory).expect("Failed to create log directory");
|
|
39
|
+
|
|
40
|
+
for (func_name, func_data) in &function_table.functions {
|
|
41
|
+
content.push_str(&format!("{:?} = {:?}\n", func_name, func_data));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
content.push_str("\n");
|
|
45
|
+
|
|
46
|
+
debugger.write_log_file(&log_directory, "functions.log", &content);
|
|
47
|
+
}
|