refactor: reorganize monolithic main package into focused packages
Move the executable entry point to `src/cmd/captioneer` and split audio, capture, config, caption, and model logic into separate packages under `src/`. Update README with new build/run commands and a package-overview section.
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
// Package audio contains the application's PCM and audio-level utilities.
|
||||
package audio
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
SampleRate = 16000
|
||||
Channels = 1
|
||||
BytesPerSample = 2
|
||||
PacketDuration = 100 * time.Millisecond
|
||||
)
|
||||
|
||||
func SamplesPerPacket() int {
|
||||
return DurationSamples(PacketDuration)
|
||||
}
|
||||
|
||||
func PCM16LEToFloat32(pcm []byte) []float32 {
|
||||
samples := make([]float32, len(pcm)/BytesPerSample)
|
||||
for i := range samples {
|
||||
samples[i] = float32(int16(binary.LittleEndian.Uint16(pcm[i*2:]))) / 32768
|
||||
}
|
||||
return samples
|
||||
}
|
||||
|
||||
func DurationSamples(duration time.Duration) int {
|
||||
return int(duration * SampleRate / time.Second)
|
||||
}
|
||||
|
||||
func RMSDBFS(samples []float32) float64 {
|
||||
if len(samples) == 0 {
|
||||
return math.Inf(-1)
|
||||
}
|
||||
|
||||
var sum float64
|
||||
for _, sample := range samples {
|
||||
sum += float64(sample * sample)
|
||||
}
|
||||
rms := math.Sqrt(sum / float64(len(samples)))
|
||||
if rms == 0 {
|
||||
return math.Inf(-1)
|
||||
}
|
||||
return 20 * math.Log10(rms)
|
||||
}
|
||||
|
||||
func FormatTime(samples int) string {
|
||||
return fmt.Sprintf("%.2fs", float64(samples)/SampleRate)
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package audio
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"math"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestPCM16LEToFloat32(t *testing.T) {
|
||||
pcm := make([]byte, 6)
|
||||
binary.LittleEndian.PutUint16(pcm[0:], uint16(0))
|
||||
binary.LittleEndian.PutUint16(pcm[2:], uint16(32767))
|
||||
binary.LittleEndian.PutUint16(pcm[4:], uint16(32768))
|
||||
got := PCM16LEToFloat32(pcm)
|
||||
want := []float32{0, 32767.0 / 32768.0, -1}
|
||||
for i := range want {
|
||||
if math.Abs(float64(got[i]-want[i])) > 0.00001 {
|
||||
t.Fatalf("sample %d = %f, want %f", i, got[i], want[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDurationSamples(t *testing.T) {
|
||||
if got := DurationSamples(time.Second); got != SampleRate {
|
||||
t.Fatalf("one second = %d samples, want %d", got, SampleRate)
|
||||
}
|
||||
if got := DurationSamples(1500 * time.Millisecond); got != 24000 {
|
||||
t.Fatalf("1.5 seconds = %d samples, want 24000", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRMSDBFS(t *testing.T) {
|
||||
if got := RMSDBFS([]float32{1, -1}); math.Abs(got) > 0.00001 {
|
||||
t.Fatalf("full-scale RMS = %f dBFS, want 0", got)
|
||||
}
|
||||
if got := RMSDBFS([]float32{0, 0}); !math.IsInf(got, -1) {
|
||||
t.Fatalf("silence = %f dBFS, want -Inf", got)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user