goborator/goborator.go
2022-01-23 21:04:09 +01:00

206 lines
5.6 KiB
Go

package goborator
// #cgo CFLAGS: -I${SRCDIR}/c-gaborator
// #cgo LDFLAGS: -L${SRCDIR}/c-gaborator/build -lcgaborator -lm -lstdc++
// #include "cgaborator.h"
import "C"
import (
"encoding/binary"
"fmt"
"io"
"unsafe"
)
type Gaborator struct {
pointer unsafe.Pointer
latency int64
sampleRate float64
audioBlockSize int
bandcenterCache []float32
firstBandCache int
audioDataToTransform []float32
fixedCoefficents [][]float32
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{
pointer: unsafe.Pointer(C.gaborator_initialize(C.int(blockSize), C.double(sampleRate), C.int(bandsPerOctave), C.double(minimumFrequency), C.double(maximumFrequency), C.double(referenceFrequency))),
sampleRate: sampleRate,
audioBlockSize: blockSize,
audioDataToTransform: make([]float32, 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) {
analysysResult := g.analyze(audioData)
for i := 0; i < len(analysysResult); i += 3 {
band := int(analysysResult[i])
audioSample := int(analysysResult[i+1])
coefficient := analysysResult[i+2]
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
// copy the oldest data to the history
g.fixedCoefficents = append(g.fixedCoefficents, g.coefficients[circularIndex])
// fill the oldest with zeros
for j := range g.coefficients[circularIndex] {
g.coefficients[circularIndex][j] = 0.
}
}
// 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)
}
}
}
func (g *Gaborator) GaborTransform(reader io.Reader) [][]float32 {
var err error
var f float32
var audioData []float32
for {
err = binary.Read(reader, binary.LittleEndian, &f)
if err != nil {
break
}
audioData = append(audioData, f)
}
//log.Printf("length file in float %d / blocks %d", len(audioData), len(audioData)/g.audioBlockSize)
//TODO: this seems to skip last block?
for floatIndex := 0; floatIndex < len(audioData); floatIndex += g.audioBlockSize {
g.gaborTransform(audioData[floatIndex : floatIndex+g.audioBlockSize])
}
g.ProcessingFinished()
return g.coefficients
}
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
}
}
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
}
func (g *Gaborator) analyze(block []float32) []float32 {
//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))
result := make([]float32, cSize)
for i, v := range unsafe.Slice(ptr, cSize) {
result[i] = float32(v)
}
return result
}