mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-11-10 04:57:26 +01:00
Compare commits
8 Commits
4cd5402cbf
...
98b1a85be3
Author | SHA1 | Date | |
---|---|---|---|
|
98b1a85be3 | ||
|
b749976a84 | ||
|
0c3b4a2d91 | ||
|
c86874b506 | ||
|
615a008ea0 | ||
|
b5ae59d9ac | ||
|
612e22f087 | ||
|
32c1e3ba0c |
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
|
||||
|
@ -3,7 +3,17 @@
|
||||
## ebiten.png
|
||||
|
||||
```
|
||||
http://www.sozai-page.com/02_sozai/b/b04/b04_002/b04_002.html
|
||||
https://web.archive.org/web/20200110174435/http://www.sozai-page.com/02_sozai/b/b04/b04_002/b04_002.html
|
||||
https://web.archive.org/web/20191229033100/http://www.sozai-page.com/rule/rule1.html
|
||||
|
||||
● 商用・個人使用を問わず自由に使用できます。
|
||||
● 画像の内容、画質、状態などにより生じた結果に当方は一切責任を負いません。
|
||||
● 全ての画像は自由に加工・修正できます。
|
||||
ただし、加工により生じた結果に当方は一切責任を負いません。
|
||||
● 加工・修正に関わらず、許可無く画像として再配布・販売することを禁止します。
|
||||
|
||||
これはあくまで「お願い」なのですが、出版・放送等メディア関係の方は使用の際にご連絡いただけると助かります。印刷物やテレビ放送での使用に制限はありませんが、書名、雑誌名、番組名、また発売日・放送日が決まっているようでしたらお知らせいただけないでしょうか。
|
||||
当サイトの画像がどのように利用されたのか、今後の参考にいたします。
|
||||
```
|
||||
|
||||
## fiveyears.jpg
|
||||
@ -107,7 +117,7 @@ CC0 1.0
|
||||
|
||||
```
|
||||
The Go gopher was designed by Renee French. (http://reneefrench.blogspot.com/)
|
||||
The design is licensed under the Creative Commons 3.0 Attributions license.
|
||||
The design is licensed under the Creative Commons 4.0 Attributions license.
|
||||
Read this article for more details: https://blog.golang.org/gopher
|
||||
```
|
||||
|
||||
@ -129,7 +139,7 @@ MIT License
|
||||
|
||||
```
|
||||
The Go gopher was designed by Renee French. (http://reneefrench.blogspot.com/)
|
||||
The design is licensed under the Creative Commons 3.0 Attributions license.
|
||||
The design is licensed under the Creative Commons 4.0 Attributions license.
|
||||
Read this article for more details: https://blog.golang.org/gopher
|
||||
```
|
||||
|
||||
@ -137,7 +147,7 @@ Read this article for more details: https://blog.golang.org/gopher
|
||||
|
||||
```
|
||||
The Go gopher was designed by Renee French. (http://reneefrench.blogspot.com/)
|
||||
The design is licensed under the Creative Commons 3.0 Attributions license.
|
||||
The design is licensed under the Creative Commons 4.0 Attributions license.
|
||||
Read this article for more details: https://blog.golang.org/gopher
|
||||
```
|
||||
|
||||
@ -151,7 +161,7 @@ https://corvussg.itch.io/2d-game-backgrounds
|
||||
|
||||
```
|
||||
The Go gopher was designed by Renee French. (http://reneefrench.blogspot.com/)
|
||||
The design is licensed under the Creative Commons 3.0 Attributions license.
|
||||
The design is licensed under the Creative Commons 4.0 Attributions license.
|
||||
Read this article for more details: https://blog.golang.org/gopher
|
||||
```
|
||||
|
||||
|
2
go.mod
2
go.mod
@ -6,7 +6,7 @@ require (
|
||||
github.com/ebitengine/gomobile v0.0.0-20240518074828-e86332849895
|
||||
github.com/ebitengine/hideconsole v1.0.0
|
||||
github.com/ebitengine/oto/v3 v3.3.0-alpha.1
|
||||
github.com/ebitengine/purego v0.8.0-alpha.2.0.20240620120633-4c3273e2bc2e
|
||||
github.com/ebitengine/purego v0.8.0-alpha.3
|
||||
github.com/gen2brain/mpeg v0.3.2-0.20240412154320-a2ac4fc8a46f
|
||||
github.com/go-text/typesetting v0.1.1
|
||||
github.com/hajimehoshi/bitmapfont/v3 v3.2.0-alpha.2
|
||||
|
4
go.sum
4
go.sum
@ -4,8 +4,8 @@ github.com/ebitengine/hideconsole v1.0.0 h1:5J4U0kXF+pv/DhiXt5/lTz0eO5ogJ1iXb8Yj
|
||||
github.com/ebitengine/hideconsole v1.0.0/go.mod h1:hTTBTvVYWKBuxPr7peweneWdkUwEuHuB3C1R/ielR1A=
|
||||
github.com/ebitengine/oto/v3 v3.3.0-alpha.1 h1:J2nBmQwPLKc4+yLObytq1jKNydI96l6EjZfgefiqGbk=
|
||||
github.com/ebitengine/oto/v3 v3.3.0-alpha.1/go.mod h1:T2/VV0UWG97GEEf4kORMU2nCneYT/YmwSTxPutSVaUg=
|
||||
github.com/ebitengine/purego v0.8.0-alpha.2.0.20240620120633-4c3273e2bc2e h1:Do0Ag7xIjxmXpZ0dSBt56bg/POw5wOjevFndam1L2cQ=
|
||||
github.com/ebitengine/purego v0.8.0-alpha.2.0.20240620120633-4c3273e2bc2e/go.mod h1:cJsFmQCiLTx+cpFhVN3pj+pQfpYnn2OLeJtaKsRmVoA=
|
||||
github.com/ebitengine/purego v0.8.0-alpha.3 h1:qoFlpGuVwJ6J85kuj6Qpyp0DBgxsNYfSY9efidSNFgA=
|
||||
github.com/ebitengine/purego v0.8.0-alpha.3/go.mod h1:b94LtM1jUWDZPKDyENVhB0WsLdLWFApjbNw5AyxmKyI=
|
||||
github.com/gen2brain/mpeg v0.3.2-0.20240412154320-a2ac4fc8a46f h1:ysqRe+lvUiL0dH5XzkH0Bz68bFMPJ4f5Si4L/HD9SGk=
|
||||
github.com/gen2brain/mpeg v0.3.2-0.20240412154320-a2ac4fc8a46f/go.mod h1:i/ebyRRv/IoHixuZ9bElZnXbmfoUVPGQpdsJ4sVuX38=
|
||||
github.com/go-text/typesetting v0.1.1 h1:bGAesCuo85nXnEN5LmFMVGAGpGkCPtHrZLi//qD7EJo=
|
||||
|
Loading…
Reference in New Issue
Block a user