2023-12-04 03:36:29 +03:00
|
|
|
/*
|
|
|
|
NAME
|
|
|
|
wav.go
|
|
|
|
|
|
|
|
DESCRIPTION
|
|
|
|
wav.go contains functions for processing wav.
|
|
|
|
|
|
|
|
AUTHOR
|
|
|
|
David Sutton <davidsutton@ausocean.org>
|
|
|
|
|
|
|
|
LICENSE
|
|
|
|
wav.go is Copyright (C) 2023 the Australian Ocean Lab (AusOcean)
|
|
|
|
|
|
|
|
It is free software: you can redistribute it and/or modify them
|
|
|
|
under the terms of the GNU General Public License as published by the
|
|
|
|
Free Software Foundation, either version 3 of the License, or (at your
|
|
|
|
option) any later version.
|
|
|
|
|
|
|
|
It is distributed in the hope that it will be useful, but WITHOUT
|
|
|
|
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
|
|
for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License in gpl.txt.
|
|
|
|
If not, see [GNU licenses](http://www.gnu.org/licenses).
|
|
|
|
*/
|
|
|
|
|
|
|
|
// Package wav provides functions for converting wav audio.
|
|
|
|
package wav
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/binary"
|
|
|
|
"fmt"
|
|
|
|
)
|
|
|
|
|
2023-12-04 05:43:33 +03:00
|
|
|
// ConvertFormat converts the common name for a format in a string type to the specific
|
|
|
|
// integer required by the wav encoder.
|
|
|
|
var ConvertFormat = map[string]int{"pcm": PCMFormat}
|
|
|
|
|
2023-12-04 03:36:29 +03:00
|
|
|
const PCMFormat = 1 // PCMFormat defines the value for pcm audio as defined by the wav std.
|
|
|
|
|
|
|
|
var (
|
|
|
|
errInvalidFormat = fmt.Errorf("invalid or no format defined")
|
|
|
|
errInvalidRate = fmt.Errorf("invalid or no sample rate defined")
|
|
|
|
errInvalidChannels = fmt.Errorf("invalid or no number of channels defined")
|
|
|
|
errInvalidBitDepth = fmt.Errorf("invalid or no bit depth defined")
|
|
|
|
)
|
|
|
|
|
|
|
|
// Metadata defines the format of the audio file for reading.
|
|
|
|
type Metadata struct {
|
|
|
|
AudioFormat int
|
|
|
|
Channels int
|
|
|
|
SampleRate int
|
|
|
|
BitDepth int
|
|
|
|
}
|
|
|
|
|
|
|
|
type WAV struct {
|
|
|
|
Metadata Metadata
|
|
|
|
Audio []byte
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write writes the given audio byte slice to the WAV, encoding the appropriate headings.
|
|
|
|
func (w *WAV) Write(p []byte) (n int, err error) {
|
|
|
|
// Create header slice.
|
|
|
|
header := make([]byte, 44)
|
|
|
|
|
|
|
|
// Write RIFF type.
|
|
|
|
copy(header[0:4], []byte("RIFF"))
|
|
|
|
|
|
|
|
// Write the size of overall file.
|
|
|
|
buf := make([]byte, 4)
|
|
|
|
binary.LittleEndian.PutUint32(buf, uint32(len(p)+44))
|
|
|
|
copy(header[4:8], buf)
|
|
|
|
|
|
|
|
// Write WAVE type.
|
|
|
|
copy(header[8:12], []byte("WAVE"))
|
|
|
|
|
|
|
|
// Write fmt chunk marker.
|
|
|
|
copy(header[12:16], []byte("fmt "))
|
|
|
|
|
|
|
|
// Write the subchunk1 Size.
|
|
|
|
binary.LittleEndian.PutUint32(buf, 16)
|
|
|
|
copy(header[16:20], buf)
|
|
|
|
|
|
|
|
// Write the encoded audio format.
|
|
|
|
if w.Metadata.AudioFormat != PCMFormat { // TODO: allow for more encoding formats.
|
|
|
|
return 0, errInvalidFormat
|
|
|
|
}
|
|
|
|
binary.LittleEndian.PutUint16(buf[0:2], 1)
|
|
|
|
copy(header[20:22], buf[0:2])
|
|
|
|
|
|
|
|
// Write the number of channels.
|
|
|
|
if w.Metadata.Channels == 0 {
|
|
|
|
return 0, errInvalidChannels
|
|
|
|
}
|
|
|
|
binary.LittleEndian.PutUint16(buf[0:2], uint16(w.Metadata.Channels))
|
|
|
|
copy(header[22:24], buf[0:2])
|
|
|
|
|
|
|
|
// Write the sample rate.
|
|
|
|
if w.Metadata.SampleRate == 0 {
|
|
|
|
return 0, errInvalidRate
|
|
|
|
}
|
|
|
|
binary.LittleEndian.PutUint32(buf[0:4], uint32(w.Metadata.SampleRate))
|
|
|
|
copy(header[24:28], buf[0:4])
|
|
|
|
|
|
|
|
// Write bit depth values.
|
|
|
|
if w.Metadata.BitDepth == 0 {
|
|
|
|
return 0, errInvalidBitDepth
|
|
|
|
}
|
|
|
|
var val uint32 = uint32((w.Metadata.SampleRate * w.Metadata.BitDepth * w.Metadata.Channels) / 8)
|
|
|
|
binary.LittleEndian.PutUint32(buf[0:4], val)
|
|
|
|
copy(header[28:32], buf[0:4])
|
|
|
|
|
|
|
|
val = uint32((w.Metadata.BitDepth * w.Metadata.Channels) / 8)
|
|
|
|
binary.LittleEndian.PutUint32(buf[0:4], val)
|
|
|
|
copy(header[32:34], buf[0:4])
|
|
|
|
|
|
|
|
binary.LittleEndian.PutUint32(buf[0:4], uint32(w.Metadata.BitDepth))
|
|
|
|
copy(header[34:36], buf[0:4])
|
|
|
|
|
|
|
|
// Mark start of data.
|
|
|
|
copy(header[36:40], []byte("data"))
|
|
|
|
|
|
|
|
// Write size of data chunk.
|
|
|
|
binary.LittleEndian.PutUint32(buf[0:4], uint32(len(p)))
|
|
|
|
copy(header[40:44], buf[0:4])
|
|
|
|
|
|
|
|
// Append audio data.
|
|
|
|
w.Audio = header
|
|
|
|
w.Audio = append(w.Audio, p...)
|
|
|
|
|
|
|
|
// Return successful write.
|
|
|
|
return len(p) + 44, nil
|
|
|
|
|
|
|
|
}
|