-
Notifications
You must be signed in to change notification settings - Fork 70
Expand file tree
/
Copy pathmic.js
More file actions
66 lines (56 loc) · 2.54 KB
/
mic.js
File metadata and controls
66 lines (56 loc) · 2.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
// Live microphone pass-through — mic → gain → speakers, with RMS meter.
// Requires the `audio-mic` package (cross-platform Node mic capture):
// npm i audio-mic
// Run: node examples/mic.js
// Run: node examples/mic.js gain=0.8
// Keys: space pause · + / - adjust gain · q quit
import { AudioContext, MediaStreamAudioSourceNode, MediaStream, CustomMediaStreamTrack } from 'web-audio-api'
import mic from 'audio-mic'
import { args, keys, status, clearLine, pausedTag } from './_util.js'
let { $ } = args()
let gainVal = parseFloat($('gain', '1'))
let sampleRate = parseInt($('rate', '44100'))
let channels = parseInt($('ch', '1'))
let bitDepth = parseInt($('bit', '16'))
let backend = $('backend') // 'miniaudio' (default) or 'process' (sox/ffmpeg fallback)
const ctx = new AudioContext({ sampleRate })
await ctx.resume()
const track = new CustomMediaStreamTrack({ kind: 'audio', label: 'mic', settings: { channelCount: channels, sampleSize: bitDepth, sampleRate } })
const stream = new MediaStream([track])
const src = new MediaStreamAudioSourceNode(ctx, { mediaStream: stream })
const gain = ctx.createGain()
const analyser = ctx.createAnalyser()
gain.gain.value = gainVal
analyser.fftSize = 1024
src.connect(gain).connect(analyser).connect(ctx.destination)
// audio-mic's read(cb) is one-shot — re-arm from inside the callback to keep draining the device.
let read = mic({ sampleRate, channels, bitDepth, ...(backend && { backend }) })
let pump = () => read((err, buf) => {
if (err || !buf) return
track.pushData(buf, { channels, bitDepth })
pump()
})
pump()
let samples = new Float32Array(analyser.fftSize)
let print = status()
let tick = setInterval(() => {
analyser.getFloatTimeDomainData(samples)
let sum = 0
for (let i = 0; i < samples.length; i++) sum += samples[i] * samples[i]
let db = 20 * Math.log10(Math.max(Math.sqrt(sum / samples.length), 1e-6))
let bars = Math.max(0, Math.min(30, Math.round((db + 60) / 2)))
let meter = '█'.repeat(bars) + '·'.repeat(30 - bars)
print(`mic → gain ${gain.gain.value.toFixed(2)} → out [${meter}] ${db.toFixed(1)}dB${pausedTag(ctx)} space · +/- · q`)
}, 50)
const cleanup = () => {
clearInterval(tick)
try { read(null) } catch {}
clearLine()
ctx.close()
}
keys({
'+': () => { gain.gain.value = Math.min(4, gain.gain.value * 1.25) },
'=': () => { gain.gain.value = Math.min(4, gain.gain.value * 1.25) },
'-': () => { gain.gain.value = Math.max(0, gain.gain.value / 1.25) },
}, cleanup, ctx)
console.log(`mic → ${channels}ch @ ${sampleRate}Hz (backend: ${read.backend || 'auto'})`)