mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-11-10 04:57:26 +01:00
Merge 615a008ea0
into b749976a84
This commit is contained in:
commit
98b1a85be3
116
audio/internal/convert/stereo.go
Normal file
116
audio/internal/convert/stereo.go
Normal 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)
|
||||
}
|
@ -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)
|
||||
}
|
150
audio/internal/convert/stereo_test.go
Normal file
150
audio/internal/convert/stereo_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -162,7 +162,7 @@ func DecodeWithoutResampling(src io.Reader) (*Stream, error) {
|
||||
var s io.ReadSeeker = i16Stream
|
||||
length := i16Stream.Length()
|
||||
if channelCount == 1 {
|
||||
s = convert.NewStereo16(s, true, false)
|
||||
s = convert.NewStereo(s, true, 16)
|
||||
length *= 2
|
||||
}
|
||||
|
||||
@ -196,7 +196,7 @@ func DecodeWithSampleRate(sampleRate int, src io.Reader) (*Stream, error) {
|
||||
var s io.ReadSeeker = i16Stream
|
||||
length := i16Stream.Length()
|
||||
if channelCount == 1 {
|
||||
s = convert.NewStereo16(s, true, false)
|
||||
s = convert.NewStereo(s, true, 16)
|
||||
length *= 2
|
||||
}
|
||||
if origSampleRate != sampleRate {
|
||||
|
@ -223,8 +223,8 @@ chunks:
|
||||
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
|
||||
if bitsPerSample != 8 && bitsPerSample != 16 {
|
||||
return nil, fmt.Errorf("wav: bits per sample must be 8 or 16 but was %d", bitsPerSample)
|
||||
if !convert.IsValidResolution(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
|
||||
headerSize += size
|
||||
@ -250,17 +250,14 @@ chunks:
|
||||
remaining: dataSize,
|
||||
}
|
||||
|
||||
if mono || bitsPerSample != 16 {
|
||||
s = convert.NewStereo16(s, mono, bitsPerSample != 16)
|
||||
if mono {
|
||||
dataSize *= 2
|
||||
}
|
||||
if bitsPerSample != 16 {
|
||||
dataSize *= 2
|
||||
}
|
||||
// Fixup the dataSize so calls to the stream's size return the size in 16-bit stereo
|
||||
// samples.
|
||||
dataSize = (dataSize * 16) / int64(bitsPerSample)
|
||||
if mono {
|
||||
dataSize *= 2
|
||||
}
|
||||
return &Stream{
|
||||
inner: s,
|
||||
inner: convert.NewStereo(s, mono, bitsPerSample),
|
||||
size: dataSize,
|
||||
sampleRate: sampleRate,
|
||||
}, nil
|
||||
|
Loading…
Reference in New Issue
Block a user