devalang_wasm/engine/audio/interpreter/statements/
loop_.rs

1use crate::engine::audio::interpreter::driver::AudioInterpreter;
2use crate::language::syntax::ast::{Statement, Value};
3use anyhow::Result;
4use std::sync::atomic::AtomicUsize;
5
6static BG_WORKER_COUNTER: AtomicUsize = AtomicUsize::new(1);
7
8impl AudioInterpreter {
9    pub fn execute_loop(&mut self, count: &Value, body: &[Statement]) -> Result<()> {
10        match count {
11            Value::Number(n) => {
12                let loop_count = (*n) as usize;
13                for _ in 0..loop_count {
14                    self.collect_events(body)?;
15                    // If a break was signalled inside the body, consume flag and exit loop
16                    if self.break_flag {
17                        self.break_flag = false;
18                        break;
19                    }
20                }
21                Ok(())
22            }
23            Value::Identifier(ident) if ident == "pass" => {
24                // offline: run in-place per beat
25                if self.background_event_tx.is_none() {
26                    let beat_secs = if self.bpm > 0.0 { 60.0 / self.bpm } else { 1.0 };
27                    let interval_secs = beat_secs.max(0.001);
28                    let mut iter_count: usize = 0;
29                    let hard_iter_cap: usize = 100_000;
30                    let start = self.cursor_time;
31                    let render_target = self.special_vars.total_duration.max(1.0);
32
33                    loop {
34                        let before_cursor = self.cursor_time;
35                        // Avoid nested beat emission when executing loop body repeatedly:
36                        // suppress beat emission during inner collection so 'on beat'/'on bar'
37                        // handlers are only executed by the outer/top-level collector.
38                        let prev = self.suppress_beat_emit;
39                        self.suppress_beat_emit = true;
40                        let res = self.collect_events(body);
41                        // restore previous flag even if collect_events returned an error
42                        self.suppress_beat_emit = prev;
43                        res?;
44                        // Break signalled inside loop body -> exit pass loop
45                        if self.break_flag {
46                            self.break_flag = false;
47                            break;
48                        }
49                        iter_count = iter_count.saturating_add(1);
50                        let after_cursor = self.cursor_time;
51                        // If the body advanced the cursor_time, we keep that (no extra gap).
52                        // Otherwise advance by the interval (pass/beat) to avoid stalling.
53                        if (after_cursor - before_cursor).abs() < f32::EPSILON {
54                            self.cursor_time += interval_secs;
55                        }
56                        // Check schedule deadline (from "at X and during Y:")
57                        if let Some(deadline) = self.schedule_deadline {
58                            if self.cursor_time >= deadline {
59                                break;
60                            }
61                        }
62                        if self.cursor_time - start >= render_target {
63                            break;
64                        }
65                        if iter_count > hard_iter_cap {
66                            break;
67                        }
68                    }
69                    return Ok(());
70                }
71
72                // live path handled elsewhere (spawning worker) in previous code paths
73                anyhow::bail!("Live background worker path should be handled earlier");
74            }
75            Value::Call { name, args } if name == "pass" => {
76                // treat as pass(ms) with offline synchronous behavior
77                let mut interval_ms: u64 = 1000;
78                if let Some(Value::Number(n)) = args.get(0) {
79                    interval_ms = (*n) as u64;
80                }
81                let interval_secs = (interval_ms as f32) / 1000.0;
82                let mut iter_count: usize = 0;
83                let hard_iter_cap: usize = 100_000;
84                let start = self.cursor_time;
85                let render_target = self.special_vars.total_duration.max(1.0);
86                loop {
87                    let before_cursor = self.cursor_time;
88                    let prev = self.suppress_beat_emit;
89                    self.suppress_beat_emit = true;
90                    let res = self.collect_events(body);
91                    self.suppress_beat_emit = prev;
92                    res?;
93                    // Break signalled inside loop body -> exit pass loop
94                    if self.break_flag {
95                        self.break_flag = false;
96                        break;
97                    }
98                    iter_count = iter_count.saturating_add(1);
99                    let after_cursor = self.cursor_time;
100                    if (after_cursor - before_cursor).abs() < f32::EPSILON {
101                        self.cursor_time += interval_secs;
102                    }
103                    // Check schedule deadline (from "at X and during Y:")
104                    if let Some(deadline) = self.schedule_deadline {
105                        if self.cursor_time >= deadline {
106                            break;
107                        }
108                    }
109                    if self.cursor_time - start >= render_target {
110                        break;
111                    }
112                    if iter_count > hard_iter_cap {
113                        break;
114                    }
115                }
116                Ok(())
117            }
118            Value::Duration(duration) => {
119                // loop pass(duration): - interval based on DurationValue
120                let interval_secs = self.duration_to_seconds(duration)?;
121                let mut iter_count: usize = 0;
122                let hard_iter_cap: usize = 100_000;
123                let start = self.cursor_time;
124                let render_target = self.special_vars.total_duration.max(1.0);
125                loop {
126                    let before_cursor = self.cursor_time;
127                    let prev = self.suppress_beat_emit;
128                    self.suppress_beat_emit = true;
129                    let res = self.collect_events(body);
130                    self.suppress_beat_emit = prev;
131                    res?;
132                    // Break signalled inside loop body -> exit pass loop
133                    if self.break_flag {
134                        self.break_flag = false;
135                        break;
136                    }
137                    iter_count = iter_count.saturating_add(1);
138                    let after_cursor = self.cursor_time;
139                    if (after_cursor - before_cursor).abs() < f32::EPSILON {
140                        self.cursor_time += interval_secs;
141                    }
142                    // Check schedule deadline (from "at X and during Y:")
143                    if let Some(deadline) = self.schedule_deadline {
144                        if self.cursor_time >= deadline {
145                            break;
146                        }
147                    }
148                    if self.cursor_time - start >= render_target {
149                        break;
150                    }
151                    if iter_count > hard_iter_cap {
152                        break;
153                    }
154                }
155                Ok(())
156            }
157            Value::Null => {
158                // Indefinite loop: run until no further audio is produced or time limit hit
159                let start_time = self.cursor_time;
160                let time_limit = 60.0_f32;
161                loop {
162                    let before_cursor = self.cursor_time;
163                    let prev = self.suppress_beat_emit;
164                    self.suppress_beat_emit = true;
165                    let res = self.collect_events(body);
166                    self.suppress_beat_emit = prev;
167                    res?;
168                    // Break signalled inside indefinite loop -> exit
169                    if self.break_flag {
170                        self.break_flag = false;
171                        break;
172                    }
173                    if (self.cursor_time - before_cursor).abs() < f32::EPSILON {
174                        break;
175                    }
176                    // Check schedule deadline (from "at X and during Y:")
177                    if let Some(deadline) = self.schedule_deadline {
178                        if self.cursor_time >= deadline {
179                            break;
180                        }
181                    }
182                    if self.cursor_time - start_time >= time_limit {
183                        break;
184                    }
185                }
186                Ok(())
187            }
188            other => anyhow::bail!(
189                "❌ Loop iterator must be a number, 'pass' or null, found: {:?}",
190                other
191            ),
192        }
193    }
194
195    pub fn execute_for(
196        &mut self,
197        variable: &str,
198        iterable: &Value,
199        body: &[Statement],
200    ) -> Result<()> {
201        let items = match iterable {
202            Value::Array(arr) => arr.clone(),
203            Value::Identifier(ident) => {
204                if let Some(Value::Array(arr)) = self.variables.get(ident) {
205                    arr.clone()
206                } else {
207                    anyhow::bail!("❌ For iterable '{}' must be an array", ident)
208                }
209            }
210            Value::Range { start, end } => {
211                let start_val = match start.as_ref() {
212                    Value::Number(n) => *n as i32,
213                    _ => anyhow::bail!("❌ Range start must be a number"),
214                };
215                let end_val = match end.as_ref() {
216                    Value::Number(n) => *n as i32,
217                    _ => anyhow::bail!("❌ Range end must be a number"),
218                };
219                (start_val..end_val)
220                    .map(|i| Value::Number(i as f32))
221                    .collect()
222            }
223            _ => anyhow::bail!(
224                "❌ For iterable must be an array or range, found: {:?}",
225                iterable
226            ),
227        };
228
229        for item in items.iter() {
230            let old_value = self.variables.insert(variable.to_string(), item.clone());
231            self.collect_events(body)?;
232            // If a break was signalled inside the body, consume flag and exit for-loop
233            if self.break_flag {
234                self.break_flag = false;
235                // Restore previous variable state before exiting
236                match old_value {
237                    Some(val) => {
238                        self.variables.insert(variable.to_string(), val);
239                    }
240                    None => {
241                        self.variables.remove(variable);
242                    }
243                }
244                break;
245            }
246            match old_value {
247                Some(val) => {
248                    self.variables.insert(variable.to_string(), val);
249                }
250                None => {
251                    self.variables.remove(variable);
252                }
253            }
254        }
255
256        Ok(())
257    }
258}