ToneListen Logo

ToneListen

Client Portal

ToneListen React Native SDK

Complete guide to integrating audio tone detection in your React Native applications

Overview & Features

🎵 Real-time Audio Processing

High-performance audio analysis using Web Audio API with Goertzel algorithm for efficient frequency detection.

🔊 Dual Tone Detection

Recognizes DTMF tone pairs with configurable tolerance and bridge tone validation for sequence accuracy.

📱 Cross-Platform Support

Works seamlessly on iOS, Android, and Web platforms with React Native 0.60+ support.

🎯 In-App Content Delivery

Automatic content display with customizable UI components for images, videos, and web pages.

🔧 TypeScript Support

Full TypeScript definitions included for better development experience and type safety.

🔐 Secure API Integration

Built-in API service with user-specific authentication and client validation.

Technical Specifications

  • Algorithm: Goertzel algorithm with Hanning windowing
  • Sample Rate: 44.1kHz (configurable)
  • Buffer Size: 4800 samples (configurable)
  • Tolerance: ±5Hz frequency matching (configurable)
  • Platform Support: iOS 11.0+, Android API 21+, React Native 0.60+

Installation & Setup

Step 1: Install the Package

bash
npm install tonelisten-react-native

Step 2: Install Peer Dependencies

bash
npm install react-native-audio-recorder-player react-native-permissions

Step 3: iOS Setup

bash
cd ios && pod install

Step 4: Configure Permissions

Quick Start Guide

1. Basic Setup

typescript
import React, { useEffect } from 'react';
import { ToneListenReactNative } from 'tonelisten-react-native';

const App = () => {
  useEffect(() => {
    const toneListener = new ToneListenReactNative({
      clientId: '562', // Your client ID
      apiKey: 'your-user-specific-api-key', // Get from admin dashboard
      baseURL: 'https://api.toneadmin.com',
      debug: true,
      enableInAppContent: true,
    });

    // Initialize and start
    toneListener.initialize().then(() => {
      toneListener.start();
    });

    return () => {
      toneListener.destroy();
    };
  }, []);

  return <YourAppComponent />;
};

2. Automatic Content Display

typescript
import React, { useEffect, useState } from 'react';
import { View } from 'react-native';
import ToneListenReactNative, { 
  InAppContentModal, 
  NotificationCenter 
} from 'tonelisten-react-native';

const App = () => {
  const [modalVisible, setModalVisible] = useState(false);
  const [modalContent, setModalContent] = useState(null);

  useEffect(() => {
    // Set up automatic content display
    const notificationCenter = NotificationCenter.getInstance();
    notificationCenter.addObserver('InAppContentReceived', (content) => {
      setModalContent(content);
      setModalVisible(true);
    });

    // Initialize ToneListen
    const toneListener = new ToneListenReactNative({
      clientId: '562',
      apiKey: 'your-api-key',
      baseURL: 'https://api.toneadmin.com',
      enableInAppContent: true,
    });

    toneListener.initialize().then(() => {
      toneListener.start();
    });

    return () => {
      toneListener.destroy();
      notificationCenter.removeObserver('InAppContentReceived');
    };
  }, []);

  return (
    <View style={{ flex: 1 }}>
      {/* Your app content */}
      <InAppContentModal 
        visible={modalVisible} 
        content={modalContent} 
        onClose={() => setModalVisible(false)} 
      />
    </View>
  );
};

Basic Usage Examples

typescript
import React from 'react';
import { View, Text, Button } from 'react-native';
import { useToneDetection } from 'tonelisten-react-native';

const ToneDetectionScreen = () => {
  const {
    isListening,
    isInitialized,
    lastSequence,
    startListening,
    stopListening,
    error
  } = useToneDetection({
    clientId: '562',
    apiKey: 'your-api-key',
    baseURL: 'https://api.toneadmin.com',
    enableInAppContent: true,
    onSequence: (event) => {
      console.log('Sequence detected:', event.sequence);
    }
  });

  return (
    <View style={{ padding: 20 }}>
      <Text>Status: {isListening ? 'Listening' : 'Stopped'}</Text>
      {lastSequence && <Text>Last Sequence: {lastSequence}</Text>}
      {error && <Text>Error: {error.message}</Text>}
      <Button
        title={isListening ? 'Stop' : 'Start'}
        onPress={isListening ? stopListening : startListening}
        disabled={!isInitialized}
      />
    </View>
  );
};

Advanced Features

Define custom tone sequences for specific use cases:

typescript
import { CustomToneManager } from 'tonelisten-react-native';

const customToneManager = new CustomToneManager();

// Add custom tone sequence
customToneManager.addCustomSequence({
  id: 'custom-sequence-1',
  frequencies: [697, 1209], // DTMF '1'
  duration: 1000,
  callback: (sequence) => {
    console.log('Custom sequence detected:', sequence);
    // Handle custom sequence
  }
});

// Remove custom sequence
customToneManager.removeCustomSequence('custom-sequence-1');

API Reference

constructor(config: ToneListenReactNativeConfig)

Creates a new ToneListenReactNative instance with the specified configuration.

initialize(): Promise<void>

Initializes the tone detection system. Must be called before starting detection.

start(): void

Starts listening for tone sequences. Requires microphone permission.

stop(): void

Stops listening for tone sequences.

updateConfig(config: Partial<ToneListenReactNativeConfig>): void

Updates the configuration of the tone listener.

getState(): ToneDetectionState

Returns the current state of the tone detection system.

destroy(): void

Cleans up resources and stops all detection processes.

Configuration Options

OptionTypeDefaultDescription
clientIdstringrequiredYour client ID from the admin dashboard
apiKeystringrequiredUser-specific API key from admin dashboard
baseURLstring'https://api.toneadmin.com'Base URL for API requests
sampleRatenumber44100Audio sample rate in Hz
bufferSizenumber4800Audio buffer size in samples
tolerancenumber5Frequency matching tolerance in Hz
bridgeTolerancenumber10Bridge tone tolerance in Hz
minPowernumber0.01Minimum power threshold
debugbooleanfalseEnable debug logging
enableInAppContentbooleanfalseEnable automatic content display
autoStartbooleanfalseStart automatically after initialization

Permissions Setup

iOS Setup

Add microphone usage description to Info.plist:

xml
<key>NSMicrophoneUsageDescription</key>
<string>This app needs access to microphone for tone detection</string>

Android Setup

Add microphone permission to AndroidManifest.xml:

xml
<uses-permission android:name="android.permission.RECORD_AUDIO" />

Runtime Permission Handling

The SDK automatically handles runtime permission requests, but you can customize the behavior:

typescript
const toneListener = new ToneListenReactNative({
  clientId: '562',
  apiKey: 'your-api-key',
  basePermissionDenied: () => {
    console.log('Microphone permission denied');
    // Show custom permission dialog
    Alert.alert(
      'Permission Required',
      'This app needs microphone access to detect tones.',
      [
        { text: 'Cancel', style: 'cancel' },
        { text: 'Settings', onPress: () => Linking.openSettings() }
      ]
    );
  }
});

Troubleshooting

Content Not Displaying

  • Check API configuration: Ensure clientId, apiKey, and baseURL are correct
  • Verify NotificationCenter: Make sure you're using NotificationCenter.getInstance() (singleton)
  • Check content type: Ensure your API returns valid actionType and actionUrl
  • Enable debug logging: Set debug: true to see detailed logs

Modal Not Appearing

  • Ensure enableInAppContent: true is set in configuration
  • Check that you're listening for 'InAppContentReceived' notifications
  • Verify the modal state management in your component

API Calls Failing

  • Verify your API key and client ID are correct
  • Check network connectivity
  • Ensure the API server is accessible
  • For web platforms, configure CORS proxy if needed

Permissions Denied

  • Ensure microphone permissions are granted
  • Check that permission descriptions are properly configured
  • Test on physical device (permissions don't work in simulators)

Complete Examples

typescript
import React, { useEffect, useState } from 'react';
import {
  SafeAreaView,
  ScrollView,
  StatusBar,
  StyleSheet,
  Text,
  View,
  TouchableOpacity,
  Alert,
} from 'react-native';
import { useToneDetection } from 'tonelisten-react-native';

const App = () => {
  const [detectionHistory, setDetectionHistory] = useState([]);

  const handleSequenceDetected = (sequence) => {
    setDetectionHistory(prev => [sequence, ...prev.slice(0, 9)]); // Keep last 10
    Alert.alert('Tone Sequence Detected', `Sequence: ${sequence}`);
  };

  const {
    isListening,
    isInitialized,
    lastSequence,
    startListening,
    stopListening,
    error
  } = useToneDetection({
    clientId: '562',
    apiKey: 'your-api-key',
    baseURL: 'https://api.toneadmin.com',
    enableInAppContent: true,
    onSequence: (event) => {
      handleSequenceDetected(event.sequence);
    },
    onDualTone: (result) => {
      console.log('Dual tone detected:', result);
    },
    onBridgeTone: (result) => {
      console.log('Bridge tone detected:', result);
    },
    onError: (err) => {
      console.error('Tone detection error:', err);
    }
  });

  return (
    <SafeAreaView style={styles.safeArea}>
      <StatusBar barStyle="dark-content" />
      <ScrollView contentInsetAdjustmentBehavior="automatic">
        <View style={styles.header}>
          <Text style={styles.headerTitle}>ToneListen React Native</Text>
          <Text style={styles.headerSubtitle}>Example App</Text>
        </View>

        <View style={styles.container}>
          <Text style={styles.title}>Tone Detection</Text>
          
          <View style={styles.statusContainer}>
            <Text style={styles.statusText}>
              Status: {isInitialized ? (isListening ? 'Listening' : 'Stopped') : 'Initializing...'}
            </Text>
            {error && <Text style={styles.errorText}>Error: {error.message}</Text>}
          </View>

          <View style={styles.buttonContainer}>
            <TouchableOpacity
              style={[styles.button, isListening ? styles.stopButton : styles.startButton]}
              onPress={isListening ? stopListening : startListening}
              disabled={!isInitialized}
            >
              <Text style={styles.buttonText}>
                {isListening ? 'Stop Listening' : 'Start Listening'}
              </Text>
            </TouchableOpacity>
          </View>

          {lastSequence && (
            <View style={styles.resultContainer}>
              <Text style={styles.resultTitle}>Last Sequence:</Text>
              <Text style={styles.resultText}>{lastSequence}</Text>
            </View>
          )}

          <View style={styles.historyContainer}>
            <Text style={styles.historyTitle}>Detection History:</Text>
            {detectionHistory.map((seq, index) => (
              <Text key={index} style={styles.historyItem}>
                {index + 1}. {seq}
              </Text>
            ))}
          </View>

          <View style={styles.infoContainer}>
            <Text style={styles.infoTitle}>Instructions:</Text>
            <Text style={styles.infoText}>
              1. Grant microphone permission when prompted{'
'}
              2. Tap "Start Listening" to begin tone detection{'
'}
              3. Play tone sequences to test detection{'
'}
              4. Detected sequences will appear below
            </Text>
          </View>
        </View>
      </ScrollView>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  safeArea: {
    flex: 1,
    backgroundColor: '#f5f5f5',
  },
  header: {
    backgroundColor: '#2196F3',
    padding: 20,
    alignItems: 'center',
  },
  headerTitle: {
    fontSize: 24,
    fontWeight: 'bold',
    color: 'white',
  },
  headerSubtitle: {
    fontSize: 16,
    color: 'white',
    marginTop: 5,
  },
  container: {
    flex: 1,
    padding: 20,
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    textAlign: 'center',
    marginBottom: 20,
    color: '#333',
  },
  statusContainer: {
    marginBottom: 20,
  },
  statusText: {
    fontSize: 16,
    textAlign: 'center',
    color: '#666',
  },
  errorText: {
    fontSize: 16,
    textAlign: 'center',
    color: '#ff0000',
    marginTop: 10,
  },
  buttonContainer: {
    marginBottom: 20,
  },
  button: {
    padding: 15,
    borderRadius: 8,
    alignItems: 'center',
  },
  startButton: {
    backgroundColor: '#4CAF50',
  },
  stopButton: {
    backgroundColor: '#f44336',
  },
  buttonText: {
    color: 'white',
    fontSize: 16,
    fontWeight: 'bold',
  },
  resultContainer: {
    marginBottom: 10,
    padding: 10,
    backgroundColor: 'white',
    borderRadius: 8,
    borderWidth: 1,
    borderColor: '#ddd',
  },
  resultTitle: {
    fontSize: 14,
    fontWeight: 'bold',
    color: '#333',
    marginBottom: 5,
  },
  resultText: {
    fontSize: 16,
    color: '#666',
  },
  historyContainer: {
    marginTop: 20,
    padding: 15,
    backgroundColor: 'white',
    borderRadius: 8,
    borderWidth: 1,
    borderColor: '#ddd',
  },
  historyTitle: {
    fontSize: 16,
    fontWeight: 'bold',
    color: '#333',
    marginBottom: 10,
  },
  historyItem: {
    fontSize: 14,
    color: '#666',
    marginBottom: 5,
  },
  infoContainer: {
    margin: 20,
    padding: 15,
    backgroundColor: '#e3f2fd',
    borderRadius: 8,
    borderLeftWidth: 4,
    borderLeftColor: '#2196F3',
  },
  infoTitle: {
    fontSize: 16,
    fontWeight: 'bold',
    color: '#1976d2',
    marginBottom: 10,
  },
  infoText: {
    fontSize: 14,
    color: '#1976d2',
    lineHeight: 20,
  },
});

export default App;