Adding modding folder with a hacked build.
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
package audio
|
||||
|
||||
// L8 is a raw, linear 8-bit sound snippet.
|
||||
type L8 struct {
|
||||
SampleRate float32
|
||||
Samples []byte
|
||||
}
|
||||
|
||||
// Empty returns true if there are no samples to play.
|
||||
func (sound L8) Empty() bool {
|
||||
return len(sound.Samples) == 0
|
||||
}
|
||||
|
||||
// Duration returns the length of the sound in seconds.
|
||||
func (sound L8) Duration() float32 {
|
||||
if sound.SampleRate <= 0 {
|
||||
return 0.0
|
||||
}
|
||||
return float32(len(sound.Samples)) / sound.SampleRate
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package voc
|
||||
|
||||
type blockType byte
|
||||
|
||||
const (
|
||||
terminator = blockType(0x00)
|
||||
soundData = blockType(0x01)
|
||||
)
|
||||
@@ -0,0 +1,113 @@
|
||||
package voc
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
|
||||
"github.com/inkyblackness/hacked/ss1"
|
||||
"github.com/inkyblackness/hacked/ss1/content/audio"
|
||||
)
|
||||
|
||||
const (
|
||||
errSourceIsNil ss1.StringError = "source is nil"
|
||||
errNoAudioFound ss1.StringError = "no audio found"
|
||||
errInvalidVersionValidity ss1.StringError = "version validity check failed"
|
||||
)
|
||||
|
||||
// Load reads from the provided source a Creative Voice Sound and returns the data.
|
||||
func Load(source io.Reader) (data audio.L8, err error) {
|
||||
if source == nil {
|
||||
return data, errSourceIsNil
|
||||
}
|
||||
|
||||
err = readAndVerifyHeader(source)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return readSoundData(source)
|
||||
}
|
||||
|
||||
func readAndVerifyHeader(source io.Reader) error {
|
||||
start := make([]byte, len(FileHeader))
|
||||
headerSize := uint16(0)
|
||||
version := uint16(0)
|
||||
versionValidity := uint16(0)
|
||||
|
||||
_, err := source.Read(start)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = binary.Read(source, binary.LittleEndian, &headerSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = binary.Read(source, binary.LittleEndian, &version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = binary.Read(source, binary.LittleEndian, &versionValidity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
calculated := ^version + versionCheckValue
|
||||
if calculated != versionValidity {
|
||||
return errInvalidVersionValidity
|
||||
}
|
||||
|
||||
skip := make([]byte, headerSize-standardHeaderSize)
|
||||
_, err = source.Read(skip)
|
||||
return err
|
||||
}
|
||||
|
||||
func readSoundData(source io.Reader) (data audio.L8, err error) {
|
||||
sampleRate := float32(0.0)
|
||||
var samples []byte
|
||||
done := false
|
||||
|
||||
for !done {
|
||||
blockStart := make([]byte, 4)
|
||||
|
||||
_, err = source.Read(blockStart)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
switch blockType(blockStart[0]) {
|
||||
case soundData:
|
||||
{
|
||||
meta := make([]byte, 2)
|
||||
_, err = source.Read(meta)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
sampleRate = divisorToSampleRate(meta[0])
|
||||
|
||||
newCount := lengthFromBlockStart(blockStart) - len(meta)
|
||||
buf := make([]byte, newCount)
|
||||
_, err = source.Read(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
oldCount := len(samples)
|
||||
newSamples := make([]byte, oldCount+newCount)
|
||||
copy(newSamples, samples)
|
||||
copy(newSamples[oldCount:], buf)
|
||||
samples = newSamples
|
||||
}
|
||||
case terminator:
|
||||
{
|
||||
done = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(samples) == 0 {
|
||||
return data, errNoAudioFound
|
||||
}
|
||||
|
||||
return audio.L8{
|
||||
SampleRate: sampleRate,
|
||||
Samples: samples,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package voc_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/inkyblackness/hacked/ss1/content/audio/voc"
|
||||
)
|
||||
|
||||
func TestLoadReturnsErrorOnNil(t *testing.T) {
|
||||
_, err := voc.Load(nil)
|
||||
|
||||
assert.Errorf(t, err, "source is nil")
|
||||
}
|
||||
|
||||
func newHeader() *bytes.Buffer {
|
||||
writer := bytes.NewBufferString(voc.FileHeader)
|
||||
version := uint16(0x010A)
|
||||
headerSize := uint16(0x001A)
|
||||
|
||||
_ = binary.Write(writer, binary.LittleEndian, headerSize)
|
||||
_ = binary.Write(writer, binary.LittleEndian, version)
|
||||
versionValidity := ^version + uint16(0x1234)
|
||||
_ = binary.Write(writer, binary.LittleEndian, versionValidity)
|
||||
|
||||
return writer
|
||||
}
|
||||
|
||||
func TestLoadReturnsErrorOnInvalidVersion(t *testing.T) {
|
||||
writer := newHeader()
|
||||
|
||||
writer.Write([]byte{0x00}) // Terminator
|
||||
|
||||
data := writer.Bytes()
|
||||
data[24] = 0x00
|
||||
source := bytes.NewReader(data)
|
||||
_, err := voc.Load(source)
|
||||
|
||||
assert.Errorf(t, err, "Version validity failed: 0x1129 != 0x1100")
|
||||
}
|
||||
|
||||
func TestLoadReturnsErrorOnValidButEmptyFile(t *testing.T) {
|
||||
writer := newHeader()
|
||||
|
||||
writer.Write([]byte{0x00}) // Terminator
|
||||
|
||||
source := bytes.NewReader(writer.Bytes())
|
||||
_, err := voc.Load(source)
|
||||
|
||||
assert.Errorf(t, err, "No audio found")
|
||||
}
|
||||
|
||||
func TestLoadReturnsSoundDataOnSampleData(t *testing.T) {
|
||||
writer := newHeader()
|
||||
|
||||
writer.Write([]byte{0x01}) // sound data
|
||||
writer.Write([]byte{0x03, 0x00, 0x00}) // block size
|
||||
writer.Write([]byte{0x64, 0x00}) // divisor, sound type
|
||||
writer.Write([]byte{0x80}) // one sample
|
||||
|
||||
writer.Write([]byte{0x00}) // Terminator
|
||||
|
||||
source := bytes.NewReader(writer.Bytes())
|
||||
data, err := voc.Load(source)
|
||||
|
||||
require.Nil(t, err)
|
||||
assert.NotNil(t, data)
|
||||
}
|
||||
|
||||
func TestLoadReturnsSoundDataWithSampleRate(t *testing.T) {
|
||||
writer := newHeader()
|
||||
|
||||
writer.Write([]byte{0x01}) // sound data
|
||||
writer.Write([]byte{0x03, 0x00, 0x00}) // block size
|
||||
writer.Write([]byte{0x9C, 0x00}) // divisor, sound type
|
||||
writer.Write([]byte{0x80}) // one sample
|
||||
|
||||
writer.Write([]byte{0x00}) // Terminator
|
||||
|
||||
source := bytes.NewReader(writer.Bytes())
|
||||
data, err := voc.Load(source)
|
||||
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, float32(10000.0), data.SampleRate)
|
||||
}
|
||||
|
||||
func TestLoadReturnsSoundDataWithSamples(t *testing.T) {
|
||||
writer := newHeader()
|
||||
samples := []byte{0x80, 0xFF, 0x00, 0xC0, 0x40, 0x7F, 0x81}
|
||||
|
||||
writer.Write([]byte{0x01}) // sound data
|
||||
writer.Write([]byte{0x09, 0x00, 0x00}) // block size
|
||||
writer.Write([]byte{0x9C, 0x00}) // divisor, sound type
|
||||
writer.Write(samples) // samples
|
||||
|
||||
writer.Write([]byte{0x00}) // Terminator
|
||||
|
||||
source := bytes.NewReader(writer.Bytes())
|
||||
data, err := voc.Load(source)
|
||||
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, samples, data.Samples)
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package voc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Save encodes the provided samples into the given writer.
|
||||
func Save(writer io.Writer, sampleRate float32, samples []byte) error {
|
||||
err := writeHeader(writer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = writeBasicSoundData(writer, sampleRate, samples)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = writeEndOfFile(writer)
|
||||
return err
|
||||
}
|
||||
|
||||
func writeHeader(writer io.Writer) error {
|
||||
version := baseVersion
|
||||
|
||||
_, err := writer.Write(bytes.NewBufferString(FileHeader).Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = binary.Write(writer, binary.LittleEndian, standardHeaderSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = binary.Write(writer, binary.LittleEndian, baseVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return binary.Write(writer, binary.LittleEndian, (^version)+versionCheckValue)
|
||||
}
|
||||
|
||||
func writeBlockHeader(writer io.Writer, block blockType, dataBytes int) error {
|
||||
_, err := writer.Write([]byte{byte(block), byte(dataBytes), byte(dataBytes >> 8), byte(dataBytes >> 16)})
|
||||
return err
|
||||
}
|
||||
|
||||
func writeBasicSoundData(writer io.Writer, sampleRate float32, samples []byte) error {
|
||||
sampleType := byte(0)
|
||||
|
||||
err := writeBlockHeader(writer, soundData, len(samples)+2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = writer.Write([]byte{sampleRateToDivisor(sampleRate), sampleType})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = writer.Write(samples)
|
||||
return err
|
||||
}
|
||||
|
||||
func writeEndOfFile(writer io.Writer) error {
|
||||
_, err := writer.Write([]byte{byte(terminator)})
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package voc
|
||||
|
||||
const (
|
||||
// FileHeader identifies VOC files.
|
||||
FileHeader string = "Creative Voice File\u001A"
|
||||
standardHeaderSize uint16 = 0x1A
|
||||
baseVersion uint16 = 0x010A
|
||||
versionCheckValue uint16 = 0x1234
|
||||
rateBase float32 = 1000000.0
|
||||
)
|
||||
|
||||
func lengthFromBlockStart(blockStart []byte) int {
|
||||
return int(blockStart[3])<<16 + int(blockStart[2])<<8 + int(blockStart[1])
|
||||
}
|
||||
|
||||
func divisorToSampleRate(divisor byte) float32 {
|
||||
return rateBase / float32(256-int(divisor))
|
||||
}
|
||||
|
||||
func sampleRateToDivisor(sampleRate float32) byte {
|
||||
return byte(256 - int(rateBase/sampleRate))
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package voc // nolint: testpackage
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestFrequencyDivisorFor11111(t *testing.T) {
|
||||
divisor := byte(0xA6)
|
||||
sampleRate := divisorToSampleRate(divisor)
|
||||
result := sampleRateToDivisor(sampleRate)
|
||||
|
||||
assert.Equal(t, divisor, result)
|
||||
}
|
||||
|
||||
func TestFrequencyDivisorFor222222(t *testing.T) {
|
||||
divisor := byte(0x2D)
|
||||
sampleRate := divisorToSampleRate(divisor)
|
||||
result := sampleRateToDivisor(sampleRate)
|
||||
|
||||
assert.Equal(t, divisor, result)
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package wav
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/inkyblackness/hacked/ss1"
|
||||
"github.com/inkyblackness/hacked/ss1/content/audio"
|
||||
)
|
||||
|
||||
const (
|
||||
errSourceIsNil ss1.StringError = "source is nil"
|
||||
errNotASupportedWave ss1.StringError = "not a supported WAV"
|
||||
)
|
||||
|
||||
// Load reads from the provided source and returns the data.
|
||||
func Load(source io.Reader) (data audio.L8, err error) {
|
||||
if source == nil {
|
||||
return data, errSourceIsNil
|
||||
}
|
||||
|
||||
var loader waveLoader
|
||||
|
||||
loader.load(source)
|
||||
if loader.err != nil {
|
||||
return data, loader.err
|
||||
}
|
||||
|
||||
data.SampleRate = loader.sampleRate
|
||||
data.Samples = loader.samples
|
||||
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package wav_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/inkyblackness/hacked/ss1/content/audio/wav"
|
||||
)
|
||||
|
||||
func TestLoadReturnsErrorOnNil(t *testing.T) {
|
||||
_, err := wav.Load(nil)
|
||||
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestLoadExtractsDataOfL8(t *testing.T) {
|
||||
input := []byte{
|
||||
0x52, 0x49, 0x46, 0x46, // "RIFF"
|
||||
0x29, 0x00, 0x00, 0x00, // len(RIFF)
|
||||
0x57, 0x41, 0x56, 0x45, // "WAVE"
|
||||
0x66, 0x6d, 0x74, 0x20, // "fmt "
|
||||
0x10, 0x00, 0x00, 0x00, // len(fmt)
|
||||
0x01, 0x00, // fmt:type
|
||||
0x01, 0x00, // fmt:channels
|
||||
0x22, 0x56, 0x00, 0x00, // fmt:samples/sec
|
||||
0x22, 0x56, 0x00, 0x00, // fmt:avgBytes/sec
|
||||
0x01, 0x00, // fmt:blockAlign
|
||||
0x08, 0x00, // fmt:bits/sample
|
||||
0x64, 0x61, 0x74, 0x61, // "data"
|
||||
0x05, 0x00, 0x00, 0x00, // len(data)
|
||||
0x00, 0x40, 0x80, 0xC0, 0xFF} // data
|
||||
|
||||
data, err := wav.Load(bytes.NewReader(input))
|
||||
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, float32(22050), data.SampleRate)
|
||||
assert.Equal(t, []byte{0x00, 0x40, 0x80, 0xC0, 0xFF}, data.Samples)
|
||||
}
|
||||
|
||||
func TestLoadExtractsDataOfL16(t *testing.T) {
|
||||
input := []byte{
|
||||
0x52, 0x49, 0x46, 0x46, // "RIFF"
|
||||
0x2E, 0x00, 0x00, 0x00, // len(RIFF)
|
||||
0x57, 0x41, 0x56, 0x45, // "WAVE"
|
||||
0x66, 0x6d, 0x74, 0x20, // "fmt "
|
||||
0x10, 0x00, 0x00, 0x00, // len(fmt)
|
||||
0x01, 0x00, // fmt:type
|
||||
0x01, 0x00, // fmt:channels
|
||||
0x22, 0x56, 0x00, 0x00, // fmt:samples/sec
|
||||
0x44, 0xAC, 0x00, 0x00, // fmt:avgBytes/sec
|
||||
0x02, 0x00, // fmt:blockAlign
|
||||
0x10, 0x00, // fmt:bits/sample
|
||||
0x64, 0x61, 0x74, 0x61, // "data"
|
||||
0x0A, 0x00, 0x00, 0x00, // len(data)
|
||||
0xAA, 0x00, 0xAA, 0x40, 0xAA, 0x7F, 0xAA, 0xC0, 0xAA, 0xFF} // data
|
||||
|
||||
data, err := wav.Load(bytes.NewReader(input))
|
||||
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, float32(22050), data.SampleRate)
|
||||
assert.Equal(t, []byte{0x80, 0xC0, 0xFF, 0x40, 0x7F}, data.Samples)
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package wav
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Save encodes the provided samples into the given writer.
|
||||
func Save(writer io.Writer, sampleRate float32, samples []byte) error {
|
||||
dataSize := uint32(len(samples))
|
||||
var fmt formatHeader
|
||||
fmtSize := fmt.size()
|
||||
contentType := riffContentTypeWave
|
||||
contentTypeSize := uint32(4)
|
||||
tagSize := uint32(8)
|
||||
tagSizes := tagSize * 2
|
||||
|
||||
fmt.base.FormatType = waveFormatTypePcm
|
||||
fmt.base.Channels = 1
|
||||
fmt.base.SamplesPerSec = uint32(sampleRate)
|
||||
fmt.base.AvgBytesPerSec = fmt.base.SamplesPerSec
|
||||
fmt.base.BlockAlign = 1
|
||||
fmt.extension.BitsPerSample = 8
|
||||
|
||||
riffTag := riffChunkTag{ChunkType: riffChunkTypeRiff, Size: tagSizes + contentTypeSize + fmtSize + dataSize}
|
||||
|
||||
_ = binary.Write(writer, binary.LittleEndian, &riffTag)
|
||||
_ = binary.Write(writer, binary.LittleEndian, &contentType)
|
||||
_ = binary.Write(writer, binary.LittleEndian, &riffChunkTag{ChunkType: riffChunkTypeFmt, Size: fmtSize})
|
||||
_ = binary.Write(writer, binary.LittleEndian, &fmt.base)
|
||||
_ = binary.Write(writer, binary.LittleEndian, &fmt.extension)
|
||||
_ = binary.Write(writer, binary.LittleEndian, &riffChunkTag{ChunkType: riffChunkTypeData, Size: dataSize})
|
||||
_, err := writer.Write(samples)
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package wav_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/inkyblackness/hacked/ss1/content/audio/wav"
|
||||
)
|
||||
|
||||
func TestSaveStoresBytes(t *testing.T) {
|
||||
sampleRate := float32(22050.0)
|
||||
samples := []byte{0x00, 0x40, 0x80, 0xC0, 0xFF}
|
||||
buf := bytes.NewBuffer(nil)
|
||||
|
||||
err := wav.Save(buf, sampleRate, samples)
|
||||
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, []byte{
|
||||
0x52, 0x49, 0x46, 0x46, // "RIFF"
|
||||
0x2B, 0x00, 0x00, 0x00, // len(RIFF)
|
||||
0x57, 0x41, 0x56, 0x45, // "WAVE"
|
||||
0x66, 0x6d, 0x74, 0x20, // "fmt "
|
||||
0x12, 0x00, 0x00, 0x00, // len(fmt)
|
||||
0x01, 0x00, // fmt:type
|
||||
0x01, 0x00, // fmt:channels
|
||||
0x22, 0x56, 0x00, 0x00, // fmt:samples/sec
|
||||
0x22, 0x56, 0x00, 0x00, // fmt:avgBytes/sec
|
||||
0x01, 0x00, // fmt:blockAlign
|
||||
0x08, 0x00, // fmt:bits/sample
|
||||
0x00, 0x00, // fmt:extensionSize
|
||||
0x64, 0x61, 0x74, 0x61, // "data"
|
||||
0x05, 0x00, 0x00, 0x00, // len(data)
|
||||
0x00, 0x40, 0x80, 0xC0, 0xFF}, buf.Bytes())
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package wav
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
|
||||
"github.com/inkyblackness/hacked/ss1"
|
||||
)
|
||||
|
||||
const (
|
||||
errUnsupportedFormat ss1.StringError = "unsupported WAVE format"
|
||||
)
|
||||
|
||||
func l8FromL8(input []byte) []byte {
|
||||
return input
|
||||
}
|
||||
|
||||
func l8FromL16(input []byte) []byte {
|
||||
samples := len(input) / 2
|
||||
output := make([]byte, samples)
|
||||
|
||||
for i := 0; i < samples; i++ {
|
||||
output[i] = byte(int(input[i*2+1]) + 0x80)
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
type waveLoader struct {
|
||||
dataRead bool
|
||||
formatRead bool
|
||||
samples []byte
|
||||
sampleRate float32
|
||||
|
||||
reader io.Reader
|
||||
err error
|
||||
|
||||
dataConverter func([]byte) []byte
|
||||
}
|
||||
|
||||
func (loader *waveLoader) load(reader io.Reader) {
|
||||
loader.reader = reader
|
||||
loader.dataConverter = l8FromL8
|
||||
loader.loadRiff()
|
||||
}
|
||||
|
||||
func (loader *waveLoader) read(data interface{}) bool {
|
||||
if loader.err == nil {
|
||||
loader.err = binary.Read(loader.reader, binary.LittleEndian, data)
|
||||
}
|
||||
return loader.err == nil
|
||||
}
|
||||
|
||||
func (loader *waveLoader) readBytes(size uint32) (data []byte) {
|
||||
data = make([]byte, int(size))
|
||||
_, loader.err = loader.reader.Read(data)
|
||||
return
|
||||
}
|
||||
|
||||
func (loader *waveLoader) loadChunk(handler func(riffChunkType, uint32)) {
|
||||
var tag riffChunkTag
|
||||
|
||||
if loader.read(&tag) {
|
||||
handler(tag.ChunkType, tag.Size)
|
||||
}
|
||||
}
|
||||
|
||||
func (loader *waveLoader) loadRiff() {
|
||||
loader.loadChunk(loader.handleRiff)
|
||||
}
|
||||
|
||||
func (loader *waveLoader) handleRiff(chunkType riffChunkType, size uint32) {
|
||||
if chunkType == riffChunkTypeRiff {
|
||||
var contentType riffContentType
|
||||
if loader.read(&contentType) && (contentType == riffContentTypeWave) {
|
||||
loader.loadWave()
|
||||
} else if loader.err == nil {
|
||||
loader.err = errNotASupportedWave
|
||||
}
|
||||
} else {
|
||||
loader.err = errNotASupportedWave
|
||||
}
|
||||
}
|
||||
|
||||
func (loader *waveLoader) loadWave() {
|
||||
for !loader.isDone() {
|
||||
loader.loadFormatOrData()
|
||||
}
|
||||
loader.samples = loader.dataConverter(loader.samples)
|
||||
}
|
||||
|
||||
func (loader *waveLoader) loadFormatOrData() {
|
||||
loader.loadChunk(loader.handleFormatOrData)
|
||||
}
|
||||
|
||||
func (loader *waveLoader) handleFormatOrData(chunkType riffChunkType, size uint32) {
|
||||
if chunkType == riffChunkTypeFmt {
|
||||
loader.loadFormat(size)
|
||||
} else if chunkType == riffChunkTypeData {
|
||||
loader.loadData(size)
|
||||
}
|
||||
}
|
||||
|
||||
func (loader *waveLoader) loadFormat(size uint32) {
|
||||
headerData := loader.readBytes(size)
|
||||
headerReader := bytes.NewReader(headerData)
|
||||
var header formatHeader
|
||||
|
||||
loader.formatRead = true
|
||||
loader.err = binary.Read(headerReader, binary.LittleEndian, &header.base)
|
||||
loader.err = binary.Read(headerReader, binary.LittleEndian, &header.extension.BitsPerSample)
|
||||
loader.sampleRate = float32(header.base.SamplesPerSec)
|
||||
|
||||
if header.extension.BitsPerSample == 16 {
|
||||
loader.dataConverter = l8FromL16
|
||||
}
|
||||
|
||||
if (header.base.FormatType != waveFormatTypePcm) ||
|
||||
(header.base.Channels != 1) ||
|
||||
((header.extension.BitsPerSample != 8) && (header.extension.BitsPerSample != 16)) {
|
||||
loader.err = errUnsupportedFormat
|
||||
}
|
||||
}
|
||||
|
||||
func (loader *waveLoader) loadData(size uint32) {
|
||||
loader.dataRead = true
|
||||
loader.samples = loader.readBytes(size)
|
||||
}
|
||||
|
||||
func (loader *waveLoader) isDone() bool {
|
||||
return (loader.err != nil) || (loader.dataRead && loader.formatRead)
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package wav
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
type waveFormatType uint16
|
||||
|
||||
const (
|
||||
waveFormatTypePcm = 1
|
||||
)
|
||||
|
||||
type waveFormat struct {
|
||||
FormatType waveFormatType
|
||||
Channels uint16
|
||||
SamplesPerSec uint32
|
||||
AvgBytesPerSec uint32
|
||||
BlockAlign uint16
|
||||
}
|
||||
|
||||
type waveFormatExtension struct {
|
||||
BitsPerSample uint16
|
||||
ExtensionSize uint16
|
||||
}
|
||||
|
||||
type formatHeader struct {
|
||||
base waveFormat
|
||||
extension waveFormatExtension
|
||||
}
|
||||
|
||||
func (header formatHeader) size() uint32 {
|
||||
return uint32(binary.Size(&header.base) + binary.Size(&header.extension))
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package wav
|
||||
|
||||
type riffChunkType uint32
|
||||
type riffContentType uint32
|
||||
|
||||
const (
|
||||
riffChunkTypeRiff = riffChunkType(0x46464952)
|
||||
riffChunkTypeFmt = riffChunkType(0x20746d66)
|
||||
riffChunkTypeData = riffChunkType(0x61746164)
|
||||
)
|
||||
|
||||
const (
|
||||
riffContentTypeWave = riffContentType(0x45564157)
|
||||
)
|
||||
|
||||
type riffChunkTag struct {
|
||||
ChunkType riffChunkType
|
||||
Size uint32
|
||||
}
|
||||
Reference in New Issue
Block a user