npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-native-audio-intelligence

v1.1.0

Published

On-device audio analysis for React Native — WAV decoding, DSP (RMS, transients), ONNX ML event detection, prominence scoring, and highlight selection via Nitro Modules

Readme

react-native-audio-intelligence

Native audio analysis for React Native via Nitro Modules. Decodes WAV files on-device, extracts DSP features (RMS, transient density, windowing), detects content heuristics, scores prominence, and selects highlight segments — all without leaving the JS thread blocked.

Features

  • WAV decoding — iOS via AVFoundation; Android via a built-in 16-bit PCM parser
  • Overlapping window analysis — 1.5 s windows with 0.75 s hop
  • Transient density — per-window sharp energy-change detection (transients/s)
  • Event detection — ONNX Runtime inference (audio_classifier.onnx) with DSP heuristic fallback: speech, music, impact, ambient, silence
  • Prominence scoring — weighted score in [0, 1] with quality penalties
  • Highlight selection — up to 3 non-overlapping peak windows
  • Synchronous version info — getVersion() and getPlatformInfo() run on the native thread

Requirements

  • React Native ≥ 0.76 (tested on 0.85)
  • react-native-nitro-modules ≥ 0.35.2
  • ONNX Runtime (installed automatically via CocoaPods on iOS and Gradle on Android)
    • iOS: onnxruntime-objc ~> 1.22.0
    • Android: onnxruntime-android 1.22.0

Installation

npm install react-native-audio-intelligence react-native-nitro-modules
# or
yarn add react-native-audio-intelligence react-native-nitro-modules

Then rebuild the native app:

npx pod-install   # iOS
npx react-native run-ios
npx react-native run-android

Quick start

import AudioIntelligence from 'react-native-audio-intelligence'

const version = AudioIntelligence.getVersion()
// => "1.1.0"

const result = await AudioIntelligence.analyzeAudioFile('/path/to/audio.wav')
console.log(result.score) // 0.0 – 1.0 prominence
console.log(result.detectedEvents) // [{ label: "speech", confidence: 0.47 }, ...]
console.log(result.windows.length) // overlapping analysis windows

The file path must be an absolute path readable by native code. React Native asset bundles are not supported directly — copy or record to a temp path first.

Platform file paths

| Platform | Example path | Notes | | -------- | -------------------------- | ----------------------------------------------- | | iOS | /tmp/real_test.wav | Simulator/device temp directory | | Android | /data/local/tmp/test.wav | Push with adb push audio.wav /data/local/tmp/ |

# Push a test file to an Android device/emulator
adb push ./fixtures/test.wav /data/local/tmp/test_transient.wav

API

getVersion(): string

Returns the native module version synchronously.

getPlatformInfo(): PlatformInfo

Returns the host OS name and version.

{ platform: 'ios' | 'android', version: string }

analyzeAudioFile(path: string): Promise<AudioAnalysisResult>

Analyzes a WAV file at the given absolute path. Resolves with a full result object; rejects if the file is missing, unreadable, or not a supported format.

Supported formats

  • iOS: any format AVFoundation can decode (WAV, AAC, MP3, etc.)
  • Android: 16-bit PCM WAV only

Types

AudioAnalysisResult

| Field | Type | Description | | ---------------- | ----------------- | -------------------------------------------------- | | score | number | Overall prominence score, clamped to [0, 1] | | tags | string[] | Top-level tags (e.g. ["voice"], ["silence"]) | | detectedEvents | DetectedEvent[] | Heuristic content detections, sorted by confidence | | highlights | Highlight[] | Up to 3 peak windows suitable for preview clips | | quality | AudioQuality | Clipping, silence, and wind-noise flags | | windows | AudioWindow[] | Overlapping per-window DSP metrics |

AudioWindow

| Field | Type | Description | | ------------------ | -------- | ----------------------------------- | | startMs | number | Window start offset in milliseconds | | durationMs | number | Window length in milliseconds | | rms | number | Root-mean-square energy [0, 1] | | transientDensity | number | Sharp energy rises per second |

DetectedEvent

{
  label: string
  confidence: number
} // confidence in [0, 1]

Labels: speech, music, impact, ambient, silence.

Highlight

{ startMs: number; durationMs: number; score: number; tags: string[] }

AudioQuality

{
  clipping: boolean
  windNoise: boolean
  silence: boolean
}

Example

A minimal screen that runs analysis on launch and renders the results inline. Adjust TEST_WAV_PATH for your platform.

import React, { useCallback, useEffect, useState } from 'react'
import {
  ActivityIndicator,
  Platform,
  ScrollView,
  StyleSheet,
  Text,
  View,
} from 'react-native'
import AudioIntelligence, {
  type AudioAnalysisResult,
} from 'react-native-audio-intelligence'

const TEST_WAV_PATH =
  Platform.OS === 'ios'
    ? '/tmp/real_test.wav'
    : '/data/local/tmp/test_transient.wav'

const LOG = '[AudioIntelligence]'

function logAnalysis(result: AudioAnalysisResult, platform: string) {
  console.log(`${LOG} ── Analysis complete (${platform}) ──`)
  console.log(`${LOG} Prominence score: ${result.score.toFixed(5)}`)
  console.log(`${LOG} Tags: ${result.tags.join(', ') || '—'}`)
  console.log(
    `${LOG} Quality: clipping=${result.quality.clipping} ` +
      `silence=${result.quality.silence} wind=${result.quality.windNoise}`
  )
  console.log(`${LOG} Windows (${result.windows.length}):`)
  result.windows.forEach((w, i) => {
    console.log(
      `${LOG}   [${i}] ${w.startMs.toFixed(0)}ms–` +
        `${(w.startMs + w.durationMs).toFixed(0)}ms  ` +
        `rms=${w.rms.toFixed(4)}  transients=${w.transientDensity.toFixed(2)}/s`
    )
  })
  if (result.highlights.length > 0) {
    console.log(`${LOG} Highlights (${result.highlights.length}):`)
    result.highlights.forEach((h, i) => {
      console.log(
        `${LOG}   [${i}] ${h.startMs.toFixed(0)}ms  score=${h.score.toFixed(3)}`
      )
    })
  }
  console.log(`${LOG} Events (${result.detectedEvents.length}):`)
  result.detectedEvents.forEach((e, i) => {
    console.log(
      `${LOG}   [${i}] ${e.label} (${(e.confidence * 100).toFixed(1)}%)`
    )
  })
}

function formatPercent(value: number) {
  return `${(value * 100).toFixed(1)}%`
}

export default function App() {
  const [status, setStatus] = useState<'idle' | 'loading' | 'done' | 'error'>(
    'idle'
  )
  const [error, setError] = useState<string | null>(null)
  const [result, setResult] = useState<AudioAnalysisResult | null>(null)
  const [version, setVersion] = useState<string | null>(null)

  const runAnalysis = useCallback(async () => {
    setStatus('loading')
    setError(null)
    setResult(null)

    try {
      const v = AudioIntelligence.getVersion()
      setVersion(v)
      console.log(`${LOG} Module v${v} · ${Platform.OS}`)
      console.log(`${LOG} File: ${TEST_WAV_PATH}`)

      const analysis = await AudioIntelligence.analyzeAudioFile(TEST_WAV_PATH)
      logAnalysis(analysis, Platform.OS)
      setResult(analysis)
      setStatus('done')
    } catch (e) {
      const message = e instanceof Error ? e.message : String(e)
      console.error(`${LOG} Analysis failed:`, message)
      setError(message)
      setStatus('error')
    }
  }, [])

  useEffect(() => {
    runAnalysis()
  }, [runAnalysis])

  return (
    <View style={styles.root}>
      <View style={styles.header}>
        <Text style={styles.title}>Audio Intelligence</Text>
        {version && <Text style={styles.subtitle}>v{version}</Text>}
      </View>

      {status === 'loading' && (
        <View style={styles.centered}>
          <ActivityIndicator size="large" color="#6C8EFF" />
          <Text style={styles.muted}>Analyzing audio…</Text>
        </View>
      )}

      {status === 'error' && (
        <View style={styles.card}>
          <Text style={styles.errorLabel}>Analysis failed</Text>
          <Text style={styles.errorText}>{error}</Text>
        </View>
      )}

      {result && (
        <ScrollView
          style={styles.scroll}
          contentContainerStyle={styles.scrollContent}
        >
          <View style={styles.scoreCard}>
            <Text style={styles.scoreLabel}>Prominence</Text>
            <Text style={styles.scoreValue}>{formatPercent(result.score)}</Text>
            <Text style={styles.tags}>{result.tags.join(' · ')}</Text>
          </View>

          <Section title={`Events (${result.detectedEvents.length})`}>
            {result.detectedEvents.map((e, i) => (
              <Row
                key={`${e.label}-${i}`}
                label={e.label}
                value={formatPercent(e.confidence)}
              />
            ))}
          </Section>

          <Section title={`Windows (${result.windows.length})`}>
            {result.windows.map((w, i) => (
              <Row
                key={i}
                label={`${w.startMs.toFixed(0)}ms`}
                value={`rms ${w.rms.toFixed(3)} · ${w.transientDensity.toFixed(1)}/s`}
              />
            ))}
          </Section>

          {result.highlights.length > 0 && (
            <Section title={`Highlights (${result.highlights.length})`}>
              {result.highlights.map((h, i) => (
                <Row
                  key={i}
                  label={`${h.startMs.toFixed(0)}ms`}
                  value={formatPercent(h.score)}
                />
              ))}
            </Section>
          )}

          <Section title="Quality">
            <Row
              label="Clipping"
              value={result.quality.clipping ? 'yes' : 'no'}
            />
            <Row
              label="Silence"
              value={result.quality.silence ? 'yes' : 'no'}
            />
            <Row
              label="Wind noise"
              value={result.quality.windNoise ? 'yes' : 'no'}
            />
          </Section>
        </ScrollView>
      )}
    </View>
  )
}

function Section({
  title,
  children,
}: {
  title: string
  children: React.ReactNode
}) {
  return (
    <View style={styles.section}>
      <Text style={styles.sectionTitle}>{title}</Text>
      <View style={styles.sectionBody}>{children}</View>
    </View>
  )
}

function Row({ label, value }: { label: string; value: string }) {
  return (
    <View style={styles.row}>
      <Text style={styles.rowLabel}>{label}</Text>
      <Text style={styles.rowValue}>{value}</Text>
    </View>
  )
}

const styles = StyleSheet.create({
  root: {
    flex: 1,
    backgroundColor: '#0D0F14',
  },
  header: {
    paddingTop: 60,
    paddingBottom: 20,
    paddingHorizontal: 24,
    borderBottomWidth: StyleSheet.hairlineWidth,
    borderBottomColor: '#1E2230',
  },
  title: {
    fontSize: 28,
    fontWeight: '700',
    color: '#F0F2F8',
    letterSpacing: -0.5,
  },
  subtitle: {
    marginTop: 4,
    fontSize: 14,
    color: '#6C8EFF',
    fontWeight: '500',
  },
  centered: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    gap: 12,
  },
  muted: {
    color: '#6B7280',
    fontSize: 15,
  },
  scroll: {
    flex: 1,
  },
  scrollContent: {
    padding: 20,
    gap: 16,
    paddingBottom: 40,
  },
  scoreCard: {
    backgroundColor: '#161A24',
    borderRadius: 16,
    padding: 24,
    alignItems: 'center',
    borderWidth: 1,
    borderColor: '#1E2230',
  },
  scoreLabel: {
    fontSize: 13,
    fontWeight: '600',
    color: '#6B7280',
    textTransform: 'uppercase',
    letterSpacing: 1,
  },
  scoreValue: {
    fontSize: 48,
    fontWeight: '700',
    color: '#6C8EFF',
    marginTop: 4,
  },
  tags: {
    marginTop: 8,
    fontSize: 14,
    color: '#9CA3AF',
  },
  section: {
    backgroundColor: '#161A24',
    borderRadius: 12,
    overflow: 'hidden',
    borderWidth: 1,
    borderColor: '#1E2230',
  },
  sectionTitle: {
    fontSize: 13,
    fontWeight: '600',
    color: '#6B7280',
    textTransform: 'uppercase',
    letterSpacing: 0.8,
    paddingHorizontal: 16,
    paddingTop: 14,
    paddingBottom: 8,
  },
  sectionBody: {
    paddingBottom: 4,
  },
  row: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    paddingHorizontal: 16,
    paddingVertical: 11,
    borderTopWidth: StyleSheet.hairlineWidth,
    borderTopColor: '#1E2230',
  },
  rowLabel: {
    fontSize: 15,
    color: '#D1D5DB',
    textTransform: 'capitalize',
  },
  rowValue: {
    fontSize: 15,
    color: '#9CA3AF',
    fontVariant: ['tabular-nums'],
  },
  card: {
    margin: 20,
    backgroundColor: '#1C1014',
    borderRadius: 12,
    padding: 20,
    borderWidth: 1,
    borderColor: '#3B1C24',
  },
  errorLabel: {
    fontSize: 16,
    fontWeight: '600',
    color: '#F87171',
    marginBottom: 8,
  },
  errorText: {
    fontSize: 14,
    color: '#FCA5A5',
    lineHeight: 20,
  },
})

Sample console output

[AudioIntelligence] Module v0.3.0 · android
[AudioIntelligence] File: /data/local/tmp/test_transient.wav
[AudioIntelligence] ── Analysis complete (android) ──
[AudioIntelligence] Prominence score: 0.38922
[AudioIntelligence] Tags: voice
[AudioIntelligence] Quality: clipping=false silence=false wind=false
[AudioIntelligence] Windows (4):
[AudioIntelligence]   [0] 0ms–1500ms  rms=0.0413  transients=0.67/s
[AudioIntelligence]   [1] 750ms–2250ms  rms=0.0598  transients=1.33/s
[AudioIntelligence]   [2] 1500ms–3000ms  rms=0.0890  transients=1.33/s
[AudioIntelligence]   [3] 2250ms–3000ms  rms=0.1099  transients=1.33/s
[AudioIntelligence] Events (1):
[AudioIntelligence]   [0] speech (47.0%)

How scoring works

The prominence score combines five weighted components:

| Component | Weight | Source | | ------------------ | ------ | ---------------------------------------- | | Volume | 0.35 | Whole-file RMS | | Transients | 0.25 | Average transient density across windows | | Frequency richness | 0.20 | RMS variance across windows | | Voice presence | 0.10 | speech event confidence | | Contrast | 0.10 | Peak-to-floor RMS ratio across windows |

Penalties are applied for clipping (−0.15), silence (−0.50), and wind noise (−0.10). The final value is clamped to [0, 1].

Event detection runs ONNX Runtime inference using the bundled audio_classifier.onnx model (mono 16 kHz input). If the model cannot be loaded or inference fails, the module falls back to DSP heuristics so analysis still completes.

Error handling

analyzeAudioFile rejects when:

  • The file does not exist at the given path
  • The file cannot be decoded (unsupported format, corrupt data)
  • On Android: the file is not 16-bit PCM WAV

Always use a try/catch (or .catch()) around the promise and surface the error message to the user.

License

MIT