2022-01-23 20:04:09 +00:00
|
|
|
package goborator
|
|
|
|
|
2022-01-25 11:04:35 +00:00
|
|
|
// #cgo pkg-config: cgaborator
|
|
|
|
// #include <cgaborator.h>
|
2022-01-23 20:04:09 +00:00
|
|
|
import "C"
|
|
|
|
import (
|
|
|
|
"fmt"
|
2022-01-26 14:08:13 +00:00
|
|
|
"log"
|
2022-01-23 20:04:09 +00:00
|
|
|
"unsafe"
|
|
|
|
)
|
|
|
|
|
|
|
|
type Gaborator struct {
|
|
|
|
pointer unsafe.Pointer
|
|
|
|
latency int64
|
|
|
|
sampleRate float64
|
|
|
|
audioBlockSize int
|
|
|
|
bandcenterCache []float32
|
|
|
|
firstBandCache int
|
2022-01-28 21:30:58 +00:00
|
|
|
coefficientOutputChannel chan []float32
|
2022-01-23 20:04:09 +00:00
|
|
|
frequencyBinTimeStepSize int
|
|
|
|
bandsPerOctave int
|
|
|
|
coefficients [][]float32
|
|
|
|
coefficientIndexOffset int
|
|
|
|
mostRecentCoefficentIndex int
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewGaborator(blockSize int, sampleRate float64, bandsPerOctave int, minimumFrequency, maximumFrequency, referenceFrequency float64, stepSize int) *Gaborator {
|
|
|
|
g := &Gaborator{
|
2022-01-26 08:38:13 +00:00
|
|
|
pointer: unsafe.Pointer(C.gaborator_initialize(C.double(sampleRate), C.int(bandsPerOctave), C.double(minimumFrequency), C.double(referenceFrequency), C.double(maximumFrequency))),
|
2022-01-23 20:04:09 +00:00
|
|
|
sampleRate: sampleRate,
|
|
|
|
audioBlockSize: blockSize,
|
|
|
|
frequencyBinTimeStepSize: stepSize,
|
|
|
|
bandsPerOctave: bandsPerOctave,
|
|
|
|
mostRecentCoefficentIndex: 0,
|
|
|
|
}
|
|
|
|
|
|
|
|
g.latency = int64(C.gaborator_get_anal_support(g.pointer))
|
|
|
|
g.bandcenterCache = g.getBandcenters()
|
|
|
|
|
|
|
|
g.firstBandCache = g.firstBand()
|
|
|
|
|
|
|
|
coefficientSize := (g.latency + 2*int64(blockSize)) / int64(stepSize)
|
|
|
|
g.coefficients = make([][]float32, coefficientSize)
|
|
|
|
for i := range g.coefficients {
|
|
|
|
g.coefficients[i] = make([]float32, g.numberOfBands())
|
|
|
|
}
|
|
|
|
g.coefficientIndexOffset = 0
|
|
|
|
|
|
|
|
return g
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *Gaborator) numberOfBands() int {
|
|
|
|
numberOfBands := 0
|
|
|
|
for _, e := range g.bandcenterCache {
|
|
|
|
if e > 0 {
|
|
|
|
numberOfBands++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return numberOfBands
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *Gaborator) firstBand() int {
|
|
|
|
|
|
|
|
for i, e := range g.bandcenterCache {
|
|
|
|
if e > 0 {
|
|
|
|
return i
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return -1
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *Gaborator) bandCenters(bandIndex int) float32 {
|
|
|
|
return g.bandcenterCache[bandIndex+g.firstBandCache]
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *Gaborator) getBandcenters() []float32 {
|
|
|
|
result := make([]float32, int(C.gaborator_bandcenters_array_length(g.pointer)))
|
|
|
|
C.gaborator_bandcenters(g.pointer, (*C.float)(&result[0]))
|
|
|
|
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
func float32Max(a, b float32) float32 {
|
|
|
|
if a > b {
|
|
|
|
return a
|
|
|
|
}
|
|
|
|
return b
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *Gaborator) gaborTransform(audioData []float32) {
|
|
|
|
|
2022-01-26 08:38:13 +00:00
|
|
|
analysisResult := g.analyze(audioData)
|
2022-01-23 20:04:09 +00:00
|
|
|
|
2022-01-26 08:38:13 +00:00
|
|
|
//The analysis result consists of a float array with three values:
|
|
|
|
// a frequency band index [i] (always an integer)
|
|
|
|
// an audio sample index [i+1] (expressed in audio samples)
|
|
|
|
// a magnitude value [i+2] (the magnitude value)
|
|
|
|
for i := 0; i < len(analysisResult); i += 3 {
|
|
|
|
band := int(analysisResult[i])
|
|
|
|
audioSample := int(analysisResult[i+1])
|
2022-01-27 18:04:22 +00:00
|
|
|
coefficient := float32(analysisResult[i+2])
|
2022-01-23 20:04:09 +00:00
|
|
|
|
|
|
|
coefficientIndex := audioSample/g.frequencyBinTimeStepSize - g.coefficientIndexOffset
|
|
|
|
bandIndex := band - g.firstBandCache
|
|
|
|
|
|
|
|
circularIndex := coefficientIndex % len(g.coefficients)
|
|
|
|
|
|
|
|
// The first results have a negative audio sample index
|
|
|
|
// ignore these
|
|
|
|
if coefficientIndex > 0 && bandIndex < len(g.coefficients[circularIndex]) {
|
|
|
|
|
|
|
|
// If a new index is reached, save the old (fixed) coefficents in the history
|
|
|
|
// Fill the array with zeros to get the max
|
|
|
|
if coefficientIndex > g.mostRecentCoefficentIndex && coefficientIndex > len(g.coefficients) {
|
|
|
|
// keep the new maximum
|
|
|
|
g.mostRecentCoefficentIndex = coefficientIndex
|
2022-01-27 18:04:22 +00:00
|
|
|
// "copy" the oldest data to the history
|
|
|
|
// the slice can be reused thanks to the oldest being filled with zeros just after
|
2022-01-28 21:30:58 +00:00
|
|
|
g.coefficientOutputChannel <- g.coefficients[circularIndex]
|
2022-01-23 20:04:09 +00:00
|
|
|
// fill the oldest with zeros
|
2022-01-27 18:04:22 +00:00
|
|
|
g.coefficients[circularIndex] = make([]float32, len(g.coefficients[circularIndex]))
|
2022-01-23 20:04:09 +00:00
|
|
|
}
|
|
|
|
// due to reduction in precision (from audio sample accuracy to steps) multiple
|
|
|
|
// magnitudes could be placed in the same stepIndex, bandIndex pair.
|
|
|
|
// We take the maximum magnitudes value.
|
|
|
|
g.coefficients[circularIndex][bandIndex] = float32Max(g.coefficients[circularIndex][bandIndex], coefficient)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-01-23 20:32:29 +00:00
|
|
|
|
2022-01-28 21:30:58 +00:00
|
|
|
func (g *Gaborator) GetChannel() chan []float32 {
|
2022-01-26 14:08:13 +00:00
|
|
|
|
2022-01-28 21:30:58 +00:00
|
|
|
if g.coefficientOutputChannel == nil {
|
|
|
|
g.coefficientOutputChannel = make(chan []float32)
|
2022-01-26 14:08:13 +00:00
|
|
|
}
|
|
|
|
|
2022-01-28 21:30:58 +00:00
|
|
|
return g.coefficientOutputChannel
|
2022-01-26 14:08:13 +00:00
|
|
|
}
|
|
|
|
|
2022-01-28 21:30:58 +00:00
|
|
|
func (g *Gaborator) GaborBlockTransform(source chan []float32) (channel chan []float32) {
|
2022-01-23 20:04:09 +00:00
|
|
|
|
2022-01-28 21:30:58 +00:00
|
|
|
channel = g.GetChannel()
|
|
|
|
go func() {
|
|
|
|
defer g.ProcessingFinished()
|
2022-01-23 20:04:09 +00:00
|
|
|
|
2022-01-28 21:30:58 +00:00
|
|
|
for {
|
|
|
|
block, more := <-source
|
|
|
|
if !more {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
err := g.Process(block)
|
|
|
|
if err != nil {
|
|
|
|
log.Panic(err)
|
|
|
|
}
|
2022-01-23 20:04:09 +00:00
|
|
|
}
|
2022-01-28 21:30:58 +00:00
|
|
|
}()
|
2022-01-25 12:59:35 +00:00
|
|
|
|
2022-01-28 21:30:58 +00:00
|
|
|
return channel
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *Gaborator) GaborTransform(source chan float32) (channel chan []float32) {
|
|
|
|
channel = g.GetChannel()
|
2022-01-23 20:04:09 +00:00
|
|
|
|
2022-01-28 21:30:58 +00:00
|
|
|
go func() {
|
|
|
|
defer g.ProcessingFinished()
|
|
|
|
audioData := make([]float32, 0, g.audioBlockSize)
|
2022-01-23 20:04:09 +00:00
|
|
|
|
2022-01-28 21:30:58 +00:00
|
|
|
for {
|
|
|
|
f, more := <-source
|
|
|
|
if !more {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
audioData = append(audioData, f)
|
|
|
|
|
|
|
|
for len(audioData) >= g.audioBlockSize {
|
|
|
|
g.gaborTransform(audioData[0:g.audioBlockSize])
|
|
|
|
audioData = audioData[:0]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
return channel
|
2022-01-23 20:04:09 +00:00
|
|
|
}
|
|
|
|
func (g *Gaborator) Process(block []float32) error {
|
|
|
|
|
|
|
|
if len(block) != g.audioBlockSize {
|
|
|
|
return fmt.Errorf("invalid block size %d != %d", len(block), g.audioBlockSize)
|
|
|
|
}
|
|
|
|
g.gaborTransform(block)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
func (g *Gaborator) ProcessingFinished() {
|
|
|
|
if g.pointer != nil {
|
|
|
|
C.gaborator_release(g.pointer)
|
|
|
|
g.pointer = nil
|
|
|
|
}
|
2022-01-28 21:30:58 +00:00
|
|
|
if g.coefficientOutputChannel != nil {
|
|
|
|
close(g.coefficientOutputChannel)
|
|
|
|
g.coefficientOutputChannel = nil
|
|
|
|
}
|
2022-01-23 20:04:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (g *Gaborator) GetStepSize() int {
|
|
|
|
return g.frequencyBinTimeStepSize
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *Gaborator) GetBlockSize() int {
|
|
|
|
return g.audioBlockSize
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *Gaborator) GetSampleRate() float64 {
|
|
|
|
return g.sampleRate
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *Gaborator) GetBandwidth() float64 {
|
|
|
|
return 1200. / float64(g.bandsPerOctave)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *Gaborator) GetLatency() int64 {
|
|
|
|
return g.latency
|
|
|
|
}
|
|
|
|
|
2022-01-27 18:04:22 +00:00
|
|
|
func (g *Gaborator) analyze(block []float32) []C.float {
|
2022-01-23 20:04:09 +00:00
|
|
|
//log.Printf("analyze block len %d", len(block))
|
|
|
|
C.gaborator_analyze(g.pointer, (*C.float)(&block[0]), C.int(len(block)))
|
|
|
|
cSize := uintptr(C.gaborator_get_array_length(g.pointer))
|
|
|
|
|
|
|
|
//log.Print(cSize)
|
|
|
|
ptr := (*C.float)(C.gaborator_get_array(g.pointer))
|
2022-01-27 18:04:22 +00:00
|
|
|
return unsafe.Slice(ptr, cSize)
|
2022-01-23 20:04:09 +00:00
|
|
|
}
|