This commit is contained in:
Jeremy Faller 2024-06-21 23:20:07 +01:00 committed by GitHub
commit 6bf9ab8b6a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 276 additions and 109 deletions

View File

@ -0,0 +1,116 @@
// Copyright 2017 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package convert
import (
"fmt"
"io"
)
// Stereo objects convert little-endian audio mono or stereo audio streams into
// 16-bit stereo stereo streams.
type Stereo struct {
source io.ReadSeeker
mono bool
bytes int
buf []byte
}
// IsValidResolution returns true if the given bit resolution is supported.
func IsValidResolution(r int) bool {
switch r {
case 8, 16, 24:
return true
}
return false
}
// NewStereo accepts an io.ReadSeeker into an audio stream. Regardless of the input
// stream, subsequent calls to Stereo.Read will return 16-bit little-endian stereo
// audio samples.
//
// Valid values for resolution are: [8,16,24]. Any invalid input will panic.
func NewStereo(source io.ReadSeeker, mono bool, resolution int) *Stereo {
if !IsValidResolution(resolution) {
panic(fmt.Errorf("unsupported resolution: %d", resolution))
}
return &Stereo{source, mono, resolution / 8, nil}
}
// Read returns audio data from the input stream as 16-bit stereo (little-endian).
func (s *Stereo) Read(b []byte) (int, error) {
// Calculate how large we need our buffer to be, and allocate it.
l := (len(b) * s.bytes) / 2
if s.mono {
l /= 2
}
if cap(s.buf) < l {
s.buf = make([]byte, l)
}
// Copy over the bits.
n, err := s.source.Read(s.buf[:l])
if err != nil && err != io.EOF {
return 0, err
}
// We now need to tweak the data into the required format.
switch {
case s.mono && s.bytes == 1:
for i := 0; i < n; i++ {
v := int16(int(s.buf[i]) * 0x100)
b[4*i+0] = byte(v)
b[4*i+1] = byte(v >> 8)
b[4*i+2] = byte(v)
b[4*i+3] = byte(v >> 8)
}
case !s.mono && s.bytes == 1:
for i := 0; i < n; i++ {
v := int16(int(s.buf[i]) * 0x100)
b[2*i+0] = byte(v)
b[2*i+1] = byte(v >> 8)
}
case s.mono:
m := s.bytes
for i := 0; i < n/m; i++ {
b[4*i+0] = s.buf[m*(i+1)-2]
b[4*i+1] = s.buf[m*(i+1)-1]
b[4*i+2] = s.buf[m*(i+1)-2]
b[4*i+3] = s.buf[m*(i+1)-1]
}
case !s.mono:
m := s.bytes
for i := 0; i < n/m; i++ {
b[2*i+0] = s.buf[m*(i+1)-2]
b[2*i+1] = s.buf[m*(i+1)-1]
}
}
// Return the number of bytes read.
if s.mono {
return (n * 4) / s.bytes, err
}
return (n * 2) / s.bytes, err
}
// Seek moves the location for next Read call in the audio stream.
func (s *Stereo) Seek(offset int64, whence int) (int64, error) {
offset = (offset * int64(s.bytes))
if s.mono {
offset /= 2
}
return s.source.Seek(offset, whence)
}

View File

@ -1,96 +0,0 @@
// Copyright 2017 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package convert
import (
"io"
)
type Stereo16 struct {
source io.ReadSeeker
mono bool
eight bool
buf []byte
}
func NewStereo16(source io.ReadSeeker, mono, eight bool) *Stereo16 {
return &Stereo16{
source: source,
mono: mono,
eight: eight,
}
}
func (s *Stereo16) Read(b []byte) (int, error) {
l := len(b)
if s.mono {
l /= 2
}
if s.eight {
l /= 2
}
if cap(s.buf) < l {
s.buf = make([]byte, l)
}
n, err := s.source.Read(s.buf[:l])
if err != nil && err != io.EOF {
return 0, err
}
switch {
case s.mono && s.eight:
for i := 0; i < n; i++ {
v := int16(int(s.buf[i])*0x101 - (1 << 15))
b[4*i] = byte(v)
b[4*i+1] = byte(v >> 8)
b[4*i+2] = byte(v)
b[4*i+3] = byte(v >> 8)
}
case s.mono && !s.eight:
for i := 0; i < n/2; i++ {
b[4*i] = s.buf[2*i]
b[4*i+1] = s.buf[2*i+1]
b[4*i+2] = s.buf[2*i]
b[4*i+3] = s.buf[2*i+1]
}
case !s.mono && s.eight:
for i := 0; i < n/2; i++ {
v0 := int16(int(s.buf[2*i])*0x101 - (1 << 15))
v1 := int16(int(s.buf[2*i+1])*0x101 - (1 << 15))
b[4*i] = byte(v0)
b[4*i+1] = byte(v0 >> 8)
b[4*i+2] = byte(v1)
b[4*i+3] = byte(v1 >> 8)
}
}
if s.mono {
n *= 2
}
if s.eight {
n *= 2
}
return n, err
}
func (s *Stereo16) Seek(offset int64, whence int) (int64, error) {
if s.mono {
offset /= 2
}
if s.eight {
offset /= 2
}
return s.source.Seek(offset, whence)
}

View File

@ -0,0 +1,150 @@
// Copyright 2024 The Ebitengine Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package convert
import (
"bytes"
"io"
"math"
"reflect"
"testing"
)
type testCase struct {
resolution int
mono bool
input []byte
expected []byte
}
func freq(n int) float64 {
return float64(n) * math.Pi / 2
}
func writeWord(w *bytes.Buffer, bits int, v uint32) {
v = v >> (32 - bits)
for i := 0; i < bits/8; i++ {
b := byte(v & 0xFF)
w.WriteByte(b)
v = v >> 8
}
}
func genSin(sinLen int, bits int, mono bool) []byte {
w := bytes.NewBuffer(nil)
for i := 0; i < sinLen; i++ {
v := uint32(float64((1<<31)-1) * (1 + math.Sin(freq(i))))
writeWord(w, bits, v)
if !mono {
writeWord(w, bits, v)
}
}
return w.Bytes()
}
func genTestCases() []testCase {
res16 := genSin(4, 16, false)
res8 := []byte{}
for i, v := range res16 {
if i%2 == 0 {
v = 0
}
res8 = append(res8, v)
}
cases := []testCase{
{8, true, nil, res8},
{8, false, nil, res8},
{16, true, nil, res16},
{16, false, nil, res16},
{24, true, nil, res16},
{24, false, nil, res16},
}
for i, test := range cases {
cases[i].input = genSin(10, test.resolution, test.mono)
}
return cases
}
func TestIsValidResolution(t *testing.T) {
tests := []struct {
v int
e bool
}{
{0, false},
{1, false},
{8, true},
{16, true},
{24, true},
{32, false},
}
for _, test := range tests {
if e := IsValidResolution(test.v); e != test.e {
t.Errorf("IsValidResolution(%d) = %t, expected %t", test.v, e, test.e)
}
}
}
func TestDecode(t *testing.T) {
tests := genTestCases()
for i, test := range tests {
s := NewStereo(bytes.NewReader(test.input), test.mono, test.resolution)
b := make([]byte, len(test.expected))
_, err := s.Read(b)
if err != nil {
t.Fatalf("[%d] Stereo.Read() returned an error: %v", i, err)
}
if !reflect.DeepEqual(b, test.expected) {
t.Errorf(`[%d] Stereo.Read
input %v
result %v
expected %v`, i, test.input, b, test.expected)
}
}
}
func TestSeek(t *testing.T) {
tests := genTestCases()
for amt := 0; amt < 10; amt += 2 {
for i, test := range tests {
s := NewStereo(bytes.NewReader(test.input), test.mono, test.resolution)
n, err := s.Seek(int64(amt), io.SeekStart)
v := amt * test.resolution / 8
if test.mono {
v /= 2
}
if err != nil {
t.Fatalf("[%d] Stereo.Seek() returned an error: %v", i, err)
}
if v != int(n) {
t.Logf("[%d] Stere.Seek() = %d, expected %d", i, n, v)
}
test.expected = test.expected[amt*2:]
b := make([]byte, len(test.expected))
_, err = s.Read(b)
if err != nil {
t.Fatalf("[%d] Stereo.Read() returned an error: %v", i, err)
}
if !reflect.DeepEqual(b, test.expected) {
t.Errorf(`[%d] Stereo.Read
input %v
result %v
expected %v`, i, test.input, b, test.expected)
}
}
}
}

View File

@ -162,7 +162,7 @@ func DecodeWithoutResampling(src io.Reader) (*Stream, error) {
var s io.ReadSeeker = i16Stream var s io.ReadSeeker = i16Stream
length := i16Stream.Length() length := i16Stream.Length()
if channelCount == 1 { if channelCount == 1 {
s = convert.NewStereo16(s, true, false) s = convert.NewStereo(s, true, 16)
length *= 2 length *= 2
} }
@ -196,7 +196,7 @@ func DecodeWithSampleRate(sampleRate int, src io.Reader) (*Stream, error) {
var s io.ReadSeeker = i16Stream var s io.ReadSeeker = i16Stream
length := i16Stream.Length() length := i16Stream.Length()
if channelCount == 1 { if channelCount == 1 {
s = convert.NewStereo16(s, true, false) s = convert.NewStereo(s, true, 16)
length *= 2 length *= 2
} }
if origSampleRate != sampleRate { if origSampleRate != sampleRate {

View File

@ -223,8 +223,8 @@ chunks:
return nil, fmt.Errorf("wav: number of channels must be 1 or 2 but was %d", channelCount) return nil, fmt.Errorf("wav: number of channels must be 1 or 2 but was %d", channelCount)
} }
bitsPerSample = int(buf[14]) | int(buf[15])<<8 bitsPerSample = int(buf[14]) | int(buf[15])<<8
if bitsPerSample != 8 && bitsPerSample != 16 { if !convert.IsValidResolution(bitsPerSample) {
return nil, fmt.Errorf("wav: bits per sample must be 8 or 16 but was %d", bitsPerSample) return nil, fmt.Errorf("wav: bits per sample must be [8,16,24,32] but was %d", bitsPerSample)
} }
sampleRate = int(buf[4]) | int(buf[5])<<8 | int(buf[6])<<16 | int(buf[7])<<24 sampleRate = int(buf[4]) | int(buf[5])<<8 | int(buf[6])<<16 | int(buf[7])<<24
headerSize += size headerSize += size
@ -250,17 +250,14 @@ chunks:
remaining: dataSize, remaining: dataSize,
} }
if mono || bitsPerSample != 16 { // Fixup the dataSize so calls to the stream's size return the size in 16-bit stereo
s = convert.NewStereo16(s, mono, bitsPerSample != 16) // samples.
if mono { dataSize = (dataSize * 16) / int64(bitsPerSample)
dataSize *= 2 if mono {
} dataSize *= 2
if bitsPerSample != 16 {
dataSize *= 2
}
} }
return &Stream{ return &Stream{
inner: s, inner: convert.NewStereo(s, mono, bitsPerSample),
size: dataSize, size: dataSize,
sampleRate: sampleRate, sampleRate: sampleRate,
}, nil }, nil