Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Sound Scanner and FFT Analyzer

0.00/5 (No votes)
16 Feb 2010 4  
Scanning analog input, and FFT convertion and analyzing.

Introduction

Due to a new project in the company I’ve been working in, we have been engaged in a project which should implement pattern recognition to detect whether an in use gearbox works well or not. The first step was to analyze the gearbox vibration which captured by an ordinary vibration sensor generated analog signals. With the assumption of resembling outcome of a microphone and a vibration sensor, we connect the sensor to the microphone input directly.

Note: If you have a problem with downloading, you can use this or this link. If the problem persists, leave your mail id so it can be sent to you directly! 

Code Explanation

After creating the project and adding PORT.DLL and SoundAnalysis.dll references, it's needed to define Sound related function at header which has been encapsulated in PORT.DLL.

Private Declare Function SOUNDIS Lib "Port.Dll" () As Integer

It's called to detect whether a sound card is available or not. If there is no sound card, the return value will be 0.

Private Declare Function SOUNDGETRATE Lib "Port.Dll" () As Integer

It's called to detect the current sampling rate. Default sampling rate is 11025 which is equal to 11KHz. Other possible sampling rate is 22050 (22KHz) and 44100 (44KHz).

Private Declare Function SOUNDGETBYTES Lib "Port.Dll" () As Integer

It's called to detect the current resolution whether may be 8 bits (1 Byte) or 16 bits (2 Bytes). Default resolution is 8 bits (1 Byte).

Private Declare Function SOUNDSETRATE Lib "Port.Dll" (ByVal Rate As Long) As Long

It's called to set the sampling rate. Accepted passing values are 11025, 22050 and 44100 and the return will be exactly equal to set value. In case of passing other values, resulted error is unknown.

Private Declare Function SOUNDSETBYTES Lib "Port.Dll" (ByVal Rate As Long) As Long

It's called to set the sampling resolution. Accepted passing values are 8 and 16 how return will be exactly equal to set value. I did not check its related error in case of other values.

Private Declare Auto Function BitBlt Lib "gdi32.dll" (ByVal hdcDest As IntPtr,
    ByVal nXDest As Integer, ByVal nYDest As Integer, ByVal nWidth As Integer,
    ByVal nHeight As Integer, ByVal hdcSrc As IntPtr, ByVal nXSrc As Integer,
    ByVal nYSrc As Integer, ByVal dwRop As System.Int32) As Boolean

It's one of the API functions so called my trace in programming which I have been using for making form printing possible. There are two functions in the program (Printing region) that clearly describe how it can be implemented.

Recording Timing NumericUpDown determines the sampling period in milliseconds which sets the timer interval.

Window Filter Size NumericUpDown allocates the length of window filter when 1 means no window filter will be implemented.

There are also two other NumericUpDown shown Freq 01 and Freq 02. These two set the frequency of two intentional noise added to the main signal string to check the correctness of FFT function which can be removed or disabled by just inserting a comment sign in _Data_Capturing() function.

Private Sub Tmr_Runner_Tick(ByVal sender As System.Object,
    ByVal e As System.EventArgs) Handles Tmr_Runner.Tick
    Try
        If Not Working Then
            Call _Stop()
            Exit Try
        End If
        Call _Data_Capturing()
        Call _Fourier_Transformation()
        Call Picture_Redrawing_Wave(_Samples_Refined)
        Call Draw_Fourier(_Arr_Spec, _Width_Fourier, _Heigth_Fourier / 2)
    Catch ex As Exception
        Call _Stop()
        MsgBox(ex.ToString, MsgBoxStyle.Critical, "FKR_Sound Scan")
    End Try
End Sub

In Timer events, four functions are called which I can say the program relies on _Data_Capturing() function.

Private Function _Data_Capturing() As Boolean
    Try
        Dim I As Integer, J As Integer
        Dim Str_Out As String = ""
        Dim _Sum As Double = 0
        Dim _X_Unit_01 As Single = _Width / _Freq_01
        Dim _X_Unit_02 As Single = _Width / _Freq_02
        Dim _Step_01 As Single = _Width / _Size
        Dim _Temp_01 As Single = 0
        Dim _Temp_02 As Single = 0
        If _Bits = _Sample_Bit.Mono Then
            ReDim _Samples(_Size - 1)
        Else
            ReDim _Samples((_Size * 2) - 1)
        End If
        SOUNDIN(_Samples, CLng(_Size))
        If _Bits = _Sample_Bit.Mono Then
            Str_Out = ""
            ReDim _Samples_Refined(_Size - 2)
            For I = 0 To (UBound(_Samples) - 1)
                'The Range is amoung 0 to 255
                _Temp_01 = (I * _Step_01 * 100) Mod (_X_Unit_01 * 100)
                _Temp_01 = _Temp_01 / (100 * _X_Unit_01)
                _Temp_02 = (I * _Step_01 * 100) Mod (_X_Unit_02 * 100)
                _Temp_02 = _Temp_02 / (100 * _X_Unit_02)
                _Samples_Refined(I) = CDbl(CInt(_Samples(I)) - 128) + 
                   (Math.Sin(2 * Math.PI * _Temp_01) * 32) + 
                   (Math.Sin(2 * Math.PI * _Temp_02) * 32)
                _Samples_Refined(I) = ((_Samples_Refined(I) / 128) * _Gain) * 128
            Next
        Else
            ReDim _Samples_Refined(_Size - 1)
            Dim _Byte_arr(1) As Byte
            For I = 0 To UBound(_Samples) Step 2
                _Byte_arr(0) = _Samples(I)
                _Byte_arr(1) = _Samples(I + 1)
                'The Range changed to 0 to 65535
                _Temp_01 = (I * _Step_01 * 50) Mod (_X_Unit_01 * 100)
                _Temp_01 = _Temp_01 / (100 * _X_Unit_01)
                _Temp_02 = (I * _Step_01 * 50) Mod (_X_Unit_02 * 100)
                _Temp_02 = _Temp_02 / (100 * _X_Unit_02)
                _Samples_Refined(I / 2) = CDbl(CInt(BitConverter.ToInt16(_Byte_arr,
                    0))) + (Math.Sin(2 * Math.PI * _Temp_01) * 512) + 
                    (Math.Sin(2 * Math.PI * _Temp_02) * 512)
                _Samples_Refined(I / 2) = ((_Samples_Refined(I / 2) / 32768) *
                    _Gain) * 32768
            Next
        End If
        'Window Filter Implementing
        ReDim _WindowFilter(_Window_Size - 1)
        For I = 0 To UBound(_Samples_Refined)
            _WindowFilter(I Mod _Window_Size) = _Samples_Refined(I)
            _Sum = 0
            For J = 0 To UBound(_WindowFilter)
                _Sum += _WindowFilter(J)
            Next
            _Samples_Refined(I) = (_Sum / _Window_Size)
        Next
    Catch ex As Exception
        MsgBox(ex.Message, MsgBoxStyle.Critical, "FKR_Sound Scan")
        Call _Stop()
    End Try
End Function

_Samples is an array being used to store captured data on it, depending on data resolution its size varied between _Size or _Size * 2.

Using SOUNDIN, the captured data is stored in _Samples. There are two variables named _Temp_01 and _Temp_02 that determine points of two intentional noise.

The _Samples_Refined is used to store refined data. Depending on resolution, two different methods are enacted.

_Samples_Refined(I) = CDbl(CInt(_Samples(I)) - 128) + 
                      (Math.Sin(2 * Math.PI * _Temp_01) * 32) + 
                      (Math.Sin(2 * Math.PI * _Temp_02) * 32)

The above is being used if 8 bit resolution is selected. In this case, the returned sampled value varies from 0 to 255 in which 128 means silent, so after casting data to a 32 bit integer, -128 added to, removes its offset. " + (Math.Sin(2 * Math.PI * _Temp_01) * 32) + (Math.Sin(2 * Math.PI * _Temp_02) * 32) " is the effect of intentional noise that can be omitted by just adding an ' before.

_Samples_Refined(I) = ((_Samples_Refined(I) / 128) * _Gain) * 128

It's used to implement user defined gain to refined signal.

It's important to note the returned captured data in 16 bits resolutions varies from -32768 to +32765 so removing the offset is not implemented.

'Window Filter Implementing
ReDim _WindowFilter(_Window_Size - 1)
For I = 0 To UBound(_Samples_Refined)
     _WindowFilter(I Mod _Window_Size) = _Samples_Refined(I)
     _Sum = 0
     For J = 0 To UBound(_WindowFilter)
         _Sum += _WindowFilter(J)
     Next
    _Samples_Refined(I) = (_Sum / _Window_Size)
Next

It's just a simple window filtering implemented before FFT function is called.

Private Function _Fourier_Transformation() As Boolean
            .
            .
            .
        _Arr_Spec = SoundAnalysis.FftAlgorithm.Calculate(_Samples_Refined)
        For _I = 0 To CInt(UBound(_Arr_Spec) * 0.95)
            If (_Size * _I / _Arr_Length >= 0) And _
			(_Size * _I / _Arr_Length < 1) Then
                _Arr_Spec(_I) = 0
            End If 
            If (_Size * _I / _Arr_Length > (_Removed_Freq - 1)) And (
                _Size * _I / _Arr_Length < (_Removed_Freq + 1)) Then
                _Arr_Spec(_I) = 0
            End If
            If _Max < _Arr_Spec(_I) Then
                _Max = _Arr_Spec(_I)
                _Index = _I
            End If
        Next
            .
            .
            .
End Function

_Arr_Spec saves the returned FFT array using SoundAnalysis.FftAlgorithm.Calculate() function.

It's needed to pay attention that the returned FFT array length is nearest upper 2's power of input array length, e.g., if input array length is 100 (2^6 < 100 < 2^7), output length will be 128 (2^7). Finding the related frequency in an array may seem a bit strange. First of all, note just first half elements are usable and the other second half is exactly the mirror reflection of elements. For calculation related frequency, you have to use this formula:

_Size * _I / _Arr_Length 

where _Size is equal to number of samples, _Arr_Length is returned FFT array (here is _Arr_Spec) and _I is elements index is FFT returned array which have to be between 0 and (_Arr_Length/2) .

If (_Size * _I / _Arr_Length >= 0) And (
    _Size * _I / _Arr_Length < 1) Then
    _Arr_Spec(_I) = 0
End If

The above piece of code is implemented to remove any effective frequency amoung 0 and 1. The same code:

If (_Size * _I / _Arr_Length > (_Removed_Freq - 1)) And (
   _Size * _I / _Arr_Length < (_Removed_Freq + 1)) Then

   _Arr_Spec(_I) = 0

End If

is doing exactly the same which removed frequency determined by related value in Removed Frequency NumericUpDown.

There are two other functions in Timer event that crystal clear using to draw captured signal and FFT signal in related picture boxes.

Points of Interest & Acknowledgements

The project I’ve made is a simple voice spectrum using a DLL as voice in capturing device meanwhile implementing FFT method helping to convert it to frequency. The project structure is simply trying to avoid usual showing off programming complexity. I have to appreciate Mr. Burkhard Kainka for his useful (but a bit CPU grappling!!) .DLL and also thank notmasteryet for very useful FFT convertor.

I would also like to give special thanks to Richard and Sean.

The project is just a simple VB.NET based initiative representation, hopefully the main has to be based on VC++ .NET using DirectSound.

Don’t hesitate to ask if you encounter any problems or have any questions.

History

  • 12th February, 2010: Initial post
  • 16th February, 2010: Updated source code

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here