-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathaudioProperties.go
145 lines (102 loc) · 3.12 KB
/
audioProperties.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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
package resound
import (
"io"
"math"
)
// AnalysisResult is an object that contains the results of an analysis performed on a stream.
type AnalysisResult struct {
Normalization float64
}
// AudioProperty is an object that allows associating an AnalysisResult for a specific stream with a name for that stream.
type AudioProperty struct {
result AnalysisResult
analyzed bool
}
func newAudioProperty() *AudioProperty {
ap := &AudioProperty{}
ap.ResetAnalyzation()
return ap
}
// A common byte slice for analysis of various streams
var byteSlice = make([]byte, 512)
// Analyze analyzes the provided audio stream, returning an AnalysisResult object.
// The stream is the audio stream to be used for scanning, and the scanCount is the number of times
// the function should scan various parts of the audio stream. The higher the scan count, the more accurate
// the results should be, but the longer the scan would take.
// A scanCount of 16 means it samples the stream 16 times evenly throughout the file.
// If a scanCount of 0 or less is provided, it will default to 64.
func (ap *AudioProperty) Analyze(stream io.ReadSeeker, scanCount int64) (AnalysisResult, error) {
if scanCount <= 0 {
scanCount = 64
}
if ap.analyzed {
return ap.result, nil
}
largest := 0.0
// Get the length of the stream normally
length, err := stream.Seek(0, io.SeekEnd)
// If there's an error, try getting the length of the stream by seeking to the end; we can't seek using io.SeekEnd for infinite loops
if err != nil {
length, err = stream.Seek(math.MaxInt64, io.SeekStart)
// If there's still an error, return the error.
if err != nil {
return AnalysisResult{}, err
}
}
// Seek back afterwards as necessary
stream.Seek(0, io.SeekStart)
seekJump := length / int64(scanCount)
pos := int64(0)
for err == nil {
_, err = stream.Read(byteSlice)
if err != nil {
break
}
audioBuffer := AudioBuffer(byteSlice)
for i := 0; i < audioBuffer.Len(); i++ {
l, r := audioBuffer.Get(i)
la := math.Abs(l)
ra := math.Abs(r)
if la > largest {
largest = la
}
if ra > largest {
largest = ra
}
}
// InfiniteLoops don't return an error if you attempt to seek too far; they just go back to the start when attempting to read
if pos+seekJump >= length {
break
}
pos += seekJump
_, err = stream.Seek(seekJump, io.SeekCurrent)
if err != nil {
return AnalysisResult{}, err
}
}
// Seek back to the beginning
_, err = stream.Seek(0, io.SeekStart)
if err != nil {
return AnalysisResult{}, err
}
ap.result = AnalysisResult{
Normalization: 1.0 / largest,
}
ap.analyzed = true
return ap.result, nil
}
func (ap *AudioProperty) ResetAnalyzation() {
ap.analyzed = false
ap.result = AnalysisResult{}
}
type AudioProperties map[any]*AudioProperty
func NewAudioProperties() AudioProperties {
return AudioProperties{}
}
// Get gets the audio property associated with some identifier. This could be, for example, the original filepath of the audio stream.
func (ap AudioProperties) Get(id any) *AudioProperty {
if _, exists := ap[id]; !exists {
ap[id] = newAudioProperty()
}
return ap[id]
}