Skip to content
This repository was archived by the owner on Mar 18, 2024. It is now read-only.

Commit ef2973b

Browse files
authored
Merge pull request #2 from gotracker/sample-formatting
Add support for proper sample formatting
2 parents ed8a144 + de23753 commit ef2973b

File tree

9 files changed

+454
-32
lines changed

9 files changed

+454
-32
lines changed

mixing/mixbuffer.go

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package mixing
22

33
import (
44
"bytes"
5-
"encoding/binary"
65
"time"
76

87
"github.com/gotracker/gomixing/sampling"
@@ -63,15 +62,14 @@ func (m *MixBuffer) Add(pos int, rhs *MixBuffer, volMtx volume.Matrix) {
6362

6463
// ToRenderData converts a mixbuffer into a byte stream intended to be
6564
// output to the output sound device
66-
func (m *MixBuffer) ToRenderData(samples int, bitsPerSample int, channels int, mixerVolume volume.Volume) []byte {
65+
func (m *MixBuffer) ToRenderData(samples int, channels int, mixerVolume volume.Volume, formatter sampling.Formatter) []byte {
6766
writer := &bytes.Buffer{}
68-
writer.Grow(samples * ((bitsPerSample + 7) / 8) * channels)
67+
writer.Grow(samples * ((formatter.Size() + 7) / 8) * channels)
6968
for _, samp := range *m {
7069
buf := samp.Apply(mixerVolume)
7170
d := buf.ToChannels(channels)
7271
for i := 0; i < channels; i++ {
73-
val := d.StaticMatrix[i].ToSample(bitsPerSample)
74-
_ = binary.Write(writer, binary.LittleEndian, val) // lint
72+
_ = formatter.Write(writer, d.StaticMatrix[i]) // lint
7573
}
7674
}
7775
return writer.Bytes()
@@ -96,7 +94,7 @@ func (m *MixBuffer) ToIntStream(outputChannels int, samples int, bitsPerSample i
9694

9795
// ToRenderDataWithBufs converts a mixbuffer into a byte stream intended to be
9896
// output to the output sound device
99-
func (m *MixBuffer) ToRenderDataWithBufs(outBuffers [][]byte, samples int, bitsPerSample int, mixerVolume volume.Volume) {
97+
func (m *MixBuffer) ToRenderDataWithBufs(outBuffers [][]byte, samples int, mixerVolume volume.Volume, formatter sampling.Formatter) {
10098
pos := 0
10199
onum := 0
102100
out := outBuffers[onum]
@@ -111,22 +109,8 @@ func (m *MixBuffer) ToRenderDataWithBufs(outBuffers [][]byte, samples int, bitsP
111109
out = outBuffers[onum]
112110
pos = 0
113111
}
114-
val := buf.StaticMatrix[c].ToSample(bitsPerSample)
115-
switch d := val.(type) {
116-
case int8:
117-
out[pos] = uint8(d)
118-
pos++
119-
case int16:
120-
binary.LittleEndian.PutUint16(out[pos:], uint16(d))
121-
pos += 2
122-
case int32:
123-
binary.LittleEndian.PutUint32(out[pos:], uint32(d))
124-
pos += 4
125-
default:
126-
writer := &bytes.Buffer{}
127-
_ = binary.Write(writer, binary.LittleEndian, val) // lint
128-
pos += copy(out[pos:], writer.Bytes())
129-
}
112+
_ = formatter.WriteAt(out, int64(pos), buf.StaticMatrix[c]) // lint
113+
pos += formatter.Size()
130114
}
131115
}
132116
}

mixing/mixer.go

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package mixing
22

3-
import "github.com/gotracker/gomixing/volume"
3+
import (
4+
"github.com/gotracker/gomixing/sampling"
5+
"github.com/gotracker/gomixing/volume"
6+
)
47

58
// Mixer is a manager for mixing multiple single- and multi-channel samples into a single multi-channel output stream
69
type Mixer struct {
@@ -10,7 +13,7 @@ type Mixer struct {
1013

1114
// NewMixBuffer returns a mixer buffer with a number of channels
1215
// of preallocated sample data
13-
func (m *Mixer) NewMixBuffer(samples int) MixBuffer {
16+
func (m Mixer) NewMixBuffer(samples int) MixBuffer {
1417
return make(MixBuffer, samples)
1518
}
1619

@@ -21,8 +24,9 @@ func GetDefaultMixerVolume(numMixedChannels int) volume.Volume {
2124
}
2225

2326
// Flatten will to a final saturation mix of all the row's channel data into a single output buffer
24-
func (m *Mixer) Flatten(panmixer PanMixer, samplesLen int, row []ChannelData, mixerVolume volume.Volume) []byte {
27+
func (m Mixer) Flatten(panmixer PanMixer, samplesLen int, row []ChannelData, mixerVolume volume.Volume, sampleFormat sampling.Format) []byte {
2528
data := m.NewMixBuffer(samplesLen)
29+
formatter := sampling.GetFormatter(sampleFormat)
2630
for _, rdata := range row {
2731
for _, cdata := range rdata {
2832
if cdata.Flush != nil {
@@ -34,12 +38,12 @@ func (m *Mixer) Flatten(panmixer PanMixer, samplesLen int, row []ChannelData, mi
3438
}
3539
}
3640
}
37-
return data.ToRenderData(samplesLen, m.BitsPerSample, m.Channels, mixerVolume)
41+
return data.ToRenderData(samplesLen, m.Channels, mixerVolume, formatter)
3842
}
3943

4044
// FlattenToInts runs a flatten on the channel data into separate channel data of int32 variety
4145
// these int32s still respect the BitsPerSample size
42-
func (m *Mixer) FlattenToInts(panmixer PanMixer, samplesLen int, row []ChannelData, mixerVolume volume.Volume) [][]int32 {
46+
func (m Mixer) FlattenToInts(panmixer PanMixer, samplesLen int, row []ChannelData, mixerVolume volume.Volume) [][]int32 {
4347
data := m.NewMixBuffer(samplesLen)
4448
for _, rdata := range row {
4549
for _, cdata := range rdata {
@@ -56,8 +60,9 @@ func (m *Mixer) FlattenToInts(panmixer PanMixer, samplesLen int, row []ChannelDa
5660
}
5761

5862
// FlattenTo will to a final saturation mix of all the row's channel data into a single output buffer
59-
func (m *Mixer) FlattenTo(resultBuffers [][]byte, panmixer PanMixer, samplesLen int, row []ChannelData, mixerVolume volume.Volume) {
63+
func (m Mixer) FlattenTo(resultBuffers [][]byte, panmixer PanMixer, samplesLen int, row []ChannelData, mixerVolume volume.Volume, sampleFormat sampling.Format) {
6064
data := m.NewMixBuffer(samplesLen)
65+
formatter := sampling.GetFormatter(sampleFormat)
6166
for _, rdata := range row {
6267
for _, cdata := range rdata {
6368
if cdata.Flush != nil {
@@ -69,5 +74,5 @@ func (m *Mixer) FlattenTo(resultBuffers [][]byte, panmixer PanMixer, samplesLen
6974
}
7075
}
7176
}
72-
data.ToRenderDataWithBufs(resultBuffers, samplesLen, m.BitsPerSample, mixerVolume)
77+
data.ToRenderDataWithBufs(resultBuffers, samplesLen, mixerVolume, formatter)
7378
}

sampling/format.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package sampling
2+
3+
// Format is the format of the sample data
4+
type Format uint8
5+
6+
const (
7+
// Format8BitUnsigned is for unsigned 8-bit data
8+
Format8BitUnsigned = Format(iota)
9+
// Format8BitSigned is for signed 8-bit data
10+
Format8BitSigned
11+
// Format16BitLEUnsigned is for unsigned, little-endian, 16-bit data
12+
Format16BitLEUnsigned
13+
// Format16BitLESigned is for signed, little-endian, 16-bit data
14+
Format16BitLESigned
15+
// Format16BitBEUnsigned is for unsigned, big-endian, 16-bit data
16+
Format16BitBEUnsigned
17+
// Format16BitBESigned is for signed, big-endian, 16-bit data
18+
Format16BitBESigned
19+
// Format32BitLEFloat is for little-endian, 32-bit floating-point data
20+
Format32BitLEFloat
21+
// Format32BitBEFloat is for big-endian, 32-bit floating-point data
22+
Format32BitBEFloat
23+
// Format64BitLEFloat is for little-endian, 64-bit floating-point data
24+
Format64BitLEFloat
25+
// Format64BitBEFloat is for big-endian, 64-bit floating-point data
26+
Format64BitBEFloat
27+
)

sampling/format_bit16.go

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package sampling
2+
3+
import (
4+
"encoding/binary"
5+
"io"
6+
7+
"github.com/gotracker/gomixing/volume"
8+
)
9+
10+
const (
11+
cSample16BitDataCoeff = 0x8000
12+
cSample16BitVolumeCoeff = volume.Volume(1) / cSample16BitDataCoeff
13+
cSample16BitBytes = 2
14+
)
15+
16+
// Sample16BitSigned is a signed 16-bit sample
17+
type Sample16BitSigned struct {
18+
byteOrder binary.ByteOrder
19+
}
20+
21+
// Volume returns the volume value for the sample
22+
func (Sample16BitSigned) volume(v int16) volume.Volume {
23+
return volume.Volume(v) * cSample16BitVolumeCoeff
24+
}
25+
26+
// fromVolume returns the volume value for the sample
27+
func (Sample16BitSigned) fromVolume(v volume.Volume) int16 {
28+
return int16(v.ToIntSample(16))
29+
}
30+
31+
// Size returns the size of the sample in bytes
32+
func (Sample16BitSigned) Size() int {
33+
return cSample16BitBytes
34+
}
35+
36+
// ReadAt reads a value from the reader provided in the byte order provided
37+
func (s Sample16BitSigned) ReadAt(data []byte, ofs int64) (volume.Volume, error) {
38+
if len(data) <= int(ofs)+(cSample16BitBytes-1) {
39+
return 0, io.EOF
40+
}
41+
if ofs < 0 {
42+
ofs = 0
43+
}
44+
45+
v := int16(s.byteOrder.Uint16(data[ofs:]))
46+
return s.volume(v), nil
47+
}
48+
49+
// WriteAt writes a value to the slice provided in the byte order provided
50+
func (s Sample16BitSigned) WriteAt(data []byte, ofs int64, v volume.Volume) error {
51+
if len(data) <= int(ofs) {
52+
return io.EOF
53+
}
54+
if ofs < 0 {
55+
ofs = 0
56+
}
57+
58+
s.byteOrder.PutUint16(data[ofs:], uint16(s.fromVolume(v)))
59+
return nil
60+
}
61+
62+
// Write writes a value to the Writer provided in the byte order provided
63+
func (s Sample16BitSigned) Write(out io.Writer, v volume.Volume) error {
64+
return binary.Write(out, s.byteOrder, s.fromVolume(v))
65+
}
66+
67+
// Sample16BitUnsigned is an unsigned 16-bit sample
68+
type Sample16BitUnsigned struct {
69+
byteOrder binary.ByteOrder
70+
}
71+
72+
// Volume returns the volume value for the sample
73+
func (Sample16BitUnsigned) volume(v uint16) volume.Volume {
74+
return volume.Volume(int16(v-cSample16BitDataCoeff)) * cSample16BitVolumeCoeff
75+
}
76+
77+
// fromVolume returns the volume value for the sample
78+
func (Sample16BitUnsigned) fromVolume(v volume.Volume) uint16 {
79+
return uint16(v.ToUintSample(16))
80+
}
81+
82+
// Size returns the size of the sample in bytes
83+
func (Sample16BitUnsigned) Size() int {
84+
return cSample16BitBytes
85+
}
86+
87+
// ReadAt reads a value from the reader provided in the byte order provided
88+
func (s Sample16BitUnsigned) ReadAt(data []byte, ofs int64) (volume.Volume, error) {
89+
if len(data) <= int(ofs)+(cSample16BitBytes-1) {
90+
return 0, io.EOF
91+
}
92+
if ofs < 0 {
93+
ofs = 0
94+
}
95+
96+
v := uint16(s.byteOrder.Uint16(data[ofs:]))
97+
return s.volume(v), nil
98+
}
99+
100+
// WriteAt writes a value to the slice provided in the byte order provided
101+
func (s Sample16BitUnsigned) WriteAt(data []byte, ofs int64, v volume.Volume) error {
102+
if len(data) <= int(ofs) {
103+
return io.EOF
104+
}
105+
if ofs < 0 {
106+
ofs = 0
107+
}
108+
109+
s.byteOrder.PutUint16(data[ofs:], s.fromVolume(v))
110+
return nil
111+
}
112+
113+
// Write writes a value to the Writer provided in the byte order provided
114+
func (s Sample16BitUnsigned) Write(out io.Writer, v volume.Volume) error {
115+
return binary.Write(out, s.byteOrder, s.fromVolume(v))
116+
}

sampling/format_bit32float.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package sampling
2+
3+
import (
4+
"encoding/binary"
5+
"io"
6+
"math"
7+
8+
"github.com/gotracker/gomixing/volume"
9+
)
10+
11+
const (
12+
//cSample32BitFloatVolumeCoeff = volume.Volume(1)
13+
cSample32BitFloatBytes = 4
14+
)
15+
16+
// Sample32BitFloat is a 32-bit floating-point sample
17+
type Sample32BitFloat struct {
18+
byteOrder binary.ByteOrder
19+
}
20+
21+
// Size returns the size of the sample in bytes
22+
func (Sample32BitFloat) Size() int {
23+
return cSample32BitFloatBytes
24+
}
25+
26+
// ReadAt reads a value from the reader provided in the byte order provided
27+
func (s Sample32BitFloat) ReadAt(data []byte, ofs int64) (volume.Volume, error) {
28+
if len(data) <= int(ofs)+(cSample32BitFloatBytes-1) {
29+
return 0, io.EOF
30+
}
31+
if ofs < 0 {
32+
ofs = 0
33+
}
34+
35+
v := math.Float32frombits(s.byteOrder.Uint32(data[ofs:]))
36+
return volume.Volume(v), nil
37+
}
38+
39+
// WriteAt writes a value to the slice provided in the byte order provided
40+
func (s Sample32BitFloat) WriteAt(data []byte, ofs int64, v volume.Volume) error {
41+
if len(data) <= int(ofs) {
42+
return io.EOF
43+
}
44+
if ofs < 0 {
45+
ofs = 0
46+
}
47+
48+
s.byteOrder.PutUint32(data[ofs:], math.Float32bits(float32(v.WithOverflowProtection())))
49+
return nil
50+
}
51+
52+
// Write writes a value to the Writer provided in the byte order provided
53+
func (s Sample32BitFloat) Write(out io.Writer, v volume.Volume) error {
54+
return binary.Write(out, s.byteOrder, math.Float32bits(float32(v.WithOverflowProtection())))
55+
}

sampling/format_bit64float.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package sampling
2+
3+
import (
4+
"encoding/binary"
5+
"io"
6+
"math"
7+
8+
"github.com/gotracker/gomixing/volume"
9+
)
10+
11+
const (
12+
//cSample64BitFloatVolumeCoeff = volume.Volume(1)
13+
cSample64BitFloatBytes = 8
14+
)
15+
16+
// Sample64BitFloat is a 64-bit floating-point sample
17+
type Sample64BitFloat struct {
18+
byteOrder binary.ByteOrder
19+
}
20+
21+
// Size returns the size of the sample in bytes
22+
func (Sample64BitFloat) Size() int {
23+
return cSample64BitFloatBytes
24+
}
25+
26+
// ReadAt reads a value from the reader provided in the byte order provided
27+
func (s Sample64BitFloat) ReadAt(data []byte, ofs int64) (volume.Volume, error) {
28+
if len(data) <= int(ofs)+(cSample64BitFloatBytes-1) {
29+
return 0, io.EOF
30+
}
31+
if ofs < 0 {
32+
ofs = 0
33+
}
34+
35+
f := math.Float64frombits(s.byteOrder.Uint64(data[ofs:]))
36+
return volume.Volume(f), nil
37+
}
38+
39+
// WriteAt writes a value to the slice provided in the byte order provided
40+
func (s Sample64BitFloat) WriteAt(data []byte, ofs int64, v volume.Volume) error {
41+
if len(data) <= int(ofs) {
42+
return io.EOF
43+
}
44+
if ofs < 0 {
45+
ofs = 0
46+
}
47+
48+
s.byteOrder.PutUint64(data[ofs:], math.Float64bits(v.WithOverflowProtection()))
49+
return nil
50+
}
51+
52+
// Write writes a value to the Writer provided in the byte order provided
53+
func (s Sample64BitFloat) Write(out io.Writer, v volume.Volume) error {
54+
return binary.Write(out, s.byteOrder, math.Float64bits(v.WithOverflowProtection()))
55+
}

0 commit comments

Comments
 (0)