liamappelbe / fftea

A simple and efficient FFT implementation for Dart
Apache License 2.0
63 stars 1 forks source link

How to compare two wav file? #44

Closed BigGitWorld closed 1 month ago

BigGitWorld commented 9 months ago

Hello. I have a reference wav file and wanna compare other wav files with it to check if same pattern is played or not. I wrote the below code (Also I used wav package to read wav file data and fl_chart to draw line charts).

import 'package:wav/wav.dart';
import 'package:fftea/fftea.dart';
...

    final Wav wav1 = await Wav.readFile("path-to-reference-sound.wav");
    List<double> wavData1 = wav1.channels[0];
    final fftxWav1 = FFT(wavData1.length);
    final freqListWav1 = fftxWav1.realFft(wavData1).discardConjugates().magnitudes();
    final List<double> decibelsWav1 = freqListWav1.map((amp) => 20 * math.log(amp) / math.ln10).toList();

    final Wav wav2 = await Wav.readFile("path-to-second-sound.wav");
    List<double> wavData2 = wav2.channels[0];
    final fftxWav2 = FFT(wavData2.length);
    final freqListWav2 = fftxWav2.realFft(wavData2).discardConjugates().magnitudes();
    final List<double> decibelsWav2 = freqListWav2.map((amp) => 20 * math.log(amp) / math.ln10).toList();

    for (int i = 0; i < decibelsWav1.length; ++i) {
      final freq = fftxWav1.frequency(i, 44100);
      chartPointsWave1.add(FlSpot(freq, freqListWav1[i]));
    }
    for (int i = 0; i < decibelsWav2.length; ++i) {
      final freq = fftxWav2.frequency(i, 44100);
      chartPointsWave2.add(FlSpot(freq, freqListWav2[i]));
    }

Now I can draw 2 line charts from chartPointsWave1 and chartPointsWave2. Is this approach true? Could you please suggest me a solution?

liamappelbe commented 9 months ago

The first question to ask yourself, when comparing these two sounds, is what differences do you want to ignore?

If you just want to check if the wavs are exactly the same, then you don't need FFT at all. You can just compare the 2 arrays of samples to make sure all the values are the same. So the question is what sorts of differences are allowed?

You need to be more clear about what you mean by comparing the sounds.

BigGitWorld commented 9 months ago

The first question to ask yourself, when comparing these two sounds, is what differences do you want to ignore?

If you just want to check if the wavs are exactly the same, then you don't need FFT at all. You can just compare the 2 arrays of samples to make sure all the values are the same. So the question is what sorts of differences are allowed?

* Are differences in volume/amplitude allowed?

* Adding small amounts of noise?

* Compression artifacts?

* Is frequency filtering allowed? Eg low pass filter?

* Time shifts? Are you trying to locate a short sound in a much longer sound?

* Other noises? Are you trying to detect one instrument in a song?

You need to be more clear about what you mean by comparing the sounds.

Thanks for your nice reply. In fact, I have an electronic buzzer that produced a sound of 5 beeps in a noise-free environment. The duration of this reference file is about 7 seconds. Now suppose that in the software test at the factory, there is another electronic buzzer (of the same brand) that must produce the same 5 beeps with the minimum and maximum sound range specified. Therefore, it is important for me that the two audio files have the same frequencies and that the audio range is within an acceptable range.

Our ultimate goal is that we want to make sure that our electronic buzzer is healthy and produces the desired sound. I have attached a zip file containing reference and a sample buzzer sound. buzzer-wav.zip

liamappelbe commented 9 months ago

Ok, try using STFT. This will break the sounds into chunks and give you FFTs of each chunk. The chunks should be small enough to give you good time resolution, but not so small that you lose important frequency info. 10-100ms (441 to 4410 samples) should work.

There will be 2 kinds of chunk you see: chunks with the buzzer sound, and chunks that are just noise. To tell the difference, compare the amplitude of the buzzer frequency to the average amplitude of all the frequencies. For buzzer chunks, the buzzer frequency will have a much larger amplitude than the other frequencies. You'll have to experiment with a good threshold for this ratio (maybe plot the ratio over time to pick a good threshold).

So the STFT gives you a stream of chunk FFTs, and then you decide if each chunk is a buzzer chunk. So at this point you essentially have a stream of bools (where true means it's a buzzer chunk). So then you can write a function that looks for a particular stream of bools. For example, if you're expecting 5 beeps in a row, and each beep is 2 seconds long, with a gap of 1 second between, and your chunk size is 100ms, then you're looking for a bool stream that looks like this:

[...false][20 * true][10 * false][20 * true][10 * false][20 * true][10 * false][20 * true][10 * false][20 * true][false...]

Though you should allow for +/- a few trues and falses, because the STFT chunk boundaries likely won't line up with the start and end of the buzzer. So expect 18-22 trues, and 8-12 falses in this example. Again you'll have to experiment.

Note that once you've analyzed the reference file to find the buzzer frequency etc, you don't actually need to STFT the reference file at runtime. Just STFT the test files and look for those patterns.

BigGitWorld commented 9 months ago

This is my reference-buzzer.wav analyzed by the Audacity app.

Screenshot from 2023-12-04 15-50-59

As you can see, I extracted the following info:

time duration of wav file: 6 seconds
useful time duration of wav file (blue color) : 1.8 seconds
number of beep (vertical red lines excluding the first and last one): 5
time duration between each beep: 300 milliseconds


Also, the following shows chart generated by the following code :

import 'package:wav/wav.dart';
import 'package:fftea/fftea.dart';
...
    final Wav wav1 = await Wav.readFile("path-to-reference-sound.wav");
    List<double> wavData1 = wav1.channels[0];
    final fftxWav1 = FFT(wavData1.length);
    final freqListWav1 = fftxWav1.realFft(wavData1).discardConjugates().magnitudes();
    final List<double> decibelsWav1 = freqListWav1.map((amp) => 20 * math.log(amp) / math.ln10).toList();

    for (int i = 0; i < decibelsWav1.length; ++i) {
      final freq = fftxWav1.frequency(i, 44100);
      chartPointsWave1.add(FlSpot(freq, freqListWav1[i]));
    }

Screenshot from 2023-12-04 16-02-36

So, which code can extract number of beeps in this case?