-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathchunk_parsing.go
192 lines (177 loc) · 4.81 KB
/
chunk_parsing.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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
package aiff
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"io/ioutil"
)
// parseChunk processes a chunk and stores the valuable information
// on the decoder if supported.
// Note that the audio chunk isn't processed using this approach.
func (d *Decoder) parseChunk(chunk *Chunk) error {
if chunk == nil {
return nil
}
switch chunk.ID {
// common chunk parsing
case COMMID:
if d.commSize > 0 {
chunk.Done()
}
if err := d.parseCommChunk(uint32(chunk.Size)); err != nil {
return err
}
// if we found the sound data before the COMM,
// we need to rewind the reader so we can properly
// set the clip reader.
if d.rewindBytes > 0 {
d.r.Seek(-d.rewindBytes, 1)
d.rewindBytes = 0
}
// audio content, should be read a different way
case SSNDID:
chunk.Done()
// Comments Chunk
case COMTID:
if err := d.parseCommentsChunk(chunk); err != nil {
fmt.Println("failed to read comments", err)
}
// Apple/Logic specific chunk
case bascID:
if err := d.parseBascChunk(chunk); err != nil {
fmt.Println("failed to read BASC chunk", err)
}
// Apple specific: packed struct AudioChannelLayout of CoreAudio
case chanID:
// See https://github.com/nu774/qaac/blob/ce73aac9bfba459c525eec5350da6346ebf547cf/chanmap.cpp
// for format information
chunk.Done()
// Apple specific transient data
case trnsID:
// TODO extract and store the transients
/*
var v1 uint16
var sensitivity uint16 // 0 to 100 %
var transientDivisions uint16 // 1 = whole note
var v3 uint16
var v5 uint16
var v4 uint16
var nbSlice uint16
int loopSize = v4 * d.AppleInfo.Beats * 2
for s := 0; s < nbSlice; s++{
slicepos := 24 * s + 0x4c;
var sv1 uint16
var sv2 uint16
var sampleBegin uint32
}
*/
chunk.Done()
// Apple specific categorization
case cateID:
if err := d.parseCateChunk(chunk); err != nil {
fmt.Println("failed to read CATE chunk", err)
}
chunk.Done()
default:
if Debug {
fmt.Printf("skipping unknown chunk %#v\n", chunk.ID[:])
}
// if we read SSN but didn't read the COMM, we need to track location
if d.SampleRate == 0 {
d.rewindBytes += int64(chunk.Size)
}
chunk.Done()
}
return nil
}
// parseCommentsChunk processes the comments chunk and adds comments as strings
// to the decoder and drains the chunk.
func (d *Decoder) parseCommentsChunk(chunk *Chunk) error {
if chunk.ID != COMTID {
return fmt.Errorf("unexpected comments chunk ID: %q", chunk.ID)
}
br := bytes.NewBuffer(make([]byte, 0, chunk.Size))
var n int64
n, d.err = io.CopyN(br, d.r, int64(chunk.Size))
if d.err != nil {
return d.err
}
if n < int64(chunk.Size) {
br.Truncate(int(n))
}
var nbrComments uint16
binary.Read(br, binary.BigEndian, &nbrComments)
for i := 0; i < int(nbrComments); i++ {
// TODO extract marker id and timestamp
io.CopyN(ioutil.Discard, br, 8) // equivalent of br.Seek(8, io.SeekCurrent) but bytes buffer doesn't implement seek
b, _ := br.ReadByte()
textB := make([]byte, int(b))
br.Read(textB)
d.Comments = append(d.Comments, string(bytes.TrimRight(textB, "\x00")))
}
return nil
}
// parseBascChunk processes the Apple specific BASC chunk
func (d *Decoder) parseBascChunk(chunk *Chunk) error {
if chunk.ID != bascID {
return fmt.Errorf("unexpected BASC chunk ID: %q", chunk.ID)
}
d.HasAppleInfo = true
var version uint32
binary.Read(chunk.R, binary.BigEndian, &version)
binary.Read(chunk.R, binary.BigEndian, &d.AppleInfo.Beats)
binary.Read(chunk.R, binary.BigEndian, &d.AppleInfo.Note)
binary.Read(chunk.R, binary.BigEndian, &d.AppleInfo.Scale)
binary.Read(chunk.R, binary.BigEndian, &d.AppleInfo.Numerator)
binary.Read(chunk.R, binary.BigEndian, &d.AppleInfo.Denominator)
chunk.ReadByte()
var loopFlag uint16
binary.Read(chunk.R, binary.BigEndian, &loopFlag)
// 1 = loop; 2 = one shot
if loopFlag == 1 {
d.AppleInfo.IsLooping = true
}
chunk.Done()
return nil
}
func (d *Decoder) parseCateChunk(chunk *Chunk) error {
if chunk.ID != cateID {
return fmt.Errorf("unexpected CATE chunk ID: %q", chunk.ID)
}
var err error
d.HasAppleInfo = true
// skip 4
tmp := make([]byte, 4)
if _, err = chunk.R.Read(tmp); err != nil {
return err
}
tmp = make([]byte, 50)
// 4 main categories: instrument, instrument category, style, substyle
for i := 0; i < 4; i++ {
if _, err = chunk.R.Read(tmp); err != nil {
return err
}
if tmp[0] > 0 {
d.AppleInfo.Tags = append(d.AppleInfo.Tags, nullTermStr(tmp))
}
}
// skip 16
tmp = make([]byte, 16)
if _, err = chunk.R.Read(tmp); err != nil {
return err
}
var numDescriptors int16
binary.Read(chunk.R, binary.BigEndian, &numDescriptors)
tmp = make([]byte, 50)
for i := 0; i < int(numDescriptors); i++ {
if _, err = chunk.R.Read(tmp); err != nil {
return err
}
if tmp[0] > 0 {
d.AppleInfo.Tags = append(d.AppleInfo.Tags, nullTermStr(tmp))
}
}
chunk.Done()
return nil
}