diff --git a/codec/wav/wav.go b/codec/wav/wav.go new file mode 100644 index 00000000..a4aef63d --- /dev/null +++ b/codec/wav/wav.go @@ -0,0 +1,131 @@ +/* +NAME + wav.go + +DESCRIPTION + wav.go contains functions for processing wav. + +AUTHOR + David Sutton + +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" +) + +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 + +}