expo-juce 0.2.16 → 0.2.17
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/build/index.d.ts +2 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +4 -0
- package/build/index.js.map +1 -1
- package/ios/ExpoJuceBridge.m +1 -1
- package/ios/ExpoJuceModule.swift +12 -11
- package/ios/JuceToneGenerator.mm +75 -70
- package/package.json +1 -1
package/build/index.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ import ExpoJuceView from './ExpoJuceView';
|
|
|
3
3
|
import { ChangeEventPayload, BeatEventPayload, ExpoJuceViewProps } from './ExpoJuce.types';
|
|
4
4
|
export declare const PI: any;
|
|
5
5
|
export declare function hello(): string;
|
|
6
|
+
/** Initialize the audio engine. Call once before using synth/transport. */
|
|
7
|
+
export declare function setup(): void;
|
|
6
8
|
export declare function playTone(frequency: number, duration: number): string;
|
|
7
9
|
export declare function setFrequency(frequency: number): void;
|
|
8
10
|
export declare function setLevel(level: number): void;
|
package/build/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoC,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAGnF,OAAO,YAAY,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAE3F,eAAO,MAAM,EAAE,KAAoB,CAAC;AAEpC,wBAAgB,KAAK,IAAI,MAAM,CAE9B;AAID,wBAAgB,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEpE;AAED,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAEpD;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAE5C;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,GAAG,UAAU,GAAG,IAAI,CAElF;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,CAExD;AAED,wBAAgB,MAAM,IAAI,IAAI,CAE7B;AAED,wBAAgB,OAAO,IAAI,IAAI,CAE9B;AAID,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAE1C;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAE3C;AAED,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAEhD;AAED,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,CAElD;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAE7C;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAE9D;AAID,wBAAgB,cAAc,IAAI,IAAI,CAErC;AAED,wBAAgB,aAAa,IAAI,IAAI,CAEpC;AAED,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAE1C;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAEvD;AAED,wBAAgB,oBAAoB,IAAI,MAAM,CAE7C;AAED,wBAAgB,QAAQ,IAAI,MAAM,CAEjC;AAED,wBAAgB,kBAAkB,IAAI,OAAO,CAE5C;AAID,wBAAsB,aAAa,CAAC,KAAK,EAAE,MAAM,gBAEhD;AAID,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,GAAG,YAAY,CAE7F;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,GAAG,YAAY,CAEzF;AAED,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoC,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAGnF,OAAO,YAAY,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAE3F,eAAO,MAAM,EAAE,KAAoB,CAAC;AAEpC,wBAAgB,KAAK,IAAI,MAAM,CAE9B;AAED,2EAA2E;AAC3E,wBAAgB,KAAK,IAAI,IAAI,CAE5B;AAID,wBAAgB,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEpE;AAED,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAEpD;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAE5C;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,GAAG,UAAU,GAAG,IAAI,CAElF;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,CAExD;AAED,wBAAgB,MAAM,IAAI,IAAI,CAE7B;AAED,wBAAgB,OAAO,IAAI,IAAI,CAE9B;AAID,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAE1C;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAE3C;AAED,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAEhD;AAED,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,CAElD;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAE7C;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAE9D;AAID,wBAAgB,cAAc,IAAI,IAAI,CAErC;AAED,wBAAgB,aAAa,IAAI,IAAI,CAEpC;AAED,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAE1C;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAEvD;AAED,wBAAgB,oBAAoB,IAAI,MAAM,CAE7C;AAED,wBAAgB,QAAQ,IAAI,MAAM,CAEjC;AAED,wBAAgB,kBAAkB,IAAI,OAAO,CAE5C;AAID,wBAAsB,aAAa,CAAC,KAAK,EAAE,MAAM,gBAEhD;AAID,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,GAAG,YAAY,CAE7F;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,GAAG,YAAY,CAEzF;AAED,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,CAAC"}
|
package/build/index.js
CHANGED
|
@@ -5,6 +5,10 @@ export const PI = ExpoJuceModule.PI;
|
|
|
5
5
|
export function hello() {
|
|
6
6
|
return ExpoJuceModule.hello();
|
|
7
7
|
}
|
|
8
|
+
/** Initialize the audio engine. Call once before using synth/transport. */
|
|
9
|
+
export function setup() {
|
|
10
|
+
ExpoJuceModule.setup();
|
|
11
|
+
}
|
|
8
12
|
// ── Synth ───────────────────────────────────────────────────────────
|
|
9
13
|
export function playTone(frequency, duration) {
|
|
10
14
|
return ExpoJuceModule.playTone(frequency, duration);
|
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { NativeModulesProxy, EventEmitter, Subscription } from 'expo-modules-core';\n\nimport ExpoJuceModule from './ExpoJuceModule';\nimport ExpoJuceView from './ExpoJuceView';\nimport { ChangeEventPayload, BeatEventPayload, ExpoJuceViewProps } from './ExpoJuce.types';\n\nexport const PI = ExpoJuceModule.PI;\n\nexport function hello(): string {\n return ExpoJuceModule.hello();\n}\n\n// ── Synth ───────────────────────────────────────────────────────────\n\nexport function playTone(frequency: number, duration: number): string {\n return ExpoJuceModule.playTone(frequency, duration);\n}\n\nexport function setFrequency(frequency: number): void {\n ExpoJuceModule.setFrequency(frequency);\n}\n\nexport function setLevel(level: number): void {\n ExpoJuceModule.setLevel(level);\n}\n\nexport function setWaveform(waveform: 'sine' | 'square' | 'saw' | 'triangle'): void {\n ExpoJuceModule.setWaveform(waveform);\n}\n\nexport function setHarmonicLevels(levels: number[]): void {\n ExpoJuceModule.setHarmonicLevels(levels);\n}\n\nexport function noteOn(): void {\n ExpoJuceModule.noteOn();\n}\n\nexport function noteOff(): void {\n ExpoJuceModule.noteOff();\n}\n\n// ── DSP Params ──────────────────────────────────────────────────────\n\nexport function setAttack(ms: number): void {\n ExpoJuceModule.setAttack(ms);\n}\n\nexport function setRelease(ms: number): void {\n ExpoJuceModule.setRelease(ms);\n}\n\nexport function setFilterCutoff(hz: number): void {\n ExpoJuceModule.setFilterCutoff(hz);\n}\n\nexport function setFilterResonance(q: number): void {\n ExpoJuceModule.setFilterResonance(q);\n}\n\nexport function setDetune(cents: number): void {\n ExpoJuceModule.setDetune(cents);\n}\n\nexport function setDSPValue(param: string, value: number): void {\n ExpoJuceModule.setDSPValue(param, value);\n}\n\n// ── Transport ───────────────────────────────────────────────────────\n\nexport function startTransport(): void {\n ExpoJuceModule.startTransport();\n}\n\nexport function stopTransport(): void {\n ExpoJuceModule.stopTransport();\n}\n\nexport function setTempo(bpm: number): void {\n ExpoJuceModule.setTempo(bpm);\n}\n\nexport function setTransportPosition(beat: number): void {\n ExpoJuceModule.setTransportPosition(beat);\n}\n\nexport function getTransportPosition(): number {\n return ExpoJuceModule.getTransportPosition();\n}\n\nexport function getTempo(): number {\n return ExpoJuceModule.getTempo();\n}\n\nexport function isTransportPlaying(): boolean {\n return ExpoJuceModule.isTransportPlaying();\n}\n\n// ── Events ──────────────────────────────────────────────────────────\n\nexport async function setValueAsync(value: string) {\n return await ExpoJuceModule.setValueAsync(value);\n}\n\nconst emitter = new EventEmitter(ExpoJuceModule ?? NativeModulesProxy.ExpoJuce);\n\nexport function addChangeListener(listener: (event: ChangeEventPayload) => void): Subscription {\n return emitter.addListener<ChangeEventPayload>('onChange', listener);\n}\n\nexport function addBeatListener(listener: (event: BeatEventPayload) => void): Subscription {\n return emitter.addListener<BeatEventPayload>('onBeat', listener);\n}\n\nexport { ExpoJuceView, ExpoJuceViewProps, ChangeEventPayload, BeatEventPayload };\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { NativeModulesProxy, EventEmitter, Subscription } from 'expo-modules-core';\n\nimport ExpoJuceModule from './ExpoJuceModule';\nimport ExpoJuceView from './ExpoJuceView';\nimport { ChangeEventPayload, BeatEventPayload, ExpoJuceViewProps } from './ExpoJuce.types';\n\nexport const PI = ExpoJuceModule.PI;\n\nexport function hello(): string {\n return ExpoJuceModule.hello();\n}\n\n/** Initialize the audio engine. Call once before using synth/transport. */\nexport function setup(): void {\n ExpoJuceModule.setup();\n}\n\n// ── Synth ───────────────────────────────────────────────────────────\n\nexport function playTone(frequency: number, duration: number): string {\n return ExpoJuceModule.playTone(frequency, duration);\n}\n\nexport function setFrequency(frequency: number): void {\n ExpoJuceModule.setFrequency(frequency);\n}\n\nexport function setLevel(level: number): void {\n ExpoJuceModule.setLevel(level);\n}\n\nexport function setWaveform(waveform: 'sine' | 'square' | 'saw' | 'triangle'): void {\n ExpoJuceModule.setWaveform(waveform);\n}\n\nexport function setHarmonicLevels(levels: number[]): void {\n ExpoJuceModule.setHarmonicLevels(levels);\n}\n\nexport function noteOn(): void {\n ExpoJuceModule.noteOn();\n}\n\nexport function noteOff(): void {\n ExpoJuceModule.noteOff();\n}\n\n// ── DSP Params ──────────────────────────────────────────────────────\n\nexport function setAttack(ms: number): void {\n ExpoJuceModule.setAttack(ms);\n}\n\nexport function setRelease(ms: number): void {\n ExpoJuceModule.setRelease(ms);\n}\n\nexport function setFilterCutoff(hz: number): void {\n ExpoJuceModule.setFilterCutoff(hz);\n}\n\nexport function setFilterResonance(q: number): void {\n ExpoJuceModule.setFilterResonance(q);\n}\n\nexport function setDetune(cents: number): void {\n ExpoJuceModule.setDetune(cents);\n}\n\nexport function setDSPValue(param: string, value: number): void {\n ExpoJuceModule.setDSPValue(param, value);\n}\n\n// ── Transport ───────────────────────────────────────────────────────\n\nexport function startTransport(): void {\n ExpoJuceModule.startTransport();\n}\n\nexport function stopTransport(): void {\n ExpoJuceModule.stopTransport();\n}\n\nexport function setTempo(bpm: number): void {\n ExpoJuceModule.setTempo(bpm);\n}\n\nexport function setTransportPosition(beat: number): void {\n ExpoJuceModule.setTransportPosition(beat);\n}\n\nexport function getTransportPosition(): number {\n return ExpoJuceModule.getTransportPosition();\n}\n\nexport function getTempo(): number {\n return ExpoJuceModule.getTempo();\n}\n\nexport function isTransportPlaying(): boolean {\n return ExpoJuceModule.isTransportPlaying();\n}\n\n// ── Events ──────────────────────────────────────────────────────────\n\nexport async function setValueAsync(value: string) {\n return await ExpoJuceModule.setValueAsync(value);\n}\n\nconst emitter = new EventEmitter(ExpoJuceModule ?? NativeModulesProxy.ExpoJuce);\n\nexport function addChangeListener(listener: (event: ChangeEventPayload) => void): Subscription {\n return emitter.addListener<ChangeEventPayload>('onChange', listener);\n}\n\nexport function addBeatListener(listener: (event: BeatEventPayload) => void): Subscription {\n return emitter.addListener<BeatEventPayload>('onBeat', listener);\n}\n\nexport { ExpoJuceView, ExpoJuceViewProps, ChangeEventPayload, BeatEventPayload };\n"]}
|
package/ios/ExpoJuceBridge.m
CHANGED
package/ios/ExpoJuceModule.swift
CHANGED
|
@@ -4,17 +4,8 @@ public class ExpoJuceModule: Module {
|
|
|
4
4
|
public func definition() -> ModuleDefinition {
|
|
5
5
|
Name("ExpoJuce")
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
// Wire native beat callback → JS "onBeat" event
|
|
11
|
-
ExpoJuceBridge.setBeatCallback { [weak self] beat, bpm in
|
|
12
|
-
self?.sendEvent("onBeat", [
|
|
13
|
-
"beat": beat,
|
|
14
|
-
"bpm": bpm,
|
|
15
|
-
])
|
|
16
|
-
}
|
|
17
|
-
}
|
|
7
|
+
// Audio engine is initialized lazily on first use, not at module load.
|
|
8
|
+
// Initializing AVAudioEngine in OnCreate can crash before JS is ready.
|
|
18
9
|
|
|
19
10
|
OnDestroy {
|
|
20
11
|
ExpoJuceBridge.shutdown()
|
|
@@ -32,6 +23,16 @@ public class ExpoJuceModule: Module {
|
|
|
32
23
|
|
|
33
24
|
// ── Synth ───────────────────────────────────────────────────────
|
|
34
25
|
|
|
26
|
+
Function("setup") {
|
|
27
|
+
ExpoJuceBridge.setup()
|
|
28
|
+
ExpoJuceBridge.setBeatCallback { [weak self] beat, bpm in
|
|
29
|
+
self?.sendEvent("onBeat", [
|
|
30
|
+
"beat": beat,
|
|
31
|
+
"bpm": bpm,
|
|
32
|
+
])
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
35
36
|
Function("playTone") { (frequency: Double, duration: Double) -> String in
|
|
36
37
|
ExpoJuceBridge.playTone(withFrequency: frequency, duration: duration)
|
|
37
38
|
return "Playing tone at \(frequency)Hz for \(duration)ms"
|
package/ios/JuceToneGenerator.mm
CHANGED
|
@@ -280,7 +280,7 @@ private:
|
|
|
280
280
|
if (self) {
|
|
281
281
|
_engine = new SynthEngine();
|
|
282
282
|
_transport = new TransportEngine();
|
|
283
|
-
[self initialize]
|
|
283
|
+
// Audio engine initialized lazily via [self initialize] called from setup
|
|
284
284
|
}
|
|
285
285
|
return self;
|
|
286
286
|
}
|
|
@@ -298,84 +298,89 @@ private:
|
|
|
298
298
|
}
|
|
299
299
|
|
|
300
300
|
NSLog(@"[ExpoJuce] Initializing audio engine...");
|
|
301
|
-
NSError *error = nil;
|
|
302
|
-
|
|
303
|
-
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
|
|
304
|
-
[audioSession setCategory:AVAudioSessionCategoryPlayback
|
|
305
|
-
withOptions:AVAudioSessionCategoryOptionMixWithOthers
|
|
306
|
-
error:&error];
|
|
307
|
-
if (error) {
|
|
308
|
-
NSLog(@"[ExpoJuce] Error setting audio session category: %@", error);
|
|
309
|
-
error = nil;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
[audioSession setPreferredIOBufferDuration:0.005 error:&error]; // 5ms
|
|
313
|
-
if (error) {
|
|
314
|
-
NSLog(@"[ExpoJuce] Error setting buffer duration: %@", error);
|
|
315
|
-
error = nil;
|
|
316
|
-
}
|
|
317
301
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
NSLog(@"[ExpoJuce] Error activating audio session: %@", error);
|
|
321
|
-
error = nil;
|
|
322
|
-
}
|
|
302
|
+
@try {
|
|
303
|
+
NSError *error = nil;
|
|
323
304
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
std::atomic<bool> crossed{false};
|
|
333
|
-
std::atomic<double> beat{0.0};
|
|
334
|
-
std::atomic<double> bpm{120.0};
|
|
335
|
-
};
|
|
336
|
-
auto *beatState = new BeatState();
|
|
337
|
-
|
|
338
|
-
self.sourceNode = [[AVAudioSourceNode alloc] initWithFormat:format renderBlock:^OSStatus(BOOL *isSilence, const AudioTimeStamp *timestamp, AVAudioFrameCount frameCount, AudioBufferList *outputData) {
|
|
339
|
-
float *leftChannel = (float *)outputData->mBuffers[0].mData;
|
|
340
|
-
float *rightChannel = (float *)outputData->mBuffers[1].mData;
|
|
341
|
-
|
|
342
|
-
for (AVAudioFrameCount i = 0; i < frameCount; i++) {
|
|
343
|
-
// Advance transport on audio thread (sample-accurate)
|
|
344
|
-
if (trans->advance(44100.0)) {
|
|
345
|
-
beatState->crossed.store(true);
|
|
346
|
-
beatState->beat.store(trans->getPosition());
|
|
347
|
-
beatState->bpm.store(trans->getTempo());
|
|
348
|
-
}
|
|
305
|
+
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
|
|
306
|
+
[audioSession setCategory:AVAudioSessionCategoryPlayback
|
|
307
|
+
withOptions:AVAudioSessionCategoryOptionMixWithOthers
|
|
308
|
+
error:&error];
|
|
309
|
+
if (error) {
|
|
310
|
+
NSLog(@"[ExpoJuce] Error setting audio session category: %@", error);
|
|
311
|
+
error = nil;
|
|
312
|
+
}
|
|
349
313
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
314
|
+
[audioSession setActive:YES error:&error];
|
|
315
|
+
if (error) {
|
|
316
|
+
NSLog(@"[ExpoJuce] Error activating audio session: %@", error);
|
|
317
|
+
error = nil;
|
|
353
318
|
}
|
|
354
319
|
|
|
355
|
-
|
|
356
|
-
|
|
320
|
+
self.audioEngine = [[AVAudioEngine alloc] init];
|
|
321
|
+
|
|
322
|
+
// Use the hardware sample rate to avoid format mismatches
|
|
323
|
+
double sampleRate = audioSession.sampleRate > 0 ? audioSession.sampleRate : 44100.0;
|
|
324
|
+
AVAudioFormat *format = [[AVAudioFormat alloc] initStandardFormatWithSampleRate:sampleRate channels:2];
|
|
325
|
+
NSLog(@"[ExpoJuce] Using sample rate: %.0f", sampleRate);
|
|
326
|
+
|
|
327
|
+
SynthEngine *eng = self.engine;
|
|
328
|
+
TransportEngine *trans = self.transport;
|
|
329
|
+
|
|
330
|
+
struct BeatState {
|
|
331
|
+
std::atomic<bool> crossed{false};
|
|
332
|
+
std::atomic<double> beat{0.0};
|
|
333
|
+
std::atomic<double> bpm{120.0};
|
|
334
|
+
};
|
|
335
|
+
auto *beatState = new BeatState();
|
|
336
|
+
|
|
337
|
+
self.sourceNode = [[AVAudioSourceNode alloc] initWithRenderBlock:^OSStatus(BOOL *isSilence, const AudioTimeStamp *timestamp, AVAudioFrameCount frameCount, AudioBufferList *outputData) {
|
|
338
|
+
double sr = sampleRate;
|
|
339
|
+
float *leftChannel = (float *)outputData->mBuffers[0].mData;
|
|
340
|
+
float *rightChannel = (outputData->mNumberBuffers > 1) ? (float *)outputData->mBuffers[1].mData : leftChannel;
|
|
341
|
+
|
|
342
|
+
for (AVAudioFrameCount i = 0; i < frameCount; i++) {
|
|
343
|
+
if (trans->advance(sr)) {
|
|
344
|
+
beatState->crossed.store(true);
|
|
345
|
+
beatState->beat.store(trans->getPosition());
|
|
346
|
+
beatState->bpm.store(trans->getTempo());
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
float sample = eng->getNextSample(sr);
|
|
350
|
+
leftChannel[i] = sample;
|
|
351
|
+
rightChannel[i] = sample;
|
|
352
|
+
}
|
|
357
353
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
[self.audioEngine connect:self.audioEngine.mainMixerNode to:self.audioEngine.outputNode format:format];
|
|
354
|
+
return noErr;
|
|
355
|
+
}];
|
|
361
356
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
} else {
|
|
367
|
-
NSLog(@"[ExpoJuce] Audio engine started successfully");
|
|
368
|
-
}
|
|
357
|
+
[self.audioEngine attachNode:self.sourceNode];
|
|
358
|
+
[self.audioEngine connect:self.sourceNode to:self.audioEngine.mainMixerNode format:format];
|
|
359
|
+
// Let the system decide the mixer→output format
|
|
360
|
+
[self.audioEngine connect:self.audioEngine.mainMixerNode to:self.audioEngine.outputNode format:nil];
|
|
369
361
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
362
|
+
[self.audioEngine prepare];
|
|
363
|
+
[self.audioEngine startAndReturnError:&error];
|
|
364
|
+
if (error) {
|
|
365
|
+
NSLog(@"[ExpoJuce] Error starting audio engine: %@", error);
|
|
366
|
+
} else {
|
|
367
|
+
NSLog(@"[ExpoJuce] Audio engine started successfully");
|
|
376
368
|
}
|
|
377
|
-
|
|
378
|
-
|
|
369
|
+
|
|
370
|
+
// Beat callback timer — polls at ~60Hz on main queue
|
|
371
|
+
self.beatTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
|
|
372
|
+
dispatch_source_set_timer(self.beatTimer, DISPATCH_TIME_NOW, 16 * NSEC_PER_MSEC, 1 * NSEC_PER_MSEC);
|
|
373
|
+
__weak typeof(self) weakSelf = self;
|
|
374
|
+
dispatch_source_set_event_handler(self.beatTimer, ^{
|
|
375
|
+
if (beatState->crossed.exchange(false) && weakSelf.beatCallback) {
|
|
376
|
+
weakSelf.beatCallback(beatState->beat.load(), beatState->bpm.load());
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
dispatch_resume(self.beatTimer);
|
|
380
|
+
|
|
381
|
+
} @catch (NSException *exception) {
|
|
382
|
+
NSLog(@"[ExpoJuce] CRASH in audio init: %@ — %@", exception.name, exception.reason);
|
|
383
|
+
}
|
|
379
384
|
}
|
|
380
385
|
|
|
381
386
|
- (void)shutdown {
|