Skip to content

Commit ca068d2

Browse files
author
Axxiant Ltd
committed
fix: SOLID move script loading from CLI to bridge and improve debug and error handling
1 parent b0655e6 commit ca068d2

29 files changed

Lines changed: 4020 additions & 116 deletions

CWD_ASSUMPTION_INVESTIGATION.md

Lines changed: 442 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
# INITIALIZATION UNDERRUN INVESTIGATION
2+
3+
**Date:** 2026-03-26
4+
**Agent:** SOLID PEDANT
5+
**Task:** Review new initialization changes for WET violations and config value differences causing underruns
6+
7+
---
8+
9+
## EXECUTIVE SUMMARY
10+
11+
**CRITICAL FINDING:** `targetSynthesizerLatency` MISMATCH between CLI and Bridge defaults
12+
13+
| Location | targetSynthesizerLatency | Impact |
14+
|----------|-------------------------|---------|
15+
| CLI (EngineConfig.cpp:18) | **0.02** (20ms) | New initialization method |
16+
| Bridge (engine_sim_bridge.cpp:129) | **0.05** (50ms) | Old initialization method |
17+
| GUI (simulator.cpp:229) | **0.02** (20ms) | Unknown |
18+
19+
**HYPOTHESIS:** The 2.5x difference in targetSynthesizerLatency (20ms vs 50ms) could cause underruns by changing buffer management behavior.
20+
21+
---
22+
23+
## NEW INITIALIZATION SEQUENCE
24+
25+
**Location:** `src/SimulationLoop.cpp:391-398`
26+
27+
```cpp
28+
bool useConfigScript = true; // Set to false to use old LoadScript method
29+
if (useConfigScript) {
30+
// NEW METHOD: Script loaded during Create
31+
engineConfig = EngineConfig::createDefaultWithScript(sampleRate, config.configPath, config.assetBasePath, config.simulationFrequency);
32+
handle = createSimulator(engineConfig, engineAPI);
33+
```
34+
35+
**OLD METHOD:** (Lines 399-414)
36+
```cpp
37+
} else {
38+
// OLD METHOD: Create then LoadScript
39+
engineConfig = EngineConfig::createDefault(sampleRate, config.simulationFrequency);
40+
handle = createSimulator(engineConfig, engineAPI);
41+
42+
// Load the script
43+
EngineSimResult loadResult = engineAPI.LoadScript(handle, config.configPath.c_str(), config.assetBasePath.c_str());
44+
```
45+
46+
---
47+
48+
## CONFIG VALUE DIFFERENCES
49+
50+
### CLI's `createDefault()` (Used by NEW method)
51+
52+
**Location:** `src/EngineConfig.cpp:11-25`
53+
54+
```cpp
55+
config.sampleRate = sampleRate; // From parameter (44100)
56+
config.inputBufferSize = 1024;
57+
config.audioBufferSize = 96000;
58+
config.simulationFrequency = simulationFrequency > 0 ? simulationFrequency : EngineConstants::DEFAULT_SIMULATION_FREQUENCY;
59+
config.fluidSimulationSteps = 8;
60+
config.targetSynthesizerLatency = 0.02; // 20ms
61+
config.volume = 1.0f;
62+
config.convolutionLevel = 0.5f;
63+
config.airNoise = 1.0f;
64+
```
65+
66+
### Bridge's `setDefaultConfig()` (Used by OLD method)
67+
68+
**Location:** `engine-sim-bridge/src/engine_sim_bridge.cpp:123-133`
69+
70+
```cpp
71+
config->sampleRate = 48000;
72+
config->inputBufferSize = 1024;
73+
config->audioBufferSize = 96000;
74+
config->simulationFrequency = 10000;
75+
config->fluidSimulationSteps = 8;
76+
config->targetSynthesizerLatency = 0.05; // 50ms
77+
config->volume = 0.5f;
78+
config->convolutionLevel = 1.0f;
79+
config->airNoise = 1.0f;
80+
```
81+
82+
---
83+
84+
## CRITICAL DIFFERENCES
85+
86+
### 1. `targetSynthesizerLatency` - 2.5x DIFFERENCE
87+
88+
**NEW (CLI):** 0.02 (20ms)
89+
**OLD (Bridge):** 0.05 (50ms)
90+
**DIFFERENCE:** 2.5x smaller latency target
91+
92+
**POTENTIAL IMPACT ON UNDERRUNS:**
93+
- Smaller latency target means tighter buffer management
94+
- Less headroom for rendering timing variations
95+
- More likely to underrun if rendering takes longer than expected
96+
- Could explain "MANY continual underruns" reported by user
97+
98+
### 2. `volume` - 2x DIFFERENCE
99+
100+
**NEW (CLI):** 1.0f (100%)
101+
**OLD (Bridge):** 0.5f (50%)
102+
**DIFFERENCE:** 2x louder
103+
104+
**POTENTIAL IMPACT:**
105+
- Louder audio could mask underruns or make them more noticeable
106+
- Not directly related to underrun count
107+
108+
### 3. `convolutionLevel` - 2x DIFFERENCE
109+
110+
**NEW (CLI):** 0.5f (50%)
111+
**OLD (Bridge):** 1.0f (100%)
112+
**DIFFERENCE:** Half the convolution effect
113+
114+
**POTENTIAL IMPACT:**
115+
- Less convolution processing = faster rendering
116+
- Should REDUCE underruns, not increase them
117+
118+
### 4. `sampleRate` - DIFFERENT
119+
120+
**NEW (CLI):** 44100 (from CLIMain.cpp:114)
121+
**OLD (Bridge):** 48000 (bridge default)
122+
123+
**POTENTIAL IMPACT:**
124+
- Different sample rate = different frame sizes
125+
- Could affect buffer calculations
126+
127+
---
128+
129+
## WET VIOLATIONS FOUND
130+
131+
### 1. Static Variable Abuse (Thread Safety Issue)
132+
133+
**Location:** `src/EngineConfig.cpp:32-35`
134+
135+
```cpp
136+
EngineSimConfig EngineConfig::createDefaultWithScript(...) {
137+
EngineSimConfig config = createDefault(sampleRate, simulationFrequency);
138+
// Note: We store pointers to the string data. The strings must remain valid during Create call.
139+
// This is safe because createDefaultWithScript returns by value and the strings
140+
// are typically stored in the SimulationConfig which has longer lifetime.
141+
static std::string scriptPathStorage; // STATIC!
142+
static std::string assetBasePathStorage; // STATIC!
143+
scriptPathStorage = scriptPath;
144+
assetBasePathStorage = assetBasePath;
145+
config.scriptPath = scriptPathStorage.c_str();
146+
config.assetBasePath = assetBasePathStorage.c_str();
147+
return config;
148+
}
149+
```
150+
151+
**SOLID VIOLATION:**
152+
- **SRP:** Using static variables for temporary storage is abuse
153+
- **Thread Safety:** Static variables are shared across ALL calls
154+
- **DANGEROUS:** If two instances are created, the second overwrites the first's pointers
155+
156+
**WHY THIS IS REALLY SHITTY:**
157+
- Comments claim "This is safe" but it's NOT
158+
- If `createDefaultWithScript` is called twice, second call overwrites first's strings
159+
- First config's `scriptPath` and `assetBasePath` pointers become INVALID
160+
- Undefined behavior or crashes possible
161+
162+
### 2. Boolean Flag for Code Path Selection
163+
164+
**Location:** `src/SimulationLoop.cpp:391`
165+
166+
```cpp
167+
bool useConfigScript = true; // Set to false to use old LoadScript method
168+
```
169+
170+
**SOLID VIOLATION:**
171+
- **OCP:** Adding boolean flags instead of using abstractions
172+
- **SRP:** Code path selection mixed with main logic
173+
174+
**Should Be:** Strategy pattern or Factory pattern
175+
176+
### 3. Duplicate Initialization Logic
177+
178+
**Location:** Multiple places
179+
180+
- `EngineConfig::createDefault()` - Creates config
181+
- `EngineConfig::createDefaultWithScript()` - Creates config with script
182+
- `EngineConfig::createAndLoad()` - Creates and loads
183+
- `EngineSimCreate()` in bridge - Creates and optionally loads script
184+
185+
**SOLID VIOLATION:**
186+
- **DRY:** Initialization logic scattered across multiple functions
187+
- **OCP:** Adding new initialization method requires modifying multiple locations
188+
189+
---
190+
191+
## BRIDGE SYNTHESIZER INITIALIZATION
192+
193+
**Location:** `engine-sim-bridge/src/engine_sim_bridge.cpp:279-283`
194+
195+
```cpp
196+
// Skip synthesizer initialization if scriptPath is provided in config
197+
// It will be initialized during loadSimulation with correct engine parameters
198+
if (!config || !config->scriptPath || strlen(config->scriptPath) == 0) {
199+
ctx->simulator->synthesizer().initialize(synthParams);
200+
}
201+
```
202+
203+
**CRITICAL:** When scriptPath is provided, synthesizer is NOT initialized during Create. It's initialized later during loadSimulation.
204+
205+
**QUESTION:** What parameters are used during loadSimulation synthesizer initialization?
206+
207+
**From earlier investigation:** The GUI's `initializeSynthesizer()` uses hardcoded values:
208+
```cpp
209+
synthParams.audioBufferSize = 44100;
210+
synthParams.audioSampleRate = 44100;
211+
synthParams.inputBufferSize = 44100; // 1 second buffer!
212+
```
213+
214+
**POTENTIAL ISSUE:** If loadSimulation uses GUI's hardcoded values, there could be parameter mismatches causing underruns.
215+
216+
---
217+
218+
## ARCHITECTURAL ISSUES
219+
220+
### Initialization Order Dependency
221+
222+
**NEW METHOD:**
223+
1. CLI creates config with targetSynthesizerLatency = 0.02
224+
2. Bridge skips synthesizer initialization (scriptPath provided)
225+
3. Bridge loads script
226+
4. Synthesizer initialized during loadSimulation (with what parameters?)
227+
228+
**OLD METHOD:**
229+
1. CLI calls Create with no scriptPath
230+
2. Bridge initializes synthesizer with targetSynthesizerLatency from config
231+
3. CLI calls LoadScript
232+
4. loadSimulation skips re-initialization (m_inputChannels != nullptr check)
233+
234+
**CRITICAL DIFFERENCE:** The NEW method relies on loadSimulation's synthesizer initialization, which may use DIFFERENT parameters than the config passed to Create.
235+
236+
---
237+
238+
## MOST LIKELY CAUSE OF UNDERRUNS
239+
240+
**HYPOTHESIS:** `targetSynthesizerLatency = 0.02` (20ms) is too aggressive
241+
242+
**Evidence:**
243+
1. Bridge default is 0.05 (50ms) - 2.5x larger
244+
2. User reports "MANY continual underruns" with new initialization
245+
3. Tighter latency target = less headroom for rendering variations
246+
4. Sound is "OK again" but with underruns - suggests audio works but buffer management is marginal
247+
248+
**TEST:** Change `targetSynthesizerLatency` from 0.02 to 0.05 in CLI config and see if underruns stop.
249+
250+
---
251+
252+
## RECOMMENDATIONS
253+
254+
### 1. Fix `targetSynthesizerLatency` Mismatch (Immediate)
255+
256+
**Change:** `src/EngineConfig.cpp:18`
257+
```cpp
258+
config.targetSynthesizerLatency = 0.05; // Match bridge default
259+
```
260+
261+
### 2. Fix Static Variable Abuse (Critical)
262+
263+
**Replace:** Static variables with proper string storage
264+
265+
**Options:**
266+
- Store strings in EngineSimConfig (not pointers)
267+
- Use std::string in EngineSimConfig instead of const char*
268+
- Pass strings directly to bridge API
269+
270+
### 3. Consolidate Initialization Logic (DRY)
271+
272+
**Eliminate:** Multiple initialization functions
273+
274+
**Create:** Single Factory that handles all cases
275+
276+
### 4. Document Parameter Sources (SRP)
277+
278+
**Question:** What parameters does loadSimulation use for synthesizer initialization?
279+
280+
**Need:** Trace through loadSimulation to document exact parameter values used.
281+
282+
---
283+
284+
## CONCLUSION
285+
286+
**ROOT CAUSE OF UNDERRUNS (LIKELY):** `targetSynthesizerLatency = 0.02` (20ms) in CLI config vs 0.05 (50ms) in bridge default.
287+
288+
**EVIDENCE:**
289+
1. 2.5x difference in latency target
290+
2. Sound OK but with "MANY continual underruns"
291+
3. Tighter latency = less headroom = more underruns
292+
293+
**ADDITIONAL ISSUES:**
294+
1. Static variable abuse in `createDefaultWithScript()` - DANGEROUS
295+
2. Multiple initialization paths with different parameters
296+
3. No clear documentation of which parameters are actually used
297+
298+
**IMMEDIATE FIX:** Change `targetSynthesizerLatency` from 0.02 to 0.05 and test if underruns stop.
299+
300+
---
301+
302+
*Investigation complete. Config value mismatch identified as most likely cause of underruns.*

0 commit comments

Comments
 (0)