sezo-audio-engine 0.0.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.
@@ -0,0 +1,549 @@
1
+ package expo.modules.audioengine
2
+
3
+ import android.util.Log
4
+ import com.sezo.audioengine.AudioEngine
5
+ import expo.modules.kotlin.Promise
6
+ import expo.modules.kotlin.modules.Module
7
+ import expo.modules.kotlin.modules.ModuleDefinition
8
+ import java.util.Collections
9
+
10
+ class ExpoAudioEngineModule : Module() {
11
+ private var audioEngine: AudioEngine? = null
12
+ private val loadedTrackIds = mutableSetOf<String>()
13
+ private val pendingExtractions = Collections.synchronizedMap(mutableMapOf<Long, PendingExtraction>())
14
+ private val progressLogState = Collections.synchronizedMap(mutableMapOf<Long, Float>())
15
+ private var lastExtractionJobId: Long? = null
16
+
17
+ private data class PendingExtraction(
18
+ val promise: Promise,
19
+ val trackId: String?,
20
+ val outputPath: String,
21
+ val format: String,
22
+ val bitrate: Int,
23
+ val operation: String
24
+ )
25
+
26
+ override fun definition() = ModuleDefinition {
27
+ Name("ExpoAudioEngineModule")
28
+
29
+ AsyncFunction("initialize") { config: Map<String, Any?> ->
30
+ Log.d(TAG, "Initialize called with config: $config")
31
+
32
+ val sampleRate = (config["sampleRate"] as? Number)?.toInt() ?: 44100
33
+ val maxTracks = (config["maxTracks"] as? Number)?.toInt() ?: 8
34
+
35
+ audioEngine = AudioEngine()
36
+ val success = audioEngine?.initialize(sampleRate, maxTracks) ?: false
37
+
38
+ if (!success) {
39
+ throw Exception("Failed to initialize audio engine")
40
+ }
41
+
42
+ Log.d(TAG, "Audio engine initialized successfully")
43
+ }
44
+
45
+ AsyncFunction("release") {
46
+ Log.d(TAG, "Release called")
47
+ audioEngine?.let { engine ->
48
+ synchronized(pendingExtractions) {
49
+ pendingExtractions.keys.forEach { engine.cancelExtraction(it) }
50
+ pendingExtractions.clear()
51
+ }
52
+ progressLogState.clear()
53
+ engine.setExtractionProgressListener(null)
54
+ engine.setExtractionCompletionListener(null)
55
+ engine.release()
56
+ engine.destroy()
57
+ }
58
+ audioEngine = null
59
+ loadedTrackIds.clear()
60
+ Log.d(TAG, "Audio engine released")
61
+ }
62
+
63
+ AsyncFunction("loadTracks") { tracks: List<Map<String, Any?>> ->
64
+ val engine = audioEngine ?: throw Exception("Engine not initialized")
65
+
66
+ tracks.forEach { track ->
67
+ val id = track["id"] as? String ?: throw Exception("Missing track id")
68
+ val uri = track["uri"] as? String ?: throw Exception("Missing track uri")
69
+ val startTimeMs = (track["startTimeMs"] as? Number)?.toDouble() ?: 0.0
70
+
71
+ Log.d(TAG, "Loading track: id=$id, uri=$uri")
72
+ val filePath = convertUriToPath(uri)
73
+
74
+ if (!engine.loadTrack(id, filePath, startTimeMs)) {
75
+ throw Exception("Failed to load track: $id from $filePath")
76
+ }
77
+
78
+ loadedTrackIds.add(id)
79
+ Log.d(TAG, "Track loaded successfully: $id")
80
+ }
81
+ }
82
+
83
+ AsyncFunction("unloadTrack") { trackId: String ->
84
+ val engine = audioEngine ?: throw Exception("Engine not initialized")
85
+
86
+ if (!engine.unloadTrack(trackId)) {
87
+ throw Exception("Failed to unload track: $trackId")
88
+ }
89
+
90
+ loadedTrackIds.remove(trackId)
91
+ Log.d(TAG, "Track unloaded: $trackId")
92
+ }
93
+
94
+ AsyncFunction("unloadAllTracks") {
95
+ val engine = audioEngine ?: throw Exception("Engine not initialized")
96
+ engine.unloadAllTracks()
97
+ loadedTrackIds.clear()
98
+ Log.d(TAG, "All tracks unloaded")
99
+ }
100
+
101
+ Function("getLoadedTracks") {
102
+ loadedTrackIds.map { mapOf("id" to it) }
103
+ }
104
+
105
+ Function("play") {
106
+ val engine = audioEngine ?: throw Exception("Engine not initialized")
107
+ engine.play()
108
+ Log.d(TAG, "Playback started")
109
+ }
110
+
111
+ Function("pause") {
112
+ val engine = audioEngine ?: throw Exception("Engine not initialized")
113
+ engine.pause()
114
+ Log.d(TAG, "Playback paused")
115
+ }
116
+
117
+ Function("stop") {
118
+ val engine = audioEngine ?: throw Exception("Engine not initialized")
119
+ engine.stop()
120
+ Log.d(TAG, "Playback stopped")
121
+ }
122
+
123
+ Function("seek") { positionMs: Double ->
124
+ val engine = audioEngine ?: throw Exception("Engine not initialized")
125
+ engine.seek(positionMs)
126
+ Log.d(TAG, "Seeked to position: $positionMs ms")
127
+ }
128
+
129
+ Function("isPlaying") {
130
+ audioEngine?.isPlaying() ?: false
131
+ }
132
+
133
+ Function("getCurrentPosition") {
134
+ audioEngine?.getCurrentPosition() ?: 0.0
135
+ }
136
+
137
+ Function("getDuration") {
138
+ audioEngine?.getDuration() ?: 0.0
139
+ }
140
+
141
+ Function("setTrackVolume") { trackId: String, volume: Double ->
142
+ val engine = audioEngine ?: throw Exception("Engine not initialized")
143
+ engine.setTrackVolume(trackId, volume.toFloat())
144
+ }
145
+
146
+ Function("setTrackMuted") { trackId: String, muted: Boolean ->
147
+ val engine = audioEngine ?: throw Exception("Engine not initialized")
148
+ engine.setTrackMuted(trackId, muted)
149
+ }
150
+
151
+ Function("setTrackSolo") { trackId: String, solo: Boolean ->
152
+ val engine = audioEngine ?: throw Exception("Engine not initialized")
153
+ engine.setTrackSolo(trackId, solo)
154
+ }
155
+
156
+ Function("setTrackPan") { trackId: String, pan: Double ->
157
+ val engine = audioEngine ?: throw Exception("Engine not initialized")
158
+ engine.setTrackPan(trackId, pan.toFloat())
159
+ }
160
+
161
+ Function("setMasterVolume") { volume: Double ->
162
+ val engine = audioEngine ?: throw Exception("Engine not initialized")
163
+ engine.setMasterVolume(volume.toFloat())
164
+ }
165
+
166
+ Function("getMasterVolume") {
167
+ audioEngine?.getMasterVolume()?.toDouble() ?: 1.0
168
+ }
169
+
170
+ Function("setPitch") { semitones: Double ->
171
+ val engine = audioEngine ?: throw Exception("Engine not initialized")
172
+ engine.setPitch(semitones.toFloat())
173
+ }
174
+
175
+ Function("getPitch") {
176
+ audioEngine?.getPitch()?.toDouble() ?: 0.0
177
+ }
178
+
179
+ Function("setSpeed") { rate: Double ->
180
+ val engine = audioEngine ?: throw Exception("Engine not initialized")
181
+ engine.setSpeed(rate.toFloat())
182
+ }
183
+
184
+ Function("getSpeed") {
185
+ audioEngine?.getSpeed()?.toDouble() ?: 1.0
186
+ }
187
+
188
+ Function("setTempoAndPitch") { tempo: Double, pitch: Double ->
189
+ val engine = audioEngine ?: throw Exception("Engine not initialized")
190
+ engine.setSpeed(tempo.toFloat())
191
+ engine.setPitch(pitch.toFloat())
192
+ }
193
+
194
+ Function("setTrackPitch") { trackId: String, semitones: Double ->
195
+ val engine = audioEngine ?: throw Exception("Engine not initialized")
196
+ engine.setTrackPitch(trackId, semitones.toFloat())
197
+ }
198
+
199
+ Function("getTrackPitch") { trackId: String ->
200
+ audioEngine?.getTrackPitch(trackId)?.toDouble() ?: 0.0
201
+ }
202
+
203
+ Function("setTrackSpeed") { trackId: String, rate: Double ->
204
+ val engine = audioEngine ?: throw Exception("Engine not initialized")
205
+ engine.setTrackSpeed(trackId, rate.toFloat())
206
+ }
207
+
208
+ Function("getTrackSpeed") { trackId: String ->
209
+ audioEngine?.getTrackSpeed(trackId)?.toDouble() ?: 1.0
210
+ }
211
+
212
+ AsyncFunction("startRecording") { config: Map<String, Any?>? ->
213
+ val engine = audioEngine ?: throw Exception("Engine not initialized")
214
+
215
+ val sampleRate = (config?.get("sampleRate") as? Number)?.toInt() ?: 44100
216
+ val channels = (config?.get("channels") as? Number)?.toInt() ?: 1
217
+ val format = (config?.get("format") as? String) ?: "aac"
218
+ val bitrate = (config?.get("bitrate") as? Number)?.toInt() ?: 128000
219
+ val quality = config?.get("quality") as? String
220
+
221
+ val actualBitrate = when (quality) {
222
+ "low" -> 64000
223
+ "medium" -> 128000
224
+ "high" -> 192000
225
+ else -> bitrate
226
+ }
227
+
228
+ val outputDir = getCacheDir()
229
+ val fileName = "recording_${System.currentTimeMillis()}.$format"
230
+ val outputPath = "$outputDir/$fileName"
231
+
232
+ Log.d(TAG, "Starting recording: $outputPath (format=$format, bitrate=$actualBitrate)")
233
+
234
+ val success = engine.startRecording(
235
+ outputPath = outputPath,
236
+ sampleRate = sampleRate,
237
+ channels = channels,
238
+ format = format,
239
+ bitrate = actualBitrate,
240
+ bitsPerSample = 16
241
+ )
242
+
243
+ if (!success) {
244
+ throw Exception("Failed to start recording")
245
+ }
246
+
247
+ Log.d(TAG, "Recording started successfully")
248
+ }
249
+
250
+ AsyncFunction("stopRecording") {
251
+ val engine = audioEngine ?: throw Exception("Engine not initialized")
252
+
253
+ Log.d(TAG, "Stopping recording")
254
+ val result = engine.stopRecording()
255
+
256
+ if (!result.success) {
257
+ throw Exception("Failed to stop recording: ${result.errorMessage}")
258
+ }
259
+
260
+ Log.d(TAG, "Recording stopped: ${result.fileSize} bytes, ${result.durationSamples} samples")
261
+
262
+ sendEvent(
263
+ "recordingStopped",
264
+ mapOf(
265
+ "uri" to "file://${result.outputPath}",
266
+ "duration" to (result.durationSamples / 44.1).toInt(),
267
+ "startTimeMs" to result.startTimeMs,
268
+ "startTimeSamples" to result.startTimeSamples,
269
+ "sampleRate" to 44100,
270
+ "channels" to 1,
271
+ "format" to "aac",
272
+ "fileSize" to result.fileSize
273
+ )
274
+ )
275
+
276
+ mapOf(
277
+ "uri" to "file://${result.outputPath}",
278
+ "duration" to (result.durationSamples / 44.1).toInt(),
279
+ "startTimeMs" to result.startTimeMs,
280
+ "startTimeSamples" to result.startTimeSamples,
281
+ "sampleRate" to 44100,
282
+ "channels" to 1,
283
+ "format" to "aac",
284
+ "fileSize" to result.fileSize
285
+ )
286
+ }
287
+
288
+ Function("isRecording") {
289
+ audioEngine?.isRecording() ?: false
290
+ }
291
+
292
+ Function("setRecordingVolume") { volume: Double ->
293
+ val engine = audioEngine ?: throw Exception("Engine not initialized")
294
+ engine.setRecordingVolume(volume.toFloat())
295
+ }
296
+
297
+ AsyncFunction("extractTrack") { trackId: String, config: Map<String, Any?>?, promise: Promise ->
298
+ val engine = audioEngine ?: throw Exception("Engine not initialized")
299
+
300
+ val format = (config?.get("format") as? String) ?: "wav"
301
+ val bitrate = (config?.get("bitrate") as? Number)?.toInt() ?: 128000
302
+ val bitsPerSample = (config?.get("bitsPerSample") as? Number)?.toInt() ?: 16
303
+ val includeEffects = (config?.get("includeEffects") as? Boolean) ?: true
304
+ val outputDir = (config?.get("outputDir") as? String) ?: getCacheDir()
305
+
306
+ val fileName = "track_${trackId}_${System.currentTimeMillis()}.$format"
307
+ val outputPath = "$outputDir/$fileName"
308
+
309
+ Log.d(TAG, "Extracting track: $trackId to $outputPath (format=$format, bitrate=$bitrate)")
310
+
311
+ val jobId = engine.startExtractTrack(
312
+ trackId = trackId,
313
+ outputPath = outputPath,
314
+ format = format,
315
+ bitrate = bitrate,
316
+ bitsPerSample = bitsPerSample,
317
+ includeEffects = includeEffects
318
+ )
319
+
320
+ if (jobId <= 0L) {
321
+ promise.reject("EXTRACTION_FAILED", "Failed to start extraction", null)
322
+ return@AsyncFunction
323
+ }
324
+
325
+ pendingExtractions[jobId] = PendingExtraction(
326
+ promise = promise,
327
+ trackId = trackId,
328
+ outputPath = outputPath,
329
+ format = format,
330
+ bitrate = bitrate,
331
+ operation = "track"
332
+ )
333
+ lastExtractionJobId = jobId
334
+ attachExtractionListeners(engine)
335
+ }
336
+
337
+ AsyncFunction("extractAllTracks") { config: Map<String, Any?>?, promise: Promise ->
338
+ val engine = audioEngine ?: throw Exception("Engine not initialized")
339
+
340
+ val format = (config?.get("format") as? String) ?: "wav"
341
+ val bitrate = (config?.get("bitrate") as? Number)?.toInt() ?: 128000
342
+ val bitsPerSample = (config?.get("bitsPerSample") as? Number)?.toInt() ?: 16
343
+ val includeEffects = (config?.get("includeEffects") as? Boolean) ?: true
344
+ val outputDir = (config?.get("outputDir") as? String) ?: getCacheDir()
345
+
346
+ val fileName = "mixed_tracks_${System.currentTimeMillis()}.$format"
347
+ val outputPath = "$outputDir/$fileName"
348
+
349
+ Log.d(TAG, "Extracting all tracks mixed to $outputPath (format=$format, bitrate=$bitrate)")
350
+
351
+ val jobId = engine.startExtractAllTracks(
352
+ outputPath = outputPath,
353
+ format = format,
354
+ bitrate = bitrate,
355
+ bitsPerSample = bitsPerSample,
356
+ includeEffects = includeEffects
357
+ )
358
+
359
+ if (jobId <= 0L) {
360
+ promise.reject("EXTRACTION_FAILED", "Failed to start extraction", null)
361
+ return@AsyncFunction
362
+ }
363
+
364
+ pendingExtractions[jobId] = PendingExtraction(
365
+ promise = promise,
366
+ trackId = null,
367
+ outputPath = outputPath,
368
+ format = format,
369
+ bitrate = bitrate,
370
+ operation = "mix"
371
+ )
372
+ lastExtractionJobId = jobId
373
+ attachExtractionListeners(engine)
374
+ }
375
+
376
+ Function("cancelExtraction") { jobId: Double? ->
377
+ val engine = audioEngine ?: throw Exception("Engine not initialized")
378
+ val resolvedJobId = jobId?.toLong() ?: lastExtractionJobId
379
+ if (resolvedJobId == null) {
380
+ false
381
+ } else {
382
+ engine.cancelExtraction(resolvedJobId)
383
+ }
384
+ }
385
+
386
+ Function("getInputLevel") {
387
+ audioEngine?.getInputLevel()?.toDouble() ?: 0.0
388
+ }
389
+
390
+ Function("getOutputLevel") { 0.0 }
391
+ Function("getTrackLevel") { _trackId: String -> 0.0 }
392
+
393
+ AsyncFunction("enableBackgroundPlayback") { _metadata: Map<String, Any?> -> }
394
+ Function("updateNowPlayingInfo") { _metadata: Map<String, Any?> -> }
395
+ AsyncFunction("disableBackgroundPlayback") { }
396
+
397
+ Events(
398
+ "playbackStateChange",
399
+ "positionUpdate",
400
+ "playbackComplete",
401
+ "trackLoaded",
402
+ "trackUnloaded",
403
+ "recordingStarted",
404
+ "recordingStopped",
405
+ "extractionProgress",
406
+ "extractionComplete",
407
+ "error"
408
+ )
409
+ }
410
+
411
+ private fun attachExtractionListeners(engine: AudioEngine) {
412
+ if (pendingExtractions.isEmpty()) {
413
+ return
414
+ }
415
+
416
+ engine.setExtractionProgressListener { jobId, progress ->
417
+ val pending = pendingExtractions[jobId] ?: return@setExtractionProgressListener
418
+ val clamped = progress.coerceIn(0.0f, 1.0f)
419
+ val lastLogged = progressLogState[jobId] ?: -1.0f
420
+ if (clamped >= 1.0f || clamped - lastLogged >= 0.05f) {
421
+ progressLogState[jobId] = clamped
422
+ Log.d(TAG, "Extraction progress job=$jobId op=${pending.operation} progress=$clamped")
423
+ }
424
+ sendEvent(
425
+ "extractionProgress",
426
+ mapOf(
427
+ "jobId" to jobId,
428
+ "progress" to clamped.toDouble(),
429
+ "trackId" to pending.trackId,
430
+ "outputPath" to pending.outputPath,
431
+ "format" to pending.format,
432
+ "operation" to pending.operation
433
+ )
434
+ )
435
+ }
436
+
437
+ engine.setExtractionCompletionListener { jobId, result ->
438
+ val pending = pendingExtractions.remove(jobId)
439
+ progressLogState.remove(jobId)
440
+ if (pending == null) {
441
+ return@setExtractionCompletionListener
442
+ }
443
+
444
+ if (!result.success) {
445
+ Log.e(TAG, "Extraction failed for job=$jobId: ${result.errorMessage}")
446
+ sendEvent(
447
+ "extractionComplete",
448
+ mapOf(
449
+ "success" to false,
450
+ "jobId" to jobId,
451
+ "trackId" to pending.trackId,
452
+ "outputPath" to pending.outputPath,
453
+ "format" to pending.format,
454
+ "bitrate" to pending.bitrate,
455
+ "errorMessage" to (result.errorMessage ?: "Unknown error"),
456
+ "operation" to pending.operation
457
+ )
458
+ )
459
+ val code = if (result.errorMessage == "Extraction cancelled") {
460
+ "EXTRACTION_CANCELLED"
461
+ } else {
462
+ "EXTRACTION_FAILED"
463
+ }
464
+ pending.promise.reject(code, result.errorMessage, null)
465
+ } else {
466
+ Log.d(TAG, "Extraction successful: ${result.fileSize} bytes, ${result.durationSamples} samples")
467
+ Log.d(TAG, "Extraction complete event (${pending.operation})")
468
+
469
+ sendEvent(
470
+ "extractionComplete",
471
+ mapOf(
472
+ "success" to true,
473
+ "jobId" to jobId,
474
+ "trackId" to pending.trackId,
475
+ "uri" to "file://${result.outputPath}",
476
+ "outputPath" to result.outputPath,
477
+ "duration" to (result.durationSamples / 44.1).toInt(),
478
+ "format" to pending.format,
479
+ "fileSize" to result.fileSize,
480
+ "bitrate" to pending.bitrate,
481
+ "operation" to pending.operation
482
+ )
483
+ )
484
+
485
+ val response = if (pending.operation == "mix") {
486
+ listOf(
487
+ mapOf(
488
+ "uri" to "file://${result.outputPath}",
489
+ "duration" to (result.durationSamples / 44.1).toInt(),
490
+ "format" to pending.format,
491
+ "fileSize" to result.fileSize,
492
+ "bitrate" to pending.bitrate
493
+ )
494
+ )
495
+ } else {
496
+ mapOf(
497
+ "trackId" to pending.trackId,
498
+ "uri" to "file://${result.outputPath}",
499
+ "duration" to (result.durationSamples / 44.1).toInt(),
500
+ "format" to pending.format,
501
+ "fileSize" to result.fileSize,
502
+ "bitrate" to pending.bitrate
503
+ )
504
+ }
505
+
506
+ pending.promise.resolve(response)
507
+ }
508
+
509
+ if (pendingExtractions.isEmpty()) {
510
+ engine.setExtractionProgressListener(null)
511
+ engine.setExtractionCompletionListener(null)
512
+ }
513
+ }
514
+ }
515
+
516
+ private fun getCacheDir(): String {
517
+ return appContext.reactContext?.cacheDir?.absolutePath ?: "/tmp"
518
+ }
519
+
520
+ private fun convertUriToPath(uri: String): String {
521
+ return when {
522
+ uri.startsWith("file://") -> {
523
+ // Remove file:// prefix
524
+ uri.substring(7)
525
+ }
526
+ uri.startsWith("/") -> {
527
+ // Already an absolute path
528
+ uri
529
+ }
530
+ uri.startsWith("content://") -> {
531
+ // TODO: Handle content:// URIs with ContentResolver
532
+ // For now, throw an error - this will be implemented when needed
533
+ throw Exception("content:// URIs not yet supported. Use file:// or absolute paths.")
534
+ }
535
+ uri.startsWith("asset://") -> {
536
+ // TODO: Handle asset:// URIs by copying to temp file
537
+ // For now, throw an error - this will be implemented when needed
538
+ throw Exception("asset:// URIs not yet supported. Use file:// or absolute paths.")
539
+ }
540
+ else -> {
541
+ throw Exception("Unsupported URI scheme: $uri. Use file:// or absolute paths.")
542
+ }
543
+ }
544
+ }
545
+
546
+ companion object {
547
+ private const val TAG = "ExpoAudioEngine"
548
+ }
549
+ }
@@ -0,0 +1,2 @@
1
+ import type { AudioEngine } from './AudioEngineModule.types';
2
+ export declare const AudioEngineModule: AudioEngine;
@@ -0,0 +1,46 @@
1
+ import { requireNativeModule } from 'expo-modules-core';
2
+ const NativeAudioEngineModule = requireNativeModule('ExpoAudioEngineModule');
3
+ export const AudioEngineModule = {
4
+ initialize: (config) => NativeAudioEngineModule.initialize(config),
5
+ release: () => NativeAudioEngineModule.release(),
6
+ loadTracks: (tracks) => NativeAudioEngineModule.loadTracks(tracks),
7
+ unloadTrack: (trackId) => NativeAudioEngineModule.unloadTrack(trackId),
8
+ unloadAllTracks: () => NativeAudioEngineModule.unloadAllTracks(),
9
+ getLoadedTracks: () => NativeAudioEngineModule.getLoadedTracks(),
10
+ play: () => NativeAudioEngineModule.play(),
11
+ pause: () => NativeAudioEngineModule.pause(),
12
+ stop: () => NativeAudioEngineModule.stop(),
13
+ seek: (positionMs) => NativeAudioEngineModule.seek(positionMs),
14
+ isPlaying: () => NativeAudioEngineModule.isPlaying(),
15
+ getCurrentPosition: () => NativeAudioEngineModule.getCurrentPosition(),
16
+ getDuration: () => NativeAudioEngineModule.getDuration(),
17
+ setTrackVolume: (trackId, volume) => NativeAudioEngineModule.setTrackVolume(trackId, volume),
18
+ setTrackMuted: (trackId, muted) => NativeAudioEngineModule.setTrackMuted(trackId, muted),
19
+ setTrackSolo: (trackId, solo) => NativeAudioEngineModule.setTrackSolo(trackId, solo),
20
+ setTrackPan: (trackId, pan) => NativeAudioEngineModule.setTrackPan(trackId, pan),
21
+ setTrackPitch: (trackId, semitones) => NativeAudioEngineModule.setTrackPitch(trackId, semitones),
22
+ getTrackPitch: (trackId) => NativeAudioEngineModule.getTrackPitch(trackId),
23
+ setTrackSpeed: (trackId, rate) => NativeAudioEngineModule.setTrackSpeed(trackId, rate),
24
+ getTrackSpeed: (trackId) => NativeAudioEngineModule.getTrackSpeed(trackId),
25
+ setMasterVolume: (volume) => NativeAudioEngineModule.setMasterVolume(volume),
26
+ getMasterVolume: () => NativeAudioEngineModule.getMasterVolume(),
27
+ setPitch: (semitones) => NativeAudioEngineModule.setPitch(semitones),
28
+ getPitch: () => NativeAudioEngineModule.getPitch(),
29
+ setSpeed: (rate) => NativeAudioEngineModule.setSpeed(rate),
30
+ getSpeed: () => NativeAudioEngineModule.getSpeed(),
31
+ setTempoAndPitch: (tempo, pitch) => NativeAudioEngineModule.setTempoAndPitch(tempo, pitch),
32
+ startRecording: (config) => NativeAudioEngineModule.startRecording(config),
33
+ stopRecording: () => NativeAudioEngineModule.stopRecording(),
34
+ isRecording: () => NativeAudioEngineModule.isRecording(),
35
+ setRecordingVolume: (volume) => NativeAudioEngineModule.setRecordingVolume(volume),
36
+ extractTrack: (trackId, config) => NativeAudioEngineModule.extractTrack(trackId, config),
37
+ extractAllTracks: (config) => NativeAudioEngineModule.extractAllTracks(config),
38
+ cancelExtraction: (jobId) => NativeAudioEngineModule.cancelExtraction(jobId),
39
+ getInputLevel: () => NativeAudioEngineModule.getInputLevel(),
40
+ getOutputLevel: () => NativeAudioEngineModule.getOutputLevel(),
41
+ getTrackLevel: (trackId) => NativeAudioEngineModule.getTrackLevel(trackId),
42
+ enableBackgroundPlayback: (metadata) => NativeAudioEngineModule.enableBackgroundPlayback(metadata),
43
+ updateNowPlayingInfo: (metadata) => NativeAudioEngineModule.updateNowPlayingInfo(metadata),
44
+ disableBackgroundPlayback: () => NativeAudioEngineModule.disableBackgroundPlayback(),
45
+ addListener: (event, callback) => NativeAudioEngineModule.addListener(event, callback)
46
+ };