Compare commits

...

8 Commits

Author SHA1 Message Date
Jeremy Faller
98b1a85be3
Merge 615a008ea0 into b749976a84 2024-06-22 23:59:27 -04:00
Hajime Hoshi
b749976a84 examples/resources/images: update the licenses for the Gopher images 2024-06-23 12:09:40 +09:00
Hajime Hoshi
0c3b4a2d91 examples/resources/images: update the license URLs for ebiten.png 2024-06-23 12:07:51 +09:00
Hajime Hoshi
c86874b506 all: update PureGo to v0.8.0-alpha.3 2024-06-23 01:23:32 +09:00
jeremyfaller
615a008ea0 address comments from PR 2024-06-20 16:29:17 -04:00
jeremyfaller
b5ae59d9ac add copyright 2024-06-20 11:49:01 -04:00
jeremyfaller
612e22f087 add support for 24 and 32 bit sound files 2024-06-20 11:46:03 -04:00
jeremyfaller
32c1e3ba0c move stereo16.go -> stereo.go 2024-06-18 15:13:16 -04:00
8 changed files with 294 additions and 117 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
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 {

View File

@ -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

View File

@ -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
View File

@ -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
View File

@ -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=