mirror of https://bitbucket.org/ausocean/av.git
Audiofiltering:
Interface the function with pre-existing data structures. The filters can now be generated into a filter type using a generic Generate function. This generates a filter off of the specifications within the filter struct. There is a generic Apply function which takes in a buffer of PCM data (defined in pcm.go), and outputs to a []byte.
This commit is contained in:
parent
75124b4494
commit
d029038db9
|
@ -1,5 +0,0 @@
|
|||
module bitbucket.org/ausocean/av/cmd/audiofiltering
|
||||
|
||||
go 1.19
|
||||
|
||||
require github.com/mjibson/go-dsp v0.0.0-20180508042940-11479a337f12
|
|
@ -1,2 +0,0 @@
|
|||
github.com/mjibson/go-dsp v0.0.0-20180508042940-11479a337f12 h1:dd7vnTDfjtwCETZDrRe+GPYNLA1jBtbZeyfyE8eZCyk=
|
||||
github.com/mjibson/go-dsp v0.0.0-20180508042940-11479a337f12/go.mod h1:i/KKcxEWEO8Yyl11DYafRPKOPVYTrhxiTRigjtEEXZU=
|
|
@ -1,259 +1,56 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"math"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"encoding/binary"
|
||||
"github.com/mjibson/go-dsp/fft"
|
||||
"github.com/mjibson/go-dsp/window"
|
||||
"math/cmplx"
|
||||
"time"
|
||||
"sync"
|
||||
|
||||
"bitbucket.org/ausocean/av/codec/pcm"
|
||||
)
|
||||
|
||||
// Define the constants to be used in the generation of the sound files.
|
||||
const(
|
||||
SampleRate float64 = 44100
|
||||
Duration = 2
|
||||
tau = math.Pi * 2
|
||||
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.
|
||||
// main is a driver function for testing the filters defined in codec/pcm/filters.go
|
||||
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.
|
||||
// Define start time for execution timing.
|
||||
start := time.Now()
|
||||
|
||||
// Generate sine waves with different frequencies to test frequency response.
|
||||
n := 100
|
||||
wg1.Add(n)
|
||||
audio := make([][]float64, n)
|
||||
for i:=0; i<n; i++ {
|
||||
go func(i int, audio [][]float64, wg *sync.WaitGroup) {
|
||||
audio[i] = generate(float64((i)*(22000/n)))
|
||||
wg.Done()
|
||||
} (i, audio, &wg1)
|
||||
}
|
||||
// Read the audio data from the file.
|
||||
input, _ := os.ReadFile("spike.pcm")
|
||||
|
||||
// Create the filter.
|
||||
filterLen := 500
|
||||
wg2.Add(1)
|
||||
ch := make(chan []float64, 1)
|
||||
go BandPass(filterLen, 2000, 5000, &wg2, ch)
|
||||
// Create Buffer in struct format defined in pcm.go
|
||||
format := pcm.BufferFormat{Rate: 44100, Channels: 1, SFormat: pcm.S16_LE}
|
||||
buf := pcm.Buffer{Format: format, Data: input}
|
||||
|
||||
// Create a filter.
|
||||
lp := pcm.Filter{BuffInfo: buf.Format, Type: pcm.LP, Upper: 10000, Taps: 50}
|
||||
lp.Generate()
|
||||
|
||||
// Combine all the generated sine waves into a single audio slice.
|
||||
wg1.Wait()
|
||||
combinedAudio := make([]float64, length)
|
||||
for i := range audio[0] {
|
||||
combinedAudio[i] = 0
|
||||
for j:=0; j<n; j++ {
|
||||
combinedAudio[i] += audio[j][i]
|
||||
}
|
||||
}
|
||||
// Apply the lowpass filter to the buffer.
|
||||
output := lp.Apply(buf)
|
||||
|
||||
// Get the filter from the filter goroutine called earlier.
|
||||
wg2.Wait()
|
||||
filter := <- ch
|
||||
// Save the transformed audio.
|
||||
f, _ := os.Create("output.pcm")
|
||||
f.Write(output[50 : len(output)-50])
|
||||
|
||||
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)
|
||||
|
||||
// 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))
|
||||
// Display execution time.
|
||||
fmt.Println("Finished execution. Total time:", 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 {
|
||||
// LEGACY FUNCTION FOR TESTING
|
||||
// generate is used to generate a sine wave of the given frequency, calls on the global variables of Duration and SampleRate (bytes)
|
||||
func generate(freq float64, SampleRate, duration int) []byte {
|
||||
|
||||
// Deteremine the number of samples required based off Duration and SampleRate.
|
||||
nsamps := Duration * SampleRate
|
||||
// Create slices to store values.
|
||||
signal := make([]byte, 2*duration*SampleRate)
|
||||
t := make([]float64, 2*duration*SampleRate)
|
||||
|
||||
// Generate the x-values to plot against.
|
||||
var angle float64 = tau / float64(nsamps)
|
||||
|
||||
// Create an array for the generated samples.
|
||||
samp := make([]float64, int(nsamps))
|
||||
|
||||
// Generate the samples.
|
||||
for i := 0; i < int(nsamps); i++ {
|
||||
samp[i] = math.Sin(angle * Frequency * float64(4*i))
|
||||
// Generate the values to plot.
|
||||
for n := range t {
|
||||
t[n] = math.Pi * float64(n) / float64(SampleRate)
|
||||
signal[n] = byte(math.Round(127 * math.Sin(freq*t[n])))
|
||||
}
|
||||
|
||||
return samp
|
||||
|
||||
}
|
||||
|
||||
// max returns the absolute highest value in a given array.
|
||||
func max(a []float64) float64 {
|
||||
|
||||
var runMax float64 = -1
|
||||
for i:= range a {
|
||||
if math.Abs(a[i]) > runMax {
|
||||
runMax = math.Abs(a[i])
|
||||
}
|
||||
}
|
||||
|
||||
return runMax
|
||||
|
||||
}
|
||||
|
||||
// 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) {
|
||||
|
||||
size := taps + 1
|
||||
fd := fc/SampleRate
|
||||
b := (2*math.Pi) * fd
|
||||
filter := make([]float64, size)
|
||||
winData := window.FlatTop(size)
|
||||
for n:=0; n<(taps/2); n++ {
|
||||
c := float64(n) - float64(taps)/2
|
||||
y := math.Sin(c*b) / (math.Pi * c)
|
||||
filter[n] = y * winData[n]
|
||||
filter[size-1-n] = filter[n]
|
||||
}
|
||||
filter[taps/2] = 2*fd*winData[taps/2]
|
||||
ch <- filter
|
||||
wg.Done()
|
||||
|
||||
}
|
||||
|
||||
// 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) {
|
||||
|
||||
size := taps + 1
|
||||
fd := fc/SampleRate
|
||||
b := (2*math.Pi) * fd
|
||||
filter := make([]float64, size)
|
||||
winData := window.FlatTop(size)
|
||||
for n:=0; n<(taps/2); n++ {
|
||||
c := float64(n) - float64(taps)/2
|
||||
y := math.Sin(c*b) / (math.Pi * c)
|
||||
filter[n] = -y * winData[n]
|
||||
filter[size-1-n] = filter[n]
|
||||
}
|
||||
filter[taps/2] = (1 - 2*fd) * winData[taps/2]
|
||||
ch <- filter
|
||||
wg.Done()
|
||||
|
||||
}
|
||||
|
||||
// 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 {
|
||||
|
||||
// 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<convLen; n++ {
|
||||
convwg.Add(1)
|
||||
go func(n int, y []float64, wg *sync.WaitGroup) {
|
||||
var sum float64 = 0
|
||||
for k:=0; k<len(x); k++ {
|
||||
if n-k >= 0 && n-k < len(h) {
|
||||
sum += x[k]*h[n-k]
|
||||
}
|
||||
}
|
||||
y[n] = sum
|
||||
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 the FFT of the signal
|
||||
FFTaudio := fft.FFTReal(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(spectrum)
|
||||
for i := range spectrum {
|
||||
spectrum[i] = spectrum[i]/maximum
|
||||
}
|
||||
|
||||
// Save the frequency response/spectrum.
|
||||
wg1.Add(2)
|
||||
go func (fileName string, length int, spectrum []float64, wg1 *sync.WaitGroup) {
|
||||
file := fileName + ".txt"
|
||||
f, _ := os.Create(file)
|
||||
for i:=0; i<len(spectrum)/2; i++ {
|
||||
fmt.Fprintf(f, "%v\n", 20*math.Log10(spectrum[i]))
|
||||
}
|
||||
fmt.Printf("Saved spectrum values to: %s\n", fileName)
|
||||
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)
|
||||
var buf [8]byte
|
||||
for i:=0; i<len(signal); i++ {
|
||||
binary.LittleEndian.PutUint64(buf[:], math.Float64bits(signal[i]))
|
||||
_, _ = f.Write(buf[:])
|
||||
}
|
||||
fmt.Printf("Saved binary values to: %s\n", fileName)
|
||||
wg1.Done()
|
||||
}(fileName, signal, wg1)
|
||||
|
||||
wg1.Done()
|
||||
|
||||
return signal
|
||||
}
|
|
@ -0,0 +1,198 @@
|
|||
package pcm
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"math"
|
||||
"sync"
|
||||
|
||||
"github.com/mjibson/go-dsp/window"
|
||||
)
|
||||
|
||||
// FilterType is the type of filter which can be generated.
|
||||
type FilterType int
|
||||
|
||||
// Currently implemented filter types.
|
||||
const (
|
||||
LP FilterType = iota
|
||||
HP
|
||||
BP
|
||||
BS
|
||||
)
|
||||
|
||||
// Filter contains the specifications of the filter, as well as the coefficients to the filter function itself.
|
||||
type Filter struct {
|
||||
Coeffs []float64
|
||||
Type FilterType
|
||||
SampleRate uint
|
||||
Lower, Upper float64
|
||||
Taps int
|
||||
BuffInfo BufferFormat
|
||||
}
|
||||
|
||||
// AudioFilter interface contains Generate and Apply. Generate is used to generate the coefficients of the filter based off
|
||||
// the specifications within the Filter struct.
|
||||
// Apply is used to apply the filter to the given buffer of PCM data (b.Data).
|
||||
type AudioFilter interface {
|
||||
Generate()
|
||||
Apply(b Buffer)
|
||||
}
|
||||
|
||||
// Generate is used to generate the coefficients of the filter function used for convolution with a signal to
|
||||
// perform specified filter.
|
||||
func (filter *Filter) Generate() {
|
||||
|
||||
// Update the sample rate from the buffer info (if supplied).
|
||||
if filter.BuffInfo.Rate != 0 {
|
||||
filter.SampleRate = filter.BuffInfo.Rate
|
||||
}
|
||||
|
||||
// Determine the type of filter to generate (based off filter.Type).
|
||||
switch filter.Type {
|
||||
case LP:
|
||||
|
||||
// Create a lowpass filter with characteristics from struct.
|
||||
size := filter.Taps + 1
|
||||
filter.Coeffs = make([]float64, size, size)
|
||||
fd := (filter.Upper * 0.7) / float64(filter.SampleRate)
|
||||
b := (2 * math.Pi) * fd
|
||||
winData := window.FlatTop(size)
|
||||
for n := 0; n < (filter.Taps / 2); n++ {
|
||||
c := float64(n) - float64(filter.Taps)/2
|
||||
y := math.Sin(c*b) / (math.Pi * c)
|
||||
filter.Coeffs[n] = (y * winData[n])
|
||||
filter.Coeffs[size-1-n] = filter.Coeffs[n]
|
||||
}
|
||||
filter.Coeffs[filter.Taps/2] = 2 * fd * winData[filter.Taps/2]
|
||||
|
||||
case HP:
|
||||
|
||||
// Create a Highpass filter with characteristics from struct.
|
||||
size := filter.Taps + 1
|
||||
filter.Coeffs = make([]float64, size, size)
|
||||
fd := (filter.Lower + 3000) / float64(filter.SampleRate)
|
||||
b := (2 * math.Pi) * fd
|
||||
winData := window.FlatTop(size)
|
||||
for n := 0; n < (filter.Taps / 2); n++ {
|
||||
c := float64(n) - float64(filter.Taps)/2
|
||||
y := math.Sin(c*b) / (math.Pi * c)
|
||||
filter.Coeffs[n] = -y * winData[n]
|
||||
filter.Coeffs[size-1-n] = filter.Coeffs[n]
|
||||
}
|
||||
filter.Coeffs[filter.Taps/2] = (1 - 2*fd) * winData[filter.Taps/2]
|
||||
|
||||
case BP:
|
||||
|
||||
// Make Low and Highpass filters.
|
||||
lp := Filter{Type: LP, SampleRate: filter.SampleRate, Upper: filter.Upper, Taps: filter.Taps}
|
||||
hp := Filter{Type: HP, SampleRate: filter.SampleRate, Lower: filter.Lower, Taps: filter.Taps}
|
||||
lp.Generate()
|
||||
hp.Generate()
|
||||
|
||||
// Convolve lowpass filter with highpass filter to get bandpass filter.
|
||||
var wg sync.WaitGroup
|
||||
ch := make(chan []float64)
|
||||
wg.Add(1)
|
||||
go Convolve(lp.Coeffs, hp.Coeffs, &wg, ch)
|
||||
wg.Wait()
|
||||
filter.Coeffs = <-ch
|
||||
|
||||
case BS:
|
||||
|
||||
// Make Low and Highpass filters.
|
||||
lp := Filter{Type: LP, SampleRate: filter.SampleRate, Upper: filter.Lower, Taps: filter.Taps}
|
||||
hp := Filter{Type: HP, SampleRate: filter.SampleRate, Lower: filter.Upper, Taps: filter.Taps}
|
||||
lp.Generate()
|
||||
hp.Generate()
|
||||
|
||||
// Add lowpass filter to highpass filter to get bandstop filter.
|
||||
size := filter.Taps + 1
|
||||
filter.Coeffs = make([]float64, size, size)
|
||||
for i := range lp.Coeffs {
|
||||
filter.Coeffs[i] = lp.Coeffs[i] + hp.Coeffs[i]
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Apply takes in a buffer of PCM audio, applies the filter and returns the filtered audio (in byte slice)
|
||||
func (filter *Filter) Apply(b Buffer) []byte {
|
||||
|
||||
// Convert input to floats.
|
||||
inputAsFloat := make([]float64, len(b.Data)/2)
|
||||
temp := make([]byte, 2)
|
||||
for i := range inputAsFloat {
|
||||
temp[0] = b.Data[2*i]
|
||||
temp[1] = b.Data[2*i+1]
|
||||
inputAsFloat[i] = float64(binary.LittleEndian.Uint16(temp))
|
||||
if inputAsFloat[i] > 32767 {
|
||||
inputAsFloat[i] -= 32768 * 2
|
||||
}
|
||||
inputAsFloat[i] /= (32767)
|
||||
}
|
||||
|
||||
// Convolve input with filter.
|
||||
var wg sync.WaitGroup
|
||||
ch := make(chan []float64, 1)
|
||||
wg.Add(1)
|
||||
go Convolve(inputAsFloat, filter.Coeffs, &wg, ch)
|
||||
wg.Wait()
|
||||
convolution := <-ch
|
||||
|
||||
// Convert convolution output back to bytes.
|
||||
var output []byte
|
||||
buf := make([]byte, 2)
|
||||
for i := range convolution {
|
||||
convolution[i] = convolution[i] * 32767
|
||||
if convolution[i] < 0 {
|
||||
convolution[i] = convolution[i] + 32768*2
|
||||
}
|
||||
binary.LittleEndian.PutUint16(buf[:], uint16(convolution[i]))
|
||||
output = append(output, buf[0], buf[1])
|
||||
}
|
||||
|
||||
return output
|
||||
|
||||
}
|
||||
|
||||
func Convolve(x, h []float64, wg *sync.WaitGroup, ch chan []float64) {
|
||||
|
||||
// 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 < convLen; n++ {
|
||||
convwg.Add(1)
|
||||
go func(n int, y []float64, convwg *sync.WaitGroup) {
|
||||
var sum float64 = 0
|
||||
for k := 0; k < len(x); k++ {
|
||||
if n-k >= 0 && n-k < len(h) {
|
||||
sum += x[k] * h[n-k]
|
||||
} else if n-k < 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
y[n] = sum
|
||||
convwg.Done()
|
||||
}(n, y, &convwg)
|
||||
}
|
||||
ch <- y
|
||||
close(ch)
|
||||
wg.Done()
|
||||
}
|
||||
|
||||
// LEGACY FUNCTION FOR TESTING
|
||||
// Max returns the absolute highest value in a given array.
|
||||
func Max(a []float64) float64 {
|
||||
|
||||
var runMax float64 = -1
|
||||
for i := range a {
|
||||
if math.Abs(a[i]) > runMax {
|
||||
runMax = math.Abs(a[i])
|
||||
}
|
||||
}
|
||||
|
||||
return runMax
|
||||
|
||||
}
|
5
go.mod
5
go.mod
|
@ -12,10 +12,11 @@ require (
|
|||
github.com/google/go-cmp v0.4.1
|
||||
github.com/kidoman/embd v0.0.0-20170508013040-d3d8c0c5c68d
|
||||
github.com/mewkiz/flac v1.0.5
|
||||
github.com/mjibson/go-dsp v0.0.0-20180508042940-11479a337f12
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/yobert/alsa v0.0.0-20180630182551-d38d89fa843e
|
||||
gocv.io/x/gocv v0.29.0
|
||||
gonum.org/v1/gonum v0.9.3
|
||||
gonum.org/v1/plot v0.10.0
|
||||
gonum.org/v1/gonum v0.8.2
|
||||
gonum.org/v1/plot v0.9.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||
)
|
||||
|
|
32
go.sum
32
go.sum
|
@ -16,13 +16,11 @@ github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWso
|
|||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||
github.com/adrianmo/go-nmea v1.1.1-0.20190109062325-c448653979f7/go.mod h1:HHPxPAm2kmev+61qmkZh7xgZF/7qHtSpsWppip2Ipv8=
|
||||
github.com/aerth/playwav v0.0.0-20170324024803-17dfe21a406f/go.mod h1:aTANbm/GXj1ilCRMsIiSpX0i7LUkqjAPj6R0fpWbLNA=
|
||||
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af h1:wVe6/Ea46ZMeNkQjjBW6xcqyQA/j5e0D6GytH95g0gQ=
|
||||
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
|
||||
github.com/ajstarks/svgo v0.0.0-20210923152817-c3b6e2f0c527 h1:NImof/JkF93OVWZY+PINgl6fPtQyF6f+hNUtZ0QZA1c=
|
||||
github.com/ajstarks/svgo v0.0.0-20210923152817-c3b6e2f0c527/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||
github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U=
|
||||
github.com/cocoonlife/goalsa v0.0.0-20160812085113-b711ae6f3eff/go.mod h1:5aLO409bJnd+jCw0t/SB/DhHkVBhPAE31lnHJnYQxy0=
|
||||
github.com/cocoonlife/testify v0.0.0-20160218172820-792cc1faeb64/go.mod h1:LoCAz53rbPcqs8Da2BjB/yDy4gxMtiSQmqnYI/DGH+U=
|
||||
|
@ -46,18 +44,13 @@ github.com/go-audio/wav v0.0.0-20181013172942-de841e69b884 h1:2TaXIaVA4ff/MHHezO
|
|||
github.com/go-audio/wav v0.0.0-20181013172942-de841e69b884/go.mod h1:UiqzUyfX0zs3pJ/DPyvS5v8sN6s5bXPUDDIVA5v8dks=
|
||||
github.com/go-fonts/dejavu v0.1.0 h1:JSajPXURYqpr+Cu8U9bt8K+XcACIHWqWrvWCKyeFmVQ=
|
||||
github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g=
|
||||
github.com/go-fonts/latin-modern v0.2.0 h1:5/Tv1Ek/QCr20C6ZOz15vw3g7GELYL98KWr8Hgo+3vk=
|
||||
github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks=
|
||||
github.com/go-fonts/liberation v0.1.1 h1:wBrPaMkrXFBW3qXpXAjiKljdVUMxn9bX2ia3XjPHoik=
|
||||
github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY=
|
||||
github.com/go-fonts/liberation v0.2.0 h1:jAkAWJP4S+OsrPLZM4/eC9iW7CtHy+HBXrEwZXWo5VM=
|
||||
github.com/go-fonts/liberation v0.2.0/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY=
|
||||
github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07 h1:OTlfMvwR1rLyf9goVmXfuS5AJn80+Vmj4rTf4n46SOs=
|
||||
github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U=
|
||||
github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81 h1:6zl3BbBhdnMkpSj2YY30qV3gDcVBGtFgVsV3+/i+mKQ=
|
||||
github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk=
|
||||
github.com/go-pdf/fpdf v0.5.0 h1:GHpcYsiDV2hdo77VTOuTF9k1sN8F8IY7NjnCo9x+NPY=
|
||||
github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M=
|
||||
github.com/go-ping/ping v0.0.0-20201115131931-3300c582a663/go.mod h1:35JbSyV/BYqHwwRA6Zr1uVDm1637YlNOU61wI797NPI=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
|
@ -79,17 +72,18 @@ github.com/mattetti/audio v0.0.0-20180912171649-01576cde1f21 h1:Hc1iKlyxNHp3CV59
|
|||
github.com/mattetti/audio v0.0.0-20180912171649-01576cde1f21/go.mod h1:LlQmBGkOuV/SKzEDXBPKauvN2UqCgzXO2XjecTGj40s=
|
||||
github.com/mewkiz/flac v1.0.5 h1:dHGW/2kf+/KZ2GGqSVayNEhL9pluKn/rr/h/QqD9Ogc=
|
||||
github.com/mewkiz/flac v1.0.5/go.mod h1:EHZNU32dMF6alpurYyKHDLYpW1lYpBZ5WrXi/VuNIGs=
|
||||
github.com/mjibson/go-dsp v0.0.0-20180508042940-11479a337f12 h1:dd7vnTDfjtwCETZDrRe+GPYNLA1jBtbZeyfyE8eZCyk=
|
||||
github.com/mjibson/go-dsp v0.0.0-20180508042940-11479a337f12/go.mod h1:i/KKcxEWEO8Yyl11DYafRPKOPVYTrhxiTRigjtEEXZU=
|
||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/phpdave11/gofpdf v1.4.2 h1:KPKiIbfwbvC/wOncwhrpRdXVj2CZTCFlw4wnoyjtHfQ=
|
||||
github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY=
|
||||
github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
|
||||
github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w=
|
||||
github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk=
|
||||
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
|
@ -129,10 +123,8 @@ golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+o
|
|||
golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20210216034530-4410531fe030 h1:lP9pYkih3DUSC641giIXa2XqfTIbbbRr0w2EOTA7wHA=
|
||||
golang.org/x/image v0.0.0-20210216034530-4410531fe030/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
|
||||
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d h1:RNPAfi2nHY7C2srAV8A49jpsYr0ADedCk1wq6fTMTvs=
|
||||
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
|
@ -152,9 +144,8 @@ golang.org/x/sys v0.0.0-20210909193231-528a39cd75f3/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
@ -163,20 +154,17 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
|
|||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
|
||||
gonum.org/v1/gonum v0.8.2 h1:CCXrcPKiGGotvnN6jfUsKk4rRqm7q09/YbKb5xCEvtM=
|
||||
gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
|
||||
gonum.org/v1/gonum v0.9.3 h1:DnoIG+QAMaF5NvxnGe/oKsgKcAc6PcUyl8q0VetfQ8s=
|
||||
gonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0=
|
||||
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0 h1:OE9mWmgKkjJyEmDAAtGMPjXu+YNeGvK9VTSHY6+Qihc=
|
||||
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
|
||||
gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
|
||||
gonum.org/v1/plot v0.9.0 h1:3sEo36Uopv1/SA/dMFFaxXoL5XyikJ9Sf2Vll/k6+2E=
|
||||
gonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY=
|
||||
gonum.org/v1/plot v0.10.0 h1:ymLukg4XJlQnYUJCp+coQq5M7BsUJFk6XQE4HPflwdw=
|
||||
gonum.org/v1/plot v0.10.0/go.mod h1:JWIHJ7U20drSQb/aDpTetJzfC1KlAPldJLpkSy88dvQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
|
|
Loading…
Reference in New Issue