Sound capture (Part 4)

Part 12345

So far, we managed to acquire a signal and to get its frequency spectrum. Now, we are interested in comparing it to a previously recorded spectrum. Again, because of the memory limitation of the ATMEGA 328, we will not record the reference spectrum in a Doubles vector. Instead, and because it’s far enough for the type of comparison that we want to perform, we will perform the spectrum comparison between vectors of Bytes. The actual spectrum and the reference spectrum shall be declared as global variable in the header section of the sketch.

uint8_t vRefSpectrum[(samples >> 1)];
uint8_t vActSpectrum[(samples >> 1)];

Before accomplishing this, we need to normalize the reference spectrum, and firstly we must calculate the highest intensity from the acquired spectrum:

	/* Find max value */
	double max = 0;
	for (uint16_t i = 1; i < ((samples >> 1) - 1); i++) {
		if (vReal[i] > max) max = vReal[i];
	}

And then apply a combined filter: only peaks are recorded, and in addition, the intensity of these peaks must exceed a threshold value.

	/* Clean, normalize and store data in actual spectrum */
	for (uint16_t i = 1; i < ((samples >> 1) - 1); i++) {
		if ((vReal[i-1] < vReal[i]) && (vReal[i] > vReal[i+1]) && (uint8_t(vReal[i] * 100.0 / max) > threshold)) {
			vActSpectrum[i] = uint8_t((vReal[i] * 255.0) / max);
		}
		else {
			vActSpectrum[i] = 0;
		}
	}

Note: This peak picker is very trivial, I know. But it works great for this type of basic application which cares about simple peak shapes.

Recording a reference spectrum is as easy as copying vActSpectrum vector in vRefSpectrum vector.

Comparing the spectrum is not so hard. The idea is to use a simple matching criteria such a percentage. The algorithm does two things in one pass and for each frequency:

  • Compute absolute differences between reference and actual spectra
  • Compute the sum of the absolute differences between the two spectra

In the end, the function returns the ratio between these two values.

uint8_t matchSpectra(void) {
/* Compute the match criteria between the reference spectrum and the actual spectrum 
	 The result is expressed in percent and ranges strictly from 0% to 100% */
	uint16_t sumOfRefOrAct = 0;
	uint16_t sumOfAbsDiff = 0;
	for (uint16_t i = 1; i < ((samples >> 1) - 1); i++) {
		/* Compute absolute differences between reference and actual spectra */
		uint8_t diff;
		if (vRefSpectrum[i] > vActSpectrum[i]){
			diff = (vRefSpectrum[i] - vActSpectrum[i]);
			sumOfRefOrAct += vRefSpectrum[i];
		}
		else if (vActSpectrum[i] > vRefSpectrum[i]){
			diff = (vActSpectrum[i] - vRefSpectrum[i]);
			sumOfRefOrAct += vActSpectrum[i];			
		} 
		else {
			diff = 0;
			sumOfRefOrAct += vRefSpectrum[i];
		}
		sumOfAbsDiff += diff;
	}
	if (sumOfRefOrAct != 0x00) {
		/* Returns the matching value in pct */
		return(uint8_t(((sumOfRefOrAct - sumOfAbsDiff) * 100.0) / sumOfRefOrAct));
	}
	else {
		/* Reference spectrum not set */
		return(0x00);
	}
}

This is a very straightforward approach, but it works really well for this particular purpose, and the code is pretty compact.

If the ratio between the observed absolute differences and the sum of all these differences is more that the matching criteria, do something such as turning a status led on!

	if (matchSpectra() > targetMatch) {
		/* Turn status led on for some milliseconds */
		PORTB |= (1 << PINB5);
		printSpectra(vRefSpectrum, vActSpectrum);
		delay(5000);
		PORTB &= ~(1 << PINB5);
	}

Please check this page if you are interested in the code.

Leave a Reply

You must be logged in to post a comment.