Created
September 13, 2022 22:04
-
-
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"
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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