Summary
NativeSdkSpectrumComputeBands in src/platform/macos/appkit_host.m crashes with an index-out-of-bounds panic when the audio sample rate is low enough that the fixed 50 Hz–16 kHz band range extends past the Nyquist frequency (e.g. 16 kHz sample rate, common for VoIP/telephony audio).
thread 9178883 panic: index 1036 out of bounds for type 'float[1024]'
src/platform/macos/appkit_host.m:8595:17 in NativeSdkSpectrumComputeBands
if (power[bin] > peak) peak = power[bin];
src/platform/macos/appkit_host.m:8743:10 in -[NativeSdkAppKitHost audioSpectrumTimerFired:]
if (!NativeSdkSpectrumComputeBands(state, self.audioSpectrumFft, window, event.audio_bands)) return;
Environment
@native-sdk/cli 0.4.0 (@native-sdk/cli-darwin-arm64)
- macOS (Darwin 25.5.0), Apple Silicon
- Crash observed while running an app via
native dev with audio playing at a 16 kHz sample rate (SIP/VoIP call audio)
Root cause
In the band-bucketing loop (appkit_host.m around line 8586):
int low_bin = (int)(low_hz / hz_per_bin);
int high_bin = (int)ceil(high_hz / hz_per_bin);
if (low_bin < 1) low_bin = 1;
if (high_bin > NATIVE_SDK_SPECTRUM_FFT_SIZE / 2 - 1) high_bin = NATIVE_SDK_SPECTRUM_FFT_SIZE / 2 - 1;
if (high_bin < low_bin) high_bin = low_bin; // <-- undoes the clamp above
float peak = 0.0f;
for (int bin = low_bin; bin <= high_bin; bin += 1) {
if (power[bin] > peak) peak = power[bin]; // <-- OOB read
}
Two problems combine:
low_bin is clamped only at the bottom (< 1), never against the top of the power[FFT_SIZE / 2] array.
if (high_bin < low_bin) high_bin = low_bin; re-raises high_bin above the top clamp applied on the previous line.
With sample_rate = 16000 and FFT_SIZE = 2048, hz_per_bin ≈ 7.8125. Bands whose low_hz exceeds the 8 kHz Nyquist produce low_bin ≥ 1024 (first offender lands at 1036, matching the panic), high_bin is clamped to 1023, then re-raised to low_bin, and the peak loop reads power[1036] out of bounds.
Any sample rate below 32 kHz can trigger this, since the top band edge is fixed at NATIVE_SDK_SPECTRUM_HIGH_HZ = 16 kHz.
Reproduction
- Build any app that enables the audio spectrum feed.
- Play audio whose engine sample rate is 16 kHz (e.g. wideband VoIP audio).
- Wait for
audioSpectrumTimerFired: to run once enough samples are buffered — the app panics.
Suggested fix
Clamp low_bin against the top of the array as well, and skip bands entirely above Nyquist rather than folding them onto the last bin, e.g.:
const int max_bin = NATIVE_SDK_SPECTRUM_FFT_SIZE / 2 - 1;
int low_bin = (int)(low_hz / hz_per_bin);
int high_bin = (int)ceil(high_hz / hz_per_bin);
if (low_bin < 1) low_bin = 1;
if (low_bin > max_bin) { // band entirely above Nyquist
bands[band] = 0; // silence, matches the floor-dB mapping
continue;
}
if (high_bin > max_bin) high_bin = max_bin;
if (high_bin < low_bin) high_bin = low_bin;
Summary
NativeSdkSpectrumComputeBandsinsrc/platform/macos/appkit_host.mcrashes with an index-out-of-bounds panic when the audio sample rate is low enough that the fixed 50 Hz–16 kHz band range extends past the Nyquist frequency (e.g. 16 kHz sample rate, common for VoIP/telephony audio).Environment
@native-sdk/cli0.4.0 (@native-sdk/cli-darwin-arm64)native devwith audio playing at a 16 kHz sample rate (SIP/VoIP call audio)Root cause
In the band-bucketing loop (
appkit_host.maround line 8586):Two problems combine:
low_binis clamped only at the bottom (< 1), never against the top of thepower[FFT_SIZE / 2]array.if (high_bin < low_bin) high_bin = low_bin;re-raiseshigh_binabove the top clamp applied on the previous line.With
sample_rate = 16000andFFT_SIZE = 2048,hz_per_bin ≈ 7.8125. Bands whoselow_hzexceeds the 8 kHz Nyquist producelow_bin ≥ 1024(first offender lands at 1036, matching the panic),high_binis clamped to 1023, then re-raised tolow_bin, and the peak loop readspower[1036]out of bounds.Any sample rate below 32 kHz can trigger this, since the top band edge is fixed at
NATIVE_SDK_SPECTRUM_HIGH_HZ= 16 kHz.Reproduction
audioSpectrumTimerFired:to run once enough samples are buffered — the app panics.Suggested fix
Clamp
low_binagainst the top of the array as well, and skip bands entirely above Nyquist rather than folding them onto the last bin, e.g.: