forked from padster/go-sound
/
cqspectrogram.go
122 lines (107 loc) · 3.15 KB
/
cqspectrogram.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
package main
import (
"bytes"
"flag"
"fmt"
"io/ioutil"
"runtime"
"time"
"github.com/padster/go-sound/cq"
f "github.com/padster/go-sound/file"
s "github.com/padster/go-sound/sounds"
"github.com/padster/go-sound/output"
"github.com/padster/go-sound/util"
)
// Generates the golden files. See test/sounds_test.go for actual test.
func main() {
// Needs to be at least 2 when doing openGL + sound output at the same time.
runtime.GOMAXPROCS(3)
sampleRate := s.CyclesPerSecond
minFreq := flag.Float64("minFreq", 27.5 * 4.0, "minimum frequency")
maxFreq := flag.Float64("maxFreq", 3520.0 / 4.0, "maximum frequency")
octaves := 3
bpo := flag.Int("bpo", 72, "Buckets per octave")
flag.Parse()
remainingArgs := flag.Args()
argCount := len(remainingArgs)
if argCount < 1 || argCount > 2 {
panic("Required: <input> [<output>] filename arguments")
}
inputFile := remainingArgs[0]
outputFile := ""
if (argCount == 2) {
outputFile = remainingArgs[1]
}
// minFreq, maxFreq, bpo := 110.0, 14080.0, 24
params := cq.NewCQParams(sampleRate, *minFreq, *maxFreq, *bpo)
spectrogram := cq.NewSpectrogram(params)
inputSound := f.Read(inputFile)
// fmt.Printf("TODO: Go back to reading %s\n", inputFile)
// inputSound := s.NewTimedSound(
// s.SumSounds(
// s.NewSineWave(440.00),
// // s.NewSineWave(440.00),
// s.NewSineWave(698.46),
// ), 10000)
inputSound.Start()
defer inputSound.Stop()
startTime := time.Now()
if outputFile != "" {
// Write to file
columns := spectrogram.ProcessChannel(inputSound.GetSamples())
outputBuffer := bytes.NewBuffer(make([]byte, 0, 1024))
width, height := 0, 0
for col := range columns {
for _, c := range col {
cq.WriteComplex(outputBuffer, c)
}
if width % 1000 == 0 {
fmt.Printf("At frame: %d\n", width)
}
width++
height = len(col)
}
fmt.Printf("Done! - %d by %d\n", width, height)
ioutil.WriteFile(outputFile, outputBuffer.Bytes(), 0644)
} else {
// No file, so play and show instead:
soundChannel, specChannel := splitChannel(inputSound.GetSamples())
go func() {
columns := spectrogram.ProcessChannel(specChannel)
toShow := util.NewSpectrogramScreen(882, *bpo * octaves, *bpo)
toShow.Render(columns, 1)
}()
output.Play(s.WrapChannelAsSound(soundChannel))
}
elapsedSeconds := time.Since(startTime).Seconds()
fmt.Printf("elapsed time (not counting init): %f sec\n", elapsedSeconds)
if outputFile == "" {
// Hang around to the view can be looked at.
for {}
}
}
// HACK - move to utils, support in both main apps.
func floatFrom16bit(input int32) float64 {
return float64(input) / (float64(1<<15) - 1.0) // Hmmm..doesn't seem right?
}
func int16FromFloat(input float64) int32 {
return int32(input * (float64(1<<15) - 1.0))
}
func floatFrom24bit(input int32) float64 {
return float64(input) / (float64(1<<23) - 1.0) // Hmmm..doesn't seem right?
}
func int24FromFloat(input float64) int32 {
return int32(input * (float64(1<<23) - 1.0))
}
func splitChannel(samples <-chan float64) (chan float64, chan float64) {
r1, r2 := make(chan float64), make(chan float64)
go func() {
for s := range samples {
r1 <- s
r2 <- s
}
close(r1)
close(r2)
}()
return r1, r2
}