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                        if self.cursor_time - start >= render_target {
57                            break;
58                        }
59                        if iter_count > hard_iter_cap {
60                            break;
61                        }
62                    }
63                    return Ok(());
64                }
65
66                // live path handled elsewhere (spawning worker) in previous code paths
67                anyhow::bail!("Live background worker path should be handled earlier");
68            }
69            Value::Call { name, args } if name == "pass" => {
70                // treat as pass(ms) with offline synchronous behavior
71                let mut interval_ms: u64 = 1000;
72                if let Some(Value::Number(n)) = args.get(0) {
73                    interval_ms = (*n) as u64;
74                }
75                let interval_secs = (interval_ms as f32) / 1000.0;
76                let mut iter_count: usize = 0;
77                let hard_iter_cap: usize = 100_000;
78                let start = self.cursor_time;
79                let render_target = self.special_vars.total_duration.max(1.0);
80                loop {
81                    let before_cursor = self.cursor_time;
82                    let prev = self.suppress_beat_emit;
83                    self.suppress_beat_emit = true;
84                    let res = self.collect_events(body);
85                    self.suppress_beat_emit = prev;
86                    res?;
87                    // Break signalled inside loop body -> exit pass loop
88                    if self.break_flag {
89                        self.break_flag = false;
90                        break;
91                    }
92                    iter_count = iter_count.saturating_add(1);
93                    let after_cursor = self.cursor_time;
94                    if (after_cursor - before_cursor).abs() < f32::EPSILON {
95                        self.cursor_time += interval_secs;
96                    }
97                    if self.cursor_time - start >= render_target {
98                        break;
99                    }
100                    if iter_count > hard_iter_cap {
101                        break;
102                    }
103                }
104                Ok(())
105            }
106            Value::Null => {
107                // Indefinite loop: run until no further audio is produced or time limit hit
108                let start_time = self.cursor_time;
109                let time_limit = 60.0_f32;
110                loop {
111                    let before_cursor = self.cursor_time;
112                    let prev = self.suppress_beat_emit;
113                    self.suppress_beat_emit = true;
114                    let res = self.collect_events(body);
115                    self.suppress_beat_emit = prev;
116                    res?;
117                    // Break signalled inside indefinite loop -> exit
118                    if self.break_flag {
119                        self.break_flag = false;
120                        break;
121                    }
122                    if (self.cursor_time - before_cursor).abs() < f32::EPSILON {
123                        break;
124                    }
125                    if self.cursor_time - start_time >= time_limit {
126                        break;
127                    }
128                }
129                Ok(())
130            }
131            other => anyhow::bail!(
132                "❌ Loop iterator must be a number, 'pass' or null, found: {:?}",
133                other
134            ),
135        }
136    }
137
138    pub fn execute_for(
139        &mut self,
140        variable: &str,
141        iterable: &Value,
142        body: &[Statement],
143    ) -> Result<()> {
144        let items = match iterable {
145            Value::Array(arr) => arr.clone(),
146            Value::Identifier(ident) => {
147                if let Some(Value::Array(arr)) = self.variables.get(ident) {
148                    arr.clone()
149                } else {
150                    anyhow::bail!("❌ For iterable '{}' must be an array", ident)
151                }
152            }
153            Value::Range { start, end } => {
154                let start_val = match start.as_ref() {
155                    Value::Number(n) => *n as i32,
156                    _ => anyhow::bail!("❌ Range start must be a number"),
157                };
158                let end_val = match end.as_ref() {
159                    Value::Number(n) => *n as i32,
160                    _ => anyhow::bail!("❌ Range end must be a number"),
161                };
162                (start_val..end_val)
163                    .map(|i| Value::Number(i as f32))
164                    .collect()
165            }
166            _ => anyhow::bail!(
167                "❌ For iterable must be an array or range, found: {:?}",
168                iterable
169            ),
170        };
171
172        for item in items.iter() {
173            let old_value = self.variables.insert(variable.to_string(), item.clone());
174            self.collect_events(body)?;
175            // If a break was signalled inside the body, consume flag and exit for-loop
176            if self.break_flag {
177                self.break_flag = false;
178                // Restore previous variable state before exiting
179                match old_value {
180                    Some(val) => {
181                        self.variables.insert(variable.to_string(), val);
182                    }
183                    None => {
184                        self.variables.remove(variable);
185                    }
186                }
187                break;
188            }
189            match old_value {
190                Some(val) => {
191                    self.variables.insert(variable.to_string(), val);
192                }
193                None => {
194                    self.variables.remove(variable);
195                }
196            }
197        }
198
199        Ok(())
200    }
201}