@edkimmel/expo-audio-stream 0.2.0 → 0.3.0

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.
Files changed (33) hide show
  1. package/NATIVE_EVENTS.md +26 -4
  2. package/README.md +33 -4
  3. package/android/src/main/java/expo/modules/audiostream/AudioRecorderManager.kt +25 -0
  4. package/android/src/main/java/expo/modules/audiostream/ExpoPlayAudioStreamModule.kt +2 -1
  5. package/android/src/main/java/expo/modules/audiostream/FrequencyBandAnalyzer.kt +153 -0
  6. package/android/src/main/java/expo/modules/audiostream/pipeline/AudioPipeline.kt +55 -0
  7. package/android/src/main/java/expo/modules/audiostream/pipeline/PipelineIntegration.kt +16 -0
  8. package/build/events.d.ts +5 -0
  9. package/build/events.d.ts.map +1 -1
  10. package/build/events.js.map +1 -1
  11. package/build/index.d.ts +2 -2
  12. package/build/index.d.ts.map +1 -1
  13. package/build/index.js +4 -2
  14. package/build/index.js.map +1 -1
  15. package/build/pipeline/types.d.ts +9 -1
  16. package/build/pipeline/types.d.ts.map +1 -1
  17. package/build/pipeline/types.js.map +1 -1
  18. package/build/types.d.ts +17 -0
  19. package/build/types.d.ts.map +1 -1
  20. package/build/types.js.map +1 -1
  21. package/docs/superpowers/plans/2026-03-13-frequency-band-analysis.md +1006 -0
  22. package/docs/superpowers/specs/2026-03-13-frequency-band-analysis-design.md +276 -0
  23. package/ios/AudioPipeline.swift +69 -2
  24. package/ios/ExpoPlayAudioStreamModule.swift +19 -3
  25. package/ios/FrequencyBandAnalyzer.swift +135 -0
  26. package/ios/Microphone.swift +29 -4
  27. package/ios/MicrophoneDataDelegate.swift +1 -1
  28. package/ios/PipelineIntegration.swift +14 -0
  29. package/package.json +1 -1
  30. package/src/events.ts +1 -0
  31. package/src/index.ts +6 -1
  32. package/src/pipeline/types.ts +9 -1
  33. package/src/types.ts +19 -0
package/NATIVE_EVENTS.md CHANGED
@@ -23,6 +23,7 @@ the recording error channel.
23
23
  | `fileUri` | `string` | Always `""` (file I/O removed) |
24
24
  | `lastEmittedSize` | `number` | Previous `totalSize` value |
25
25
  | `mimeType` | `string` | e.g. `"audio/wav"` |
26
+ | `frequencyBands` | `{ low, mid, high }?` | dB-scaled RMS energy per band (0–1). Present only when `frequencyBandConfig` is passed to `startMicrophone`. |
26
27
 
27
28
  **Error variant** (same event name, different shape):
28
29
 
@@ -96,11 +97,10 @@ Fired after each audio chunk finishes playback.
96
97
 
97
98
  ---
98
99
 
99
- ## Pipeline Events (Android only)
100
+ ## Pipeline Events
100
101
 
101
- These events are emitted by `AudioPipeline` via `PipelineIntegration`. They are
102
- not available on iOS, which still uses the legacy `SoundStarted`/`SoundChunkPlayed`
103
- path.
102
+ These events are emitted by `AudioPipeline` via `PipelineIntegration` on both
103
+ Android and iOS.
104
104
 
105
105
  ### `PipelineStateChanged`
106
106
 
@@ -200,6 +200,28 @@ The pipeline transitions from `draining` to `idle`.
200
200
 
201
201
  ---
202
202
 
203
+ ### `PipelineFrequencyBands`
204
+
205
+ Fired at the interval configured by `frequencyBandIntervalMs` during pipeline
206
+ playback. Uses IIR-based frequency splitting and dB-scaled RMS energy.
207
+
208
+ | Field | Type | Notes |
209
+ |---|---|---|
210
+ | `low` | `number` | 0–1, energy below `lowCrossoverHz` (default 300 Hz) |
211
+ | `mid` | `number` | 0–1, energy between crossover frequencies |
212
+ | `high` | `number` | 0–1, energy above `highCrossoverHz` (default 2000 Hz) |
213
+
214
+ **Platform:** Android, iOS
215
+
216
+ **JS response:**
217
+ - Drive a visual audio meter or waveform visualization.
218
+ - Values are dB-scaled from raw RMS so they map well to UI bar heights.
219
+ - When no new audio has been pushed, the last known band values are re-emitted
220
+ to maintain a steady cadence. Values drop to zero only when the pipeline is
221
+ idle (disconnected or between turns).
222
+
223
+ ---
224
+
203
225
  ### `PipelineAudioFocusLost`
204
226
 
205
227
  Fired when another app takes audio focus (phone call, navigation, music).
package/README.md CHANGED
@@ -24,8 +24,13 @@ const { recordingResult, subscription } =
24
24
  onAudioStream: async (event) => {
25
25
  // event.data: base64-encoded PCM chunk
26
26
  // event.soundLevel: current mic level (dB)
27
+ // event.frequencyBands: { low, mid, high } (0–1) if configured
27
28
  sendToBackend(event.data);
28
29
  },
30
+ frequencyBandConfig: {
31
+ lowCrossoverHz: 300,
32
+ highCrossoverHz: 2000,
33
+ },
29
34
  });
30
35
 
31
36
  // Later:
@@ -73,6 +78,7 @@ const result = await Pipeline.connect({
73
78
  sampleRate: 24000,
74
79
  channelCount: 1,
75
80
  targetBufferMs: 80,
81
+ frequencyBandIntervalMs: 100, // optional: emit frequency bands every 100ms
76
82
  });
77
83
 
78
84
  // Subscribe to events
@@ -199,6 +205,7 @@ interface RecordingConfig {
199
205
  encoding?: "pcm_32bit" | "pcm_16bit" | "pcm_8bit";
200
206
  interval?: number; // ms between audio data emissions (default 1000)
201
207
  onAudioStream?: (event: AudioDataEvent) => Promise<void>;
208
+ frequencyBandConfig?: FrequencyBandConfig; // enable frequency band analysis on mic audio
202
209
  }
203
210
  ```
204
211
 
@@ -216,9 +223,11 @@ interface SoundConfig {
216
223
 
217
224
  ```typescript
218
225
  interface ConnectPipelineOptions {
219
- sampleRate?: number; // default 24000
220
- channelCount?: number; // default 1 (mono)
221
- targetBufferMs?: number; // ms to buffer before priming gate opens (default 80)
226
+ sampleRate?: number; // default 24000
227
+ channelCount?: number; // default 1 (mono)
228
+ targetBufferMs?: number; // ms to buffer before priming gate opens (default 80)
229
+ frequencyBandIntervalMs?: number; // emit PipelineFrequencyBands every N ms (omit to disable)
230
+ frequencyBandConfig?: FrequencyBandConfig; // crossover frequencies (optional)
222
231
  }
223
232
  ```
224
233
 
@@ -233,13 +242,32 @@ interface PushPipelineAudioOptions {
233
242
  }
234
243
  ```
235
244
 
245
+ ### FrequencyBandConfig
246
+
247
+ ```typescript
248
+ interface FrequencyBandConfig {
249
+ lowCrossoverHz?: number; // boundary between low and mid bands (default 300)
250
+ highCrossoverHz?: number; // boundary between mid and high bands (default 2000)
251
+ }
252
+ ```
253
+
254
+ ### FrequencyBands
255
+
256
+ ```typescript
257
+ interface FrequencyBands {
258
+ low: number; // 0–1, dB-scaled RMS energy below lowCrossoverHz
259
+ mid: number; // 0–1, dB-scaled RMS energy between crossovers
260
+ high: number; // 0–1, dB-scaled RMS energy above highCrossoverHz
261
+ }
262
+ ```
263
+
236
264
  ## Events
237
265
 
238
266
  ### Core Events
239
267
 
240
268
  | Event | Payload | Description |
241
269
  |-------|---------|-------------|
242
- | `AudioData` | `{ encoded, position, deltaSize, totalSize, soundLevel, ... }` | Emitted during mic capture at the configured interval. |
270
+ | `AudioData` | `{ encoded, position, deltaSize, totalSize, soundLevel, frequencyBands?, ... }` | Emitted during mic capture at the configured interval. Includes `frequencyBands` when `frequencyBandConfig` is set. |
243
271
  | `SoundChunkPlayed` | `{ isFinal: boolean }` | A queued chunk finished playing. `isFinal` when the queue is empty. |
244
272
  | `SoundStarted` | (none) | Playback began for a new turn. |
245
273
  | `DeviceReconnected` | `{ reason }` | Audio route changed (headphones, Bluetooth, etc). |
@@ -254,6 +282,7 @@ interface PushPipelineAudioOptions {
254
282
  | `PipelineZombieDetected` | `{ playbackHead, stalledMs }` | Audio track stalled. |
255
283
  | `PipelineUnderrun` | `{ count }` | Jitter buffer underrun (silence inserted). |
256
284
  | `PipelineDrained` | `{ turnId }` | All buffered audio for the turn has been played. |
285
+ | `PipelineFrequencyBands` | `{ low, mid, high }` | Frequency band energy (0–1) emitted at `frequencyBandIntervalMs`. |
257
286
  | `PipelineAudioFocusLost` | (empty) | Another app took audio focus. |
258
287
  | `PipelineAudioFocusResumed` | (empty) | Audio focus regained. |
259
288
 
@@ -36,6 +36,7 @@ class AudioRecorderManager(
36
36
 
37
37
  // Flag to control whether actual audio data or silence is sent
38
38
  private var isSilent = false
39
+ private var frequencyBandAnalyzer: FrequencyBandAnalyzer? = null
39
40
 
40
41
  private lateinit var recordingConfig: RecordingConfig
41
42
  private var mimeType = "audio/wav"
@@ -166,6 +167,14 @@ class AudioRecorderManager(
166
167
 
167
168
  recordingThread = Thread { recordingProcess() }.apply { start() }
168
169
 
170
+ // Create frequency band analyzer
171
+ val bandConfig = options["frequencyBandConfig"] as? Map<*, *>
172
+ frequencyBandAnalyzer = FrequencyBandAnalyzer(
173
+ sampleRate = recordingConfig.sampleRate,
174
+ lowCrossoverHz = (bandConfig?.get("lowCrossoverHz") as? Number)?.toFloat() ?: 300f,
175
+ highCrossoverHz = (bandConfig?.get("highCrossoverHz") as? Number)?.toFloat() ?: 2000f
176
+ )
177
+
169
178
  val result = bundleOf(
170
179
  "fileUri" to "",
171
180
  "channels" to recordingConfig.channels,
@@ -218,6 +227,7 @@ class AudioRecorderManager(
218
227
  pausedDuration = 0
219
228
  totalDataSize = 0
220
229
  streamUuid = null
230
+ frequencyBandAnalyzer = null
221
231
  lastEmittedSize = 0
222
232
 
223
233
  Log.d(Constants.TAG, "Audio resources cleaned up")
@@ -373,6 +383,16 @@ class AudioRecorderManager(
373
383
  // Calculate power level (using concise expression)
374
384
  val soundLevel = if (isSilent) -160.0f else audioDataEncoder.calculatePowerLevel(audioData, length)
375
385
 
386
+ // Compute frequency bands
387
+ val bands = if (isSilent) {
388
+ FrequencyBands.ZERO
389
+ } else {
390
+ frequencyBandAnalyzer?.let { analyzer ->
391
+ analyzer.processSamplesFromBytes(audioData, length)
392
+ analyzer.harvest()
393
+ }
394
+ }
395
+
376
396
  mainHandler.post {
377
397
  try {
378
398
  eventSender.sendExpoEvent(
@@ -384,6 +404,11 @@ class AudioRecorderManager(
384
404
  "position" to positionInMs,
385
405
  "mimeType" to mimeType,
386
406
  "soundLevel" to soundLevel,
407
+ "frequencyBands" to bundleOf(
408
+ "low" to (bands?.low ?: 0f),
409
+ "mid" to (bands?.mid ?: 0f),
410
+ "high" to (bands?.high ?: 0f)
411
+ ),
387
412
  "totalSize" to totalDataSize.toLong(),
388
413
  "streamUuid" to streamUuid
389
414
  )
@@ -106,7 +106,8 @@ class ExpoPlayAudioStreamModule : Module(), EventSender {
106
106
  PipelineIntegration.EVENT_UNDERRUN,
107
107
  PipelineIntegration.EVENT_DRAINED,
108
108
  PipelineIntegration.EVENT_AUDIO_FOCUS_LOST,
109
- PipelineIntegration.EVENT_AUDIO_FOCUS_RESUMED
109
+ PipelineIntegration.EVENT_AUDIO_FOCUS_RESUMED,
110
+ PipelineIntegration.EVENT_FREQUENCY_BANDS
110
111
  )
111
112
 
112
113
  // Initialize managers for playback and for recording
@@ -0,0 +1,153 @@
1
+ package expo.modules.audiostream
2
+
3
+ import java.util.concurrent.locks.ReentrantLock
4
+ import kotlin.concurrent.withLock
5
+ import kotlin.math.PI
6
+ import kotlin.math.log10
7
+ import kotlin.math.max
8
+ import kotlin.math.min
9
+ import kotlin.math.sqrt
10
+
11
+ /**
12
+ * RMS energy per frequency band, range [0, 1].
13
+ */
14
+ data class FrequencyBands(
15
+ val low: Float,
16
+ val mid: Float,
17
+ val high: Float
18
+ ) {
19
+ companion object {
20
+ val ZERO = FrequencyBands(0f, 0f, 0f)
21
+ }
22
+ }
23
+
24
+ /**
25
+ * Lightweight IIR-based frequency band analyzer.
26
+ *
27
+ * Uses two parallel single-pole low-pass filters to split audio into
28
+ * low / mid / high bands and accumulate RMS energy.
29
+ * Thread-safe: [processSamples] and [harvest] may be called from
30
+ * different threads (guarded by an internal lock).
31
+ */
32
+ class FrequencyBandAnalyzer(
33
+ sampleRate: Int,
34
+ lowCrossoverHz: Float = 300f,
35
+ highCrossoverHz: Float = 2000f
36
+ ) {
37
+ // ── Coefficients (immutable after init) ──────────────────────────
38
+ private val alphaLow: Float = min(1f, (2f * PI.toFloat() * lowCrossoverHz) / sampleRate)
39
+ private val alphaHigh: Float = min(1f, (2f * PI.toFloat() * highCrossoverHz) / sampleRate)
40
+
41
+ // ── Filter state ─────────────────────────────────────────────────
42
+ private var lp1: Float = 0f
43
+ private var lp2: Float = 0f
44
+
45
+ // ── Energy accumulators ──────────────────────────────────────────
46
+ private var lowE: Float = 0f
47
+ private var midE: Float = 0f
48
+ private var highE: Float = 0f
49
+ private var count: Int = 0
50
+
51
+ // ── Synchronization ──────────────────────────────────────────────
52
+ private val lock = ReentrantLock()
53
+
54
+ /**
55
+ * Process a batch of PCM16 samples. Accumulates energy — does NOT
56
+ * produce output. Call [harvest] to read and reset.
57
+ */
58
+ fun processSamples(samples: ShortArray, length: Int = samples.size) {
59
+ lock.withLock {
60
+ for (i in 0 until length) {
61
+ val s = samples[i].toFloat() / 32768f
62
+
63
+ lp1 += alphaLow * (s - lp1)
64
+ lp2 += alphaHigh * (s - lp2)
65
+
66
+ val low = lp1
67
+ val high = s - lp2
68
+ val mid = s - low - high
69
+
70
+ lowE += low * low
71
+ midE += mid * mid
72
+ highE += high * high
73
+ count++
74
+ }
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Process PCM16 samples from a ByteArray (little-endian Int16).
80
+ */
81
+ fun processSamplesFromBytes(data: ByteArray, length: Int = data.size) {
82
+ val sampleCount = length / 2
83
+ val samples = ShortArray(sampleCount)
84
+ val buf = java.nio.ByteBuffer.wrap(data, 0, length)
85
+ .order(java.nio.ByteOrder.LITTLE_ENDIAN)
86
+ .asShortBuffer()
87
+ buf.get(samples)
88
+ processSamples(samples, sampleCount)
89
+ }
90
+
91
+ /** Whether any samples have been accumulated since the last harvest/reset. */
92
+ fun hasData(): Boolean = lock.withLock { count > 0 }
93
+
94
+ /**
95
+ * Read accumulated band energy scaled to 0–1 using dB mapping,
96
+ * then reset accumulators.
97
+ *
98
+ * Raw RMS of speech/music PCM typically sits around 0.01–0.15,
99
+ * which is unusable for a visual meter. Converting to dB and
100
+ * mapping the range [–60 dB, 0 dB] → [0, 1] gives perceptually
101
+ * meaningful values.
102
+ */
103
+ fun harvest(): FrequencyBands {
104
+ lock.withLock {
105
+ if (count == 0) return FrequencyBands.ZERO
106
+
107
+ val n = count.toFloat()
108
+ val result = FrequencyBands(
109
+ low = rmsToScaled(sqrt(lowE / n)),
110
+ mid = rmsToScaled(sqrt(midE / n)),
111
+ high = rmsToScaled(sqrt(highE / n))
112
+ )
113
+
114
+ lowE = 0f
115
+ midE = 0f
116
+ highE = 0f
117
+ count = 0
118
+
119
+ return result
120
+ }
121
+ }
122
+
123
+ companion object {
124
+ /** Floor in dB — anything below this maps to 0. */
125
+ private const val DB_FLOOR = -60f
126
+
127
+ /**
128
+ * Convert raw RMS (0…1) to a 0–1 meter value via dB scaling.
129
+ * rms 0.09 → –20.9 dB → 0.65
130
+ * rms 0.01 → –40 dB → 0.33
131
+ * rms 0.001 → –60 dB → 0.0
132
+ */
133
+ private fun rmsToScaled(rms: Float): Float {
134
+ if (rms <= 0f) return 0f
135
+ val db = 20f * log10(rms)
136
+ return max(0f, min(1f, (db - DB_FLOOR) / -DB_FLOOR))
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Zero all state (filter accumulators + energy).
142
+ */
143
+ fun reset() {
144
+ lock.withLock {
145
+ lp1 = 0f
146
+ lp2 = 0f
147
+ lowE = 0f
148
+ midE = 0f
149
+ highE = 0f
150
+ count = 0
151
+ }
152
+ }
153
+ }
@@ -13,6 +13,8 @@ import android.os.Looper
13
13
  import android.provider.Settings
14
14
  import android.util.Base64
15
15
  import android.util.Log
16
+ import expo.modules.audiostream.FrequencyBandAnalyzer
17
+ import expo.modules.audiostream.FrequencyBands
16
18
  import java.nio.ByteBuffer
17
19
  import java.nio.ByteOrder
18
20
  import java.util.concurrent.atomic.AtomicBoolean
@@ -49,6 +51,7 @@ interface PipelineListener {
49
51
  fun onDrained(turnId: String)
50
52
  fun onAudioFocusLost()
51
53
  fun onAudioFocusResumed()
54
+ fun onFrequencyBands(low: Float, mid: Float, high: Float)
52
55
  }
53
56
 
54
57
  // ────────────────────────────────────────────────────────────────────────────
@@ -84,6 +87,9 @@ class AudioPipeline(
84
87
  private val sampleRate: Int,
85
88
  private val channelCount: Int,
86
89
  private val targetBufferMs: Int,
90
+ private val frequencyBandIntervalMs: Int = 100,
91
+ private val lowCrossoverHz: Float = 300f,
92
+ private val highCrossoverHz: Float = 2000f,
87
93
  private val listener: PipelineListener
88
94
  ) {
89
95
  companion object {
@@ -184,6 +190,11 @@ class AudioPipeline(
184
190
  // ── Underrun debounce ───────────────────────────────────────────────
185
191
  private var lastReportedUnderrunCount = 0
186
192
 
193
+ // ── Frequency band analysis ──────────────────────────────────────
194
+ private var frequencyBandAnalyzer: FrequencyBandAnalyzer? = null
195
+ private var frequencyBandExecutor: java.util.concurrent.ScheduledExecutorService? = null
196
+ @Volatile private var lastEmittedBands: FrequencyBands? = null
197
+
187
198
  // ── State ───────────────────────────────────────────────────────────
188
199
  @Volatile private var state: PipelineState = PipelineState.IDLE
189
200
  private val mainHandler = Handler(Looper.getMainLooper())
@@ -260,6 +271,14 @@ class AudioPipeline(
260
271
  // ── 7. Reset telemetry ──────────────────────────────────────
261
272
  resetTelemetry()
262
273
 
274
+ // ── 8. Frequency band analyzer ──────────────────────────
275
+ frequencyBandAnalyzer = FrequencyBandAnalyzer(
276
+ sampleRate = sampleRate,
277
+ lowCrossoverHz = lowCrossoverHz,
278
+ highCrossoverHz = highCrossoverHz
279
+ )
280
+ startFrequencyBandTimer()
281
+
263
282
  setState(PipelineState.IDLE)
264
283
  Log.d(TAG, "Connected — sampleRate=$sampleRate ch=$channelCount " +
265
284
  "frameSamples=$frameSizeSamples targetBuffer=${targetBufferMs}ms")
@@ -284,6 +303,12 @@ class AudioPipeline(
284
303
  zombieThread?.interrupt()
285
304
  zombieThread = null
286
305
 
306
+ // Stop frequency band timer
307
+ frequencyBandExecutor?.shutdownNow()
308
+ frequencyBandExecutor = null
309
+ frequencyBandAnalyzer = null
310
+ lastEmittedBands = null
311
+
287
312
  // Remove VolumeGuard
288
313
  removeVolumeGuard()
289
314
 
@@ -357,6 +382,7 @@ class AudioPipeline(
357
382
  // so real audio plays immediately without waiting behind queued silence.
358
383
  pendingFlush.set(true)
359
384
  setState(PipelineState.STREAMING)
385
+ frequencyBandAnalyzer?.reset()
360
386
  }
361
387
 
362
388
  // ── Decode base64 → PCM shorts ──────────────────────────────
@@ -400,6 +426,7 @@ class AudioPipeline(
400
426
  playbackStartedForTurn = false
401
427
  lastReportedUnderrunCount = 0
402
428
  setState(PipelineState.IDLE)
429
+ frequencyBandAnalyzer?.reset()
403
430
  }
404
431
  }
405
432
 
@@ -458,6 +485,13 @@ class AudioPipeline(
458
485
  frame.fill(0)
459
486
  }
460
487
 
488
+ // Analyze frequency bands on the raw Int16 samples.
489
+ // Only feed real audio (streaming/draining) — not silence frames
490
+ // written while idle/priming, which would dilute RMS energy.
491
+ if (!audioFocusLost.get() && (state == PipelineState.STREAMING || state == PipelineState.DRAINING)) {
492
+ frequencyBandAnalyzer?.processSamples(frame, frame.size)
493
+ }
494
+
461
495
  // Write to AudioTrack (BLOCKING — will park thread until space available)
462
496
  try {
463
497
  val written = track.write(frame, 0, frame.size, AudioTrack.WRITE_BLOCKING)
@@ -577,6 +611,27 @@ class AudioPipeline(
577
611
  }
578
612
  }
579
613
 
614
+ // ════════════════════════════════════════════════════════════════════
615
+ // Frequency band emission
616
+ // ════════════════════════════════════════════════════════════════════
617
+
618
+ private fun startFrequencyBandTimer() {
619
+ val executor = java.util.concurrent.Executors.newSingleThreadScheduledExecutor { r ->
620
+ Thread(r, "AudioPipeline-FreqBands").apply { isDaemon = true }
621
+ }
622
+ executor.scheduleAtFixedRate({
623
+ if (!running.get()) return@scheduleAtFixedRate
624
+ val analyzer = frequencyBandAnalyzer ?: return@scheduleAtFixedRate
625
+ val bands = if (analyzer.hasData()) {
626
+ analyzer.harvest().also { lastEmittedBands = it }
627
+ } else {
628
+ lastEmittedBands ?: return@scheduleAtFixedRate
629
+ }
630
+ listener.onFrequencyBands(bands.low, bands.mid, bands.high)
631
+ }, frequencyBandIntervalMs.toLong(), frequencyBandIntervalMs.toLong(), java.util.concurrent.TimeUnit.MILLISECONDS)
632
+ frequencyBandExecutor = executor
633
+ }
634
+
580
635
  // ════════════════════════════════════════════════════════════════════
581
636
  // VolumeGuard
582
637
  // ════════════════════════════════════════════════════════════════════
@@ -92,6 +92,7 @@ class PipelineIntegration(
92
92
  const val EVENT_DRAINED = "PipelineDrained"
93
93
  const val EVENT_AUDIO_FOCUS_LOST = "PipelineAudioFocusLost"
94
94
  const val EVENT_AUDIO_FOCUS_RESUMED = "PipelineAudioFocusResumed"
95
+ const val EVENT_FREQUENCY_BANDS = "PipelineFrequencyBands"
95
96
  }
96
97
 
97
98
  private var pipeline: AudioPipeline? = null
@@ -116,12 +117,19 @@ class PipelineIntegration(
116
117
  val sampleRate = (options["sampleRate"] as? Number)?.toInt() ?: 24000
117
118
  val channelCount = (options["channelCount"] as? Number)?.toInt() ?: 1
118
119
  val targetBufferMs = (options["targetBufferMs"] as? Number)?.toInt() ?: 80
120
+ val frequencyBandIntervalMs = (options["frequencyBandIntervalMs"] as? Number)?.toInt() ?: 100
121
+ val bandConfig = options["frequencyBandConfig"] as? Map<*, *>
122
+ val lowCrossoverHz = (bandConfig?.get("lowCrossoverHz") as? Number)?.toFloat() ?: 300f
123
+ val highCrossoverHz = (bandConfig?.get("highCrossoverHz") as? Number)?.toFloat() ?: 2000f
119
124
 
120
125
  pipeline = AudioPipeline(
121
126
  context = context,
122
127
  sampleRate = sampleRate,
123
128
  channelCount = channelCount,
124
129
  targetBufferMs = targetBufferMs,
130
+ frequencyBandIntervalMs = frequencyBandIntervalMs,
131
+ lowCrossoverHz = lowCrossoverHz,
132
+ highCrossoverHz = highCrossoverHz,
125
133
  listener = this
126
134
  )
127
135
  pipeline!!.connect()
@@ -303,6 +311,14 @@ class PipelineIntegration(
303
311
  sendEvent(EVENT_AUDIO_FOCUS_RESUMED, Bundle())
304
312
  }
305
313
 
314
+ override fun onFrequencyBands(low: Float, mid: Float, high: Float) {
315
+ sendEvent(EVENT_FREQUENCY_BANDS, Bundle().apply {
316
+ putFloat("low", low)
317
+ putFloat("mid", mid)
318
+ putFloat("high", high)
319
+ })
320
+ }
321
+
306
322
  // ── Helper ──────────────────────────────────────────────────────────
307
323
 
308
324
  private fun sendEvent(eventName: String, params: Bundle) {
package/build/events.d.ts CHANGED
@@ -11,6 +11,11 @@ export interface AudioEventPayload {
11
11
  mimeType: string;
12
12
  streamUuid: string;
13
13
  soundLevel?: number;
14
+ frequencyBands?: {
15
+ low: number;
16
+ mid: number;
17
+ high: number;
18
+ };
14
19
  }
15
20
  export type SoundChunkPlayedEventPayload = {
16
21
  isFinal: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,KAAK,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAGzE,MAAM,MAAM,YAAY,GAAG,iBAAiB,CAAC;AAM7C,MAAM,WAAW,iBAAiB;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,4BAA4B,GAAG;IACzC,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,eAAO,MAAM,wBAAwB;;;;CAI3B,CAAC;AAEX,MAAM,MAAM,uBAAuB,GACjC,CAAC,OAAO,wBAAwB,CAAC,CAAC,MAAM,OAAO,wBAAwB,CAAC,CAAC;AAE3E,MAAM,MAAM,6BAA6B,GAAG;IAC1C,MAAM,EAAE,uBAAuB,CAAC;CACjC,CAAC;AAEF,eAAO,MAAM,WAAW;;;;;CAKvB,CAAC;AAEF,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,OAAO,CAAC,IAAI,CAAC,GACpD,iBAAiB,CAEnB;AAED,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,CAAC,KAAK,EAAE,4BAA4B,KAAK,OAAO,CAAC,IAAI,CAAC,GAC/D,iBAAiB,CAEnB;AAED,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,OAAO,EAChD,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,GAAG,SAAS,KAAK,OAAO,CAAC,IAAI,CAAC,GAChD,iBAAiB,CAEnB"}
1
+ {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,KAAK,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAGzE,MAAM,MAAM,YAAY,GAAG,iBAAiB,CAAC;AAM7C,MAAM,WAAW,iBAAiB;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CAC7D;AAED,MAAM,MAAM,4BAA4B,GAAG;IACzC,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,eAAO,MAAM,wBAAwB;;;;CAI3B,CAAC;AAEX,MAAM,MAAM,uBAAuB,GACjC,CAAC,OAAO,wBAAwB,CAAC,CAAC,MAAM,OAAO,wBAAwB,CAAC,CAAC;AAE3E,MAAM,MAAM,6BAA6B,GAAG;IAC1C,MAAM,EAAE,uBAAuB,CAAC;CACjC,CAAC;AAEF,eAAO,MAAM,WAAW;;;;;CAKvB,CAAC;AAEF,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,OAAO,CAAC,IAAI,CAAC,GACpD,iBAAiB,CAEnB;AAED,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,CAAC,KAAK,EAAE,4BAA4B,KAAK,OAAO,CAAC,IAAI,CAAC,GAC/D,iBAAiB,CAEnB;AAED,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,OAAO,EAChD,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,GAAG,SAAS,KAAK,OAAO,CAAC,IAAI,CAAC,GAChD,iBAAiB,CAEnB"}
@@ -1 +1 @@
1
- {"version":3,"file":"events.js","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA,2CAA2C;AAE3C,OAAO,EAAE,YAAY,EAA0B,MAAM,mBAAmB,CAAC;AAKzE,OAAO,yBAAyB,MAAM,6BAA6B,CAAC;AAEpE,MAAM,OAAO,GAAG,IAAI,YAAY,CAAC,yBAAyB,CAAC,CAAC;AAmB5D,MAAM,CAAC,MAAM,wBAAwB,GAAG;IACtC,kBAAkB,EAAE,oBAAoB;IACxC,oBAAoB,EAAE,sBAAsB;IAC5C,OAAO,EAAE,SAAS;CACV,CAAC;AASX,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,SAAS,EAAE,WAAW;IACtB,gBAAgB,EAAE,kBAAkB;IACpC,YAAY,EAAE,cAAc;IAC5B,iBAAiB,EAAE,mBAAmB;CACvC,CAAC;AAEF,MAAM,UAAU,qBAAqB,CACnC,QAAqD;IAErD,OAAQ,OAAe,CAAC,WAAW,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;AAC7D,CAAC;AAED,MAAM,UAAU,2BAA2B,CACzC,QAAgE;IAEhE,OAAQ,OAAe,CAAC,WAAW,CAAC,kBAAkB,EAAE,QAAQ,CAAC,CAAC;AACpE,CAAC;AAED,MAAM,UAAU,gBAAgB,CAC9B,SAAiB,EACjB,QAAiD;IAEjD,OAAQ,OAAe,CAAC,WAAW,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;AAC3D,CAAC","sourcesContent":["// packages/expo-audio-stream/src/events.ts\n\nimport { EventEmitter, type EventSubscription } from \"expo-modules-core\";\n\n// Type alias for backwards compatibility\nexport type Subscription = EventSubscription;\n\nimport ExpoPlayAudioStreamModule from \"./ExpoPlayAudioStreamModule\";\n\nconst emitter = new EventEmitter(ExpoPlayAudioStreamModule);\n\nexport interface AudioEventPayload {\n encoded?: string;\n buffer?: Float32Array;\n fileUri: string;\n lastEmittedSize: number;\n position: number;\n deltaSize: number;\n totalSize: number;\n mimeType: string;\n streamUuid: string;\n soundLevel?: number;\n}\n\nexport type SoundChunkPlayedEventPayload = {\n isFinal: boolean;\n};\n\nexport const DeviceReconnectedReasons = {\n newDeviceAvailable: \"newDeviceAvailable\",\n oldDeviceUnavailable: \"oldDeviceUnavailable\",\n unknown: \"unknown\",\n} as const;\n\nexport type DeviceReconnectedReason =\n (typeof DeviceReconnectedReasons)[keyof typeof DeviceReconnectedReasons];\n\nexport type DeviceReconnectedEventPayload = {\n reason: DeviceReconnectedReason;\n};\n\nexport const AudioEvents = {\n AudioData: \"AudioData\",\n SoundChunkPlayed: \"SoundChunkPlayed\",\n SoundStarted: \"SoundStarted\",\n DeviceReconnected: \"DeviceReconnected\",\n};\n\nexport function addAudioEventListener(\n listener: (event: AudioEventPayload) => Promise<void>\n): EventSubscription {\n return (emitter as any).addListener(\"AudioData\", listener);\n}\n\nexport function addSoundChunkPlayedListener(\n listener: (event: SoundChunkPlayedEventPayload) => Promise<void>\n): EventSubscription {\n return (emitter as any).addListener(\"SoundChunkPlayed\", listener);\n}\n\nexport function subscribeToEvent<T extends unknown>(\n eventName: string,\n listener: (event: T | undefined) => Promise<void>\n): EventSubscription {\n return (emitter as any).addListener(eventName, listener);\n}\n"]}
1
+ {"version":3,"file":"events.js","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA,2CAA2C;AAE3C,OAAO,EAAE,YAAY,EAA0B,MAAM,mBAAmB,CAAC;AAKzE,OAAO,yBAAyB,MAAM,6BAA6B,CAAC;AAEpE,MAAM,OAAO,GAAG,IAAI,YAAY,CAAC,yBAAyB,CAAC,CAAC;AAoB5D,MAAM,CAAC,MAAM,wBAAwB,GAAG;IACtC,kBAAkB,EAAE,oBAAoB;IACxC,oBAAoB,EAAE,sBAAsB;IAC5C,OAAO,EAAE,SAAS;CACV,CAAC;AASX,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,SAAS,EAAE,WAAW;IACtB,gBAAgB,EAAE,kBAAkB;IACpC,YAAY,EAAE,cAAc;IAC5B,iBAAiB,EAAE,mBAAmB;CACvC,CAAC;AAEF,MAAM,UAAU,qBAAqB,CACnC,QAAqD;IAErD,OAAQ,OAAe,CAAC,WAAW,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;AAC7D,CAAC;AAED,MAAM,UAAU,2BAA2B,CACzC,QAAgE;IAEhE,OAAQ,OAAe,CAAC,WAAW,CAAC,kBAAkB,EAAE,QAAQ,CAAC,CAAC;AACpE,CAAC;AAED,MAAM,UAAU,gBAAgB,CAC9B,SAAiB,EACjB,QAAiD;IAEjD,OAAQ,OAAe,CAAC,WAAW,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;AAC3D,CAAC","sourcesContent":["// packages/expo-audio-stream/src/events.ts\n\nimport { EventEmitter, type EventSubscription } from \"expo-modules-core\";\n\n// Type alias for backwards compatibility\nexport type Subscription = EventSubscription;\n\nimport ExpoPlayAudioStreamModule from \"./ExpoPlayAudioStreamModule\";\n\nconst emitter = new EventEmitter(ExpoPlayAudioStreamModule);\n\nexport interface AudioEventPayload {\n encoded?: string;\n buffer?: Float32Array;\n fileUri: string;\n lastEmittedSize: number;\n position: number;\n deltaSize: number;\n totalSize: number;\n mimeType: string;\n streamUuid: string;\n soundLevel?: number;\n frequencyBands?: { low: number; mid: number; high: number };\n}\n\nexport type SoundChunkPlayedEventPayload = {\n isFinal: boolean;\n};\n\nexport const DeviceReconnectedReasons = {\n newDeviceAvailable: \"newDeviceAvailable\",\n oldDeviceUnavailable: \"oldDeviceUnavailable\",\n unknown: \"unknown\",\n} as const;\n\nexport type DeviceReconnectedReason =\n (typeof DeviceReconnectedReasons)[keyof typeof DeviceReconnectedReasons];\n\nexport type DeviceReconnectedEventPayload = {\n reason: DeviceReconnectedReason;\n};\n\nexport const AudioEvents = {\n AudioData: \"AudioData\",\n SoundChunkPlayed: \"SoundChunkPlayed\",\n SoundStarted: \"SoundStarted\",\n DeviceReconnected: \"DeviceReconnected\",\n};\n\nexport function addAudioEventListener(\n listener: (event: AudioEventPayload) => Promise<void>\n): EventSubscription {\n return (emitter as any).addListener(\"AudioData\", listener);\n}\n\nexport function addSoundChunkPlayedListener(\n listener: (event: SoundChunkPlayedEventPayload) => Promise<void>\n): EventSubscription {\n return (emitter as any).addListener(\"SoundChunkPlayed\", listener);\n}\n\nexport function subscribeToEvent<T extends unknown>(\n eventName: string,\n listener: (event: T | undefined) => Promise<void>\n): EventSubscription {\n return (emitter as any).addListener(eventName, listener);\n}\n"]}
package/build/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { EventSubscription } from "expo-modules-core";
2
2
  type Subscription = EventSubscription;
3
- import { AudioDataEvent, AudioRecording, RecordingConfig, StartRecordingResult, SoundConfig, PlaybackMode, Encoding, EncodingTypes, PlaybackModes, IAudioBufferConfig, IAudioPlayPayload, IAudioFrame, BufferHealthState, IBufferHealthMetrics, IAudioBufferManager, IFrameProcessor, IQualityMonitor, BufferedStreamConfig, SmartBufferConfig, SmartBufferMode, NetworkConditions } from "./types";
3
+ import { AudioDataEvent, AudioRecording, RecordingConfig, StartRecordingResult, SoundConfig, PlaybackMode, Encoding, EncodingTypes, FrequencyBands, PlaybackModes, IAudioBufferConfig, IAudioPlayPayload, IAudioFrame, BufferHealthState, IBufferHealthMetrics, IAudioBufferManager, IFrameProcessor, IQualityMonitor, BufferedStreamConfig, SmartBufferConfig, SmartBufferMode, NetworkConditions } from "./types";
4
4
  import { SoundChunkPlayedEventPayload, AudioEvents, DeviceReconnectedReason, DeviceReconnectedEventPayload } from "./events";
5
5
  declare const SuspendSoundEventTurnId = "suspend-sound-events";
6
6
  export declare class ExpoPlayAudioStream {
@@ -117,7 +117,7 @@ export declare class ExpoPlayAudioStream {
117
117
  status?: string;
118
118
  }>;
119
119
  }
120
- export { AudioDataEvent, SoundChunkPlayedEventPayload, DeviceReconnectedReason, DeviceReconnectedEventPayload, AudioRecording, RecordingConfig, StartRecordingResult, AudioEvents, SuspendSoundEventTurnId, SoundConfig, PlaybackMode, Encoding, EncodingTypes, PlaybackModes, IAudioBufferConfig, IAudioPlayPayload, IAudioFrame, BufferHealthState, IBufferHealthMetrics, IAudioBufferManager, IFrameProcessor, IQualityMonitor, BufferedStreamConfig, SmartBufferConfig, SmartBufferMode, NetworkConditions, };
120
+ export { AudioDataEvent, SoundChunkPlayedEventPayload, DeviceReconnectedReason, DeviceReconnectedEventPayload, AudioRecording, RecordingConfig, StartRecordingResult, AudioEvents, SuspendSoundEventTurnId, SoundConfig, PlaybackMode, Encoding, EncodingTypes, FrequencyBands, PlaybackModes, IAudioBufferConfig, IAudioPlayPayload, IAudioFrame, BufferHealthState, IBufferHealthMetrics, IAudioBufferManager, IFrameProcessor, IQualityMonitor, BufferedStreamConfig, SmartBufferConfig, SmartBufferMode, NetworkConditions, };
121
121
  export type { EventSubscription } from "expo-modules-core";
122
122
  export type { Subscription } from "./events";
123
123
  export { Pipeline } from "./pipeline";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/build/index.js CHANGED
@@ -72,7 +72,7 @@ export class ExpoPlayAudioStream {
72
72
  const { onAudioStream, ...options } = recordingConfig;
73
73
  if (onAudioStream && typeof onAudioStream == "function") {
74
74
  subscription = addAudioEventListener(async (event) => {
75
- const { fileUri, deltaSize, totalSize, position, encoded, soundLevel, } = event;
75
+ const { fileUri, deltaSize, totalSize, position, encoded, soundLevel, frequencyBands, } = event;
76
76
  if (!encoded) {
77
77
  console.error(`[ExpoPlayAudioStream] Encoded audio data is missing`);
78
78
  throw new Error("Encoded audio data is missing");
@@ -84,6 +84,7 @@ export class ExpoPlayAudioStream {
84
84
  eventDataSize: deltaSize,
85
85
  totalSize,
86
86
  soundLevel,
87
+ frequencyBands,
87
88
  });
88
89
  });
89
90
  }
@@ -125,7 +126,7 @@ export class ExpoPlayAudioStream {
125
126
  */
126
127
  static subscribeToAudioEvents(onMicrophoneStream) {
127
128
  return addAudioEventListener(async (event) => {
128
- const { fileUri, deltaSize, totalSize, position, encoded, soundLevel } = event;
129
+ const { fileUri, deltaSize, totalSize, position, encoded, soundLevel, frequencyBands } = event;
129
130
  if (!encoded) {
130
131
  console.error(`[ExpoPlayAudioStream] Encoded audio data is missing`);
131
132
  throw new Error("Encoded audio data is missing");
@@ -137,6 +138,7 @@ export class ExpoPlayAudioStream {
137
138
  eventDataSize: deltaSize,
138
139
  totalSize,
139
140
  soundLevel,
141
+ frequencyBands,
140
142
  });
141
143
  });
142
144
  }