|
-
Feb 27th, 2015, 01:02 PM
#1
[VB6] - Circular spectrum visualizer.
Hello everyone! I wrote the source code of the graphical audio spectrum visualizer. The sound is analyzed through a standard recording device, i.e. you can select the microphone and view its spectrum, or you can select stereo mixer and view a spectrum of a playback sound.
This visualizer allows to adjust the number of displayed octaves, transparency of background and amplification.
You can also load a palette from an external PNG file with 32ARGB format. It also supports the following effects: "blur" and "burning". You can view a spectrum of a signal represented in the two modes: arcs (rings) and sectors (pies). If you use the ring view an octave is mapped to radial coordinate and a semitone to angle. The separated harmonics are placed along the same line; color represents an intensity. The sectors view maps the amount of signal to the radial coordinate, the frequency in octaves to the color, the frequency in semitones to the angular coordinate.
This idea was suggested to me by Vladislav Petrovky (aka Hacker). His idea was a little different.
HOW DOES IT WORK?
Initially it creates the buffers for sound and buffer bitmaps. Further it starts the sound capture process and waits when a buffer will be filled. When a buffer has been filled it begins processing. Firstly it performs the Fast Fourier Transform in order to transform a signal to the frequency domain form. Before performing it applies the Hamming window in order to reduce distortions because a signal has discontinuity at the edges of a buffer. When a signal has been translated to the frequency domain the buffer contains complex value that represent the vectors. The module (length) of a vector implies the energy of signal in that frequency and the argument (angle) implies phase of a harmonic in that frequency:
We need the energy of frequency although the phase information allows to determine the frequency more accurately considering the phase difference. I don't use phase information in this project. The drawing method is different for each appearance mode. In order to boost the work it uses the precalculated coordinates named MapData. This array contains the angles of arcs and sectors for the current appearance mode. When coordinates has been calculated it calculates the amount of frequency for each FFT bin figuring out the length of a vector. This value is uses as the index in the color palette after converting the value to a range from 0 to 255. Further GDI+ draws the necessary primitives depending on the appearance mode. Note that all drawing occur onto the buffer bitmap not on window. I specially have not mentioned about the Release procedure that animates the background. This procedure applies an effect to the buffer bitmap before signal processing. It uses the Fade property that determines the speed of the disappearance of previous drawing bitmap. It just decrease the alpha value of the entire bitmap. When you use an effect it also works with the bits of the buffer bitmap and decreases the alpha value. For instance, if the blur effect has been selected it averages the near pixels (analog of low-pass filtering) then it decreases the alpha value for all pixels depending on Fade property. Eventually it draws buffer bitmap onto the main window. Thus it draws the energy of the spectrum of signal in the polar coordinates. It can be used as the start point for the notes or chord recognition. Thank for attention!
Regards,
Кривоус Анатолий (The trick).
modAudio.bas module:
Code:
' // modAudio.bas - module for audio capture
' // © Krivous Anatoly Anatolevich (The trick), 2014
Option Explicit
Public Type WAVEFORMATEX ' // Audio format structure
wFormatTag As Integer ' // Audio type (WAVE/MP3/etc.)
nChannels As Integer ' // Number of channels (mono/stereo)
nSamplesPerSec As Long ' // Sample rate
nAvgBytesPerSec As Long ' // Number of bytes per second
nBlockAlign As Integer ' // One block (all channels) align in bytes
wBitsPerSample As Integer ' // Number of bits per sample
cbSize As Integer ' // Number of extra bytes
End Type
Public Type WAVEHDR ' // Buffer header structure
lpData As Long ' // Pointer to buffer data
dwBufferLength As Long ' // Size of buffer in bytes
dwBytesRecorded As Long ' // Number of written bytes
dwUser As Long ' // User data
dwFlags As Long ' // Flags
dwLoops As Long ' // Number of playings
lpNext As Long
Reserved As Long
End Type
Public Type BUFFER ' // Buffer structure
Data() As Integer ' // Data
Header As WAVEHDR ' // Header
End Type
Public Declare Function waveInOpen Lib "winmm.dll" ( _
ByRef lphWaveIn As Long, _
ByVal uDeviceID As Long, _
ByRef lpFormat As WAVEFORMATEX, _
ByVal dwCallback As Long, _
ByVal dwInstance As Long, _
ByVal dwFlags As Long) As Long
Public Declare Function waveInPrepareHeader Lib "winmm.dll" ( _
ByVal hWaveIn As Long, _
ByRef lpWaveInHdr As WAVEHDR, _
ByVal uSize As Long) As Long
Public Declare Function waveInReset Lib "winmm.dll" ( _
ByVal hWaveIn As Long) As Long
Public Declare Function waveInStart Lib "winmm.dll" ( _
ByVal hWaveIn As Long) As Long
Public Declare Function waveInStop Lib "winmm.dll" ( _
ByVal hWaveIn As Long) As Long
Public Declare Function waveInUnprepareHeader Lib "winmm.dll" ( _
ByVal hWaveIn As Long, _
ByRef lpWaveInHdr As WAVEHDR, _
ByVal uSize As Long) As Long
Public Declare Function waveInClose Lib "winmm.dll" ( _
ByVal hWaveIn As Long) As Long
Public Declare Function waveInGetErrorText Lib "winmm.dll" _
Alias "waveInGetErrorTextA" ( _
ByVal err As Long, _
ByVal lpText As String, _
ByVal uSize As Long) As Long
Public Declare Function waveInAddBuffer Lib "winmm.dll" ( _
ByVal hWaveIn As Long, _
ByRef lpWaveInHdr As WAVEHDR, _
ByVal uSize As Long) As Long
Public Const mSampleRate As Long = 44100 ' // Default sample rate
Public Const BufSizeMS As Single = 0.03 ' // Size of buffer in ms
Public Const WAVE_MAPPER As Long = -1&
Public Const CALLBACK_WINDOW As Long = &H10000
Public Const WAVE_FORMAT_PCM As Long = 1
Public Const MM_WIM_DATA As Long = &H3C0
Dim hWave As Long ' // Handle of wave input device
Dim Fmt As WAVEFORMATEX ' // Current format of capture
Dim Buffers() As BUFFER ' // Buffers
' // This function initializes capture
Public Function InitCapture() As Boolean
Dim ret As Long
Dim msg As String
Dim i As Long
Dim count As Long
' // Set format of capture
With Fmt
.cbSize = 0
.wFormatTag = WAVE_FORMAT_PCM
.wBitsPerSample = 16
.nSamplesPerSec = mSampleRate
.nChannels = 2
.nBlockAlign = .nChannels * .wBitsPerSample / 8
.nAvgBytesPerSec = .nSamplesPerSec * .nBlockAlign
End With
' // Calculate size of buffer in samples
count = Fmt.nAvgBytesPerSec * BufSizeMS
count = count - (count Mod Fmt.nBlockAlign)
' // Open default recording device
ret = waveInOpen(hWave, WAVE_MAPPER, Fmt, frmMain.hwnd, 0, CALLBACK_WINDOW)
If ret Then ShowMessage ret: Exit Function
' // Create 4 buffers
ReDim Buffers(3)
' // Prepare buffers
For i = 0 To UBound(Buffers)
With Buffers(i)
ReDim .Data(count - 1)
.Header.lpData = VarPtr(.Data(0))
.Header.dwBufferLength = count * 2
.Header.dwFlags = 0
.Header.dwLoops = 0
ret = waveInPrepareHeader(hWave, .Header, Len(.Header))
If ret Then ShowMessage ret: Exit Function
End With
Next i
' // Send buffers to device
For i = 0 To UBound(Buffers)
ret = waveInAddBuffer(hWave, Buffers(i).Header, Len(Buffers(i).Header))
If ret Then ShowMessage ret: Exit Function
Next i
' // Begin recording (capture)
ret = waveInStart(hWave)
If ret Then ShowMessage ret: Exit Function
' // Success
InitCapture = True
End Function
' // Stop capture
Public Sub EndCapture()
Dim i As Long
' // Reset device and return all the buffers to application
waveInReset hWave
' // Stop capture
waveInStop hWave
' // Release buffers headers
For i = 0 To UBound(Buffers)
waveInUnprepareHeader hWave, Buffers(i).Header, Len(Buffers(i).Header)
Next
' // Close capture device
waveInClose hWave
End Sub
' // This function is called when a buffer has been filled
Public Function OnCapture( _
ByRef Hdr As WAVEHDR) As Boolean
Dim i As Long
' // Get buffer index
i = modAudio.GetBufferIndex(Hdr.lpData)
If i = -1 Then Exit Function
' // Redraw
modMain.Draw modAudio.Buffers(i).Data
' // Send buffer to device
waveInAddBuffer hWave, Buffers(i).Header, Len(Buffers(i).Header)
End Function
' // This function returns buffer index by pointer
Private Function GetBufferIndex( _
ByVal Ptr As Long) As Long
Dim i As Long
For i = 0 To UBound(Buffers)
If Buffers(i).Header.lpData = Ptr Then GetBufferIndex = i: Exit Function
Next
GetBufferIndex = -1
End Function
' // This procedure shows an error message
Private Sub ShowMessage( _
ByVal Code As Long)
Dim msg As String
msg = Space(255)
waveInGetErrorText Code, msg, Len(msg)
MsgBox "Error capture." & vbNewLine & msg
End Sub
Last edited by The trick; Apr 12th, 2016 at 09:43 AM.
Reason: Translation
Tags for this Thread
Posting Permissions
- You may not post new threads
- You may not post replies
- You may not post attachments
- You may not edit your posts
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|