Instructions to use alaatiger989/Jambonz_impl with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- NeMo
How to use alaatiger989/Jambonz_impl with NeMo:
# tag did not correspond to a valid NeMo domain.
- Notebooks
- Google Colab
- Kaggle
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>ASR WebSocket Testing Client with Sample Rate Analysis</title> | |
| <style> | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| min-height: 100vh; | |
| padding: 20px; | |
| } | |
| .container { | |
| background: rgba(255, 255, 255, 0.95); | |
| backdrop-filter: blur(10px); | |
| border-radius: 20px; | |
| padding: 40px; | |
| box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1); | |
| max-width: 800px; | |
| margin: 0 auto; | |
| border: 1px solid rgba(255, 255, 255, 0.2); | |
| } | |
| .header { | |
| text-align: center; | |
| margin-bottom: 30px; | |
| } | |
| .header h1 { | |
| color: #333; | |
| font-size: 2.5em; | |
| font-weight: 300; | |
| margin-bottom: 10px; | |
| } | |
| .header p { | |
| color: #666; | |
| font-size: 1.1em; | |
| } | |
| .connection-section { | |
| margin-bottom: 30px; | |
| } | |
| .input-group { | |
| margin-bottom: 20px; | |
| } | |
| .input-group label { | |
| display: block; | |
| margin-bottom: 8px; | |
| color: #333; | |
| font-weight: 500; | |
| } | |
| .input-group input, .input-group select { | |
| width: 100%; | |
| padding: 12px 16px; | |
| border: 2px solid #e1e5e9; | |
| border-radius: 10px; | |
| font-size: 16px; | |
| transition: all 0.3s ease; | |
| background: rgba(255, 255, 255, 0.8); | |
| } | |
| .input-group input:focus, .input-group select:focus { | |
| outline: none; | |
| border-color: #667eea; | |
| box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); | |
| } | |
| .btn { | |
| padding: 12px 24px; | |
| border: none; | |
| border-radius: 10px; | |
| font-size: 16px; | |
| font-weight: 500; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| text-transform: uppercase; | |
| letter-spacing: 0.5px; | |
| } | |
| .btn:disabled { | |
| opacity: 0.6; | |
| cursor: not-allowed; | |
| } | |
| .btn-connect { | |
| background: linear-gradient(135deg, #4CAF50, #45a049); | |
| color: white; | |
| width: 100%; | |
| } | |
| .btn-connect:hover:not(:disabled) { | |
| transform: translateY(-2px); | |
| box-shadow: 0 5px 15px rgba(76, 175, 80, 0.3); | |
| } | |
| .btn-disconnect { | |
| background: linear-gradient(135deg, #f44336, #da190b); | |
| color: white; | |
| width: 100%; | |
| } | |
| .audio-controls { | |
| display: flex; | |
| justify-content: center; | |
| gap: 20px; | |
| margin: 30px 0; | |
| } | |
| .btn-mic { | |
| background: linear-gradient(135deg, #2196F3, #1976D2); | |
| color: white; | |
| width: 80px; | |
| height: 80px; | |
| border-radius: 50%; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 24px; | |
| } | |
| .btn-mic:hover:not(:disabled) { | |
| transform: scale(1.1); | |
| box-shadow: 0 10px 25px rgba(33, 150, 243, 0.3); | |
| } | |
| .btn-mic.recording { | |
| background: linear-gradient(135deg, #f44336, #da190b); | |
| animation: pulse 1.5s infinite; | |
| } | |
| .btn-stop { | |
| background: linear-gradient(135deg, #FF9800, #F57C00); | |
| color: white; | |
| width: 80px; | |
| height: 80px; | |
| border-radius: 50%; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 24px; | |
| } | |
| @keyframes pulse { | |
| 0% { transform: scale(1); } | |
| 50% { transform: scale(1.05); } | |
| 100% { transform: scale(1); } | |
| } | |
| .status { | |
| text-align: center; | |
| margin: 20px 0; | |
| padding: 12px; | |
| border-radius: 10px; | |
| font-weight: 500; | |
| } | |
| .status.connected { | |
| background: rgba(76, 175, 80, 0.1); | |
| color: #4CAF50; | |
| border: 1px solid rgba(76, 175, 80, 0.3); | |
| } | |
| .status.disconnected { | |
| background: rgba(244, 67, 54, 0.1); | |
| color: #f44336; | |
| border: 1px solid rgba(244, 67, 54, 0.3); | |
| } | |
| .status.recording { | |
| background: rgba(33, 150, 243, 0.1); | |
| color: #2196F3; | |
| border: 1px solid rgba(33, 150, 243, 0.3); | |
| } | |
| .stats-section { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 20px; | |
| margin: 20px 0; | |
| } | |
| .stat-box { | |
| background: rgba(0, 0, 0, 0.05); | |
| border-radius: 10px; | |
| padding: 15px; | |
| text-align: center; | |
| border: 1px solid rgba(0, 0, 0, 0.1); | |
| } | |
| .stat-value { | |
| font-size: 2em; | |
| font-weight: bold; | |
| color: #667eea; | |
| margin-bottom: 5px; | |
| } | |
| .stat-label { | |
| color: #666; | |
| font-size: 0.9em; | |
| } | |
| .response-section { | |
| margin-top: 30px; | |
| } | |
| .response-box { | |
| background: rgba(0, 0, 0, 0.05); | |
| border-radius: 10px; | |
| padding: 20px; | |
| min-height: 200px; | |
| max-height: 400px; | |
| overflow-y: auto; | |
| border: 1px solid rgba(0, 0, 0, 0.1); | |
| font-family: 'Courier New', monospace; | |
| font-size: 12px; | |
| white-space: pre-wrap; | |
| word-wrap: break-word; | |
| } | |
| .debug-section { | |
| margin-top: 20px; | |
| } | |
| .debug-box { | |
| background: rgba(0, 0, 0, 0.8); | |
| color: #00ff00; | |
| border-radius: 10px; | |
| padding: 15px; | |
| min-height: 150px; | |
| max-height: 300px; | |
| overflow-y: auto; | |
| font-family: 'Courier New', monospace; | |
| font-size: 11px; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <div class="header"> | |
| <h1>π€ ASR Sample Rate Analyzer</h1> | |
| <p>WebSocket-based Speech Recognition with Audio Analysis</p> | |
| </div> | |
| <div class="connection-section"> | |
| <div class="input-group"> | |
| <label for="websocketUrl">WebSocket URL:</label> | |
| <input type="text" id="websocketUrl" value="ws://185.208.206.135:5005" placeholder="ws://185.208.206.135:5005"> | |
| </div> | |
| <div class="input-group"> | |
| <label for="targetSampleRate">Target Sample Rate (Hz):</label> | |
| <select id="targetSampleRate"> | |
| <option value="8000">8000 Hz (Default)</option> | |
| <option value="16000">16000 Hz</option> | |
| <option value="22050">22050 Hz</option> | |
| <option value="44100">44100 Hz</option> | |
| </select> | |
| </div> | |
| <div class="input-group"> | |
| <label for="chunkSize">Audio Chunk Size (samples):</label> | |
| <select id="chunkSize"> | |
| <option value="1024">1024 samples</option> | |
| <option value="2048">2048 samples</option> | |
| <option value="4096" selected>4096 samples</option> | |
| <option value="8192">8192 samples</option> | |
| </select> | |
| </div> | |
| <div class="input-group"> | |
| <label for="Interim-Results"> Interim-Results:</label> | |
| <select id="interimResults"> | |
| <option value="true">True</option> | |
| <option value="false">False</option> | |
| </select> | |
| </div> | |
| <div class="input-group"> | |
| <label for="language">Language Code:</label> | |
| <input type="text" id="language_code" value="en-US" placeholder="en-US"> | |
| </div> | |
| <button id="connectBtn" class="btn btn-connect">Connect to Debug Server</button> | |
| <button id="disconnectBtn" class="btn btn-disconnect" style="display: none;">Disconnect</button> | |
| </div> | |
| <div id="status" class="status disconnected">Disconnected</div> | |
| <div class="stats-section"> | |
| <div class="stat-box"> | |
| <div class="stat-value" id="actualSampleRate">0</div> | |
| <div class="stat-label">Calculated Sample Rate (Hz)</div> | |
| </div> | |
| <div class="stat-box"> | |
| <div class="stat-value" id="bytesSent">0</div> | |
| <div class="stat-label">Total Bytes Sent</div> | |
| </div> | |
| <div class="stat-box"> | |
| <div class="stat-value" id="chunksSent">0</div> | |
| <div class="stat-label">Audio Chunks Sent</div> | |
| </div> | |
| <div class="stat-box"> | |
| <div class="stat-value" id="duration">0.0s</div> | |
| <div class="stat-label">Recording Duration</div> | |
| </div> | |
| </div> | |
| <div class="audio-controls"> | |
| <button id="micBtn" class="btn btn-mic" disabled title="Start Recording">π€</button> | |
| <button id="stopBtn" class="btn btn-stop" disabled title="Stop Recording">βΉοΈ</button> | |
| </div> | |
| <div class="response-section"> | |
| <h3>Server Responses:</h3> | |
| <div id="responseBox" class="response-box">Waiting for connection...</div> | |
| </div> | |
| <div class="debug-section"> | |
| <h3>Debug Console:</h3> | |
| <div id="debugBox" class="debug-box">Ready to connect...</div> | |
| </div> | |
| </div> | |
| <script> | |
| class SampleRateAnalyzer { | |
| constructor() { | |
| this.websocket = null; | |
| this.audioContext = null; | |
| this.mediaRecorder = null; | |
| this.audioStream = null; | |
| this.processor = null; | |
| this.isRecording = false; | |
| this.isConnected = false; | |
| // Audio analysis variables | |
| this.startTime = null; | |
| this.totalBytesSent = 0; | |
| this.chunksSent = 0; | |
| this.targetSampleRate = 8000; | |
| this.chunkSize = 4096; | |
| this.initializeElements(); | |
| this.attachEventListeners(); | |
| } | |
| initializeElements() { | |
| this.elements = { | |
| websocketUrl: document.getElementById('websocketUrl'), | |
| targetSampleRate: document.getElementById('targetSampleRate'), | |
| chunkSize: document.getElementById('chunkSize'), | |
| connectBtn: document.getElementById('connectBtn'), | |
| disconnectBtn: document.getElementById('disconnectBtn'), | |
| micBtn: document.getElementById('micBtn'), | |
| stopBtn: document.getElementById('stopBtn'), | |
| status: document.getElementById('status'), | |
| responseBox: document.getElementById('responseBox'), | |
| debugBox: document.getElementById('debugBox'), | |
| actualSampleRate: document.getElementById('actualSampleRate'), | |
| bytesSent: document.getElementById('bytesSent'), | |
| chunksSent: document.getElementById('chunksSent'), | |
| duration: document.getElementById('duration') | |
| }; | |
| } | |
| attachEventListeners() { | |
| this.elements.connectBtn.addEventListener('click', () => this.connect()); | |
| this.elements.disconnectBtn.addEventListener('click', () => this.disconnect()); | |
| this.elements.micBtn.addEventListener('click', () => this.startRecording()); | |
| this.elements.stopBtn.addEventListener('click', () => this.stopRecording()); | |
| this.elements.targetSampleRate.addEventListener('change', (e) => { | |
| this.targetSampleRate = parseInt(e.target.value); | |
| this.debugLog(`Target sample rate changed to: ${this.targetSampleRate} Hz`); | |
| }); | |
| this.elements.chunkSize.addEventListener('change', (e) => { | |
| this.chunkSize = parseInt(e.target.value); | |
| this.debugLog(`Chunk size changed to: ${this.chunkSize} samples`); | |
| }); | |
| } | |
| debugLog(message) { | |
| const timestamp = new Date().toLocaleTimeString(); | |
| const debugBox = this.elements.debugBox; | |
| debugBox.innerHTML += `[${timestamp}] ${message}\n`; | |
| debugBox.scrollTop = debugBox.scrollHeight; | |
| } | |
| updateStatus(message, type) { | |
| this.elements.status.textContent = message; | |
| this.elements.status.className = `status ${type}`; | |
| } | |
| updateStats() { | |
| if (this.startTime) { | |
| const elapsed = (Date.now() - this.startTime) / 1000; | |
| this.elements.duration.textContent = `${elapsed.toFixed(1)}s`; | |
| // Calculate actual sample rate | |
| if (elapsed > 0.5) { | |
| const totalSamples = this.totalBytesSent / 2; // 16-bit samples | |
| const calculatedRate = totalSamples / elapsed; | |
| this.elements.actualSampleRate.textContent = Math.round(calculatedRate); | |
| // Log if there's a significant difference | |
| const difference = Math.abs(calculatedRate - this.targetSampleRate); | |
| if (difference > 100) { | |
| this.debugLog(`β οΈ Sample rate mismatch! Target: ${this.targetSampleRate}Hz, Actual: ${Math.round(calculatedRate)}Hz`); | |
| } | |
| } | |
| } | |
| this.elements.bytesSent.textContent = this.totalBytesSent.toLocaleString(); | |
| this.elements.chunksSent.textContent = this.chunksSent.toLocaleString(); | |
| } | |
| async connect() { | |
| const url = this.elements.websocketUrl.value.trim(); | |
| if (!url) { | |
| alert('Please enter a WebSocket URL'); | |
| return; | |
| } | |
| try { | |
| this.updateStatus('Connecting...', 'disconnected'); | |
| this.elements.connectBtn.disabled = true; | |
| this.debugLog(`Attempting to connect to: ${url}`); | |
| this.websocket = new WebSocket(url); | |
| this.websocket.binaryType = 'arraybuffer'; | |
| // Set a connection timeout | |
| const connectionTimeout = setTimeout(() => { | |
| if (this.websocket.readyState === WebSocket.CONNECTING) { | |
| this.websocket.close(); | |
| this.debugLog('β Connection timeout'); | |
| this.updateStatus('Connection Timeout', 'disconnected'); | |
| this.resetConnection(); | |
| } | |
| }, 10000); // 10 second timeout | |
| this.websocket.onopen = () => { | |
| clearTimeout(connectionTimeout); | |
| this.isConnected = true; | |
| this.updateStatus('Connected to Debug Server', 'connected'); | |
| this.elements.connectBtn.style.display = 'none'; | |
| this.elements.disconnectBtn.style.display = 'block'; | |
| this.elements.micBtn.disabled = false; | |
| this.elements.responseBox.textContent = 'Connected to debug server. Ready to test sample rate...'; | |
| this.debugLog('β WebSocket connected successfully'); | |
| }; | |
| this.websocket.onmessage = (event) => { | |
| if (typeof event.data === 'string') { | |
| try { | |
| const response = JSON.parse(event.data); | |
| this.displayResponse('JSON Response', response); | |
| this.debugLog(`π¨ Received JSON: ${JSON.stringify(response)}`); | |
| } catch (e) { | |
| this.displayResponse('Text Response', event.data); | |
| this.debugLog(`π¨ Received Text: ${event.data}`); | |
| } | |
| } else { | |
| this.displayResponse('Binary Response', `Received binary data: ${event.data.byteLength} bytes`); | |
| this.debugLog(`π¨ Received Binary: ${event.data.byteLength} bytes`); | |
| } | |
| }; | |
| this.websocket.onerror = (error) => { | |
| clearTimeout(connectionTimeout); | |
| console.error('WebSocket error:', error); | |
| this.debugLog(`β WebSocket error: ${error.message || 'Unknown error'}`); | |
| this.updateStatus('Connection Error', 'disconnected'); | |
| this.resetConnection(); | |
| }; | |
| this.websocket.onclose = (event) => { | |
| clearTimeout(connectionTimeout); | |
| this.isConnected = false; | |
| const reason = event.reason || 'No reason provided'; | |
| this.debugLog(`π WebSocket closed: Code ${event.code}, Reason: ${reason}`); | |
| this.updateStatus(`Disconnected (Code: ${event.code})`, 'disconnected'); | |
| this.resetConnection(); | |
| this.displayResponse('Connection Closed', `Code: ${event.code}, Reason: ${reason}`); | |
| }; | |
| } catch (error) { | |
| console.error('Connection failed:', error); | |
| this.debugLog(`β Connection failed: ${error.message}`); | |
| this.updateStatus('Connection Failed', 'disconnected'); | |
| this.resetConnection(); | |
| } | |
| } | |
| disconnect() { | |
| if (this.isRecording) { | |
| this.stopRecording(); | |
| } | |
| if (this.websocket && this.websocket.readyState === WebSocket.OPEN) { | |
| this.debugLog('π Manually disconnecting...'); | |
| this.websocket.close(1000, 'Client disconnect'); | |
| } | |
| this.resetConnection(); | |
| } | |
| resetConnection() { | |
| this.isConnected = false; | |
| this.elements.connectBtn.disabled = false; | |
| this.elements.connectBtn.style.display = 'block'; | |
| this.elements.disconnectBtn.style.display = 'none'; | |
| this.elements.micBtn.disabled = true; | |
| this.elements.stopBtn.disabled = true; | |
| this.stopRecording(); | |
| // Reset stats | |
| this.totalBytesSent = 0; | |
| this.chunksSent = 0; | |
| this.startTime = null; | |
| this.updateStats(); | |
| } | |
| // Convert Float32Array to Int16Array (LINEAR16 PCM) | |
| floatTo16BitPCM(float32Array) { | |
| const int16Array = new Int16Array(float32Array.length); | |
| for (let i = 0; i < float32Array.length; i++) { | |
| const clipped = Math.max(-1, Math.min(1, float32Array[i])); | |
| int16Array[i] = clipped * 0x7FFF; | |
| } | |
| return int16Array; | |
| } | |
| // Resample audio to target sample rate | |
| resampleAudio(audioBuffer, sourceSampleRate, targetSampleRate) { | |
| if (sourceSampleRate === targetSampleRate) { | |
| return audioBuffer; | |
| } | |
| const ratio = sourceSampleRate / targetSampleRate; | |
| const targetLength = Math.round(audioBuffer.length / ratio); | |
| const resampled = new Float32Array(targetLength); | |
| for (let i = 0; i < targetLength; i++) { | |
| const sourceIndex = i * ratio; | |
| const sourceIndexFloor = Math.floor(sourceIndex); | |
| const sourceIndexCeil = Math.min(sourceIndexFloor + 1, audioBuffer.length - 1); | |
| const weight = sourceIndex - sourceIndexFloor; | |
| resampled[i] = audioBuffer[sourceIndexFloor] * (1 - weight) + | |
| audioBuffer[sourceIndexCeil] * weight; | |
| } | |
| return resampled; | |
| } | |
| async startRecording() { | |
| if (!this.isConnected) { | |
| alert('Please connect to debug server first'); | |
| return; | |
| } | |
| try { | |
| this.debugLog(`π€ Starting recording with target sample rate: ${this.targetSampleRate} Hz`); | |
| // Initialize audio context | |
| this.audioContext = new (window.AudioContext || window.webkitAudioContext)(); | |
| this.debugLog(`π Audio context created with sample rate: ${this.audioContext.sampleRate} Hz`); | |
| // Get microphone stream | |
| this.audioStream = await navigator.mediaDevices.getUserMedia({ | |
| audio: { | |
| echoCancellation: false, | |
| noiseSuppression: false, | |
| autoGainControl: false, | |
| channelCount: 1 | |
| } | |
| }); | |
| const source = this.audioContext.createMediaStreamSource(this.audioStream); | |
| // Create processor with specified chunk size | |
| this.processor = this.audioContext.createScriptProcessor(this.chunkSize, 1, 1); | |
| this.processor.onaudioprocess = (event) => { | |
| if (!this.isRecording || !this.websocket || this.websocket.readyState !== WebSocket.OPEN) { | |
| return; | |
| } | |
| const inputBuffer = event.inputBuffer; | |
| const audioData = inputBuffer.getChannelData(0); | |
| // Resample to target sample rate | |
| const resampled = this.resampleAudio(audioData, this.audioContext.sampleRate, this.targetSampleRate); | |
| // Convert to LINEAR16 PCM | |
| const pcmData = this.floatTo16BitPCM(resampled); | |
| // Send binary audio data | |
| this.websocket.send(pcmData.buffer); | |
| // Update stats | |
| this.totalBytesSent += pcmData.buffer.byteLength; | |
| this.chunksSent += 1; | |
| this.updateStats(); | |
| if (this.chunksSent % 10 === 0) { // Log every 10 chunks | |
| this.debugLog(`π Sent ${this.chunksSent} chunks, ${this.totalBytesSent} bytes`); | |
| } | |
| }; | |
| // Connect audio nodes | |
| source.connect(this.processor); | |
| this.processor.connect(this.audioContext.destination); | |
| // Send START message | |
| const startMessage = { | |
| type: "start", | |
| language: this.language_code, | |
| format: "raw", | |
| encoding: "LINEAR16", | |
| interimResults: this.interimResults, | |
| sampleRateHz: this.targetSampleRate, | |
| options: { | |
| testClient: true, | |
| chunkSize: this.chunkSize | |
| } | |
| }; | |
| this.websocket.send(JSON.stringify(startMessage)); | |
| this.displayResponse('Sent START', startMessage); | |
| this.debugLog(`π€ Sent START message: ${JSON.stringify(startMessage)}`); | |
| this.isRecording = true; | |
| this.startTime = Date.now(); | |
| // Update UI | |
| this.elements.micBtn.classList.add('recording'); | |
| this.elements.micBtn.disabled = true; | |
| this.elements.stopBtn.disabled = false; | |
| this.updateStatus(`Recording @ ${this.targetSampleRate}Hz`, 'recording'); | |
| } catch (error) { | |
| console.error('Failed to start recording:', error); | |
| this.debugLog(`β Recording failed: ${error.message}`); | |
| alert('Failed to access microphone. Please check permissions.'); | |
| this.stopRecording(); | |
| } | |
| } | |
| stopRecording() { | |
| if (this.isRecording) { | |
| this.isRecording = false; | |
| this.debugLog('π Stopping recording...'); | |
| // Send STOP message | |
| if (this.websocket && this.websocket.readyState === WebSocket.OPEN) { | |
| const stopMessage = { type: "stop" }; | |
| this.websocket.send(JSON.stringify(stopMessage)); | |
| this.displayResponse('Sent STOP', stopMessage); | |
| this.debugLog(`π€ Sent STOP message`); | |
| } | |
| } | |
| // Clean up audio resources | |
| if (this.processor) { | |
| this.processor.disconnect(); | |
| this.processor = null; | |
| } | |
| if (this.audioContext) { | |
| this.audioContext.close().then(() => { | |
| this.audioContext = null; | |
| this.debugLog('π Audio context closed'); | |
| }); | |
| } | |
| if (this.audioStream) { | |
| this.audioStream.getTracks().forEach(track => track.stop()); | |
| this.audioStream = null; | |
| this.debugLog('π€ Microphone stream stopped'); | |
| } | |
| // Update UI | |
| this.elements.micBtn.classList.remove('recording'); | |
| this.elements.micBtn.disabled = false; | |
| this.elements.stopBtn.disabled = true; | |
| if (this.isConnected) { | |
| this.updateStatus('Connected - Recording stopped', 'connected'); | |
| } | |
| // Final stats update | |
| this.updateStats(); | |
| } | |
| displayResponse(messageType, response) { | |
| const responseBox = this.elements.responseBox; | |
| const timestamp = new Date().toLocaleTimeString(); | |
| let content = `[${timestamp}] ${messageType}:\n`; | |
| if (typeof response === 'object') { | |
| content += JSON.stringify(response, null, 2); | |
| } else { | |
| content += response; | |
| } | |
| responseBox.innerHTML += content + '\n\n'; | |
| responseBox.scrollTop = responseBox.scrollHeight; | |
| } | |
| } | |
| // Initialize when page loads | |
| document.addEventListener('DOMContentLoaded', () => { | |
| new SampleRateAnalyzer(); | |
| }); | |
| </script> | |
| </body> | |
| </html> |