-
-
Save sebpiq/4128537 to your computer and use it in GitHub Desktop.
# Copyright © 2020 Sébastien Piquemal [email protected] | |
# This work is free. You can redistribute it and/or modify it under the | |
# terms of the Do What The Fuck You Want To Public License, Version 2, | |
# as published by Sam Hocevar. See the license text below for more details. | |
# | |
# -------------------------------------------------------------------- | |
# | |
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE | |
# Version 2, December 2004 | |
# | |
# Copyright (C) 2020 Sébastien Piquemal <[email protected]> | |
# | |
# Everyone is permitted to copy and distribute verbatim or modified | |
# copies of this license document, and changing it is allowed as long | |
# as the name is changed. | |
# | |
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE | |
# TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION | |
# | |
# 0. You just DO WHAT THE FUCK YOU WANT TO. | |
import math | |
def goertzel(samples, sample_rate, *freqs): | |
""" | |
Implementation of the Goertzel algorithm, useful for calculating individual | |
terms of a discrete Fourier transform. | |
`samples` is a windowed one-dimensional signal originally sampled at `sample_rate`. | |
The function returns 2 arrays, one containing the actual frequencies calculated, | |
the second the coefficients `(real part, imag part, power)` for each of those frequencies. | |
For simple spectral analysis, the power is usually enough. | |
Example of usage : | |
freqs, results = goertzel(some_samples, 44100, (400, 500), (1000, 1100)) | |
""" | |
window_size = len(samples) | |
f_step = sample_rate / float(window_size) | |
f_step_normalized = 1.0 / window_size | |
# Calculate all the DFT bins we have to compute to include frequencies | |
# in `freqs`. | |
bins = set() | |
for f_range in freqs: | |
f_start, f_end = f_range | |
k_start = int(math.floor(f_start / f_step)) | |
k_end = int(math.ceil(f_end / f_step)) | |
if k_end > window_size - 1: raise ValueError('frequency out of range %s' % k_end) | |
bins = bins.union(range(k_start, k_end)) | |
# For all the bins, calculate the DFT term | |
n_range = range(0, window_size) | |
freqs = [] | |
results = [] | |
for k in bins: | |
# Bin frequency and coefficients for the computation | |
f = k * f_step_normalized | |
w_real = 2.0 * math.cos(2.0 * math.pi * f) | |
w_imag = math.sin(2.0 * math.pi * f) | |
# Doing the calculation on the whole sample | |
d1, d2 = 0.0, 0.0 | |
for n in n_range: | |
y = samples[n] + w_real * d1 - d2 | |
d2, d1 = d1, y | |
# Storing results `(real part, imag part, power)` | |
results.append(( | |
0.5 * w_real * d1 - d2, w_imag * d1, | |
d2**2 + d1**2 - w_real * d1 * d2) | |
) | |
freqs.append(f * sample_rate) | |
return freqs, results | |
if __name__ == '__main__': | |
# quick test | |
import numpy as np | |
import pylab | |
# generating test signals | |
SAMPLE_RATE = 44100 | |
WINDOW_SIZE = 1024 | |
t = np.linspace(0, 1, SAMPLE_RATE)[:WINDOW_SIZE] | |
sine_wave = np.sin(2*np.pi*440*t) + np.sin(2*np.pi*1020*t) | |
sine_wave = sine_wave * np.hamming(WINDOW_SIZE) | |
sine_wave2 = np.sin(2*np.pi*880*t) + np.sin(2*np.pi*1500*t) | |
sine_wave2 = sine_wave2 * np.hamming(WINDOW_SIZE) | |
# applying Goertzel on those signals, and plotting results | |
freqs, results = goertzel(sine_wave, SAMPLE_RATE, (400, 500), (1000, 1100)) | |
pylab.subplot(2, 2, 1) | |
pylab.title('(1) Sine wave 440Hz + 1020Hz') | |
pylab.plot(t, sine_wave) | |
pylab.subplot(2, 2, 3) | |
pylab.title('(1) Goertzel Algo, freqency ranges : [400, 500] and [1000, 1100]') | |
pylab.plot(freqs, np.array(results)[:,2], 'o') | |
pylab.ylim([0,100000]) | |
freqs, results = goertzel(sine_wave2, SAMPLE_RATE, (400, 500), (1000, 1100)) | |
pylab.subplot(2, 2, 2) | |
pylab.title('(2) Sine wave 660Hz + 1200Hz') | |
pylab.plot(t, sine_wave2) | |
pylab.subplot(2, 2, 4) | |
pylab.title('(2) Goertzel Algo, freqency ranges : [400, 500] and [1000, 1100]') | |
pylab.plot(freqs, np.array(results)[:,2], 'o') | |
pylab.ylim([0,100000]) | |
pylab.show() |
Hey,
I am curious about your implementation from line 50 to 54, and I just sign up to ask.
The codes are rewritten below,
# Storing results "(real part, imag part, power)"
results.append((
0.5 * w_real * d1 - d2, w_imag * d1,
d2**2 + d1**2 - w_real * d1 * d2)
)
From the wikipedia (link1) and a C implementation (link2) of Goertzel Algorithm, It seems the right codes should be
results.append((
d1 - 0.5 * w_real * d2, w_imag * d2,
d2**2 + d1**2 - w_real * d1 * d2)
)
which, however, yields incorrect results for the phase angle of a signal.
Can you explain why your implementation works fine with phase angle results?
Or kindly recommend some references.
Thanks in advance.
BTW, the main reference of the C implementation from link2 is from link3.
link1: https://en.wikipedia.org/wiki/Goertzel_algorithm
link2: http://stackoverflow.com/questions/11579367/implementation-of-goertzel-algorithm-in-c
link3: http://www.embedded.com/Home/PrintView?contentItemId=4024443
Hi Sebpiq,
Nice code and algo. I need it in n-dimensions, (currently its in 1 dimension), any tweaks to generalize it for multiple dimensions?
What is the license on this code?
Fixed ... now works. Many thanks to all the people on stack overflow and dsp.stackexchange.com who helped me with this :
http://stackoverflow.com/questions/13499852/scipy-fourier-transform-of-a-few-selected-frequencies
http://dsp.stackexchange.com/questions/6063/failed-to-implement-goertzel-algorithm-in-python/6065#6065