Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Detect audio frequency from microphone with node.js

I'm using node.js on a Raspberry Pi to steer some instruments. I would like to have a mic listening for a specific signal, say a tone at 500 Hz, and trigger an event when it is heard.

Having looked at multiple node.js libraries, node-core-audio (https://www.npmjs.com/package/node-core-audio) is the one that comes closest, but it fails on compilation.

Can anybody recommend a good way to do this?

like image 268
Morten Avatar asked Mar 17 '23 15:03

Morten


1 Answers

Just use some parts of the Fourier transform for the frequency you are interested in.

Scalar multiply your input signal with two 500 Hz tones shifted one quarter of a wavelength and with a total root mean square (RMS) of one, which means that you scale the vector with n^-½.

var sampleSize = 2000;
var sampleRate = 44100; // Or whatever in use (Hz)
var tone = 500; // tone to detect in Hz
var sin500Hz = Array(sampleSize);
var cos500Hz = Array(sampleSize);
for (var i = 0; i < sampleSize; i++) {
  sin500Hz[i] = Math.sin(2*Math.PI*tone/sampleRate*i)/Math.sqrt(sampleSize); 
  cos500Hz[i] = Math.cos(2*Math.PI*tone/sampleRate*i)/Math.sqrt(sampleSize); 
}

Scalar multiply the signal input with the two vectors.

function findTone(inputSamples) {
  var amplitudeSin = 0;
  var amplitudeCos = 0;
  for (var i = 0; i < sampleSize; i++) {
    amplitudeSin += inputSamples[i]*sin500Hz[i]; 
    amplitudeCos += inputSamples[i]*cos500Hz[i]; 
  }
  return Math.sqrt(amplitudeSin*amplitudeSin + amplitudeCos*amplitudeCos); 
}

You may want to compare this value with the total amplitude of the signal (take the RMS of all samples in the interval) otherwise noise can also be detected with findTone().

function noiseLevel(inputSamples) {
  var power = 0;
  var average = 0;
  for (var i = 0; i < sampleSize; i++) {
    average += inputSamples[i]; 
  }
  average /= sampleSize;
  for (var i = 0; i < sampleSize; i++) {
    power += Math.pow(inputSamples[i] - average, 2); 
  }
  return Math.sqrt(power); 
}

So the detection you want is probably some minimum value of the quota findTone(inputSamples)/noiseLevel(inputSamples).

If you use too many samples the algorithm will be very precise and maybe more precise than you wish if you have a biased or noisy 500 Hz input signal. Then a band pass filter is an alternative.

It is a waste of CPU cycles to use FFT if you only want one or some few parts of a spectrum and FFT neither allows arbitrary frequencies nor arbitrary sampling sets.

like image 155
David Jonsson Avatar answered Apr 02 '23 01:04

David Jonsson