@devaloop/devalang 0.0.1-alpha.1 → 0.0.1-alpha.11

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.
Files changed (168) hide show
  1. package/.devalang +9 -0
  2. package/Cargo.toml +15 -6
  3. package/README.md +79 -81
  4. package/docs/CHANGELOG.md +213 -0
  5. package/docs/ROADMAP.md +11 -8
  6. package/docs/TODO.md +32 -29
  7. package/examples/bank.deva +9 -0
  8. package/examples/condition.deva +20 -0
  9. package/examples/duration.deva +9 -0
  10. package/examples/group.deva +12 -0
  11. package/examples/index.deva +12 -5
  12. package/examples/loop.deva +16 -0
  13. package/examples/samples/hat-808.wav +0 -0
  14. package/examples/synth.deva +14 -0
  15. package/examples/variables.deva +9 -0
  16. package/out-tsc/bin/devalang.exe +0 -0
  17. package/out-tsc/scripts/version/fetch.js +1 -5
  18. package/package.json +5 -4
  19. package/project-version.json +3 -3
  20. package/rust/cli/bank.rs +455 -0
  21. package/rust/cli/build.rs +114 -28
  22. package/rust/cli/check.rs +96 -103
  23. package/rust/cli/driver.rs +280 -0
  24. package/rust/cli/init.rs +79 -0
  25. package/rust/cli/install.rs +17 -0
  26. package/rust/cli/mod.rs +8 -1
  27. package/rust/cli/play.rs +193 -0
  28. package/rust/cli/template.rs +57 -0
  29. package/rust/cli/update.rs +4 -0
  30. package/rust/common/cdn.rs +11 -0
  31. package/rust/common/mod.rs +1 -0
  32. package/rust/config/driver.rs +76 -0
  33. package/rust/config/loader.rs +110 -0
  34. package/rust/config/mod.rs +2 -0
  35. package/rust/core/audio/engine.rs +242 -0
  36. package/rust/core/audio/evaluator.rs +31 -0
  37. package/rust/core/audio/interpreter/arrow_call.rs +142 -0
  38. package/rust/core/audio/interpreter/call.rs +70 -0
  39. package/rust/core/audio/interpreter/condition.rs +69 -0
  40. package/rust/core/audio/interpreter/driver.rs +236 -0
  41. package/rust/core/audio/interpreter/let_.rs +19 -0
  42. package/rust/core/audio/interpreter/load.rs +18 -0
  43. package/rust/core/audio/interpreter/loop_.rs +67 -0
  44. package/rust/core/audio/interpreter/mod.rs +12 -0
  45. package/rust/core/audio/interpreter/sleep.rs +36 -0
  46. package/rust/core/audio/interpreter/spawn.rs +84 -0
  47. package/rust/core/audio/interpreter/tempo.rs +16 -0
  48. package/rust/core/audio/interpreter/trigger.rs +102 -0
  49. package/rust/core/audio/loader/mod.rs +1 -0
  50. package/rust/core/audio/loader/trigger.rs +64 -0
  51. package/rust/core/audio/mod.rs +6 -0
  52. package/rust/core/audio/player.rs +54 -0
  53. package/rust/core/audio/renderer.rs +54 -0
  54. package/rust/core/builder/mod.rs +70 -27
  55. package/rust/core/debugger/lexer.rs +27 -0
  56. package/rust/core/debugger/mod.rs +13 -49
  57. package/rust/core/debugger/preprocessor.rs +27 -0
  58. package/rust/core/debugger/store.rs +25 -0
  59. package/rust/core/error/mod.rs +60 -0
  60. package/rust/core/lexer/handler/arrow.rs +31 -0
  61. package/rust/core/lexer/handler/at.rs +21 -0
  62. package/rust/core/lexer/handler/brace.rs +41 -0
  63. package/rust/core/lexer/handler/colon.rs +21 -0
  64. package/rust/core/lexer/handler/comment.rs +30 -0
  65. package/rust/core/lexer/handler/dot.rs +21 -0
  66. package/rust/core/lexer/handler/driver.rs +241 -0
  67. package/rust/core/lexer/handler/identifier.rs +41 -0
  68. package/rust/core/lexer/handler/indent.rs +52 -0
  69. package/rust/core/lexer/handler/mod.rs +15 -0
  70. package/rust/core/lexer/handler/newline.rs +23 -0
  71. package/rust/core/lexer/handler/number.rs +31 -0
  72. package/rust/core/lexer/handler/operator.rs +44 -0
  73. package/rust/core/lexer/handler/slash.rs +21 -0
  74. package/rust/core/lexer/handler/string.rs +63 -0
  75. package/rust/core/lexer/mod.rs +37 -319
  76. package/rust/core/lexer/token.rs +87 -0
  77. package/rust/core/mod.rs +6 -2
  78. package/rust/core/parser/driver.rs +339 -0
  79. package/rust/core/parser/handler/arrow_call.rs +151 -0
  80. package/rust/core/parser/handler/at.rs +162 -0
  81. package/rust/core/parser/handler/bank.rs +41 -0
  82. package/rust/core/parser/handler/condition.rs +74 -0
  83. package/rust/core/parser/handler/dot.rs +178 -0
  84. package/rust/core/parser/handler/identifier/call.rs +41 -0
  85. package/rust/core/parser/handler/identifier/group.rs +75 -0
  86. package/rust/core/parser/handler/identifier/let_.rs +133 -0
  87. package/rust/core/parser/handler/identifier/mod.rs +51 -0
  88. package/rust/core/parser/handler/identifier/sleep.rs +33 -0
  89. package/rust/core/parser/handler/identifier/spawn.rs +41 -0
  90. package/rust/core/parser/handler/identifier/synth.rs +65 -0
  91. package/rust/core/parser/handler/loop_.rs +72 -0
  92. package/rust/core/parser/handler/mod.rs +8 -0
  93. package/rust/core/parser/handler/tempo.rs +47 -0
  94. package/rust/core/parser/mod.rs +3 -200
  95. package/rust/core/parser/statement.rs +96 -0
  96. package/rust/core/preprocessor/loader.rs +308 -0
  97. package/rust/core/preprocessor/mod.rs +2 -24
  98. package/rust/core/preprocessor/module.rs +42 -56
  99. package/rust/core/preprocessor/processor.rs +76 -0
  100. package/rust/core/preprocessor/resolver/bank.rs +41 -51
  101. package/rust/core/preprocessor/resolver/call.rs +123 -0
  102. package/rust/core/preprocessor/resolver/condition.rs +92 -0
  103. package/rust/core/preprocessor/resolver/driver.rs +232 -0
  104. package/rust/core/preprocessor/resolver/group.rs +61 -0
  105. package/rust/core/preprocessor/resolver/let_.rs +31 -0
  106. package/rust/core/preprocessor/resolver/loop_.rs +76 -67
  107. package/rust/core/preprocessor/resolver/mod.rs +12 -111
  108. package/rust/core/preprocessor/resolver/spawn.rs +58 -0
  109. package/rust/core/preprocessor/resolver/synth.rs +50 -0
  110. package/rust/core/preprocessor/resolver/tempo.rs +40 -61
  111. package/rust/core/preprocessor/resolver/trigger.rs +90 -154
  112. package/rust/core/preprocessor/resolver/value.rs +78 -0
  113. package/rust/core/shared/bank.rs +21 -0
  114. package/rust/core/shared/duration.rs +9 -0
  115. package/rust/core/shared/mod.rs +3 -0
  116. package/rust/core/shared/value.rs +29 -0
  117. package/rust/core/store/export.rs +28 -0
  118. package/rust/core/store/global.rs +39 -0
  119. package/rust/core/store/import.rs +28 -0
  120. package/rust/core/store/mod.rs +4 -0
  121. package/rust/core/store/variable.rs +28 -0
  122. package/rust/core/utils/mod.rs +2 -0
  123. package/rust/core/utils/path.rs +31 -0
  124. package/rust/core/utils/validation.rs +37 -0
  125. package/rust/installer/bank.rs +55 -0
  126. package/rust/installer/mod.rs +1 -0
  127. package/rust/lib.rs +162 -1
  128. package/rust/main.rs +104 -31
  129. package/rust/utils/file.rs +35 -0
  130. package/rust/utils/installer.rs +56 -0
  131. package/rust/utils/logger.rs +108 -34
  132. package/rust/utils/mod.rs +5 -3
  133. package/rust/utils/{loader.rs → spinner.rs} +2 -0
  134. package/rust/utils/watcher.rs +33 -0
  135. package/templates/minimal/.devalang +5 -0
  136. package/templates/minimal/README.md +202 -0
  137. package/templates/minimal/src/index.deva +2 -0
  138. package/templates/welcome/.devalang +5 -0
  139. package/templates/welcome/README.md +202 -0
  140. package/templates/welcome/samples/kick-808.wav +0 -0
  141. package/templates/welcome/src/index.deva +13 -0
  142. package/templates/welcome/src/variables.deva +5 -0
  143. package/typescript/scripts/version/fetch.ts +1 -6
  144. package/docs/COMMANDS.md +0 -31
  145. package/docs/SYNTAX.md +0 -148
  146. package/examples/exported.deva +0 -7
  147. package/rust/audio/mod.rs +0 -1
  148. package/rust/cli/new.rs +0 -1
  149. package/rust/core/parser/at.rs +0 -142
  150. package/rust/core/parser/bank.rs +0 -42
  151. package/rust/core/parser/dot.rs +0 -107
  152. package/rust/core/parser/identifer.rs +0 -91
  153. package/rust/core/parser/loop_.rs +0 -62
  154. package/rust/core/parser/tempo.rs +0 -42
  155. package/rust/core/parser/variable.rs +0 -129
  156. package/rust/core/preprocessor/dependencies.rs +0 -54
  157. package/rust/core/preprocessor/resolver/at.rs +0 -24
  158. package/rust/core/types/cli.rs +0 -160
  159. package/rust/core/types/mod.rs +0 -7
  160. package/rust/core/types/module.rs +0 -41
  161. package/rust/core/types/parser.rs +0 -73
  162. package/rust/core/types/statement.rs +0 -105
  163. package/rust/core/types/store.rs +0 -116
  164. package/rust/core/types/token.rs +0 -83
  165. package/rust/core/types/variable.rs +0 -32
  166. package/rust/runner/executer.rs +0 -44
  167. package/rust/runner/mod.rs +0 -1
  168. package/rust/utils/path.rs +0 -46
@@ -0,0 +1,242 @@
1
+ use std::{ collections::HashMap, fs::File, io::BufReader, path::Path };
2
+ use hound::{ SampleFormat, WavSpec, WavWriter };
3
+ use rodio::{ Decoder, Source };
4
+
5
+ use crate::core::{
6
+ store::variable::VariableTable,
7
+ utils::path::{ normalize_path, resolve_relative_path },
8
+ };
9
+
10
+ const SAMPLE_RATE: u32 = 44100;
11
+ const CHANNELS: u16 = 2;
12
+
13
+ #[derive(Debug, Clone, PartialEq)]
14
+ pub struct AudioEngine {
15
+ pub volume: f32,
16
+ pub variables: VariableTable,
17
+ pub buffer: Vec<i16>,
18
+ pub module_name: String,
19
+ }
20
+
21
+ impl AudioEngine {
22
+ pub fn new(module_name: String) -> Self {
23
+ AudioEngine {
24
+ volume: 1.0,
25
+ buffer: vec![],
26
+ variables: VariableTable::new(),
27
+ module_name,
28
+ }
29
+ }
30
+
31
+ pub fn get_buffer(&self) -> &[i16] {
32
+ &self.buffer
33
+ }
34
+
35
+ pub fn get_normalized_buffer(&self) -> Vec<f32> {
36
+ self.buffer
37
+ .iter()
38
+ .map(|&s| (s as f32) / 32768.0)
39
+ .collect()
40
+ }
41
+
42
+ pub fn mix(&mut self, other: &AudioEngine) {
43
+ let max_len = self.buffer.len().max(other.buffer.len());
44
+ self.buffer.resize(max_len, 0);
45
+
46
+ for (i, &sample) in other.buffer.iter().enumerate() {
47
+ self.buffer[i] = self.buffer[i].saturating_add(sample);
48
+ }
49
+ }
50
+
51
+ pub fn merge_with(&mut self, other: AudioEngine) {
52
+ if other.buffer.iter().all(|&s| s == 0) {
53
+ eprintln!("⚠️ Skipping merge: other buffer is silent");
54
+ return;
55
+ }
56
+
57
+ if self.buffer.iter().all(|&s| s == 0) {
58
+ self.buffer = other.buffer;
59
+ self.variables.variables.extend(other.variables.variables);
60
+ return;
61
+ }
62
+
63
+ self.mix(&other);
64
+ self.variables.variables.extend(other.variables.variables);
65
+ }
66
+
67
+ pub fn set_duration(&mut self, duration_secs: f32) {
68
+ let total_samples = (duration_secs * (SAMPLE_RATE as f32) * (CHANNELS as f32)) as usize;
69
+
70
+ if self.buffer.len() < total_samples {
71
+ self.buffer.resize(total_samples, 0);
72
+ }
73
+ }
74
+
75
+ pub fn set_variables(&mut self, variables: VariableTable) {
76
+ self.variables = variables;
77
+ }
78
+
79
+ pub fn generate_wav_file(&mut self, output_dir: &String) -> Result<(), String> {
80
+ if self.buffer.len() % (CHANNELS as usize) != 0 {
81
+ self.buffer.push(0);
82
+ println!("Completed buffer to respect stereo format.");
83
+ }
84
+
85
+ let spec = WavSpec {
86
+ channels: CHANNELS,
87
+ sample_rate: SAMPLE_RATE,
88
+ bits_per_sample: 16,
89
+ sample_format: SampleFormat::Int,
90
+ };
91
+
92
+ let mut writer = WavWriter::create(output_dir, spec).map_err(|e|
93
+ format!("Error creating WAV file: {}", e)
94
+ )?;
95
+
96
+ for sample in &self.buffer {
97
+ writer.write_sample(*sample).map_err(|e| format!("Error writing sample: {:?}", e))?;
98
+ }
99
+
100
+ writer.finalize().map_err(|e| format!("Error finalizing WAV: {:?}", e))?;
101
+
102
+ Ok(())
103
+ }
104
+
105
+ pub fn insert_note(
106
+ &mut self,
107
+ waveform: String,
108
+ freq: f32,
109
+ amp: f32,
110
+ start_time_ms: f32,
111
+ duration_ms: f32
112
+ ) {
113
+ let sample_rate = SAMPLE_RATE as f32;
114
+ let channels = CHANNELS as usize;
115
+
116
+ let total_samples = ((duration_ms / 1000.0) * sample_rate) as usize;
117
+ let start_sample = ((start_time_ms / 1000.0) * sample_rate) as usize;
118
+ let amplitude = (i16::MAX as f32) * amp.clamp(0.0, 1.0);
119
+
120
+ let mut samples = Vec::with_capacity(total_samples);
121
+ let fade_len = (sample_rate * 0.01) as usize; // 10 ms fade
122
+
123
+ for i in 0..total_samples {
124
+ let t = ((start_sample + i) as f32) / sample_rate;
125
+ let phase = 2.0 * std::f32::consts::PI * freq * t;
126
+
127
+ let mut value = match waveform.as_str() {
128
+ "sine" => phase.sin(),
129
+ "square" => if phase.sin() >= 0.0 { 1.0 } else { -1.0 }
130
+ "saw" => 2.0 * (freq * t - (freq * t + 0.5).floor()),
131
+ "triangle" => (2.0 * (2.0 * (freq * t).fract() - 1.0)).abs() * 2.0 - 1.0,
132
+ _ => 0.0,
133
+ };
134
+
135
+ // Fade in/out
136
+ if i < fade_len {
137
+ value *= (i as f32) / (fade_len as f32);
138
+ } else if i >= total_samples - fade_len {
139
+ value *= ((total_samples - i) as f32) / (fade_len as f32);
140
+ }
141
+
142
+ samples.push((value * amplitude) as i16);
143
+ }
144
+
145
+ // Convert to stereo
146
+ let stereo_samples: Vec<i16> = samples
147
+ .iter()
148
+ .flat_map(|s| vec![*s, *s])
149
+ .collect();
150
+
151
+ let offset = start_sample * channels;
152
+ let required_len = offset + stereo_samples.len();
153
+
154
+ if self.buffer.len() < required_len {
155
+ self.buffer.resize(required_len, 0);
156
+ }
157
+
158
+ for (i, sample) in stereo_samples.iter().enumerate() {
159
+ if *sample != 0 {
160
+ }
161
+ self.buffer[offset + i] = self.buffer[offset + i].saturating_add(*sample);
162
+ }
163
+ }
164
+
165
+ pub fn insert_sample(
166
+ &mut self,
167
+ filepath: &str,
168
+ time_secs: f32,
169
+ dur_sec: f32,
170
+ effects: Option<HashMap<String, f32>>
171
+ ) {
172
+ if filepath.is_empty() {
173
+ eprintln!("❌ Empty file path provided for audio sample.");
174
+ return;
175
+ }
176
+
177
+ let mut resolved_path = String::new();
178
+
179
+ if filepath.starts_with("devalang://") {
180
+ let root = Path::new(env!("CARGO_MANIFEST_DIR"));
181
+ let parts = filepath.split("devalang://").collect::<Vec<&str>>();
182
+ let object_parts = parts.get(1).unwrap_or(&"").split("/").collect::<Vec<&str>>();
183
+ let object_type = object_parts.get(0).unwrap_or(&"").to_lowercase();
184
+ let object_dir = object_parts.get(1).unwrap_or(&"").to_string();
185
+ let object_name = object_parts.get(2).unwrap_or(&"").to_string();
186
+
187
+ if object_type.contains("bank") {
188
+ resolved_path = root
189
+ .join(".deva")
190
+ .join("bank")
191
+ .join(object_dir)
192
+ .join(format!("{}.wav", object_name))
193
+ .to_str()
194
+ .unwrap_or("")
195
+ .to_string();
196
+ } else {
197
+ eprintln!("❌ Unsupported devalang:// object type: {}", object_type);
198
+ return;
199
+ }
200
+ }
201
+
202
+ let file = BufReader::new(File::open(resolved_path).expect("Failed to open audio file"));
203
+ let decoder = Decoder::new(file).expect("Failed to decode audio file");
204
+
205
+ // Mono or stereo reading possible here, we will duplicate in L/R
206
+ let max_mono_samples = (dur_sec * (SAMPLE_RATE as f32)) as usize;
207
+ let samples: Vec<i16> = decoder.convert_samples().take(max_mono_samples).collect();
208
+
209
+ if samples.is_empty() {
210
+ eprintln!("No samples found in the audio file: {}", filepath);
211
+ return;
212
+ }
213
+
214
+ // TODO Apply effects here if needed
215
+ let offset = (time_secs * (SAMPLE_RATE as f32) * (CHANNELS as f32)) as usize;
216
+ let required_len = offset + samples.len() * (CHANNELS as usize);
217
+ let padded_required_len = if required_len % 2 == 1 {
218
+ required_len + 1
219
+ } else {
220
+ required_len
221
+ };
222
+
223
+ self.buffer.resize(padded_required_len, 0);
224
+ self.pad_samples(&samples, time_secs);
225
+ }
226
+
227
+ fn pad_samples(&mut self, samples: &[i16], time_secs: f32) {
228
+ let offset = (time_secs * (SAMPLE_RATE as f32) * (CHANNELS as f32)) as usize;
229
+
230
+ for (i, &sample) in samples.iter().enumerate() {
231
+ let adjusted_sample = ((sample as f32) * self.volume).round() as i16;
232
+
233
+ let left_pos = offset + i * 2;
234
+ let right_pos = left_pos + 1;
235
+
236
+ if right_pos < self.buffer.len() {
237
+ self.buffer[left_pos] = self.buffer[left_pos].saturating_add(adjusted_sample); // gauche
238
+ self.buffer[right_pos] = self.buffer[right_pos].saturating_add(adjusted_sample); // droite
239
+ }
240
+ }
241
+ }
242
+ }
@@ -0,0 +1,31 @@
1
+ use crate::core::{ shared::value::Value, store::variable::VariableTable };
2
+
3
+ pub fn evaluate_condition_string(expr: &str, vars: &VariableTable) -> bool {
4
+ let tokens: Vec<&str> = expr.split_whitespace().collect();
5
+ if tokens.len() != 3 {
6
+ return false;
7
+ }
8
+
9
+ let left = tokens[0];
10
+ let op = tokens[1];
11
+ let right = tokens[2];
12
+
13
+ let left_val = match vars.get(left) {
14
+ Some(Value::Number(n)) => *n,
15
+ _ => {
16
+ return false;
17
+ }
18
+ };
19
+
20
+ let right_val: f32 = right.parse().unwrap_or(0.0);
21
+
22
+ match op {
23
+ ">" => left_val > right_val,
24
+ "<" => left_val < right_val,
25
+ ">=" => left_val >= right_val,
26
+ "<=" => left_val <= right_val,
27
+ "==" => (left_val - right_val).abs() < f32::EPSILON,
28
+ "!=" => (left_val - right_val).abs() > f32::EPSILON,
29
+ _ => false,
30
+ }
31
+ }
@@ -0,0 +1,142 @@
1
+ use crate::core::{
2
+ audio::engine::AudioEngine,
3
+ parser::statement::{ Statement, StatementKind },
4
+ shared::value::Value,
5
+ store::variable::VariableTable,
6
+ };
7
+
8
+ use std::collections::HashMap;
9
+
10
+ pub fn interprete_call_arrow_statement(
11
+ stmt: &Statement,
12
+ audio_engine: &mut AudioEngine,
13
+ variable_table: &VariableTable,
14
+ base_bpm: f32,
15
+ base_duration: f32,
16
+ max_end_time: &mut f32,
17
+ mut cursor_time: Option<&mut f32>,
18
+ update_cursor: bool
19
+ ) -> (f32, f32) {
20
+ let cursor_copy = cursor_time
21
+ .as_ref()
22
+ .map(|c| **c)
23
+ .unwrap_or(0.0);
24
+
25
+ if let StatementKind::ArrowCall { target, method, args } = &stmt.kind {
26
+ let Some(Value::Map(synth_map)) = variable_table.get(target) else {
27
+ println!("❌ Synth '{}' not found in variable table", target);
28
+ return (*max_end_time, cursor_copy);
29
+ };
30
+
31
+ let Some(Value::String(entity)) = synth_map.get("entity") else {
32
+ println!("❌ Missing 'entity' key in synth '{}'.", target);
33
+ return (*max_end_time, cursor_copy);
34
+ };
35
+
36
+ if entity != "synth" {
37
+ println!("❌ '{}' is not a synth, entity is '{}'.", target, entity);
38
+ return (*max_end_time, cursor_copy);
39
+ }
40
+
41
+ let Some(Value::Map(value_map)) = synth_map.get("value") else {
42
+ println!("❌ Missing 'value' map in synth '{}'.", target);
43
+ return (*max_end_time, cursor_copy);
44
+ };
45
+
46
+ let Some(Value::String(waveform)) = value_map.get("waveform") else {
47
+ println!("❌ Missing or invalid 'waveform' in synth '{}'.", target);
48
+ return (*max_end_time, cursor_copy);
49
+ };
50
+
51
+ let Some(Value::Map(params)) = value_map.get("parameters") else {
52
+ println!("❌ Missing or invalid 'parameters' in synth '{}'.", target);
53
+ return (*max_end_time, cursor_copy);
54
+ };
55
+
56
+ let freq = extract_f32(params, "freq", base_bpm).unwrap_or(440.0);
57
+ let amp = extract_f32(params, "amp", base_bpm).unwrap_or(1.0);
58
+
59
+ if method == "note" {
60
+ let Some(Value::Identifier(note_name)) = args.get(0) else {
61
+ println!("❌ Invalid or missing argument for 'note' method on '{}'.", target);
62
+ return (*max_end_time, cursor_copy);
63
+ };
64
+
65
+ let mut final_note_params = HashMap::new();
66
+ if let Some(Value::Map(note_params)) = args.get(1) {
67
+ for (key, value) in note_params {
68
+ final_note_params.insert(key.clone(), value.clone());
69
+ }
70
+ }
71
+
72
+ let duration_ms = extract_f32(&final_note_params, "duration", base_bpm).unwrap_or(
73
+ base_duration
74
+ );
75
+ let duration_secs = duration_ms / 1000.0;
76
+
77
+ let final_freq = note_to_freq(note_name);
78
+ let start_time = cursor_copy;
79
+ let end_time = start_time + duration_secs;
80
+
81
+ audio_engine.insert_note(
82
+ waveform.clone(),
83
+ final_freq,
84
+ amp,
85
+ start_time * 1000.0,
86
+ duration_ms
87
+ );
88
+
89
+ *max_end_time = (*max_end_time).max(end_time);
90
+
91
+ if update_cursor {
92
+ if let Some(c) = cursor_time.as_mut() {
93
+ **c = end_time;
94
+ }
95
+ }
96
+
97
+ return (*max_end_time, end_time);
98
+ } else {
99
+ println!("❌ Unknown method '{}' on synth '{}'.", method, target);
100
+ }
101
+ }
102
+
103
+ (*max_end_time, cursor_copy)
104
+ }
105
+
106
+ fn extract_f32(map: &HashMap<String, Value>, key: &str, base_bpm: f32) -> Option<f32> {
107
+ map.get(key).and_then(|v| {
108
+ match v {
109
+ Value::Number(n) => Some(*n),
110
+ Value::Beat(beat_str) => {
111
+ let parts: Vec<&str> = beat_str.split('/').collect();
112
+ if parts.len() == 2 {
113
+ let numerator = parts[0].parse::<f32>().ok()?;
114
+ let denominator = parts[1].parse::<f32>().ok()?;
115
+
116
+ Some((numerator / denominator) * ((60.0 / base_bpm) * 1000.0))
117
+ } else {
118
+ None
119
+ }
120
+ }
121
+ _ => None,
122
+ }
123
+ })
124
+ }
125
+
126
+ fn note_to_freq(note: &str) -> f32 {
127
+ let notes = vec!["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
128
+
129
+ if note.len() < 2 || note.len() > 3 {
130
+ return 440.0;
131
+ }
132
+
133
+ let (name, octave_str) = note.split_at(note.len() - 1);
134
+ let semitone = notes
135
+ .iter()
136
+ .position(|&n| n == name)
137
+ .unwrap_or(9) as i32;
138
+ let octave = octave_str.parse::<i32>().unwrap_or(4);
139
+ let midi_note = (octave + 1) * 12 + semitone;
140
+
141
+ 440.0 * (2.0_f32).powf(((midi_note as f32) - 69.0) / 12.0)
142
+ }
@@ -0,0 +1,70 @@
1
+ use crate::core::{
2
+ audio::{ engine::AudioEngine, interpreter::{ driver::execute_audio_block } },
3
+ parser::statement::{ Statement, StatementKind },
4
+ shared::{ duration::Duration, value::Value },
5
+ store::variable::VariableTable,
6
+ };
7
+
8
+ pub fn interprete_call_statement(
9
+ stmt: &Statement,
10
+ audio_engine: AudioEngine,
11
+ variable_table: VariableTable,
12
+ base_bpm: f32,
13
+ base_duration: f32,
14
+ max_end_time: f32,
15
+ cursor_time: f32,
16
+ all_statements: &Vec<Statement>
17
+ ) -> (AudioEngine, f32, f32, f32) {
18
+ match &stmt.value {
19
+ Value::String(identifier) | Value::Identifier(identifier) => {
20
+ if
21
+ let Some(group_stmt) = all_statements
22
+ .iter()
23
+ .find(|s| {
24
+ matches!(s.kind, StatementKind::Group) &&
25
+ s.value.get("identifier") == Some(&Value::String(identifier.clone()))
26
+ })
27
+ {
28
+ if let Some(Value::Block(block)) = group_stmt.value.get("body") {
29
+ let (eng, _, end_time) = execute_audio_block(
30
+ audio_engine,
31
+ variable_table,
32
+ block.clone(),
33
+ base_bpm,
34
+ base_duration,
35
+ max_end_time,
36
+ cursor_time
37
+ );
38
+ return (eng, max_end_time.max(end_time), end_time, cursor_time);
39
+ } else {
40
+ eprintln!("❌ Group '{}' found but no valid body block", identifier);
41
+ }
42
+ } else {
43
+ eprintln!("❌ Group '{}' not found in statements", identifier);
44
+ }
45
+ }
46
+
47
+ Value::Map(map) => {
48
+ if let Some(Value::Block(block)) = map.get("body") {
49
+ let (eng, _, end_time) = execute_audio_block(
50
+ audio_engine,
51
+ variable_table,
52
+ block.clone(),
53
+ base_bpm,
54
+ base_duration,
55
+ max_end_time,
56
+ cursor_time
57
+ );
58
+ return (eng, max_end_time.max(end_time), end_time, cursor_time);
59
+ } else {
60
+ eprintln!("❌ Call map has no 'body' block");
61
+ }
62
+ }
63
+
64
+ other => {
65
+ eprintln!("❌ Invalid call statement: expected identifier or map, found {:?}", other);
66
+ }
67
+ }
68
+
69
+ (audio_engine, base_bpm, max_end_time, cursor_time)
70
+ }
@@ -0,0 +1,69 @@
1
+ use crate::core::{
2
+ audio::{
3
+ engine::AudioEngine,
4
+ evaluator::evaluate_condition_string,
5
+ interpreter::driver::execute_audio_block,
6
+ },
7
+ parser::statement::Statement,
8
+ shared::value::Value,
9
+ store::variable::VariableTable,
10
+ };
11
+
12
+ pub fn interprete_condition_statement(
13
+ stmt: &Statement,
14
+ audio_engine: AudioEngine,
15
+ variable_table: VariableTable,
16
+ base_bpm: f32,
17
+ base_duration: f32,
18
+ max_end_time: f32,
19
+ cursor_time: f32
20
+ ) -> (AudioEngine, f32, f32) {
21
+ let mut engine = audio_engine.clone();
22
+ let mut vars = variable_table.clone();
23
+ let mut cur_time = cursor_time;
24
+ let mut max_time = max_end_time;
25
+
26
+ let mut current = stmt.value.clone();
27
+
28
+ loop {
29
+ let Value::Map(map) = current else {
30
+ break;
31
+ };
32
+
33
+ let should_execute = match map.get("condition") {
34
+ Some(Value::Boolean(b)) => *b,
35
+ Some(Value::String(expr)) => evaluate_condition_string(expr, &vars),
36
+ Some(_) => false,
37
+ None => true,
38
+ };
39
+
40
+ if should_execute {
41
+ if let Some(Value::Block(block)) = map.get("body") {
42
+ let (new_engine, _, new_max) = execute_audio_block(
43
+ engine,
44
+ vars,
45
+ block.clone(),
46
+ base_bpm,
47
+ base_duration,
48
+ max_time,
49
+ cur_time
50
+ );
51
+ return (new_engine, new_max, new_max);
52
+ } else {
53
+ break;
54
+ }
55
+ }
56
+
57
+ // Advance to the next condition
58
+ match map.get("next") {
59
+ Some(Value::Map(next_map)) => {
60
+ current = Value::Map(next_map.clone());
61
+ }
62
+ _ => {
63
+ break;
64
+ }
65
+ }
66
+ }
67
+
68
+ (audio_engine, max_end_time, cursor_time)
69
+ }