Detect a Frequency Change in a Step Wise Frequency Chirp

I am driving a system with a step-wise frequency chirp from 50-400 hz with a frequency step of 0.01 hz. I am having difficulty identifying the points of frequency change as the data collected has an indeterminate amount of time at 50 Hz and an indeterminate amount of time at 400 Hz. Since the time between frequency changes is 0.1 seconds an fft calculation doesn't provide the proper frequency resolution to determine this change. I also tried calculating the gradient of the frequency chirp with inconclusive results as well as attempting a lock-in calculation. I wrote some sample code that has a randomly located frequency step followed by another 0.1 secs later that I have been trying to diagnose with varying success. Ultimately I am trying to identify the beginning and end of the frequency chirp since at that point the step changes are known but this change point identification was attempted to identify the point of change between 50 and 50.01, then backtracking to find the beginning of the series. Any suggestions would be very much appreciated.

import numpy as np
from scipy import signal
import matplotlib.pyplot as plt

fs = 10000
t = np.linspace(0,1,1*fs)
x = np.zeros(len(t))

step_loc = int(2500 + 5000*np.random.random(1))

x[:step_loc] = np.sin(50*2*pi*t[:step_loc])
x[step_loc:step_loc+1000] = np.sin(50.01*2*pi*t[step_loc:step_loc+1000])
x[step_loc+1000:] = np.sin(50.02*2*pi*t[step_loc+1000:])

lock = x*(np.cos(50*2*pi*t) + 1j*np.sin(50*2*pi*t))
b, a = signal.butter(5,30/10000, 'low')
lock_filt = signal.filtfilt(b, a, lock)

plt.plot(t,x)
plt.plot(t,grad*3e-3)     #this is offering very inconsistent identifiable results
plt.plot(t,lock_filt*1e1) #although this can identify the general area of the random frequency change i
# dont know how to extend it to identifying the actual point
• Have you tried monitoring phase versus time instead of frequency? Mar 22 '20 at 19:52
• @DanBoschen Thank you, it doesnt seem that the phase noticeably changes with the change of frequency. Should it?
– Jon
Mar 22 '20 at 21:02
• Or is there a method aside from a lockin calculation that would find the wave's phase with time?
– Jon
Mar 22 '20 at 21:13
• Yes it should as frequency by definition is the slope (derivative) of phase. I recommend detrending the phase versus time at your starting frequency and then monitor for when it starts to ramp up. Mar 22 '20 at 21:23
• @DanBoschen That worked, thank you!
– Jon
Mar 22 '20 at 22:50

There are many way to tackle this:

1. Time - Frequency Analysis
Classic choice would be a spectrogram but probably a Fourier Synchrosqueezed Transform would do a better job (Have a look at even more advanced approaches ssqueezepy - Synchrosqueezing, wavelet transforms, and time frequency analysis in Python by @OverLordGoldDragon).
2. Parameter Estimation
Methods for optimization of the instant parameter. Usually by Non Linear Least Squares.
3. Bayesian / Prior Based Estimation
Methods with some prior (Sparsity) for estimation of the parameters. See Estimation of Amplitude, Frequency and Phase of Linear Combination of Harmonic Signal Beyond the Leakage Resolution of DFT.

But, for creativity, I'd like even one more approach - Tracking.
Similar to what I did in Estimate and Track the Amplitude, Frequency and Phase of a Sine Signal Using a Kalman Filter.

We basically track the parameters of the signal and once we see a "Jump" we can say there is a change.

I recreated a similar signal to yours.
The basic tracking of the Extended Kalman Filter can be seen in the linked question. But the summary is given by: With noise level for a good measurement of 33 [dB] we can see the tracker is estimating well the signal and smoothens the noise. So we can easily see the jumps and with more tweaking of the parameters of the Kalman Filter it will be more visible.

The code is available at my StackExchange Signal Processing Q64772 GitHub Repository (Look at the SignalProcessing\Q64772 folder).

• +1, advertises competitor. I gave this a shot; the jumps are far too small, in frequency and time (as you've noticed and adjusted) relative to number of samples, and estimation very brittle against noise. Sufficiently large jumps can be tracked nearly perfectly without noise, and acceptably with noise. Didn't dig exhaustively but I doubt SSQ is well-suited for this task. Aug 19 '21 at 20:04
• @OverLordGoldDragon, I took exactly the same sampling rate. The number of samples is more by 500 (Just for easy multiplication). 170 samples more per section (At this sampling rate it is less than 2%) doesn't make the difference. If SSQ didn't go well I guess the tracking option isn't bad at all.
– Royi
Aug 19 '21 at 21:22
• The second jump is >tripled in duration (1000 -> >10000/3) and frequency jumps are x10 (.01 -> .1); that's a big difference. Yes, I reckon your method wins here - but wonder if anything can be done with .01 jumps & 33dB. Aug 20 '21 at 10:13
• I think it can be done if the periods are longer. You're right, I missed the fact the sections are not equally divided.
– Royi
Aug 20 '21 at 13:45

I suggest monitoring the phase versus time directly instead of frequency. Frequency is the derivative of phase so the slope of the phase would indicate the frequency. Detrend the phase slope for the starting frequency and then the point in time where the phase starts to ramp up should be easier to detect. The window in which to detect this change will be balanced with the SNR of the signal itself.

Since frequency is the derivative of phase, it will be more sensitive to high frequency noise (the derivative is a high pass function), which is why monitoring phase directly could be a more reliable approach.