mirror of
https://github.com/vsariola/sointu.git
synced 2025-05-28 03:10:24 -04:00
refactor: move Wav and Raw methods as members of AudioBuffer
This commit is contained in:
parent
33625c6f40
commit
0187cc66ec
111
audio.go
111
audio.go
@ -1,5 +1,12 @@
|
|||||||
package sointu
|
package sointu
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
// AudioBuffer is a buffer of stereo audio samples of variable length, each
|
// AudioBuffer is a buffer of stereo audio samples of variable length, each
|
||||||
// sample represented by a slice of [2]float32. [0] is left channel, [1] is
|
// sample represented by a slice of [2]float32. [0] is left channel, [1] is
|
||||||
// right
|
// right
|
||||||
@ -22,3 +29,107 @@ type AudioContext interface {
|
|||||||
Output() AudioOutput
|
Output() AudioOutput
|
||||||
Close() error
|
Close() error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wav converts a stereo signal of 32-bit floats (L R L R..., length should be
|
||||||
|
// divisible by 2) into a valid WAV-file, returned as a []byte array.
|
||||||
|
//
|
||||||
|
// If pcm16 is set to true, the samples in the WAV-file will be 16-bit signed
|
||||||
|
// integers; otherwise the samples will be 32-bit floats
|
||||||
|
func (buffer AudioBuffer) Wav(pcm16 bool) ([]byte, error) {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
wavHeader(len(buffer)*2, pcm16, buf)
|
||||||
|
err := buffer.rawToBuffer(pcm16, buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Wav failed: %v", err)
|
||||||
|
}
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Raw converts a stereo signal of 32-bit floats (L R L R..., length should be
|
||||||
|
// divisible by 2) into a raw audio file, returned as a []byte array.
|
||||||
|
//
|
||||||
|
// If pcm16 is set to true, the samples will be 16-bit signed integers;
|
||||||
|
// otherwise the samples will be 32-bit floats
|
||||||
|
func (buffer AudioBuffer) Raw(pcm16 bool) ([]byte, error) {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
err := buffer.rawToBuffer(pcm16, buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Raw failed: %v", err)
|
||||||
|
}
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (data AudioBuffer) rawToBuffer(pcm16 bool, buf *bytes.Buffer) error {
|
||||||
|
var err error
|
||||||
|
if pcm16 {
|
||||||
|
int16data := make([][2]int16, len(data))
|
||||||
|
for i, v := range data {
|
||||||
|
int16data[i][0] = int16(clamp(int(v[0]*math.MaxInt16), math.MinInt16, math.MaxInt16))
|
||||||
|
int16data[i][1] = int16(clamp(int(v[1]*math.MaxInt16), math.MinInt16, math.MaxInt16))
|
||||||
|
}
|
||||||
|
err = binary.Write(buf, binary.LittleEndian, int16data)
|
||||||
|
} else {
|
||||||
|
err = binary.Write(buf, binary.LittleEndian, data)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not binary write data to binary buffer: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// wavHeader writes a wave header for either float32 or int16 .wav file into the
|
||||||
|
// bytes.buffer. It needs to know the length of the buffer and assumes stereo
|
||||||
|
// sound, so the length in stereo samples (L + R) is bufferlength / 2. If pcm16
|
||||||
|
// = true, then the header is for int16 audio; pcm16 = false means the header is
|
||||||
|
// for float32 audio. Assumes 44100 Hz sample rate.
|
||||||
|
func wavHeader(bufferLength int, pcm16 bool, buf *bytes.Buffer) {
|
||||||
|
// Refer to: http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html
|
||||||
|
numChannels := 2
|
||||||
|
sampleRate := 44100
|
||||||
|
var bytesPerSample, chunkSize, fmtChunkSize, waveFormat int
|
||||||
|
var factChunk bool
|
||||||
|
if pcm16 {
|
||||||
|
bytesPerSample = 2
|
||||||
|
chunkSize = 36 + bytesPerSample*bufferLength
|
||||||
|
fmtChunkSize = 16
|
||||||
|
waveFormat = 1 // PCM
|
||||||
|
factChunk = false
|
||||||
|
} else {
|
||||||
|
bytesPerSample = 4
|
||||||
|
chunkSize = 50 + bytesPerSample*bufferLength
|
||||||
|
fmtChunkSize = 18
|
||||||
|
waveFormat = 3 // IEEE float
|
||||||
|
factChunk = true
|
||||||
|
}
|
||||||
|
buf.Write([]byte("RIFF"))
|
||||||
|
binary.Write(buf, binary.LittleEndian, uint32(chunkSize))
|
||||||
|
buf.Write([]byte("WAVE"))
|
||||||
|
buf.Write([]byte("fmt "))
|
||||||
|
binary.Write(buf, binary.LittleEndian, uint32(fmtChunkSize))
|
||||||
|
binary.Write(buf, binary.LittleEndian, uint16(waveFormat))
|
||||||
|
binary.Write(buf, binary.LittleEndian, uint16(numChannels))
|
||||||
|
binary.Write(buf, binary.LittleEndian, uint32(sampleRate))
|
||||||
|
binary.Write(buf, binary.LittleEndian, uint32(sampleRate*numChannels*bytesPerSample)) // avgBytesPerSec
|
||||||
|
binary.Write(buf, binary.LittleEndian, uint16(numChannels*bytesPerSample)) // blockAlign
|
||||||
|
binary.Write(buf, binary.LittleEndian, uint16(8*bytesPerSample)) // bits per sample
|
||||||
|
if fmtChunkSize > 16 {
|
||||||
|
binary.Write(buf, binary.LittleEndian, uint16(0)) // size of extension
|
||||||
|
}
|
||||||
|
if factChunk {
|
||||||
|
buf.Write([]byte("fact"))
|
||||||
|
binary.Write(buf, binary.LittleEndian, uint32(4)) // fact chunk size
|
||||||
|
binary.Write(buf, binary.LittleEndian, uint32(bufferLength)) // sample length
|
||||||
|
}
|
||||||
|
buf.Write([]byte("data"))
|
||||||
|
binary.Write(buf, binary.LittleEndian, uint32(bytesPerSample*bufferLength))
|
||||||
|
}
|
||||||
|
|
||||||
|
func clamp(value, min, max int) int {
|
||||||
|
if value < min {
|
||||||
|
return min
|
||||||
|
}
|
||||||
|
if value > max {
|
||||||
|
return max
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
112
audioexport.go
112
audioexport.go
@ -1,112 +0,0 @@
|
|||||||
package sointu
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Wav converts a stereo signal of 32-bit floats (L R L R..., length should be
|
|
||||||
// divisible by 2) into a valid WAV-file, returned as a []byte array.
|
|
||||||
//
|
|
||||||
// If pcm16 is set to true, the samples in the WAV-file will be 16-bit signed
|
|
||||||
// integers; otherwise the samples will be 32-bit floats
|
|
||||||
func Wav(buffer AudioBuffer, pcm16 bool) ([]byte, error) {
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
wavHeader(len(buffer)*2, pcm16, buf)
|
|
||||||
err := rawToBuffer(buffer, pcm16, buf)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Wav failed: %v", err)
|
|
||||||
}
|
|
||||||
return buf.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Raw converts a stereo signal of 32-bit floats (L R L R..., length should be
|
|
||||||
// divisible by 2) into a raw audio file, returned as a []byte array.
|
|
||||||
//
|
|
||||||
// If pcm16 is set to true, the samples will be 16-bit signed integers;
|
|
||||||
// otherwise the samples will be 32-bit floats
|
|
||||||
func Raw(buffer AudioBuffer, pcm16 bool) ([]byte, error) {
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
err := rawToBuffer(buffer, pcm16, buf)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Raw failed: %v", err)
|
|
||||||
}
|
|
||||||
return buf.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func rawToBuffer(data AudioBuffer, pcm16 bool, buf *bytes.Buffer) error {
|
|
||||||
var err error
|
|
||||||
if pcm16 {
|
|
||||||
int16data := make([][2]int16, len(data))
|
|
||||||
for i, v := range data {
|
|
||||||
int16data[i][0] = int16(clamp(int(v[0]*math.MaxInt16), math.MinInt16, math.MaxInt16))
|
|
||||||
int16data[i][1] = int16(clamp(int(v[1]*math.MaxInt16), math.MinInt16, math.MaxInt16))
|
|
||||||
}
|
|
||||||
err = binary.Write(buf, binary.LittleEndian, int16data)
|
|
||||||
} else {
|
|
||||||
err = binary.Write(buf, binary.LittleEndian, data)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not binary write data to binary buffer: %v", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// wavHeader writes a wave header for either float32 or int16 .wav file into the
|
|
||||||
// bytes.buffer. It needs to know the length of the buffer and assumes stereo
|
|
||||||
// sound, so the length in stereo samples (L + R) is bufferlength / 2. If pcm16
|
|
||||||
// = true, then the header is for int16 audio; pcm16 = false means the header is
|
|
||||||
// for float32 audio. Assumes 44100 Hz sample rate.
|
|
||||||
func wavHeader(bufferLength int, pcm16 bool, buf *bytes.Buffer) {
|
|
||||||
// Refer to: http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html
|
|
||||||
numChannels := 2
|
|
||||||
sampleRate := 44100
|
|
||||||
var bytesPerSample, chunkSize, fmtChunkSize, waveFormat int
|
|
||||||
var factChunk bool
|
|
||||||
if pcm16 {
|
|
||||||
bytesPerSample = 2
|
|
||||||
chunkSize = 36 + bytesPerSample*bufferLength
|
|
||||||
fmtChunkSize = 16
|
|
||||||
waveFormat = 1 // PCM
|
|
||||||
factChunk = false
|
|
||||||
} else {
|
|
||||||
bytesPerSample = 4
|
|
||||||
chunkSize = 50 + bytesPerSample*bufferLength
|
|
||||||
fmtChunkSize = 18
|
|
||||||
waveFormat = 3 // IEEE float
|
|
||||||
factChunk = true
|
|
||||||
}
|
|
||||||
buf.Write([]byte("RIFF"))
|
|
||||||
binary.Write(buf, binary.LittleEndian, uint32(chunkSize))
|
|
||||||
buf.Write([]byte("WAVE"))
|
|
||||||
buf.Write([]byte("fmt "))
|
|
||||||
binary.Write(buf, binary.LittleEndian, uint32(fmtChunkSize))
|
|
||||||
binary.Write(buf, binary.LittleEndian, uint16(waveFormat))
|
|
||||||
binary.Write(buf, binary.LittleEndian, uint16(numChannels))
|
|
||||||
binary.Write(buf, binary.LittleEndian, uint32(sampleRate))
|
|
||||||
binary.Write(buf, binary.LittleEndian, uint32(sampleRate*numChannels*bytesPerSample)) // avgBytesPerSec
|
|
||||||
binary.Write(buf, binary.LittleEndian, uint16(numChannels*bytesPerSample)) // blockAlign
|
|
||||||
binary.Write(buf, binary.LittleEndian, uint16(8*bytesPerSample)) // bits per sample
|
|
||||||
if fmtChunkSize > 16 {
|
|
||||||
binary.Write(buf, binary.LittleEndian, uint16(0)) // size of extension
|
|
||||||
}
|
|
||||||
if factChunk {
|
|
||||||
buf.Write([]byte("fact"))
|
|
||||||
binary.Write(buf, binary.LittleEndian, uint32(4)) // fact chunk size
|
|
||||||
binary.Write(buf, binary.LittleEndian, uint32(bufferLength)) // sample length
|
|
||||||
}
|
|
||||||
buf.Write([]byte("data"))
|
|
||||||
binary.Write(buf, binary.LittleEndian, uint32(bytesPerSample*bufferLength))
|
|
||||||
}
|
|
||||||
|
|
||||||
func clamp(value, min, max int) int {
|
|
||||||
if value < min {
|
|
||||||
return min
|
|
||||||
}
|
|
||||||
if value > max {
|
|
||||||
return max
|
|
||||||
}
|
|
||||||
return value
|
|
||||||
}
|
|
@ -100,7 +100,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if *rawOut {
|
if *rawOut {
|
||||||
raw, err := sointu.Raw(buffer, *pcm)
|
raw, err := buffer.Raw(*pcm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not generate .raw file: %v", err)
|
return fmt.Errorf("could not generate .raw file: %v", err)
|
||||||
}
|
}
|
||||||
@ -109,7 +109,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if *wavOut {
|
if *wavOut {
|
||||||
wav, err := sointu.Wav(buffer, *pcm)
|
wav, err := buffer.Wav(*pcm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not generate .wav file: %v", err)
|
return fmt.Errorf("could not generate .wav file: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -145,7 +145,7 @@ func (t *Tracker) exportWav(w io.WriteCloser, pcm16 bool) {
|
|||||||
t.Alert.Update(fmt.Sprintf("Error rendering the song during export: %v", err), Error, time.Second*3)
|
t.Alert.Update(fmt.Sprintf("Error rendering the song during export: %v", err), Error, time.Second*3)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
buffer, err := sointu.Wav(data, pcm16)
|
buffer, err := data.Wav(pcm16)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Alert.Update(fmt.Sprintf("Error converting to .wav: %v", err), Error, time.Second*3)
|
t.Alert.Update(fmt.Sprintf("Error converting to .wav: %v", err), Error, time.Second*3)
|
||||||
return
|
return
|
||||||
|
Loading…
Reference in New Issue
Block a user