diff --git a/cmd/audiofiltering/main.go b/cmd/audiofiltering/main.go index c8a17d28..fb56e774 100644 --- a/cmd/audiofiltering/main.go +++ b/cmd/audiofiltering/main.go @@ -12,7 +12,7 @@ import( "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( SampleRate float64 = 44100 Duration = 2 @@ -20,12 +20,17 @@ const( 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() { + // Create waitgroups to ensure program runs correctly. var wg1, wg2 sync.WaitGroup + + // Take a measurement of the starting point of the program execution to use for execution timing. 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 wg1.Add(n) audio := make([][]float64, n) @@ -36,14 +41,15 @@ func main() { } (i, audio, &wg1) } - // create filter + // Create the filter. filterLen := 500 wg2.Add(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() - // combine audio combinedAudio := make([]float64, length) for i := range audio[0] { combinedAudio[i] = 0 @@ -52,34 +58,41 @@ func main() { } } + // Get the filter from the filter goroutine called earlier. wg2.Wait() filter := <- ch - // convolve sinc with audio - wg1.Add(1) - // go SaveAudioData(combinedAudio, "unfiltered", &wg1) + wg1.Add(2) + // Save the unfiltered audio to compare the filtered audio against. + go SaveAudioData(combinedAudio, "unfiltered", &wg1) + + // Apply the filter to the combined audio file by computing the convolution of the 2 slices. wg2.Add(1) 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() 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 { - // deteremine number of samples based off duration and sample rate + // Deteremine the number of samples required based off Duration and SampleRate. nsamps := Duration * SampleRate - // generate x-values + // Generate the x-values to plot against. var angle float64 = tau / float64(nsamps) - // create sample array + // Create an array for the generated samples. samp := make([]float64, int(nsamps)) - // generate samples and write to file + // Generate the samples. for i := 0; i < int(nsamps); 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 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) { - // create sinc function size := taps + 1 fd := fc/SampleRate 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) { - // create sinc function size := taps + 1 fd := fc/SampleRate 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 { - // conv waitgroup - // var convwg sync.WaitGroup + // Create a waitgroup to be used in goroutines called by Convolution + var convwg sync.WaitGroup + + // Compute the convolution convLen := len(x)+len(h)-1 y := make([]float64, convLen) for n:=0; n= 0 && n-k < len(h) { @@ -156,34 +205,31 @@ func Convolve (x, h []float64, wg *sync.WaitGroup) []float64 { } } y[n] = sum - } (n, y) - + wg.Done() + } (n, y, &convwg) } wg.Done() 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) { - // compute fft of signal + // Compute the FFT of the signal FFTaudio := fft.FFTReal(signal) - // normalise and save signal + // Normalise the frequency response (-1:1). spectrum := make([]float64, len(signal)) for i := range FFTaudio { spectrum[i] = cmplx.Abs(FFTaudio[i])/float64(length) } - // maximum := Max(signal) - // for i := range signal { - // signal[i] = signal[i]/maximum - // } - maximum := Max(spectrum) + maximum := max(spectrum) for i := range spectrum { spectrum[i] = spectrum[i]/maximum } - // SAVE + // Save the frequency response/spectrum. wg1.Add(2) go func (fileName string, length int, spectrum []float64, wg1 *sync.WaitGroup) { file := fileName + ".txt" @@ -195,6 +241,7 @@ func SaveAudioData (signal []float64, fileName string, wg1 *sync.WaitGroup) { wg1.Done() }(fileName, 44100, spectrum, wg1) + // Save the audio file. go func (fileName string, signal []float64, wg1 *sync.WaitGroup) { file := fileName + ".bin" f, _ := os.Create(file) @@ -209,28 +256,4 @@ func SaveAudioData (signal []float64, fileName string, wg1 *sync.WaitGroup) { 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)) -// } -// } \ No newline at end of file +} \ No newline at end of file