as I am working on an audio-tool for retro-needs, I turned to ChatGPT for getting help with audio-filters. Although, I am an engineer, my knowledge in audio-algorithms is... neglectable...
But, ChatGPT had a full day to set things right. And it somehow delivered. After about 20 iterations, the following code emerged.
I'd appreciate, if some of you would have a look, try things out and give me some feedback about improving the code, enhancing the experience and creating better code for all of us. I'd like to donate this part to the community - but it should be flawless!
Thanks!
Features so far:
- cross platform
- works with 8, 16, 24, 32 bits
- mono and stereo
- should also work within 8 to 192 khz
- uses a hamming-filter, you can set the window-size (the lesser, the more noise, but also less computing efforts)
- functions for bandpass, lowpass, highpass, notch, resonance (how does this work?), 3-band-EQ (highly questionable implementation?)
- create a sound, e.g. with "GenerateWhiteNoise", apply a filter, save as .wav
- or, load a .wav and put the data into a buffer.
- optimisations in mind:
- use a lookup table for sinus
- add more filters (like a BassBoost (tm), a TrebleEnlighter (tm), frequencies sweeps, more EQs, what you need?
- all routines may be optimised enough to use them in a realtime environment, but that needs to be tested
Code: Select all
Global DefaultSampleRate.i = 44100
Global DefaultBitDepth.i = 16
Global DefaultChannels.i = 2
#TwoPI = 2 * #PI
;- Filter
; globale variablen
Global Dim coefficients.i(0) ; filterkoeffizienten
Global Dim hamming.i(0) ; hamming-fenster
Global windowsize
; hamming-fenster vorberechnen
Procedure PrecomputeHamming(newwindowsize.i)
Protected i
windowsize = newwindowsize
ReDim hamming.i(windowsize - 1)
ReDim coefficients.i(windowsize - 1)
; Hamming-Werte berechnen
For i = 0 To windowsize - 1
; Verwende die Hamming-Formel
Protected scaledValue.d = 0.54 - 0.46 * Cos((#TwoPI * i) / (windowsize - 1))
hamming(i) = Int(scaledValue * 100000) ; Skalierung auf Integer
Next
EndProcedure
; FIR-Smooth-Filter für verschiedene Bit-Tiefen
Procedure SmoothFilter(*tempBuffer)
Protected samples.i = MemorySize(*tempBuffer) / (DefaultBitDepth / 8)
Protected Dim filtered.i(samples - 1)
Protected i.i, j.i
; Normierungsfaktor berechnen
Protected normalizationFactor.i = 0
For i = 0 To windowsize - 1
normalizationFactor + coefficients(i)
Next
If normalizationFactor = 0
Debug "Normalization factor is zero in SmoothFilter. Check coefficients."
ProcedureReturn
EndIf
; Normierung der Koeffizienten
For i = 0 To windowsize - 1
coefficients(i) = coefficients(i) * 100000 / normalizationFactor
Next
; Debug: Normierte Summe prüfen
Protected totalCoefficients.i = 0
For i = 0 To windowsize - 1
totalCoefficients + coefficients(i)
Next
Debug "Sum of coefficients (should be 100000): " + Str(totalCoefficients)
; Filter anwenden
For i = 0 To samples - 1
For j = 0 To windowsize - 1
If (i - j) >= 0 And (i - j) < samples
Select DefaultBitDepth
Case 8
filtered(i) + ((PeekA(*tempBuffer + (i - j)) - 128) * coefficients(j)) / 100000
Case 16
filtered(i) + (PeekW(*tempBuffer + (i - j) * 2) * coefficients(j)) / 100000
Case 24
; 24-Bit Wert manuell berechnen
Protected sample24.i
sample24 = PeekB(*tempBuffer + (i - j) * 3)
sample24 | (PeekB(*tempBuffer + (i - j) * 3 + 1) << 8)
sample24 | (PeekB(*tempBuffer + (i - j) * 3 + 2) << 16)
If sample24 & $800000
sample24 - 16777216 ; Negativbereich für 24 Bit
EndIf
filtered(i) + (sample24 * coefficients(j)) / 100000
Case 32
filtered(i) + (PeekL(*tempBuffer + (i - j) * 4) * coefficients(j)) / 100000
EndSelect
EndIf
Next
Next
; Ergebnis zurückschreiben in den Originalbuffer
For i = 0 To samples - 1
Protected value.i = filtered(i)
Select DefaultBitDepth
Case 8
If value > 127 : value = 127 : ElseIf value < -128 : value = -128 : EndIf
PokeA(*tempBuffer + i, value + 128)
Case 16
If value > 32767 : value = 32767 : ElseIf value < -32768 : value = -32768 : EndIf
PokeW(*tempBuffer + i * 2, value)
Case 24
If value > 8388607 : value = 8388607 : ElseIf value < -8388608 : value = -8388608 : EndIf
PokeB(*tempBuffer + i * 3, value & $FF)
PokeB(*tempBuffer + i * 3 + 1, (value >> 8) & $FF)
PokeB(*tempBuffer + i * 3 + 2, (value >> 16) & $FF)
Case 32
If value > 2147483647 : value = 2147483647 : ElseIf value < -2147483648 : value = -2147483648 : EndIf
PokeL(*tempBuffer + i * 4, value)
EndSelect
Next
EndProcedure
; FIR-Bandpass-Filter
Procedure ApplyBandpassFilter(*tempBuffer, lowFreq.i, highFreq.i)
Protected i
If lowFreq < 0 Or highFreq < 0 Or lowFreq >= DefaultSampleRate / 2 Or highFreq >= DefaultSampleRate / 2 Or lowFreq >= highFreq
Debug "Invalid frequency range: lowFreq=" + Str(lowFreq) + ", highFreq=" + Str(highFreq)
ProcedureReturn
EndIf
Protected scaledLowFreq.d = lowFreq / DefaultSampleRate
Protected scaledHighFreq.d = highFreq / DefaultSampleRate
Protected freqdiff.d = scaledHighFreq - scaledLowFreq
Protected freqdiff_double.d = freqdiff * 2
Protected pi_freqdiff = #PI * freqdiff
Protected half_windowsize = windowsize / 2
Protected i_minus_windowsize
; Filterkoeffizienten berechnen
For i = 0 To windowsize - 1
i_minus_windowsize = i - half_windowsize
If i_minus_windowsize <> 0
coefficients(i) = (2 * Sin(pi_freqdiff * i_minus_windowsize) / i_minus_windowsize) * hamming(i)
Else
coefficients(i) = freqdiff_double * hamming(i)
EndIf
Next
; Buffer filtern
SmoothFilter(*tempBuffer)
EndProcedure
; FIR-Tiefpass-Filter
Procedure ApplyLowpassFilter(*tempBuffer, cutoffFreq.i)
Protected i.i
; Filterkoeffizienten berechnen
Protected scaledCutoff.d = cutoffFreq / DefaultSampleRate ; Grenzfrequenz skalieren
Protected half_windowsize = windowsize / 2
Protected double_scaledCutoff.d = scaledCutoff * 2
Protected pi_scaledCutoff.d = #TwoPI * scaledCutoff
Protected i_minus_windowsize
For i = 0 To windowsize - 1
i_minus_windowsize = i - half_windowsize
If i_minus_windowsize <> 0
coefficients(i) = (Sin(pi_scaledCutoff * i_minus_windowsize) / i_minus_windowsize) * hamming(i)
Else
coefficients(i) = double_scaledCutoff * hamming(i) ; Division durch Null vermeiden
EndIf
Next
; Buffer direkt filtern
SmoothFilter(*tempBuffer)
EndProcedure
; FIR-Hochpass-Filter
Procedure ApplyHighpassFilter(*tempBuffer, cutoffFreq.i)
Protected i.i
; Skalierung der Grenzfrequenz
Protected scaledCutoff.d = cutoffFreq / DefaultSampleRate
Protected half_windowsize = windowsize / 2
Protected double_scaledCutoff.d = 2 * scaledCutoff
Protected pi_scaledCutoff.d = #TwoPI * scaledCutoff
Protected i_minus_windowsize
For i = 0 To windowsize - 1
i_minus_windowsize = i - half_windowsize
If i_minus_windowsize <> 0
coefficients(i) = (-Sin(pi_scaledCutoff * i_minus_windowsize) / i_minus_windowsize) * hamming(i)
Else
coefficients(i) = (1 - double_scaledCutoff) * hamming(i) ; Division durch Null vermeiden
EndIf
Next
; Buffer direkt filtern
SmoothFilter(*tempBuffer)
EndProcedure
; FIR-Notch-Filter
Procedure ApplyNotchFilter(*tempBuffer, centerFreq.i, bandwidth.i)
Protected i.i
; Skalierung der Frequenzen
Protected scaledCenterFreq.d = centerFreq / DefaultSampleRate
Protected scaledBandwidth.d = bandwidth / DefaultSampleRate
Protected half_windowsize = windowsize / 2
Protected double_scaledbandwith.d = #TwoPI * scaledBandwidth
Protected pi_scaledbandwith.d = #PI * scaledBandwidth
Protected i_minus_windowsize
; Filterkoeffizienten berechnen
For i = 0 To windowsize - 1
i_minus_windowsize = i - half_windowsize
If i_minus_windowsize <> 0
coefficients(i) = ((Sin(pi_scaledbandwith * i_minus_windowsize) - Sin(double_scaledbandwith * i_minus_windowsize)) / i_minus_windowsize) * hamming(i)
Else
coefficients(i) = (1 - scaledBandwidth) * hamming(i) ; Division durch Null vermeiden
EndIf
Next
; Buffer direkt filtern
SmoothFilter(*tempBuffer)
EndProcedure
; FIR-Resonanz-Filter
Procedure ApplyResonanceFilter(*tempBuffer, centerFreq.i, resonanceFactor.f)
Protected i.i
; Frequenzskalierung
Protected scaledCenterFreq.d = centerFreq / DefaultSampleRate
Protected half_windowsize = windowsize / 2
Protected double_scaledCenterFreq.d = #TwoPI * scaledCenterFreq
Protected i_minus_windowsize
; Filterkoeffizienten berechnen
For i = 0 To windowsize - 1
i_minus_windowsize = i - half_windowsize
If i_minus_windowsize <> 0
coefficients(i) = (Sin(double_scaledCenterFreq * i_minus_windowsize) / i_minus_windowsize) * resonanceFactor * hamming(i)
Else
coefficients(i) = resonanceFactor * hamming(i) ; Division durch Null vermeiden
EndIf
Next
; Buffer direkt filtern
SmoothFilter(*tempBuffer)
EndProcedure
; 3 Band-Equalizer !!
Procedure Apply3BandEqualizer(*tempBuffer, lowFreq.i, lowGain.i, midFreq.i, midGain.i, highFreq.i, highGain.i, lowWidth.i = 100, midWidth.i = 200, highWidth.i = 1000)
Protected totalSamples.i = MemorySize(*tempBuffer) / (DefaultBitDepth / 8)
Protected i
Dim originalBuffer.i(totalSamples - 1) ; Puffer für das Original
; Originaldaten speichern
CopyMemory(*tempBuffer, @originalBuffer(), MemorySize(*tempBuffer))
; Band 1: Low
Protected scaledLowGain.d = lowGain / 100.0 ; Verstärkungsfaktor skalieren
ApplyBandpassFilter(*tempBuffer, lowFreq - lowWidth / 2, lowFreq + lowWidth / 2)
For i = 0 To totalSamples - 1
Select DefaultBitDepth
Case 8
PokeA(*tempBuffer + i, (PeekA(*tempBuffer + i) - 128) * scaledLowGain + 128)
Case 16
PokeW(*tempBuffer + i * 2, PeekW(*tempBuffer + i * 2) * scaledLowGain)
Case 24
Protected sample24.i = PeekB(*tempBuffer + i * 3) | (PeekB(*tempBuffer + i * 3 + 1) << 8) | (PeekB(*tempBuffer + i * 3 + 2) << 16)
If sample24 & $800000
sample24 | -16777216
EndIf
sample24 * scaledLowGain
PokeB(*tempBuffer + i * 3, sample24 & $FF)
PokeB(*tempBuffer + i * 3 + 1, (sample24 >> 8) & $FF)
PokeB(*tempBuffer + i * 3 + 2, (sample24 >> 16) & $FF)
Case 32
PokeL(*tempBuffer + i * 4, PeekL(*tempBuffer + i * 4) * scaledLowGain)
EndSelect
Next
; Band 2: Mid
CopyMemory(@originalBuffer(), *tempBuffer, MemorySize(*tempBuffer))
Protected scaledMidGain.d = midGain / 100.0
ApplyBandpassFilter(*tempBuffer, midFreq - midWidth / 2, midFreq + midWidth / 2)
For i = 0 To totalSamples - 1
Select DefaultBitDepth
Case 8
PokeA(*tempBuffer + i, (PeekA(*tempBuffer + i) - 128) * scaledMidGain + 128)
Case 16
PokeW(*tempBuffer + i * 2, PeekW(*tempBuffer + i * 2) * scaledMidGain)
Case 24
sample24.i = PeekB(*tempBuffer + i * 3) | (PeekB(*tempBuffer + i * 3 + 1) << 8) | (PeekB(*tempBuffer + i * 3 + 2) << 16)
If sample24 & $800000
sample24 | -16777216
EndIf
sample24 * scaledMidGain
PokeB(*tempBuffer + i * 3, sample24 & $FF)
PokeB(*tempBuffer + i * 3 + 1, (sample24 >> 8) & $FF)
PokeB(*tempBuffer + i * 3 + 2, (sample24 >> 16) & $FF)
Case 32
PokeL(*tempBuffer + i * 4, PeekL(*tempBuffer + i * 4) * scaledMidGain)
EndSelect
Next
; Band 3: High
CopyMemory(@originalBuffer(), *tempBuffer, MemorySize(*tempBuffer))
Protected scaledHighGain.d = highGain / 100.0
ApplyBandpassFilter(*tempBuffer, highFreq - highWidth / 2, highFreq + highWidth / 2)
For i = 0 To totalSamples - 1
Select DefaultBitDepth
Case 8
PokeA(*tempBuffer + i, (PeekA(*tempBuffer + i) - 128) * scaledHighGain + 128)
Case 16
PokeW(*tempBuffer + i * 2, PeekW(*tempBuffer + i * 2) * scaledHighGain)
Case 24
sample24.i = PeekB(*tempBuffer + i * 3) | (PeekB(*tempBuffer + i * 3 + 1) << 8) | (PeekB(*tempBuffer + i * 3 + 2) << 16)
If sample24 & $800000
sample24 | -16777216
EndIf
sample24 * scaledHighGain
PokeB(*tempBuffer + i * 3, sample24 & $FF)
PokeB(*tempBuffer + i * 3 + 1, (sample24 >> 8) & $FF)
PokeB(*tempBuffer + i * 3 + 2, (sample24 >> 16) & $FF)
Case 32
PokeL(*tempBuffer + i * 4, PeekL(*tempBuffer + i * 4) * scaledHighGain)
EndSelect
Next
EndProcedure
Procedure SaveWaveFile(filename.s, *tempBuffer)
If *tempBuffer = 0
ProcedureReturn -1
EndIf
Protected file, datasize, headersize = 44
datasize = MemorySize(*tempBuffer)
file = CreateFile(#PB_Any, filename)
If file
; RIFF-Header
WriteString(file, "RIFF", #PB_Ascii)
WriteLong(file, datasize + headersize - 8)
WriteString(file, "WAVE", #PB_Ascii)
; fmt-Chunk
WriteString(file, "fmt ", #PB_Ascii)
WriteLong(file, 16) ; Größe des fmt-Chunks
WriteWord(file, 1) ; PCM-Format
WriteWord(file, DefaultChannels) ; Kanäle (Mono oder Stereo)
WriteLong(file, DefaultSampleRate); Abtastrate
WriteLong(file, DefaultSampleRate * DefaultChannels * (DefaultBitDepth / 8)) ; Byte-Rate
WriteWord(file, DefaultChannels * (DefaultBitDepth / 8)) ; Blockgröße
WriteWord(file, DefaultBitDepth) ; Bits pro Sample
; data-Chunk
WriteString(file, "data", #PB_Ascii)
WriteLong(file, datasize) ; Größe des Datenbereichs
WriteData(file, *tempBuffer, datasize)
CloseFile(file)
EndIf
EndProcedure
Procedure GenerateWhiteNoise(durationMs.i)
Protected totalSamples = DefaultSampleRate * durationMs / 1000 * DefaultChannels
Protected *whiteNoise = AllocateMemory(totalSamples * (DefaultBitDepth / 8))
If *whiteNoise
RandomData(*whitenoise, MemorySize(*whiteNoise))
EndIf
ProcedureReturn *whiteNoise
EndProcedure
PrecomputeHamming(101) ; the higher, the "better sounding" the filter should work
Define *noise = GenerateWhiteNoise(2000) ; make 2 seconds
ApplyLowpassFilter(*noise, 500)
SaveWaveFile("noise.wav", *noise)