Skip to content

Instantly share code, notes, and snippets.

@qgustavor
Created September 13, 2022 22:04
Show Gist options
  • Save qgustavor/58e5d922e84b9dadaea5e6e01624d9a2 to your computer and use it in GitHub Desktop.
Save qgustavor/58e5d922e84b9dadaea5e6e01624d9a2 to your computer and use it in GitHub Desktop.
Download nircmdc, ffmpeg and deno, run using "deno run -A microphone-keyboard.js"
import FFT from 'https://raw.githubusercontent.com/qgustavor/stream-audio-fingerprint/master/src/lib/fft.ts'
let device = Deno.args[0]
if (!device) {
const devicesProcess = await Deno.run({
cmd: ['ffmpeg', '-list_devices', 'true', '-f', 'dshow', '-i', 'dummy'],
stderr: 'piped'
})
const devicesOutput = new TextDecoder().decode(await devicesProcess.stderrOutput())
const devices = Array.from(devicesOutput.matchAll(/\[dshow.* "(.*)" \(audio\)/g)).map(e => e[1])
devices.sort((a, b) => {
if (a.match(/microphone/i)) return -1
if (b.match(/microphone/i)) return 1
return a.localeCompare(b)
})
device = devices[0]
}
console.log('Using device', device)
let handler
async function startListening () {
const listenerProcess = await Deno.run({
cmd: ['ffmpeg', '-f', 'dshow', '-i', 'audio=' + device, '-ar', '22050', '-ac', '1', '-f', 's16le', '-acodec', 'pcm_s16le', '-'],
stdout: 'piped',
stderr: 'null'
})
const nfft = 512
const step = nfft / 2
const bps = 2
const fft = new FFT(nfft)
const hwin = Array(nfft).fill(null).map((_f, i) => (
0.5 * (1 - Math.cos(((2 * Math.PI) * i) / (nfft - 1)))
))
let buffer = new Uint8Array(0)
let bufferDelta = 0
let stepIndex = 0
let lastMatchedIndex
let lastMatchedTime = Date.now()
let timeBetweenMatches = 500
let minVolume = Number(Deno.args[1]) || 0.01
for await (const chunk of listenerProcess.stdout.readable) {
const concatedBuffer = new Uint8Array(buffer.length + chunk.length)
concatedBuffer.set(buffer, 0)
concatedBuffer.set(chunk, buffer.length)
buffer = concatedBuffer
const bufferView = new DataView(concatedBuffer.buffer)
while ((stepIndex + nfft) * bps < buffer.length + bufferDelta) {
const data = new Array(nfft) // window data
const image = new Array(nfft).fill(0)
// fill the data, windowed (HWIN) and scaled
for (let i = 0, limit = nfft; i < limit; i++) {
const readInt = bufferView.getInt16((stepIndex + i) * bps - bufferDelta, true)
data[i] = (hwin[i] * readInt) / Math.pow(2, 8 * bps - 1)
}
stepIndex += step
fft.forward(data, image) // compute FFT
const largestValue = fft.spectrum.reduce((a, b) => a > b ? a : b)
if (largestValue > minVolume) {
const largestIndex = fft.spectrum.indexOf(largestValue)
if (lastMatchedIndex !== largestIndex || lastMatchedTime + timeBetweenMatches < Date.now()) {
lastMatchedTime = Date.now()
lastMatchedIndex = largestIndex
if (handler) handler(largestIndex)
}
}
}
}
}
startListening()
console.log('--- Configuring frequencies ---')
const keys = {}
while (true) {
const key = prompt('Press some key to be emulated or press enter to continue:')
if (!key) break
console.log('Make a sound:')
const value = await new Promise(resolve => { handler = resolve })
console.log('Frequency %s registered', value)
keys[value] = key
}
console.log('--- Emulating keys ---')
while (true) {
const value = await new Promise(resolve => { handler = resolve })
const key = keys[value]
console.log('Got frequency %s key %s', value, key)
if (!key) continue
await Deno.run({
cmd: ['nircmdc.exe', 'sendkey', key, 'press'],
})
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment