expo-juce 0.5.2-rc.0 → 0.5.2-rc.2
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/README.md +240 -244
- package/ios/ExpoJuce.podspec +67 -66
- package/ios/{BeeperAudioEngine.h → ExpoJuceAudioEngine.h} +1 -1
- package/ios/{BeeperAudioEngine.mm → ExpoJuceAudioEngine.mm} +1017 -969
- package/ios/ExpoJuceBridge.m +30 -30
- package/ios/GraphPlayer.h +161 -157
- package/ios/JuceConfig.h +1 -1
- package/ios/NoteEventQueue.h +143 -0
- package/ios/PolySynthProcessor.h +26 -31
- package/ios/PolySynthProcessor.mm +46 -91
- package/ios/TransportEngine.h +12 -0
- package/ios/TransportEngine.mm +12 -0
- package/ios/Voice.h +6 -0
- package/ios/Voice.mm +3 -0
- package/ios/VoiceAllocator.h +1 -1
- package/ios/VoiceAllocator.mm +2 -1
- package/ios/juce_modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp +2 -2
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -1,244 +1,240 @@
|
|
|
1
|
-
# expo-juce
|
|
2
|
-
|
|
3
|
-
Realtime DSP w/C++ & JUCE
|
|
4
|
-
|
|
5
|
-
expo-juce embeds [JUCE](https://juce.com) 8 in an Expo / React Native app as an Expo
|
|
6
|
-
module: a JUCE-owned audio device drives an `AudioProcessorGraph` with an 8-voice
|
|
7
|
-
polyphonic synth and a transport, controlled from JS through synchronous JSI calls.
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
│
|
|
24
|
-
|
|
25
|
-
│
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
//
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
//
|
|
102
|
-
ExpoJuce.
|
|
103
|
-
ExpoJuce.
|
|
104
|
-
ExpoJuce.
|
|
105
|
-
|
|
106
|
-
//
|
|
107
|
-
ExpoJuce.
|
|
108
|
-
ExpoJuce.
|
|
109
|
-
ExpoJuce.
|
|
110
|
-
ExpoJuce.
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
ExpoJuce.
|
|
114
|
-
ExpoJuce.
|
|
115
|
-
ExpoJuce.
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
ExpoJuce.
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
ExpoJuce.
|
|
123
|
-
ExpoJuce.
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
`
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
├──
|
|
182
|
-
|
|
183
|
-
├──
|
|
184
|
-
├──
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
npm
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
measurement in [docs/latency.md](docs/latency.md#6-slow-motion-measurement-procedure).
|
|
225
|
-
|
|
226
|
-
### JUCE vendoring
|
|
227
|
-
|
|
228
|
-
`npm run download-juce` fetches JUCE 8.0.1 into `JUCE/` and `npm run vendor-juce` copies the needed modules into
|
|
229
|
-
`ios/juce_modules` (stripping tests, demos and non-iOS code). Each JUCE module is compiled as
|
|
230
|
-
its own translation unit (`ios/JuceModule_*.mm`); `ios/JuceConfig.h` holds the module flags and
|
|
231
|
-
`JUCE_STANDALONE_APPLICATION 0`.
|
|
232
|
-
|
|
233
|
-
## Documentation
|
|
234
|
-
|
|
235
|
-
- [docs/latency.md](docs/latency.md) — latency budget, signal path, changes, panel fields, slow-motion measurement
|
|
236
|
-
- [
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
## License
|
|
243
|
-
|
|
244
|
-
MIT
|
|
1
|
+
# expo-juce
|
|
2
|
+
|
|
3
|
+
Realtime DSP w/C++ & JUCE
|
|
4
|
+
|
|
5
|
+
expo-juce embeds [JUCE](https://juce.com) 8 in an Expo / React Native app as an Expo
|
|
6
|
+
module: a JUCE-owned audio device drives an `AudioProcessorGraph` with an 8-voice
|
|
7
|
+
polyphonic synth and a transport, controlled from JS through synchronous JSI calls.
|
|
8
|
+
iOS only for now; the Android and web modules are scaffolds.
|
|
9
|
+
|
|
10
|
+
> **Branches.** `main` is a stale scaffold. Current work is on `@aaron.smulktis/develop`;
|
|
11
|
+
> the diagnostics and latency work is on `debug/audio-diagnostics`.
|
|
12
|
+
|
|
13
|
+
## Architecture
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
JS (src/index.ts) setup / noteOn / params / transport / getDiagnostics
|
|
17
|
+
│ JSI (Expo Modules API)
|
|
18
|
+
ExpoJuceModule.swift Function (sync) / AsyncFunction (setup, shutdown), onBeat events
|
|
19
|
+
│
|
|
20
|
+
ExpoJuceBridge (ObjC) plain ObjC surface for Swift
|
|
21
|
+
│
|
|
22
|
+
ExpoJuceAudioEngine (ObjC++) AVAudioSession Playback + MixWithOthers; setup steps 0–11 on main thread
|
|
23
|
+
│
|
|
24
|
+
juce::AudioDeviceManager ─► GraphPlayer (AudioIODeviceCallback: graph, peaks, NaNs, test tone)
|
|
25
|
+
│
|
|
26
|
+
juce::AudioProcessorGraph PolySynthProcessor ─► audio output node
|
|
27
|
+
├─ NoteEventQueue: note-on ring (256) drained at block start; releases as marks
|
|
28
|
+
├─ 8 × Voice: dsp::Oscillator → StateVariableTPTFilter (LP) → ADSR
|
|
29
|
+
└─ TransportEngine (tempo / position / beat crossings)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Roadmap: Full Audio Workstation
|
|
33
|
+
|
|
34
|
+
expo-juce is being built toward full audio workstation capabilities. The work is decomposed into independent sub-projects, each building on the previous:
|
|
35
|
+
|
|
36
|
+
### Sub-project 1: Polyphonic Synth Engine
|
|
37
|
+
**Status:** Done — working on device (iPhone, 2026-10-22)
|
|
38
|
+
|
|
39
|
+
Replaced the mono SynthEngine + AVAudioEngine with a JUCE-owned audio stack built on `AudioProcessorGraph`. 8-voice polyphonic synth with per-voice oscillator, SVF filter, and full ADSR envelope. MIDI-style API (`noteOn(note, velocity)` / `noteOff(note)`). JUCE owns device management and output for cross-platform support (iOS, macOS, Windows, Linux).
|
|
40
|
+
|
|
41
|
+
### Diagnostics, recovery and latency
|
|
42
|
+
**Status:** Diagnostics done (0.5.2-diag.x); recovery and latency margin in 0.5.2 — see the [latency guide](docs/latency.md)
|
|
43
|
+
|
|
44
|
+
`setup()` rejects with the failing step, `getDiagnostics()` exposes every layer from device callbacks to synth peak, `playTestTone()` bypasses the graph, an offline harness reproduces the device callback on Linux. Root causes of the first on-device silence (fixed 2026-10-22): a stale Metro bundle running the app on stubs, infinite recursion in `-setBeatCallback:`, `setup()` always resolving, and expo-av switching the shared session to Ambient. 0.5.2 adds: 128-sample buffer, no allocation on the audio thread, native `JucePadView` touch path, 2 ms default attack, session re-assert and interruption / media-reset recovery (keeping synth settings and transport state across rebuilds).
|
|
45
|
+
|
|
46
|
+
### Sub-project 2: Effects Chain
|
|
47
|
+
**Status:** Planned
|
|
48
|
+
|
|
49
|
+
Add `AudioProcessor` nodes for standard effects: reverb, delay, distortion, EQ, compressor. Inserted into the graph after the synth node on a master effects bus. Each effect is an independent processor — can be bypassed, reordered, or removed.
|
|
50
|
+
|
|
51
|
+
### Sub-project 3: Sample Playback / Drum Machine
|
|
52
|
+
**Status:** Planned — an earlier JUCE sampler prototype is the starting point
|
|
53
|
+
|
|
54
|
+
`SamplerProcessor` node that loads audio files and triggers playback via `noteOn`. Supports multi-sample mapping (e.g., different samples per MIDI note). Integrates with the transport for sequenced playback.
|
|
55
|
+
|
|
56
|
+
### Sub-project 4: Audio Input Processing
|
|
57
|
+
**Status:** Planned
|
|
58
|
+
|
|
59
|
+
Route mic/line input through the `AudioProcessorGraph` for realtime effects processing (guitar pedal board style). The session is currently `Playback` + `MixWithOthers` with no input channels (`initialise(0, 2)`); this sub-project switches to `PlayAndRecord`, opens input channels and adds an input node routed through effects processors.
|
|
60
|
+
|
|
61
|
+
### Sub-project 5: Modular Routing
|
|
62
|
+
**Status:** Planned
|
|
63
|
+
|
|
64
|
+
Expose the `AudioProcessorGraph` topology to TypeScript — add, remove, and connect nodes from JS. Enables user-configurable signal chains, parallel processing, send/return buses, and sidechain routing.
|
|
65
|
+
|
|
66
|
+
### Sub-project 6: Electron / Desktop
|
|
67
|
+
**Status:** Planned
|
|
68
|
+
|
|
69
|
+
Compile the same C++ engine for macOS, Windows, and Linux. JUCE abstracts platform audio (CoreAudio, WASAPI, ALSA). Package as an Electron native addon. Same graph, same processors, same API — different platform.
|
|
70
|
+
|
|
71
|
+
## Quick Start
|
|
72
|
+
|
|
73
|
+
### Installation
|
|
74
|
+
|
|
75
|
+
Requires an Expo dev client or EAS build (not Expo Go). Pin an exact version; prereleases are
|
|
76
|
+
published under a dist-tag:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
npm install expo-juce@0.5.2-diag.1 --save-exact # dist-tag "diag"; "latest" is 0.5.1
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The native code is compiled by CocoaPods during prebuild / EAS build (`ios/ExpoJuce.podspec`,
|
|
83
|
+
JUCE vendored in `ios/juce_modules`).
|
|
84
|
+
|
|
85
|
+
### Usage
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
import * as ExpoJuce from 'expo-juce';
|
|
89
|
+
|
|
90
|
+
// Once, e.g. on first mount. Rejects with code ERR_AUDIO_SETUP, message "step N: <error>".
|
|
91
|
+
await ExpoJuce.setup();
|
|
92
|
+
// 0.5.2+: optional device request (defaults 128 samples / 48 kHz; actual values may differ)
|
|
93
|
+
// await ExpoJuce.setup({ bufferSize: 128, sampleRate: 48000 });
|
|
94
|
+
|
|
95
|
+
// Notes — synchronous, never wait on a lock; the audio thread picks them up at the next block
|
|
96
|
+
// (note-offs and allNotesOff are never dropped, even mid-recovery)
|
|
97
|
+
ExpoJuce.noteOn(60, 127); // note 0–127, velocity 0–127
|
|
98
|
+
ExpoJuce.noteOff(60);
|
|
99
|
+
ExpoJuce.allNotesOff();
|
|
100
|
+
|
|
101
|
+
// Global synth parameters — synchronous, std::atomic
|
|
102
|
+
ExpoJuce.setWaveform('saw'); // 'sine' | 'square' | 'saw' | 'triangle'
|
|
103
|
+
ExpoJuce.setFilterCutoff(2000); // Hz, 20–20000 (default 20000)
|
|
104
|
+
ExpoJuce.setFilterResonance(0.3); // 0–1 → Q 0.707–10
|
|
105
|
+
ExpoJuce.setAttack(10); // ms, 1–10000 (default 10; 2 from 0.5.2+)
|
|
106
|
+
ExpoJuce.setDecay(1); // ms (default 1)
|
|
107
|
+
ExpoJuce.setSustain(1); // 0–1 (default 1)
|
|
108
|
+
ExpoJuce.setRelease(200); // ms (default 200)
|
|
109
|
+
ExpoJuce.setDetune(0); // cents, −1200…1200
|
|
110
|
+
ExpoJuce.setMasterLevel(0.5); // 0–1 (default 0.5)
|
|
111
|
+
|
|
112
|
+
// Transport
|
|
113
|
+
ExpoJuce.setTempo(120); // BPM, 20–400
|
|
114
|
+
ExpoJuce.startTransport();
|
|
115
|
+
const sub = ExpoJuce.addBeatListener(({ beat, bpm }) => { /* … */ });
|
|
116
|
+
ExpoJuce.getTransportPosition(); ExpoJuce.getTempo(); ExpoJuce.isTransportPlaying();
|
|
117
|
+
ExpoJuce.setTransportPosition(0);
|
|
118
|
+
ExpoJuce.stopTransport();
|
|
119
|
+
sub.remove();
|
|
120
|
+
|
|
121
|
+
// Diagnostics
|
|
122
|
+
const d = ExpoJuce.getDiagnostics(); // AudioDiagnostics snapshot, see src/ExpoJuce.types.ts
|
|
123
|
+
ExpoJuce.playTestTone(500); // 440 Hz into the device output, bypasses graph + synth;
|
|
124
|
+
// false if the engine/device is not running
|
|
125
|
+
|
|
126
|
+
await ExpoJuce.shutdown();
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### `JucePadView` (0.5.2+)
|
|
130
|
+
|
|
131
|
+
A native view that plays a note on touch from the iOS main thread, without the JS thread,
|
|
132
|
+
and reports to JS afterwards for visuals and haptics. Multi-touch safe: note off is sent when
|
|
133
|
+
the last touch on the view lifts.
|
|
134
|
+
|
|
135
|
+
```tsx
|
|
136
|
+
import { JucePadView } from 'expo-juce';
|
|
137
|
+
|
|
138
|
+
<JucePadView
|
|
139
|
+
note={60}
|
|
140
|
+
velocity={127}
|
|
141
|
+
enabled
|
|
142
|
+
onNoteOn={() => {/* light the pad, haptic */}}
|
|
143
|
+
onNoteOff={() => {/* unlight */}}
|
|
144
|
+
style={{ width: 80, height: 80 }}
|
|
145
|
+
/>
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Check the shipped type definitions for the exact event payloads once 0.5.2 is published.
|
|
149
|
+
|
|
150
|
+
### Diagnostics
|
|
151
|
+
|
|
152
|
+
`getDiagnostics()` returns setup state (`isReady`, `setupStep`, `lastError`), device
|
|
153
|
+
(`deviceName`, `sampleRate`, `bufferSize`, `outputLatencySamples`), counters
|
|
154
|
+
(`callbackCount`, `graphProcessCount`, `synthProcessCount`, `notesQueued/Consumed/Dropped`,
|
|
155
|
+
`nanCount`, `toneSamplesMixed`), peaks (`outputPeak`, `synthPeak`), timing
|
|
156
|
+
(`lastNoteToRenderMs`) and the AVAudioSession state (`sessionCategory`, `sessionSampleRate`,
|
|
157
|
+
`sessionIOBufferMs`, `sessionOutputLatencyMs`, `otherAudioPlaying`, `routeOutputs`). 0.5.2+ adds
|
|
158
|
+
`lastTouchToRenderMs`, `preferredBufferSize`, `preferredSampleRate`, `sessionReassertCount`, `interruptionCount`,
|
|
159
|
+
`mediaResetCount`, `lastRecoveryError`, `engineState`. A debug screen in the app can show them
|
|
160
|
+
as a pass/fail ladder — see [docs/latency.md](docs/latency.md#5-a-diagnostics-panel).
|
|
161
|
+
|
|
162
|
+
Synth settings and transport state (tempo, position, playing) survive the engine's automatic
|
|
163
|
+
rebuilds after a media-services reset or a stalled device; held notes do not.
|
|
164
|
+
|
|
165
|
+
## Latency
|
|
166
|
+
|
|
167
|
+
Budget < 40 ms touch-to-sound, target ≤ 25 ms. Baseline on iPhone 16 Pro speaker: 48 kHz,
|
|
168
|
+
257-sample buffer, panel estimate 24.2 ms (before the touch path). The signal path, every
|
|
169
|
+
change, how to read the panel and the **slow-motion measurement procedure** are in
|
|
170
|
+
[docs/latency.md](docs/latency.md).
|
|
171
|
+
|
|
172
|
+
## Development
|
|
173
|
+
|
|
174
|
+
### Repository structure
|
|
175
|
+
|
|
176
|
+
```
|
|
177
|
+
expo-juce/
|
|
178
|
+
├── src/ # TypeScript API (index.ts, ExpoJuce.types.ts), web stub, JUCE vendoring scripts
|
|
179
|
+
├── ios/ # Swift module, ObjC bridge, ObjC++ engine, JuceModule_*.mm, podspec
|
|
180
|
+
│ └── juce_modules/ # Vendored JUCE 8.0.1 modules
|
|
181
|
+
├── android/ # Kotlin scaffold (no audio engine yet)
|
|
182
|
+
├── test/harness/ # Offline JUCE graph harness (WSL, CMake)
|
|
183
|
+
├── example/ # Old example app (Expo 51, pinned expo-juce 0.1.1) — stale
|
|
184
|
+
├── docs/
|
|
185
|
+
│ └── latency.md # Latency guide and measurement procedure
|
|
186
|
+
└── package.json
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Build and publish (Windows)
|
|
190
|
+
|
|
191
|
+
There is no Mac or Xcode on the dev machine. The `expo-module-scripts` lifecycle scripts are
|
|
192
|
+
bash and fail under npm on Windows, so build and publish by hand from Git Bash:
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
npm install
|
|
196
|
+
# 1. bump "version" in package.json, e.g. 0.5.2-diag.2
|
|
197
|
+
CI=1 bash node_modules/expo-module-scripts/bin/expo-module-build # tsc → build/
|
|
198
|
+
npm publish --tag diag --ignore-scripts # npm 2FA code required
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
`--ignore-scripts` skips `prepublishOnly`, so step 1's build is mandatory. Prereleases go
|
|
202
|
+
under a dist-tag (`diag`) so `latest` is not moved.
|
|
203
|
+
|
|
204
|
+
Then in the consuming app: `npm install expo-juce@<version> --save-exact`, check that
|
|
205
|
+
`package-lock.json` still has its macOS-only optional entries (a lockfile regenerated on
|
|
206
|
+
Windows can drop them, and EAS's `npm ci` on macOS then fails), and run a new EAS build (EAS
|
|
207
|
+
cannot resolve `file:` dependencies, which is why every native change goes through npm).
|
|
208
|
+
|
|
209
|
+
### Verifying without Xcode
|
|
210
|
+
|
|
211
|
+
1. **TypeScript:** `npx tsc --noEmit` here and in the consuming app.
|
|
212
|
+
2. **Engine C++:** the offline harness compiles the vendored JUCE headless with `Voice`,
|
|
213
|
+
`VoiceAllocator`, `TransportEngine`, `PolySynthProcessor` and a copy of `GraphPlayer`, replays the
|
|
214
|
+
`doSetup` order and drives the callback with iOS-like block sizes. From `test/harness` in
|
|
215
|
+
WSL (or any Linux shell):
|
|
216
|
+
```sh
|
|
217
|
+
cmake -S . -B build && cmake --build build -j16 && ./build/harness
|
|
218
|
+
```
|
|
219
|
+
See [test/harness/README.md](test/harness/README.md) (case `g2` is an expected-FAIL negative
|
|
220
|
+
control). Extend it for every new graph node.
|
|
221
|
+
3. **ObjC++ / Swift:** careful review (no compiler locally); the EAS build is the first compile.
|
|
222
|
+
4. **Device:** a diagnostics panel built on `getDiagnostics()`
|
|
223
|
+
([docs/latency.md](docs/latency.md#5-a-diagnostics-panel)), then the slow-motion
|
|
224
|
+
measurement in [docs/latency.md](docs/latency.md#6-slow-motion-measurement-procedure).
|
|
225
|
+
|
|
226
|
+
### JUCE vendoring
|
|
227
|
+
|
|
228
|
+
`npm run download-juce` fetches JUCE 8.0.1 into `JUCE/` and `npm run vendor-juce` copies the needed modules into
|
|
229
|
+
`ios/juce_modules` (stripping tests, demos and non-iOS code). Each JUCE module is compiled as
|
|
230
|
+
its own translation unit (`ios/JuceModule_*.mm`); `ios/JuceConfig.h` holds the module flags and
|
|
231
|
+
`JUCE_STANDALONE_APPLICATION 0`.
|
|
232
|
+
|
|
233
|
+
## Documentation
|
|
234
|
+
|
|
235
|
+
- [docs/latency.md](docs/latency.md) — latency budget, signal path, changes, panel fields, slow-motion measurement
|
|
236
|
+
- [test/harness/README.md](test/harness/README.md) — the offline engine harness
|
|
237
|
+
|
|
238
|
+
## License
|
|
239
|
+
|
|
240
|
+
MIT
|