Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
decode cue chunk (encoder's missing)
- Loading branch information
Showing
6 changed files
with
203 additions
and
50 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
package wav | ||
|
||
import ( | ||
"bytes" | ||
"encoding/binary" | ||
"fmt" | ||
|
||
"github.com/mattetti/audio/riff" | ||
) | ||
|
||
// CuePoint defines an offset which marks a noteworthy sections of the audio | ||
// content. For example, the beginning and end of a verse in a song may have cue | ||
// points to make them easier to find. | ||
type CuePoint struct { | ||
// ID is the unique identifier of the cue point | ||
ID [4]byte | ||
// Play position specifies the sample offset associated with the cue point | ||
// in terms of the sample's position in the final stream of samples | ||
// generated by the play list. If a play list chunk is | ||
// specified, the position value is equal to the sample number at which this | ||
// cue point will occur during playback of the entire play list as defined | ||
// by the play list's order. If no play list chunk is specified this value | ||
// should be 0. | ||
Position uint32 | ||
// DataChunkID - This value specifies the four byte ID used by the chunk | ||
// containing the sample that corresponds to this cue point. A Wave file | ||
// with no play list is always "data". A Wave file with a play list | ||
// containing both sample data and silence may be either "data" or "slnt". | ||
DataChunkID [4]byte | ||
// ChunkStart specifies the byte offset into the Wave List Chunk of the | ||
// chunk containing the sample that corresponds to this cue point. This is | ||
// the same chunk described by the Data Chunk ID value. If no Wave List | ||
// Chunk exists in the Wave file, this value is 0. If a Wave List Chunk | ||
// exists, this is the offset into the "wavl" chunk. The first chunk in the | ||
// Wave List Chunk would be specified with a value of 0. | ||
ChunkStart uint32 | ||
// BlockStart specifies the byte offset into the "data" or "slnt" Chunk to | ||
// the start of the block containing the sample. The start of a block is | ||
// defined as the first byte in uncompressed PCM wave data or the last byte | ||
// in compressed wave data where decompression can begin to find the value | ||
// of the corresponding sample value. | ||
BlockStart uint32 | ||
// SampleOffset specifies an offset into the block (specified by Block | ||
// Start) for the sample that corresponds to the cue point. In uncompressed | ||
// PCM waveform data, this is simply the byte offset into the "data" chunk. | ||
// In compressed waveform data, this value is equal to the number of samples | ||
// (may or may not be bytes) from the Block Start to the sample that | ||
// corresponds to the cue point. | ||
SampleOffset uint32 | ||
} | ||
|
||
// DecodeCueChunk decodes the optional cue chunk and extracts cue points. | ||
func DecodeCueChunk(d *Decoder, ch *riff.Chunk) error { | ||
if ch == nil { | ||
return fmt.Errorf("can't decode a nil chunk") | ||
} | ||
if d == nil { | ||
return fmt.Errorf("nil decoder") | ||
} | ||
if ch.ID == CIDCue { | ||
// read the entire chunk in memory | ||
buf := make([]byte, ch.Size) | ||
var err error | ||
if _, err = ch.Read(buf); err != nil { | ||
return fmt.Errorf("failed to read the CUE chunk - %v", err) | ||
} | ||
r := bytes.NewReader(buf) | ||
var nbrCues uint32 | ||
if err := binary.Read(r, binary.LittleEndian, &nbrCues); err != nil { | ||
return fmt.Errorf("failed to read the number of cues - %v", err) | ||
} | ||
if nbrCues > 0 { | ||
fmt.Println("nbrCues", nbrCues) | ||
if d.Metadata == nil { | ||
d.Metadata = &Metadata{} | ||
} | ||
d.Metadata.CuePoints = []*CuePoint{} | ||
scratch := make([]byte, 4) | ||
for i := uint32(0); i < nbrCues; i++ { | ||
c := &CuePoint{} | ||
if _, err = r.Read(scratch); err != nil { | ||
return fmt.Errorf("failed to read the cue point ID") | ||
} | ||
copy(c.ID[:], scratch[:4]) | ||
if err := binary.Read(r, binary.LittleEndian, &c.Position); err != nil { | ||
return err | ||
} | ||
if _, err = r.Read(scratch); err != nil { | ||
return fmt.Errorf("failed to read the data chunk id") | ||
} | ||
copy(c.DataChunkID[:], scratch[:4]) | ||
if err := binary.Read(r, binary.LittleEndian, &c.ChunkStart); err != nil { | ||
return err | ||
} | ||
if err := binary.Read(r, binary.LittleEndian, &c.BlockStart); err != nil { | ||
return err | ||
} | ||
if err := binary.Read(r, binary.LittleEndian, &c.SampleOffset); err != nil { | ||
return err | ||
} | ||
d.Metadata.CuePoints = append(d.Metadata.CuePoints, c) | ||
} | ||
} | ||
|
||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
package wav_test | ||
|
||
import ( | ||
"os" | ||
"reflect" | ||
"testing" | ||
|
||
"github.com/go-audio/wav" | ||
) | ||
|
||
func TestDecoder_ReadMetadata(t *testing.T) { | ||
testCases := []struct { | ||
in string | ||
metadata *wav.Metadata | ||
}{ | ||
{in: "fixtures/listinfo.wav", | ||
metadata: &wav.Metadata{ | ||
Artist: "artist", Title: "track title", Product: "album title", | ||
TrackNbr: "42", CreationDate: "2017", Genre: "genre", Comments: "my comment", | ||
}, | ||
}, | ||
{in: "fixtures/kick.wav"}, | ||
{in: "fixtures/flloop.wav", metadata: &wav.Metadata{ | ||
Software: "FL Studio (beta)", | ||
CuePoints: []*wav.CuePoint{ | ||
0: {ID: [4]uint8{0x1, 0x0, 0x0, 0x0}, Position: 0x0, DataChunkID: [4]uint8{'d', 'a', 't', 'a'}}, | ||
1: {ID: [4]uint8{0x2, 0x0, 0x0, 0x0}, Position: 0x1a5e, DataChunkID: [4]uint8{'d', 'a', 't', 'a'}, SampleOffset: 0x1a5e}, | ||
2: {ID: [4]uint8{0x3, 0x0, 0x0, 0x0}, Position: 0x34bc, DataChunkID: [4]uint8{'d', 'a', 't', 'a'}, SampleOffset: 0x34bc}, | ||
3: {ID: [4]uint8{0x4, 0x0, 0x0, 0x0}, Position: 0x4f1a, DataChunkID: [4]uint8{'d', 'a', 't', 'a'}, SampleOffset: 0x4f1a}, | ||
4: {ID: [4]uint8{0x5, 0x0, 0x0, 0x0}, Position: 0x6978, DataChunkID: [4]uint8{'d', 'a', 't', 'a'}, SampleOffset: 0x6978}, | ||
5: {ID: [4]uint8{0x6, 0x0, 0x0, 0x0}, Position: 0x83d6, DataChunkID: [4]uint8{0x64, 0x61, 0x74, 0x61}, SampleOffset: 0x83d6}, | ||
6: {ID: [4]uint8{0x7, 0x0, 0x0, 0x0}, Position: 0x9e34, DataChunkID: [4]uint8{0x64, 0x61, 0x74, 0x61}, SampleOffset: 0x9e34}, | ||
7: {ID: [4]uint8{0x8, 0x0, 0x0, 0x0}, Position: 0xb892, DataChunkID: [4]uint8{0x64, 0x61, 0x74, 0x61}, SampleOffset: 0xb892}, | ||
8: {ID: [4]uint8{0x9, 0x0, 0x0, 0x0}, Position: 0xd2f0, DataChunkID: [4]uint8{0x64, 0x61, 0x74, 0x61}, SampleOffset: 0xd2f0}, | ||
9: {ID: [4]uint8{0xa, 0x0, 0x0, 0x0}, Position: 0xed4e, DataChunkID: [4]uint8{0x64, 0x61, 0x74, 0x61}, SampleOffset: 0xed4e}, | ||
10: {ID: [4]uint8{0xb, 0x0, 0x0, 0x0}, Position: 0x107ac, DataChunkID: [4]uint8{0x64, 0x61, 0x74, 0x61}, SampleOffset: 0x107ac}, | ||
11: {ID: [4]uint8{0xc, 0x0, 0x0, 0x0}, Position: 0x1220a, DataChunkID: [4]uint8{0x64, 0x61, 0x74, 0x61}, SampleOffset: 0x1220a}, | ||
12: {ID: [4]uint8{0xd, 0x0, 0x0, 0x0}, Position: 0x13c68, DataChunkID: [4]uint8{0x64, 0x61, 0x74, 0x61}, SampleOffset: 0x13c68}, | ||
13: {ID: [4]uint8{0xe, 0x0, 0x0, 0x0}, Position: 0x156c6, DataChunkID: [4]uint8{0x64, 0x61, 0x74, 0x61}, SampleOffset: 0x156c6}, | ||
14: {ID: [4]uint8{0xf, 0x0, 0x0, 0x0}, Position: 0x17124, DataChunkID: [4]uint8{0x64, 0x61, 0x74, 0x61}, SampleOffset: 0x17124}, | ||
15: {ID: [4]uint8{0x10, 0x0, 0x0, 0x0}, Position: 0x18b82, DataChunkID: [4]uint8{0x64, 0x61, 0x74, 0x61}, SampleOffset: 0x18b82}, | ||
}, | ||
SamplerInfo: &wav.SamplerInfo{SamplePeriod: 22676, MIDIUnityNote: 60, NumSampleLoops: 1, | ||
Loops: []*wav.SampleLoop{ | ||
{CuePointID: [4]byte{0, 0, 2, 0}, Type: 1024, Start: 0, End: 107999, Fraction: 0, PlayCount: 0}, | ||
}}, | ||
}}, | ||
} | ||
|
||
for _, tc := range testCases { | ||
t.Run(tc.in, func(t *testing.T) { | ||
f, err := os.Open(tc.in) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
d := wav.NewDecoder(f) | ||
d.ReadMetadata() | ||
if err = d.Err(); err != nil { | ||
t.Fatal(err) | ||
} | ||
if tc.metadata != nil { | ||
if tc.metadata.SamplerInfo != nil { | ||
if !reflect.DeepEqual(tc.metadata.SamplerInfo, d.Metadata.SamplerInfo) { | ||
t.Fatalf("Expected sampler info\n%#v to equal\n%#v\n", d.Metadata.SamplerInfo, tc.metadata.SamplerInfo) | ||
} | ||
} | ||
if tc.metadata.CuePoints != nil { | ||
if !reflect.DeepEqual(tc.metadata.CuePoints, d.Metadata.CuePoints) { | ||
for i, c := range d.Metadata.CuePoints { | ||
if !reflect.DeepEqual(c, tc.metadata.CuePoints[i]) { | ||
t.Errorf("[%d] expected %#v got %#v", i, tc.metadata.CuePoints[i], c) | ||
} | ||
} | ||
t.Errorf("Expected cue points\n%#v to equal\n%#v\n", d.Metadata.CuePoints, tc.metadata.CuePoints) | ||
} | ||
} | ||
|
||
if !reflect.DeepEqual(tc.metadata, d.Metadata) { | ||
t.Fatalf("Expected\n%#v\n to equal\n%#v\n", d.Metadata, tc.metadata) | ||
} | ||
} | ||
f.Close() | ||
}) | ||
} | ||
} |