Author Topic: DetectPeakFrequency  (Read 872 times)

Ed1966

  • Posts: 248
DetectPeakFrequency
« on: 8 Feb '23 - 10:34 »
Hi  ;)

Is there a DetectPeakFrequency for Delphi using Windows?
Or how to do that myself.

Regards,
Eduard.

Ian @ un4seen

  • Administrator
  • Posts: 26035
Re: DetectPeakFrequency
« Reply #1 on: 8 Feb '23 - 15:29 »
You can get it from FFT data, ie. check which FFT bin has the highest value. I'm not a Delphi user myself, but it'll be basically the same in any language, and here's how it could look in C/C++:

Code: [Select]
float fft[2048];
BASS_ChannelGetData(handle, fft, BASS_DATA_FFT4096); // get FFT data (4096 sample)
int peakbin = 0;
float peakval = 0;
for (int a = 1; a < 2048; a++) {
if (peakval < fft[a]) { // found a new peak
peakval = fft[a];
peakbin = a;
}
}
float rate;
BASS_ChannelGetAttribute(handle, BASS_ATTRIB_FREQ, &rate); // get sample rate
float peakfreq = peakbin * rate / 4096; // get frequency of peak bin

Ed1966

  • Posts: 248
Re: DetectPeakFrequency
« Reply #2 on: 9 Feb '23 - 08:16 »
Thank you. I still see some wrong frequencies so I'm going to check that out first. Do not know why. I'll come back to this next week.  ;)

Ed1966

  • Posts: 248
Re: DetectPeakFrequency
« Reply #3 on: 9 Feb '23 - 12:03 »
I produce something like this.

Code: [Select]
var
  MasterFreq0,
  PeakFreq0: FLOAT;
function GetRealBitrateFrom(FullArtist: string): Cardinal;
var
  PeakBin: Integer;
  Rate: FLOAT;
  PeakVal: FLOAT;
  FFTData0: array [0..2048] of Single;
var
  AStream: HSTREAM;
  A0: Integer;
begin
  AStream := BASS_StreamCreateFile(False, PChar(FullArtist), 0, 0, BASS_SAMPLE_FLOAT or BASS_STREAM_PRESCAN or BASS_STREAM_DECODE or BASS_UNICODE);
  BASS_ChannelGetAttribute(AStream, BASS_ATTRIB_FREQ, Rate); // get sample rate
  if (BASS_ErrorGetCode > 0) then
    Exit;

  PeakBin := 0;
  PeakVal := 0.0;
  BASS_ChannelGetData(AStream, @FFTData0, BASS_DATA_FFT4096); // get FFT data (4096 sample)
  for A0 := 2 to 2047 do
  begin
    if (Peakval < FFTData0[A0]) then // found a new peak
    begin
      PeakVal := FFTData0[A0];
      PeakBin := A0;
    end;
  end;
  PeakFreq0 := PeakBin * (Rate / 4096); // get frequency of peak bin
  BASS_StreamFree(AStream);
end;

function GetRealBitrateFull(FullArtist: string): Cardinal;
var
  PeakBin: Integer;
  Rate: FLOAT;
  PeakVal: FLOAT;
  FFTData0: array [0..2048] of Single;
var
  AStream: HSTREAM;
  A0: Integer;
begin
  AStream := BASS_StreamCreateFile(False, PChar(FullArtist), 0, 0, BASS_SAMPLE_FLOAT or BASS_STREAM_PRESCAN or BASS_STREAM_DECODE or BASS_UNICODE);
  BASS_ChannelGetAttribute(AStream, BASS_ATTRIB_FREQ, Rate); // get sample rate
  if (BASS_ErrorGetCode > 0) then
    Exit;

  MasterFreq0 := 0;
  while BASS_ChannelIsActive(AStream) <> 0 do
  begin
    PeakBin := 0;
    PeakVal := 0.0;
    BASS_ChannelGetData(AStream, @FFTData0, BASS_DATA_FFT4096); // get FFT data (4096 sample)
    for A0 := 2 to 2047 do
    begin
      if (Peakval < FFTData0[A0]) then // found a new peak
      begin
        PeakVal := FFTData0[A0];
        PeakBin := A0;
      end;
    end;
    PeakFreq0 := PeakBin * (Rate / 4096); // get frequency of peak bin
    if (MasterFreq0 < PeakFreq0) then
      MasterFreq0 := PeakFreq0;
  end;
  BASS_StreamFree(AStream);
end;

Ignore title proc 'GetRealBitrateFull' because that what i want to reaches. Not important for now.
The first is not always correct and de second is better. But there is a max. High frequency situation wilt most mp3 songs. 
A0 start with 2 is better dan start whit 0 or 1.

Thank you.

Ian @ un4seen

  • Administrator
  • Posts: 26035
Re: DetectPeakFrequency
« Reply #4 on: 9 Feb '23 - 17:52 »
I forgot to include the BASS_DATA_FFT_REMOVEDC flag in the BASS_ChannelGetData call. Adding that will make the FFT bin 1 value more useful.

If you want to get the peak frequency over the entire file then you should sum the FFT data from all of the BASS_ChannelGetData calls, and then check the summed values at the end. Something like this:

Code: [Select]
float fft[2048] = {0};
while (true) {
float temp[2048];
if (BASS_ChannelGetData(handle, temp, BASS_DATA_FFT4096 | BASS_DATA_FFT_REMOVEDC) == -1) // get FFT data (4096 sample)
break;
for (int a = 1; a < 2048; a++)
fft[a] += temp[a]; // add to the previous data
}
// check fft array here like before...

Btw, you don't need the BASS_STREAM_PRESCAN flag for this (because you're not getting the length or seeking), so you can remove that from the BASS_StreamCreateFile calls to save a bit of time.

Ed1966

  • Posts: 248
Re: DetectPeakFrequency
« Reply #5 on: 10 Feb '23 - 11:27 »
I think it's going well.  :D
Maybe you have an idea to ignore anything lower than -80dB?
So only the peak between 0 .. -60db as an example.

Ian @ un4seen

  • Administrator
  • Posts: 26035
Re: DetectPeakFrequency
« Reply #6 on: 10 Feb '23 - 14:35 »
You can calculate a threshold value like this:

Code: [Select]
float threshold = pow(10, db / 20.0);

And then use it here:

Code: [Select]
for (int a = 1; a < 2048; a++)
if (temp[a] >= threshold)
fft[a] += temp[a]; // add to the previous data

Ed1966

  • Posts: 248
Re: DetectPeakFrequency
« Reply #7 on: 10 Feb '23 - 16:21 »
Thank you  :)

Ed1966

  • Posts: 248
Re: DetectPeakFrequency
« Reply #8 on: 20 Feb '23 - 09:57 »
One question?
How many milliseconds is one call to BASS_ChannelGetData?

Code: [Select]
BASS_ChannelGetData(AStream, @FFTData0, BASS_DATA_FFT4096 or BASS_DATA_FFT_REMOVEDC); // get FFT data (4096 sample)

10ms or 20ms ? OR?

Ian @ un4seen

  • Administrator
  • Posts: 26035
Re: DetectPeakFrequency
« Reply #9 on: 20 Feb '23 - 11:31 »
The FFT size determines how much data will be used in the BASS_ChannelGetData call. In this example, it's 4096 samples, which is approx 93ms at 44100Hz (4096/44100).

Ed1966

  • Posts: 248
Re: DetectPeakFrequency
« Reply #10 on: 21 Feb '23 - 08:17 »
Good to know. Thank you.  ;)