mirror of https://bitbucket.org/ausocean/av.git
Audiofiltering:
Implement bandpass filter by combining lowpass and highpass filters.
This commit is contained in:
parent
49db1041d3
commit
75124b4494
|
@ -12,7 +12,7 @@ import(
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
// define constants used in the generation of the sound waves
|
// Define the constants to be used in the generation of the sound files.
|
||||||
const(
|
const(
|
||||||
SampleRate float64 = 44100
|
SampleRate float64 = 44100
|
||||||
Duration = 2
|
Duration = 2
|
||||||
|
@ -20,12 +20,17 @@ const(
|
||||||
length int = 88200
|
length int = 88200
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// main is a driver function which generates a sound file which contains sine waves from across the spectrum.
|
||||||
|
// Filters can then be generated and applied to the audio, in order to test their frequency response.
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
|
// Create waitgroups to ensure program runs correctly.
|
||||||
var wg1, wg2 sync.WaitGroup
|
var wg1, wg2 sync.WaitGroup
|
||||||
|
|
||||||
|
// Take a measurement of the starting point of the program execution to use for execution timing.
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
// generate sine waves with different frequencies to test frequency response
|
// Generate sine waves with different frequencies to test frequency response.
|
||||||
n := 100
|
n := 100
|
||||||
wg1.Add(n)
|
wg1.Add(n)
|
||||||
audio := make([][]float64, n)
|
audio := make([][]float64, n)
|
||||||
|
@ -36,14 +41,15 @@ func main() {
|
||||||
} (i, audio, &wg1)
|
} (i, audio, &wg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// create filter
|
// Create the filter.
|
||||||
filterLen := 500
|
filterLen := 500
|
||||||
wg2.Add(1)
|
wg2.Add(1)
|
||||||
ch := make(chan []float64, 1)
|
ch := make(chan []float64, 1)
|
||||||
go HighPass(filterLen, 2000, &wg2, ch)
|
go BandPass(filterLen, 2000, 5000, &wg2, ch)
|
||||||
|
|
||||||
|
|
||||||
|
// Combine all the generated sine waves into a single audio slice.
|
||||||
wg1.Wait()
|
wg1.Wait()
|
||||||
// combine audio
|
|
||||||
combinedAudio := make([]float64, length)
|
combinedAudio := make([]float64, length)
|
||||||
for i := range audio[0] {
|
for i := range audio[0] {
|
||||||
combinedAudio[i] = 0
|
combinedAudio[i] = 0
|
||||||
|
@ -52,34 +58,41 @@ func main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the filter from the filter goroutine called earlier.
|
||||||
wg2.Wait()
|
wg2.Wait()
|
||||||
filter := <- ch
|
filter := <- ch
|
||||||
|
|
||||||
// convolve sinc with audio
|
wg1.Add(2)
|
||||||
wg1.Add(1)
|
// Save the unfiltered audio to compare the filtered audio against.
|
||||||
// go SaveAudioData(combinedAudio, "unfiltered", &wg1)
|
go SaveAudioData(combinedAudio, "unfiltered", &wg1)
|
||||||
|
|
||||||
|
// Apply the filter to the combined audio file by computing the convolution of the 2 slices.
|
||||||
wg2.Add(1)
|
wg2.Add(1)
|
||||||
filteredAudio := Convolve(combinedAudio, filter, &wg2)
|
filteredAudio := Convolve(combinedAudio, filter, &wg2)
|
||||||
wg2.Wait()
|
|
||||||
go SaveAudioData(filteredAudio, "highpass-2KHz", &wg1)
|
|
||||||
|
|
||||||
|
// Save the filtered audio.
|
||||||
|
wg2.Wait()
|
||||||
|
go SaveAudioData(filteredAudio, "bandpass-2-5KHz", &wg1)
|
||||||
|
|
||||||
|
// Print the total time of execution, to compare different algorithms.
|
||||||
wg1.Wait()
|
wg1.Wait()
|
||||||
fmt.Println(time.Since(start))
|
fmt.Println(time.Since(start))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// generate is used to generate a sine wave of the given frequency, calls on the global variables of Duration and SampleRate
|
||||||
func generate(Frequency float64) []float64 {
|
func generate(Frequency float64) []float64 {
|
||||||
|
|
||||||
// deteremine number of samples based off duration and sample rate
|
// Deteremine the number of samples required based off Duration and SampleRate.
|
||||||
nsamps := Duration * SampleRate
|
nsamps := Duration * SampleRate
|
||||||
|
|
||||||
// generate x-values
|
// Generate the x-values to plot against.
|
||||||
var angle float64 = tau / float64(nsamps)
|
var angle float64 = tau / float64(nsamps)
|
||||||
|
|
||||||
// create sample array
|
// Create an array for the generated samples.
|
||||||
samp := make([]float64, int(nsamps))
|
samp := make([]float64, int(nsamps))
|
||||||
|
|
||||||
// generate samples and write to file
|
// Generate the samples.
|
||||||
for i := 0; i < int(nsamps); i++ {
|
for i := 0; i < int(nsamps); i++ {
|
||||||
samp[i] = math.Sin(angle * Frequency * float64(4*i))
|
samp[i] = math.Sin(angle * Frequency * float64(4*i))
|
||||||
}
|
}
|
||||||
|
@ -88,7 +101,8 @@ func generate(Frequency float64) []float64 {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Max (a []float64) float64 {
|
// max returns the absolute highest value in a given array.
|
||||||
|
func max(a []float64) float64 {
|
||||||
|
|
||||||
var runMax float64 = -1
|
var runMax float64 = -1
|
||||||
for i:= range a {
|
for i:= range a {
|
||||||
|
@ -101,9 +115,10 @@ func Max (a []float64) float64 {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LowPass returns an array of coeffiecients over the given channel that can be used in a convolution with a signal to perform
|
||||||
|
// a lowpass filtering with the given cut-off frequency fc.
|
||||||
func LowPass (taps int, fc float64, wg *sync.WaitGroup, ch chan []float64) {
|
func LowPass (taps int, fc float64, wg *sync.WaitGroup, ch chan []float64) {
|
||||||
|
|
||||||
// create sinc function
|
|
||||||
size := taps + 1
|
size := taps + 1
|
||||||
fd := fc/SampleRate
|
fd := fc/SampleRate
|
||||||
b := (2*math.Pi) * fd
|
b := (2*math.Pi) * fd
|
||||||
|
@ -121,9 +136,10 @@ func LowPass (taps int, fc float64, wg *sync.WaitGroup, ch chan []float64) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HighPass returns an array of coeffiecients over the given channel that can be used in a convolution with a signal to perform
|
||||||
|
// a highpass filtering with the given cut-off frequency fc.
|
||||||
func HighPass (taps int, fc float64, wg *sync.WaitGroup, ch chan []float64) {
|
func HighPass (taps int, fc float64, wg *sync.WaitGroup, ch chan []float64) {
|
||||||
|
|
||||||
// create sinc function
|
|
||||||
size := taps + 1
|
size := taps + 1
|
||||||
fd := fc/SampleRate
|
fd := fc/SampleRate
|
||||||
b := (2*math.Pi) * fd
|
b := (2*math.Pi) * fd
|
||||||
|
@ -141,14 +157,47 @@ func HighPass (taps int, fc float64, wg *sync.WaitGroup, ch chan []float64) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BandPass returns an array of coeffiecients over the given channel that can be used in a convolution with a signal to perform
|
||||||
|
// a bandpass filtering with the given lower and upper cutoff frequencies (using the low and highpass filter functions).
|
||||||
|
func BandPass (taps int, lower, upper float64, wg *sync.WaitGroup, ch chan []float64) {
|
||||||
|
|
||||||
|
// Create a waitgroup and channel to be used internally within the function.
|
||||||
|
var bpwg sync.WaitGroup
|
||||||
|
bpch := make(chan []float64, 2)
|
||||||
|
|
||||||
|
// Call low and highpass filter functions.
|
||||||
|
filters := make([][]float64, 2)
|
||||||
|
bpwg.Add(2)
|
||||||
|
go LowPass(taps, upper, &bpwg, bpch)
|
||||||
|
go HighPass(taps, lower, &bpwg, bpch)
|
||||||
|
bpwg.Wait()
|
||||||
|
for i:=0; i<2; i++ {
|
||||||
|
filters[i] = <- bpch
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convolve lowpass filter with highpass filter to get bandpass filter.
|
||||||
|
bpwg.Add(1)
|
||||||
|
bpFilter := Convolve(filters[0], filters[1], &bpwg)
|
||||||
|
ch <- bpFilter
|
||||||
|
|
||||||
|
wg.Done()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Convolve returns the real convolution of the 2 given arrays.
|
||||||
func Convolve (x, h []float64, wg *sync.WaitGroup) []float64 {
|
func Convolve (x, h []float64, wg *sync.WaitGroup) []float64 {
|
||||||
|
|
||||||
// conv waitgroup
|
// Create a waitgroup to be used in goroutines called by Convolution
|
||||||
// var convwg sync.WaitGroup
|
var convwg sync.WaitGroup
|
||||||
|
|
||||||
|
// Compute the convolution
|
||||||
convLen := len(x)+len(h)-1
|
convLen := len(x)+len(h)-1
|
||||||
y := make([]float64, convLen)
|
y := make([]float64, convLen)
|
||||||
for n:=0; n<convLen; n++ {
|
for n:=0; n<convLen; n++ {
|
||||||
go func(n int, y []float64) {
|
convwg.Add(1)
|
||||||
|
go func(n int, y []float64, wg *sync.WaitGroup) {
|
||||||
var sum float64 = 0
|
var sum float64 = 0
|
||||||
for k:=0; k<len(x); k++ {
|
for k:=0; k<len(x); k++ {
|
||||||
if n-k >= 0 && n-k < len(h) {
|
if n-k >= 0 && n-k < len(h) {
|
||||||
|
@ -156,34 +205,31 @@ func Convolve (x, h []float64, wg *sync.WaitGroup) []float64 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
y[n] = sum
|
y[n] = sum
|
||||||
} (n, y)
|
wg.Done()
|
||||||
|
} (n, y, &convwg)
|
||||||
}
|
}
|
||||||
wg.Done()
|
wg.Done()
|
||||||
return y
|
return y
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SaveAudioData takes an input signal and filename and saves the frequency spectrum (to .txt) and the audio signal (to .bin).
|
||||||
func SaveAudioData (signal []float64, fileName string, wg1 *sync.WaitGroup) {
|
func SaveAudioData (signal []float64, fileName string, wg1 *sync.WaitGroup) {
|
||||||
|
|
||||||
// compute fft of signal
|
// Compute the FFT of the signal
|
||||||
FFTaudio := fft.FFTReal(signal)
|
FFTaudio := fft.FFTReal(signal)
|
||||||
|
|
||||||
// normalise and save signal
|
// Normalise the frequency response (-1:1).
|
||||||
spectrum := make([]float64, len(signal))
|
spectrum := make([]float64, len(signal))
|
||||||
for i := range FFTaudio {
|
for i := range FFTaudio {
|
||||||
spectrum[i] = cmplx.Abs(FFTaudio[i])/float64(length)
|
spectrum[i] = cmplx.Abs(FFTaudio[i])/float64(length)
|
||||||
}
|
}
|
||||||
// maximum := Max(signal)
|
maximum := max(spectrum)
|
||||||
// for i := range signal {
|
|
||||||
// signal[i] = signal[i]/maximum
|
|
||||||
// }
|
|
||||||
maximum := Max(spectrum)
|
|
||||||
for i := range spectrum {
|
for i := range spectrum {
|
||||||
spectrum[i] = spectrum[i]/maximum
|
spectrum[i] = spectrum[i]/maximum
|
||||||
}
|
}
|
||||||
|
|
||||||
// SAVE
|
// Save the frequency response/spectrum.
|
||||||
wg1.Add(2)
|
wg1.Add(2)
|
||||||
go func (fileName string, length int, spectrum []float64, wg1 *sync.WaitGroup) {
|
go func (fileName string, length int, spectrum []float64, wg1 *sync.WaitGroup) {
|
||||||
file := fileName + ".txt"
|
file := fileName + ".txt"
|
||||||
|
@ -195,6 +241,7 @@ func SaveAudioData (signal []float64, fileName string, wg1 *sync.WaitGroup) {
|
||||||
wg1.Done()
|
wg1.Done()
|
||||||
}(fileName, 44100, spectrum, wg1)
|
}(fileName, 44100, spectrum, wg1)
|
||||||
|
|
||||||
|
// Save the audio file.
|
||||||
go func (fileName string, signal []float64, wg1 *sync.WaitGroup) {
|
go func (fileName string, signal []float64, wg1 *sync.WaitGroup) {
|
||||||
file := fileName + ".bin"
|
file := fileName + ".bin"
|
||||||
f, _ := os.Create(file)
|
f, _ := os.Create(file)
|
||||||
|
@ -209,28 +256,4 @@ func SaveAudioData (signal []float64, fileName string, wg1 *sync.WaitGroup) {
|
||||||
|
|
||||||
wg1.Done()
|
wg1.Done()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// func SincRedundant (nsamps int, fc float64) []float64 {
|
|
||||||
|
|
||||||
// // n is number of samples either side of n = 0
|
|
||||||
// h := make([]float64, nsamps*2 + 1)
|
|
||||||
// for n:=-nsamps; n<=nsamps; n++ {
|
|
||||||
// if n == 0 {
|
|
||||||
// h[n+nsamps] = 2*fc
|
|
||||||
// } else {
|
|
||||||
// h[n+nsamps] = math.Sin(2*math.Pi*fc*float64(n))/(math.Pi*float64(n))
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return h
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func Sinc (x float64) float64 {
|
|
||||||
// if x == 0 {
|
|
||||||
// return
|
|
||||||
// } else {
|
|
||||||
// h[n+nsamps] = math.Sin(2*math.Pi*fc*float64(n))/(math.Pi*float64(n))
|
|
||||||
// }
|
|
||||||
// }
|
|
Loading…
Reference in New Issue