expo-juce 0.5.0 → 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.
- package/ios/BeeperAudioEngine.mm +77 -158
- package/package.json +1 -1
package/ios/BeeperAudioEngine.mm
CHANGED
|
@@ -8,12 +8,15 @@
|
|
|
8
8
|
#include "PolySynthProcessor.h"
|
|
9
9
|
#include "TransportEngine.h"
|
|
10
10
|
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
|
|
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).
|
|
14
|
+
__attribute__((constructor))
|
|
15
|
+
static void initJuceMessageManager() {
|
|
16
|
+
juce::MessageManager::getInstance();
|
|
17
|
+
}
|
|
14
18
|
|
|
15
19
|
// Lightweight AudioIODeviceCallback that drives an AudioProcessorGraph.
|
|
16
|
-
// Replaces juce::AudioProcessorPlayer (from juce_audio_utils, which we don't use).
|
|
17
20
|
class GraphPlayer : public juce::AudioIODeviceCallback {
|
|
18
21
|
public:
|
|
19
22
|
void setProcessor(juce::AudioProcessor* p) { processor = p; }
|
|
@@ -65,13 +68,11 @@ private:
|
|
|
65
68
|
@property (nonatomic, strong) dispatch_source_t beatTimer;
|
|
66
69
|
@end
|
|
67
70
|
|
|
68
|
-
//
|
|
69
|
-
// IMPORTANT: No JUCE smart pointers or std::unique_ptr at file scope —
|
|
70
|
-
// their constructors/destructors engage JUCE internals before MessageManager is ready.
|
|
71
|
+
// Raw pointers — no JUCE smart pointers at file scope.
|
|
71
72
|
static juce::AudioDeviceManager* sDeviceManager = nullptr;
|
|
72
73
|
static GraphPlayer* sPlayer = nullptr;
|
|
73
74
|
static juce::AudioProcessorGraph* sGraph = nullptr;
|
|
74
|
-
static PolySynthProcessor* sSynth = nullptr;
|
|
75
|
+
static PolySynthProcessor* sSynth = nullptr;
|
|
75
76
|
static TransportEngine* sTransport = nullptr;
|
|
76
77
|
|
|
77
78
|
@implementation BeeperAudioEngine
|
|
@@ -94,24 +95,30 @@ static TransportEngine* sTransport = nullptr;
|
|
|
94
95
|
}
|
|
95
96
|
|
|
96
97
|
- (void)setup {
|
|
97
|
-
|
|
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
|
+
}
|
|
98
103
|
|
|
99
104
|
if (self.isReady) {
|
|
100
105
|
NSLog(@"[BeeperAudioEngine] Already initialized, skipping");
|
|
101
106
|
return;
|
|
102
107
|
}
|
|
103
108
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
juce::MessageManager::getInstance();
|
|
110
|
-
juce::initialiseJuce_GUI();
|
|
111
|
-
sJuceInitialized = true;
|
|
109
|
+
@try {
|
|
110
|
+
[self doSetup];
|
|
111
|
+
} @catch (NSException *exception) {
|
|
112
|
+
NSLog(@"[BeeperAudioEngine] EXCEPTION in setup: %@ — %@", exception.name, exception.reason);
|
|
113
|
+
[self cleanup];
|
|
112
114
|
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
- (void)doSetup {
|
|
118
|
+
NSLog(@"[BeeperAudioEngine] Step 0: initialiseJuce_GUI");
|
|
119
|
+
juce::initialiseJuce_GUI();
|
|
113
120
|
|
|
114
|
-
|
|
121
|
+
NSLog(@"[BeeperAudioEngine] Step 1: AVAudioSession (Playback)");
|
|
115
122
|
NSError *sessionError = nil;
|
|
116
123
|
AVAudioSession *session = [AVAudioSession sharedInstance];
|
|
117
124
|
[session setCategory:AVAudioSessionCategoryPlayback
|
|
@@ -120,128 +127,92 @@ static TransportEngine* sTransport = nullptr;
|
|
|
120
127
|
if (sessionError) {
|
|
121
128
|
NSLog(@"[BeeperAudioEngine] AVAudioSession category error: %@", sessionError);
|
|
122
129
|
}
|
|
123
|
-
sessionError = nil;
|
|
124
130
|
[session setActive:YES error:&sessionError];
|
|
125
131
|
if (sessionError) {
|
|
126
132
|
NSLog(@"[BeeperAudioEngine] AVAudioSession activation error: %@", sessionError);
|
|
127
133
|
}
|
|
128
134
|
|
|
129
|
-
|
|
135
|
+
NSLog(@"[BeeperAudioEngine] Step 2: TransportEngine");
|
|
130
136
|
sTransport = new TransportEngine();
|
|
131
137
|
|
|
132
|
-
|
|
138
|
+
NSLog(@"[BeeperAudioEngine] Step 3: AudioDeviceManager");
|
|
133
139
|
sDeviceManager = new juce::AudioDeviceManager();
|
|
134
140
|
|
|
135
|
-
|
|
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
|
-
|
|
140
|
-
sDeviceManager = nullptr;
|
|
141
|
-
delete sTransport;
|
|
142
|
-
sTransport = nullptr;
|
|
145
|
+
[self cleanup];
|
|
143
146
|
return;
|
|
144
147
|
}
|
|
145
148
|
|
|
146
|
-
// 5. Create AudioProcessorGraph and add nodes BEFORE preparing
|
|
147
149
|
double sr = sDeviceManager->getAudioDeviceSetup().sampleRate;
|
|
148
150
|
int bufSize = sDeviceManager->getAudioDeviceSetup().bufferSize;
|
|
151
|
+
NSLog(@"[BeeperAudioEngine] Step 5: AudioProcessorGraph (sr=%.0f, buf=%d)", sr, bufSize);
|
|
149
152
|
|
|
150
153
|
sGraph = new juce::AudioProcessorGraph();
|
|
151
154
|
sGraph->setPlayConfigDetails(0, 2, sr, bufSize);
|
|
152
155
|
|
|
153
|
-
|
|
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
|
-
|
|
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
|
-
|
|
165
|
-
sGraph->addConnection({
|
|
166
|
-
|
|
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}});
|
|
173
172
|
|
|
174
|
-
|
|
173
|
+
NSLog(@"[BeeperAudioEngine] Step 10: prepareToPlay");
|
|
175
174
|
sGraph->prepareToPlay(sr, bufSize);
|
|
176
175
|
|
|
177
|
-
|
|
176
|
+
NSLog(@"[BeeperAudioEngine] Step 11: GraphPlayer + addAudioCallback");
|
|
178
177
|
sPlayer = new GraphPlayer();
|
|
179
178
|
sPlayer->setProcessor(sGraph);
|
|
180
179
|
sDeviceManager->addAudioCallback(sPlayer);
|
|
181
180
|
|
|
182
181
|
self.isReady = true;
|
|
183
|
-
NSLog(@"[BeeperAudioEngine]
|
|
184
|
-
sDeviceManager->getAudioDeviceSetup().sampleRate,
|
|
185
|
-
sDeviceManager->getAudioDeviceSetup().bufferSize);
|
|
182
|
+
NSLog(@"[BeeperAudioEngine] DONE — audio stack initialized (sr=%.0f, buf=%d)", sr, bufSize);
|
|
186
183
|
}
|
|
187
184
|
|
|
188
|
-
- (void)
|
|
189
|
-
|
|
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
|
+
}
|
|
190
195
|
|
|
196
|
+
- (void)shutdown {
|
|
197
|
+
if (![NSThread isMainThread]) return;
|
|
191
198
|
if (!self.isReady) return;
|
|
192
199
|
|
|
193
200
|
NSLog(@"[BeeperAudioEngine] Shutting down...");
|
|
194
|
-
|
|
195
201
|
[self stopBeatTimer];
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
sSynth->allNotesOff();
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
if (sDeviceManager && sPlayer) {
|
|
202
|
-
sDeviceManager->removeAudioCallback(sPlayer);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
if (sPlayer) {
|
|
206
|
-
sPlayer->setProcessor(nullptr);
|
|
207
|
-
delete sPlayer;
|
|
208
|
-
sPlayer = nullptr;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
sSynth = nullptr; // owned by graph, don't delete
|
|
212
|
-
delete sGraph;
|
|
213
|
-
sGraph = nullptr;
|
|
214
|
-
|
|
215
|
-
if (sDeviceManager) {
|
|
216
|
-
sDeviceManager->closeAudioDevice();
|
|
217
|
-
delete sDeviceManager;
|
|
218
|
-
sDeviceManager = nullptr;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
if (sTransport) {
|
|
222
|
-
delete sTransport;
|
|
223
|
-
sTransport = nullptr;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
self.isReady = false;
|
|
202
|
+
if (sSynth) sSynth->allNotesOff();
|
|
203
|
+
[self cleanup];
|
|
227
204
|
NSLog(@"[BeeperAudioEngine] Shutdown complete");
|
|
228
205
|
}
|
|
229
206
|
|
|
230
207
|
// ── Synth forwarding ──
|
|
231
208
|
|
|
232
209
|
- (void)noteOn:(int)note velocity:(int)velocity {
|
|
233
|
-
if (!self.isReady || !sSynth)
|
|
234
|
-
NSLog(@"[BeeperAudioEngine] noteOn ignored — engine not initialized. Call setup() first.");
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
210
|
+
if (!self.isReady || !sSynth) return;
|
|
237
211
|
sSynth->noteOn(note, velocity);
|
|
238
212
|
}
|
|
239
213
|
|
|
240
214
|
- (void)noteOff:(int)note {
|
|
241
|
-
if (!self.isReady || !sSynth)
|
|
242
|
-
NSLog(@"[BeeperAudioEngine] noteOff ignored — engine not initialized.");
|
|
243
|
-
return;
|
|
244
|
-
}
|
|
215
|
+
if (!self.isReady || !sSynth) return;
|
|
245
216
|
sSynth->noteOff(note);
|
|
246
217
|
}
|
|
247
218
|
|
|
@@ -252,84 +223,34 @@ static TransportEngine* sTransport = nullptr;
|
|
|
252
223
|
|
|
253
224
|
- (void)setWaveform:(NSString *)waveform {
|
|
254
225
|
if (!self.isReady || !sSynth) return;
|
|
255
|
-
if ([waveform isEqualToString:@"square"])
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
} else if ([waveform isEqualToString:@"triangle"]) {
|
|
260
|
-
sSynth->setWaveform(Waveform::Triangle);
|
|
261
|
-
} else {
|
|
262
|
-
sSynth->setWaveform(Waveform::Sine);
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
- (void)setFilterCutoff:(double)hz {
|
|
267
|
-
if (self.isReady && sSynth) sSynth->setFilterCutoff(hz);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
- (void)setFilterResonance:(double)q {
|
|
271
|
-
if (self.isReady && sSynth) sSynth->setFilterResonance(q);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
- (void)setAttack:(double)ms {
|
|
275
|
-
if (self.isReady && sSynth) sSynth->setAttack(ms);
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
- (void)setDecay:(double)ms {
|
|
279
|
-
if (self.isReady && sSynth) sSynth->setDecay(ms);
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
- (void)setSustain:(double)level {
|
|
283
|
-
if (self.isReady && sSynth) sSynth->setSustain(level);
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
- (void)setRelease:(double)ms {
|
|
287
|
-
if (self.isReady && sSynth) sSynth->setRelease(ms);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
- (void)setDetune:(double)cents {
|
|
291
|
-
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);
|
|
292
230
|
}
|
|
293
231
|
|
|
294
|
-
- (void)
|
|
295
|
-
|
|
296
|
-
}
|
|
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); }
|
|
297
240
|
|
|
298
241
|
// ── Transport ──
|
|
299
242
|
|
|
300
|
-
- (void)startTransport {
|
|
301
|
-
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
- (
|
|
305
|
-
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
- (void)setTempo:(double)bpm {
|
|
309
|
-
if (self.isReady && sTransport) sTransport->setTempo(bpm);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
- (void)setTransportPosition:(double)beat {
|
|
313
|
-
if (self.isReady && sTransport) sTransport->setPosition(beat);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
- (double)getTransportPosition {
|
|
317
|
-
return (self.isReady && sTransport) ? sTransport->getPosition() : 0.0;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
- (double)getTempo {
|
|
321
|
-
return (self.isReady && sTransport) ? sTransport->getTempo() : 120.0;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
- (BOOL)isTransportPlaying {
|
|
325
|
-
return (self.isReady && sTransport) ? sTransport->isPlaying() : NO;
|
|
326
|
-
}
|
|
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; }
|
|
327
250
|
|
|
328
251
|
// ── Beat events ──
|
|
329
252
|
|
|
330
|
-
- (void)setBeatCallback:(BeatCallback)callback {
|
|
331
|
-
self.beatCallback = callback;
|
|
332
|
-
}
|
|
253
|
+
- (void)setBeatCallback:(BeatCallback)callback { self.beatCallback = callback; }
|
|
333
254
|
|
|
334
255
|
- (void)startBeatTimer {
|
|
335
256
|
if (self.beatTimer) return;
|
|
@@ -357,8 +278,6 @@ static TransportEngine* sTransport = nullptr;
|
|
|
357
278
|
self.beatCallback = nil;
|
|
358
279
|
}
|
|
359
280
|
|
|
360
|
-
- (void)dealloc {
|
|
361
|
-
[self shutdown];
|
|
362
|
-
}
|
|
281
|
+
- (void)dealloc { [self shutdown]; }
|
|
363
282
|
|
|
364
283
|
@end
|