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.
- package/ios/BeeperAudioEngine.mm +83 -161
- package/package.json +1 -1
package/ios/BeeperAudioEngine.mm
CHANGED
|
@@ -8,16 +8,15 @@
|
|
|
8
8
|
#include "PolySynthProcessor.h"
|
|
9
9
|
#include "TransportEngine.h"
|
|
10
10
|
|
|
11
|
-
//
|
|
12
|
-
//
|
|
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
|
|
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
|
-
//
|
|
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;
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
114
|
-
withOptions:
|
|
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
|
-
|
|
127
|
-
juce::MessageManager::getInstance();
|
|
128
|
-
|
|
129
|
-
// 2. Create transport
|
|
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
|
-
|
|
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,
|
|
149
|
-
sDeviceManager->getAudioDeviceSetup().bufferSize);
|
|
150
|
-
sGraph->prepareToPlay(sDeviceManager->getAudioDeviceSetup().sampleRate,
|
|
151
|
-
sDeviceManager->getAudioDeviceSetup().bufferSize);
|
|
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
|
-
|
|
168
|
-
|
|
169
|
-
sGraph->
|
|
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
|
-
|
|
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]
|
|
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)
|
|
186
|
-
|
|
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
|
-
|
|
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
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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)
|
|
292
|
-
|
|
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
|
-
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
- (
|
|
302
|
-
|
|
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
|