expo-juce 0.4.1 → 0.5.1

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.
@@ -8,16 +8,15 @@
8
8
  #include "PolySynthProcessor.h"
9
9
  #include "TransportEngine.h"
10
10
 
11
- // Initialize JUCE at dylib load time — BEFORE any static C++ objects run.
12
- // This is critical: without MessageManager, JUCE statics crash.
11
+ // Ensure MessageManager exists before any JUCE static initializers.
12
+ // On iOS, __attribute__((constructor)) runs on the main thread for
13
+ // statically-linked frameworks (which Expo modules are).
13
14
  __attribute__((constructor))
14
- static void initJuceEarly() {
15
+ static void initJuceMessageManager() {
15
16
  juce::MessageManager::getInstance();
16
- juce::initialiseJuce_GUI();
17
17
  }
18
18
 
19
19
  // Lightweight AudioIODeviceCallback that drives an AudioProcessorGraph.
20
- // Replaces juce::AudioProcessorPlayer (from juce_audio_utils, which we don't use).
21
20
  class GraphPlayer : public juce::AudioIODeviceCallback {
22
21
  public:
23
22
  void setProcessor(juce::AudioProcessor* p) { processor = p; }
@@ -69,13 +68,11 @@ private:
69
68
  @property (nonatomic, strong) dispatch_source_t beatTimer;
70
69
  @end
71
70
 
72
- // C++ members stored as raw pointers (ObjC class can't have C++ members inline).
73
- // IMPORTANT: No JUCE smart pointers or std::unique_ptr at file scope —
74
- // their constructors/destructors engage JUCE internals before MessageManager is ready.
71
+ // Raw pointers no JUCE smart pointers at file scope.
75
72
  static juce::AudioDeviceManager* sDeviceManager = nullptr;
76
73
  static GraphPlayer* sPlayer = nullptr;
77
74
  static juce::AudioProcessorGraph* sGraph = nullptr;
78
- static PolySynthProcessor* sSynth = nullptr; // owned by graph
75
+ static PolySynthProcessor* sSynth = nullptr;
79
76
  static TransportEngine* sTransport = nullptr;
80
77
 
81
78
  @implementation BeeperAudioEngine
@@ -98,147 +95,124 @@ static TransportEngine* sTransport = nullptr;
98
95
  }
99
96
 
100
97
  - (void)setup {
101
- NSAssert([NSThread isMainThread], @"setup() must be called on main thread");
98
+ if (![NSThread isMainThread]) {
99
+ NSLog(@"[BeeperAudioEngine] ERROR: setup() called off main thread, dispatching to main");
100
+ dispatch_sync(dispatch_get_main_queue(), ^{ [self setup]; });
101
+ return;
102
+ }
102
103
 
103
104
  if (self.isReady) {
104
105
  NSLog(@"[BeeperAudioEngine] Already initialized, skipping");
105
106
  return;
106
107
  }
107
108
 
108
- NSLog(@"[BeeperAudioEngine] Initializing JUCE audio stack...");
109
+ @try {
110
+ [self doSetup];
111
+ } @catch (NSException *exception) {
112
+ NSLog(@"[BeeperAudioEngine] EXCEPTION in setup: %@ — %@", exception.name, exception.reason);
113
+ [self cleanup];
114
+ }
115
+ }
109
116
 
110
- // 0. Configure AVAudioSession before JUCE takes over
117
+ - (void)doSetup {
118
+ NSLog(@"[BeeperAudioEngine] Step 0: initialiseJuce_GUI");
119
+ juce::initialiseJuce_GUI();
120
+
121
+ NSLog(@"[BeeperAudioEngine] Step 1: AVAudioSession (Playback)");
111
122
  NSError *sessionError = nil;
112
123
  AVAudioSession *session = [AVAudioSession sharedInstance];
113
- [session setCategory:AVAudioSessionCategoryPlayAndRecord
114
- withOptions:(AVAudioSessionCategoryOptionMixWithOthers |
115
- AVAudioSessionCategoryOptionDefaultToSpeaker)
124
+ [session setCategory:AVAudioSessionCategoryPlayback
125
+ withOptions:AVAudioSessionCategoryOptionMixWithOthers
116
126
  error:&sessionError];
117
127
  if (sessionError) {
118
128
  NSLog(@"[BeeperAudioEngine] AVAudioSession category error: %@", sessionError);
119
129
  }
120
- sessionError = nil;
121
130
  [session setActive:YES error:&sessionError];
122
131
  if (sessionError) {
123
132
  NSLog(@"[BeeperAudioEngine] AVAudioSession activation error: %@", sessionError);
124
133
  }
125
134
 
126
- // 1. Initialize MessageManager (attaches to existing CFRunLoop)
127
- juce::MessageManager::getInstance();
128
-
129
- // 2. Create transport
135
+ NSLog(@"[BeeperAudioEngine] Step 2: TransportEngine");
130
136
  sTransport = new TransportEngine();
131
137
 
132
- // 3. Create AudioDeviceManager
138
+ NSLog(@"[BeeperAudioEngine] Step 3: AudioDeviceManager");
133
139
  sDeviceManager = new juce::AudioDeviceManager();
134
140
 
135
- // 4. Initialize device: 0 inputs, 2 outputs (stereo)
141
+ NSLog(@"[BeeperAudioEngine] Step 4: initialise(0, 2)");
136
142
  juce::String error = sDeviceManager->initialise(0, 2, nullptr, true);
137
143
  if (error.isNotEmpty()) {
138
144
  NSLog(@"[BeeperAudioEngine] AudioDeviceManager init failed: %s", error.toRawUTF8());
139
- delete sDeviceManager;
140
- sDeviceManager = nullptr;
141
- delete sTransport;
142
- sTransport = nullptr;
145
+ [self cleanup];
143
146
  return;
144
147
  }
145
148
 
146
- // 5. Create AudioProcessorGraph
149
+ double sr = sDeviceManager->getAudioDeviceSetup().sampleRate;
150
+ int bufSize = sDeviceManager->getAudioDeviceSetup().bufferSize;
151
+ NSLog(@"[BeeperAudioEngine] Step 5: AudioProcessorGraph (sr=%.0f, buf=%d)", sr, bufSize);
152
+
147
153
  sGraph = new juce::AudioProcessorGraph();
148
- sGraph->setPlayConfigDetails(0, 2, sDeviceManager->getAudioDeviceSetup().sampleRate,
149
- sDeviceManager->getAudioDeviceSetup().bufferSize);
150
- sGraph->prepareToPlay(sDeviceManager->getAudioDeviceSetup().sampleRate,
151
- sDeviceManager->getAudioDeviceSetup().bufferSize);
154
+ sGraph->setPlayConfigDetails(0, 2, sr, bufSize);
152
155
 
153
- // 6. Add PolySynthProcessor node
156
+ NSLog(@"[BeeperAudioEngine] Step 6: PolySynthProcessor");
154
157
  auto synthProcessor = std::make_unique<PolySynthProcessor>();
155
158
  sSynth = synthProcessor.get();
156
159
  sSynth->setTransport(sTransport);
160
+
161
+ NSLog(@"[BeeperAudioEngine] Step 7: addNode (synth)");
157
162
  auto synthNode = sGraph->addNode(std::move(synthProcessor));
158
163
 
159
- // 7. Add output node
164
+ NSLog(@"[BeeperAudioEngine] Step 8: addNode (output)");
160
165
  auto outputNode = sGraph->addNode(
161
166
  std::make_unique<juce::AudioProcessorGraph::AudioGraphIOProcessor>(
162
167
  juce::AudioProcessorGraph::AudioGraphIOProcessor::audioOutputNode));
163
168
 
164
- // 8. Connect synth → output (stereo: channels 0 and 1)
165
- sGraph->addConnection({
166
- {synthNode->nodeID, 0},
167
- {outputNode->nodeID, 0}
168
- });
169
- sGraph->addConnection({
170
- {synthNode->nodeID, 1},
171
- {outputNode->nodeID, 1}
172
- });
169
+ NSLog(@"[BeeperAudioEngine] Step 9: connections");
170
+ sGraph->addConnection({{synthNode->nodeID, 0}, {outputNode->nodeID, 0}});
171
+ sGraph->addConnection({{synthNode->nodeID, 1}, {outputNode->nodeID, 1}});
172
+
173
+ NSLog(@"[BeeperAudioEngine] Step 10: prepareToPlay");
174
+ sGraph->prepareToPlay(sr, bufSize);
173
175
 
174
- // 9. Create player and attach to device
176
+ NSLog(@"[BeeperAudioEngine] Step 11: GraphPlayer + addAudioCallback");
175
177
  sPlayer = new GraphPlayer();
176
178
  sPlayer->setProcessor(sGraph);
177
179
  sDeviceManager->addAudioCallback(sPlayer);
178
180
 
179
181
  self.isReady = true;
180
- NSLog(@"[BeeperAudioEngine] JUCE audio stack initialized (sr=%.0f, buf=%d)",
181
- sDeviceManager->getAudioDeviceSetup().sampleRate,
182
- sDeviceManager->getAudioDeviceSetup().bufferSize);
182
+ NSLog(@"[BeeperAudioEngine] DONE audio stack initialized (sr=%.0f, buf=%d)", sr, bufSize);
183
183
  }
184
184
 
185
- - (void)shutdown {
186
- NSAssert([NSThread isMainThread], @"shutdown() must be called on main thread");
185
+ - (void)cleanup {
186
+ NSLog(@"[BeeperAudioEngine] Cleaning up after failure...");
187
+ if (sDeviceManager && sPlayer) sDeviceManager->removeAudioCallback(sPlayer);
188
+ delete sPlayer; sPlayer = nullptr;
189
+ sSynth = nullptr;
190
+ delete sGraph; sGraph = nullptr;
191
+ if (sDeviceManager) { sDeviceManager->closeAudioDevice(); delete sDeviceManager; sDeviceManager = nullptr; }
192
+ delete sTransport; sTransport = nullptr;
193
+ self.isReady = false;
194
+ }
187
195
 
196
+ - (void)shutdown {
197
+ if (![NSThread isMainThread]) return;
188
198
  if (!self.isReady) return;
189
199
 
190
200
  NSLog(@"[BeeperAudioEngine] Shutting down...");
191
-
192
201
  [self stopBeatTimer];
193
-
194
- if (sSynth) {
195
- sSynth->allNotesOff();
196
- }
197
-
198
- if (sDeviceManager && sPlayer) {
199
- sDeviceManager->removeAudioCallback(sPlayer);
200
- }
201
-
202
- if (sPlayer) {
203
- sPlayer->setProcessor(nullptr);
204
- delete sPlayer;
205
- sPlayer = nullptr;
206
- }
207
-
208
- sSynth = nullptr; // owned by graph, don't delete
209
- delete sGraph;
210
- sGraph = nullptr;
211
-
212
- if (sDeviceManager) {
213
- sDeviceManager->closeAudioDevice();
214
- delete sDeviceManager;
215
- sDeviceManager = nullptr;
216
- }
217
-
218
- if (sTransport) {
219
- delete sTransport;
220
- sTransport = nullptr;
221
- }
222
-
223
- self.isReady = false;
202
+ if (sSynth) sSynth->allNotesOff();
203
+ [self cleanup];
224
204
  NSLog(@"[BeeperAudioEngine] Shutdown complete");
225
205
  }
226
206
 
227
207
  // ── Synth forwarding ──
228
208
 
229
209
  - (void)noteOn:(int)note velocity:(int)velocity {
230
- if (!self.isReady || !sSynth) {
231
- NSLog(@"[BeeperAudioEngine] noteOn ignored — engine not initialized. Call setup() first.");
232
- return;
233
- }
210
+ if (!self.isReady || !sSynth) return;
234
211
  sSynth->noteOn(note, velocity);
235
212
  }
236
213
 
237
214
  - (void)noteOff:(int)note {
238
- if (!self.isReady || !sSynth) {
239
- NSLog(@"[BeeperAudioEngine] noteOff ignored — engine not initialized.");
240
- return;
241
- }
215
+ if (!self.isReady || !sSynth) return;
242
216
  sSynth->noteOff(note);
243
217
  }
244
218
 
@@ -249,84 +223,34 @@ static TransportEngine* sTransport = nullptr;
249
223
 
250
224
  - (void)setWaveform:(NSString *)waveform {
251
225
  if (!self.isReady || !sSynth) return;
252
- if ([waveform isEqualToString:@"square"]) {
253
- sSynth->setWaveform(Waveform::Square);
254
- } else if ([waveform isEqualToString:@"saw"]) {
255
- sSynth->setWaveform(Waveform::Saw);
256
- } else if ([waveform isEqualToString:@"triangle"]) {
257
- sSynth->setWaveform(Waveform::Triangle);
258
- } else {
259
- sSynth->setWaveform(Waveform::Sine);
260
- }
261
- }
262
-
263
- - (void)setFilterCutoff:(double)hz {
264
- if (self.isReady && sSynth) sSynth->setFilterCutoff(hz);
265
- }
266
-
267
- - (void)setFilterResonance:(double)q {
268
- if (self.isReady && sSynth) sSynth->setFilterResonance(q);
269
- }
270
-
271
- - (void)setAttack:(double)ms {
272
- if (self.isReady && sSynth) sSynth->setAttack(ms);
273
- }
274
-
275
- - (void)setDecay:(double)ms {
276
- if (self.isReady && sSynth) sSynth->setDecay(ms);
277
- }
278
-
279
- - (void)setSustain:(double)level {
280
- if (self.isReady && sSynth) sSynth->setSustain(level);
281
- }
282
-
283
- - (void)setRelease:(double)ms {
284
- if (self.isReady && sSynth) sSynth->setRelease(ms);
285
- }
286
-
287
- - (void)setDetune:(double)cents {
288
- if (self.isReady && sSynth) sSynth->setDetune(cents);
226
+ if ([waveform isEqualToString:@"square"]) sSynth->setWaveform(Waveform::Square);
227
+ else if ([waveform isEqualToString:@"saw"]) sSynth->setWaveform(Waveform::Saw);
228
+ else if ([waveform isEqualToString:@"triangle"]) sSynth->setWaveform(Waveform::Triangle);
229
+ else sSynth->setWaveform(Waveform::Sine);
289
230
  }
290
231
 
291
- - (void)setMasterLevel:(double)level {
292
- if (self.isReady && sSynth) sSynth->setMasterLevel(level);
293
- }
232
+ - (void)setFilterCutoff:(double)hz { if (self.isReady && sSynth) sSynth->setFilterCutoff(hz); }
233
+ - (void)setFilterResonance:(double)q { if (self.isReady && sSynth) sSynth->setFilterResonance(q); }
234
+ - (void)setAttack:(double)ms { if (self.isReady && sSynth) sSynth->setAttack(ms); }
235
+ - (void)setDecay:(double)ms { if (self.isReady && sSynth) sSynth->setDecay(ms); }
236
+ - (void)setSustain:(double)level { if (self.isReady && sSynth) sSynth->setSustain(level); }
237
+ - (void)setRelease:(double)ms { if (self.isReady && sSynth) sSynth->setRelease(ms); }
238
+ - (void)setDetune:(double)cents { if (self.isReady && sSynth) sSynth->setDetune(cents); }
239
+ - (void)setMasterLevel:(double)level { if (self.isReady && sSynth) sSynth->setMasterLevel(level); }
294
240
 
295
241
  // ── Transport ──
296
242
 
297
- - (void)startTransport {
298
- if (self.isReady && sTransport) sTransport->start();
299
- }
300
-
301
- - (void)stopTransport {
302
- if (self.isReady && sTransport) sTransport->stop();
303
- }
304
-
305
- - (void)setTempo:(double)bpm {
306
- if (self.isReady && sTransport) sTransport->setTempo(bpm);
307
- }
308
-
309
- - (void)setTransportPosition:(double)beat {
310
- if (self.isReady && sTransport) sTransport->setPosition(beat);
311
- }
312
-
313
- - (double)getTransportPosition {
314
- return (self.isReady && sTransport) ? sTransport->getPosition() : 0.0;
315
- }
316
-
317
- - (double)getTempo {
318
- return (self.isReady && sTransport) ? sTransport->getTempo() : 120.0;
319
- }
320
-
321
- - (BOOL)isTransportPlaying {
322
- return (self.isReady && sTransport) ? sTransport->isPlaying() : NO;
323
- }
243
+ - (void)startTransport { if (self.isReady && sTransport) sTransport->start(); }
244
+ - (void)stopTransport { if (self.isReady && sTransport) sTransport->stop(); }
245
+ - (void)setTempo:(double)bpm { if (self.isReady && sTransport) sTransport->setTempo(bpm); }
246
+ - (void)setTransportPosition:(double)beat { if (self.isReady && sTransport) sTransport->setPosition(beat); }
247
+ - (double)getTransportPosition { return (self.isReady && sTransport) ? sTransport->getPosition() : 0.0; }
248
+ - (double)getTempo { return (self.isReady && sTransport) ? sTransport->getTempo() : 120.0; }
249
+ - (BOOL)isTransportPlaying { return (self.isReady && sTransport) ? sTransport->isPlaying() : NO; }
324
250
 
325
251
  // ── Beat events ──
326
252
 
327
- - (void)setBeatCallback:(BeatCallback)callback {
328
- self.beatCallback = callback;
329
- }
253
+ - (void)setBeatCallback:(BeatCallback)callback { self.beatCallback = callback; }
330
254
 
331
255
  - (void)startBeatTimer {
332
256
  if (self.beatTimer) return;
@@ -354,8 +278,6 @@ static TransportEngine* sTransport = nullptr;
354
278
  self.beatCallback = nil;
355
279
  }
356
280
 
357
- - (void)dealloc {
358
- [self shutdown];
359
- }
281
+ - (void)dealloc { [self shutdown]; }
360
282
 
361
283
  @end
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-juce",
3
- "version": "0.4.1",
3
+ "version": "0.5.1",
4
4
  "description": "Realtime DSP w/C++ & JUCE",
5
5
  "type": "module",
6
6
  "main": "build/index.js",