The basic idea i'm working on is automatic fault detection using audio signals captured from Motor. I have like set of sample audio signal which are recorded when there is no fault and with fault at different rpm. While hearing the audio I can clearly identify and differentiate the audio signal. But how can I differentiate that technically or programtically. Basically im from computer science background , so im having a hard time to find the method of implementation whether it can be found by audio analysis itself or if I want to implement Machine learning on audio signals

Sample signals are here

1.3500rpm-clean 2.3500rpm-fault

  • $\begingroup$ Clean has 117 Hz and harmonics, while Fault contains frequencies between the harmonics, too. It seems the fundamental frequency is not constant? (Since one file is slightly different from the other) If it were always the same, you could just check for content at the bad frequencies using fixed bandpass filters, but if the motor speed varies, you need to do more work than that. $\endgroup$
    – endolith
    Jul 18, 2017 at 14:39
  • $\begingroup$ What do you meant by frequency between harmonics ? Please bear with me. And is there any other idea to move forward $\endgroup$ Jul 18, 2017 at 15:08
  • $\begingroup$ If you do a frequency analysis of the signals, clean might have spikes at 100, 200, 300 etc while fault would have 100, 150, 200, 250, etc. $\endgroup$
    – endolith
    Jul 18, 2017 at 15:15
  • $\begingroup$ Is the RPM variable? $\endgroup$
    – endolith
    Jul 18, 2017 at 15:16
  • $\begingroup$ Integer multiples of the fundamental frequency are called harmonics $\endgroup$
    – endolith
    Jul 18, 2017 at 15:17

3 Answers 3


The reference @Stan gives is a good one, but there's still the question of what measure to use.

Previously, I've seen kurtosis work well. However, it doesn't seem to be that good on this data set.

The two things I'd look at for this are:

  • The energy in the signal, and
  • The distribution of the sample values (PDF estimate).

Below are several plots analysing a small segment of the left channel of your data. They are:

  • The time domain signal.
  • The frequency domain signal.
  • The frequency domain signal after bandpass filtering the area between 0.4 and 0.8 of $f_s/2$.
  • The PDF estimate from the raw data.
  • The PDF estimate from the band pass filtered data.
  • The 100 sample energy for the whole signal.

Perhaps the easiest one is the last: while there is overlap between the energy values, there isn't much and they seem well split.

The second one is probably the raw PDF estimate: check which one is closest to a uniform distribution. You can see in the time domain plot that the vibes signal is generally further away from the origin that the clean signal.

The second plot shows the same as the last on the 3 x 2, but is the 100 sample energy of the bandpass filtered signal. That looks better still.

enter image description here

enter image description here

R Code Only Below


clean <- readWave('3500-clean.wav')
vibes <- readWave('3500-vibes.wav')

t_index <- seq(10000,11000)

plot(clean@left[t_index], type='l', col='blue', lwd=3)
lines(vibes@left[t_index], col='red')
title('Time doman 10000:11000')

plot(log(abs(fft(clean@left[t_index]))[1:500]), type='l', col='blue', ylim = c(8,15))
lines(log(abs(fft(vibes@left[t_index]))[1:500]), col='red')
title('Frequency domain 10000:11000')

bpf <- butter(10,c(0.4,0.8), type='pass')

clean_f <- filter(bpf, clean@left[t_index])
vibes_f <- filter(bpf, vibes@left[t_index])
plot(abs(fft(clean_f))[1:500], type='l', col='blue', lwd=3)
lines(abs(fft(vibes_f))[1:500], col='red')
title('Band Pass Filtered 10000:11000')

vibes_pdf <- density(vibes@left[t_index])
clean_pdf <- density(clean@left[t_index])
plot(clean_pdf$y/sum(clean_pdf$y), type='l', col='blue', lwd=3)
lines(vibes_pdf$y/sum(vibes_pdf$y), col='red')
title('PDF Estimate (Original)')

vibes_f_pdf <- density(vibes_f)
clean_f_pdf <- density(clean_f)
plot(clean_f_pdf$y/sum(clean_f_pdf$y), type='l', col='blue', lwd=3)
lines(vibes_f_pdf$y/sum(vibes_f_pdf$y), col='red')
title('PDF Estimate (filtered)')

maf <- rep(1,100)

clean_e <- filter(maf, 1, (clean@left[t_index])^2)
vibes_e <- filter(maf, 1, (vibes@left[t_index])^2)
plot(clean_e, type='l', col='blue', lwd=3, ylim=c(min(clean_e,vibes_e), max(clean_e,vibes_e)))
lines(vibes_e, col='red')
title('100 sample energy')

You might want to look at:

Detection of Abrupt Changes: Theory and Application1 Michele Basseville ` IRISA/CNRS Rennes, France Igor V. Nikiforov Institute of Control Sciences Moscow, Russia

with the free access PDF from MIT.

It's a little old, but was advanced when it came out.

There are a lot of Google results for this topic.

  • $\begingroup$ Yes, Basseville & Nikiforov is a great place to start, even now. $\endgroup$
    – Peter K.
    Jul 18, 2017 at 17:00

Here's what I was saying in the comments:

clean vs fault showing harmonics

The green dots show the harmonics (fundamental frequency × 1, 2, 3, 4 ...), while the red dots show "distortion products" between the harmonics. The red dots are the frequency components that make the machine sound bad.

If you know what the machine's RPM is, then you can make notch/comb filters to measure the harmonic frequencies vs the non-harmonic frequencies, and when the non-harmonics are above some relative threshold, you know it's gone bad.

If you don't know the RPM, you'll have to measure it by identifying which frequency is the fundamental (which is not necessarily the strongest one). You can limit your search to RPMs the machine is capable of producing, etc. But the dirty waveform looks like a signal of an octave lower that's missing the fundamental, so you have to be careful you don't measure the wrong frequency and calculate that the signal is clean.

Here's the Python code to make this plot:

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

fig, (clean, vibes) = plt.subplots(2, 1, sharex=True, sharey=True)

N = 2**16
window = signal.kaiser(N, 40)

filename = '3500-clean.flac'  # converted to mono
data, fs = read(filename)
f, Pxx = signal.welch(data, fs=fs, window=window, nperseg=N)
clean.plot(f, 20*np.log10(Pxx))

filename = '3500-vibes.flac'
data, fs = read(filename)
f, Pxx = signal.welch(data, fs=fs, window=window, nperseg=N)
vibes.plot(f, 20*np.log10(Pxx))

faxis = np.fft.rfftfreq(N, 1/fs)
plt.xlim(0, 1000)
plt.ylim(-140, -50)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.