mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-11-10 04:57:26 +01:00
audio: refactoring: remove audio/internal/cbackend
This commit is contained in:
parent
2826555044
commit
2b248ef783
@ -20,20 +20,40 @@ package audio
|
|||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2/audio/internal/cbackend"
|
"github.com/hajimehoshi/oto/v2/mux"
|
||||||
|
|
||||||
|
"github.com/hajimehoshi/ebiten/v2/internal/cbackend"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newContext(sampleRate, channelCount, bitDepthInBytes int) (context, chan struct{}, error) {
|
func newContext(sampleRate, channelCount, bitDepthInBytes int) (context, chan struct{}, error) {
|
||||||
ctx, ready, err := cbackend.NewContext(sampleRate, channelCount, bitDepthInBytes)
|
ready := make(chan struct{})
|
||||||
return &contextProxy{ctx}, ready, err
|
close(ready)
|
||||||
|
|
||||||
|
c := &contextProxy{mux.New(sampleRate, channelCount, bitDepthInBytes)}
|
||||||
|
cbackend.OpenAudio(sampleRate, channelCount, c.mux.ReadFloat32s)
|
||||||
|
return c, ready, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// contextProxy is a proxy between cbackend.Context and context.
|
// contextProxy is a proxy between cbackend.Context and context.
|
||||||
type contextProxy struct {
|
type contextProxy struct {
|
||||||
*cbackend.Context
|
mux *mux.Mux
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPlayer implements context.
|
// NewPlayer implements context.
|
||||||
func (c *contextProxy) NewPlayer(r io.Reader) player {
|
func (c *contextProxy) NewPlayer(r io.Reader) player {
|
||||||
return c.Context.NewPlayer(r)
|
return c.mux.NewPlayer(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *contextProxy) Suspend() error {
|
||||||
|
// Do nothing so far.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *contextProxy) Resume() error {
|
||||||
|
// Do nothing so far.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *contextProxy) Err() error {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1,67 +0,0 @@
|
|||||||
// Copyright 2021 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.
|
|
||||||
|
|
||||||
//go:build ebitenginecbackend || ebitencbackend
|
|
||||||
// +build ebitenginecbackend ebitencbackend
|
|
||||||
|
|
||||||
package cbackend
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2/internal/cbackend"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Context struct {
|
|
||||||
sampleRate int
|
|
||||||
channelCount int
|
|
||||||
bitDepthInBytes int
|
|
||||||
|
|
||||||
players *players
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewContext(sampleRate, channelCount, bitDepthInBytes int) (*Context, chan struct{}, error) {
|
|
||||||
c := &Context{
|
|
||||||
sampleRate: sampleRate,
|
|
||||||
channelCount: channelCount,
|
|
||||||
bitDepthInBytes: bitDepthInBytes,
|
|
||||||
players: newPlayers(),
|
|
||||||
}
|
|
||||||
cbackend.OpenAudio(sampleRate, channelCount, c.players.read)
|
|
||||||
ready := make(chan struct{})
|
|
||||||
close(ready)
|
|
||||||
return c, ready, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Context) NewPlayer(src io.Reader) *Player {
|
|
||||||
return newPlayer(c, src)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Context) Suspend() error {
|
|
||||||
// Do nothing so far.
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Context) Resume() error {
|
|
||||||
// Do nothing so far.
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Context) Err() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Context) defaultBufferSize() int {
|
|
||||||
return c.sampleRate * c.channelCount * c.bitDepthInBytes / 2 // 0.5[s]
|
|
||||||
}
|
|
@ -1,435 +0,0 @@
|
|||||||
// Copyright 2021 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.
|
|
||||||
|
|
||||||
//go:build ebitenginecbackend || ebitencbackend
|
|
||||||
// +build ebitenginecbackend ebitencbackend
|
|
||||||
|
|
||||||
// TODO: This implementation is very similar to github.com/hajimehoshi/oto/v2's player.go
|
|
||||||
// Unify them if possible.
|
|
||||||
|
|
||||||
package cbackend
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"runtime"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
type playerState int
|
|
||||||
|
|
||||||
const (
|
|
||||||
playerPaused playerState = iota
|
|
||||||
playerPlay
|
|
||||||
playerClosed
|
|
||||||
)
|
|
||||||
|
|
||||||
type players struct {
|
|
||||||
players map[*playerImpl]struct{}
|
|
||||||
buf []float32
|
|
||||||
cond *sync.Cond
|
|
||||||
}
|
|
||||||
|
|
||||||
func newPlayers() *players {
|
|
||||||
p := &players{
|
|
||||||
cond: sync.NewCond(&sync.Mutex{}),
|
|
||||||
}
|
|
||||||
go p.loop()
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ps *players) shouldWait() bool {
|
|
||||||
for p := range ps.players {
|
|
||||||
if p.canReadSourceToBuffer() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ps *players) wait() {
|
|
||||||
ps.cond.L.Lock()
|
|
||||||
defer ps.cond.L.Unlock()
|
|
||||||
|
|
||||||
for ps.shouldWait() {
|
|
||||||
ps.cond.Wait()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ps *players) loop() {
|
|
||||||
var players []*playerImpl
|
|
||||||
for {
|
|
||||||
ps.wait()
|
|
||||||
|
|
||||||
ps.cond.L.Lock()
|
|
||||||
players = players[:0]
|
|
||||||
for p := range ps.players {
|
|
||||||
players = append(players, p)
|
|
||||||
}
|
|
||||||
ps.cond.L.Unlock()
|
|
||||||
|
|
||||||
for _, p := range players {
|
|
||||||
p.readSourceToBuffer()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ps *players) addPlayer(player *playerImpl) {
|
|
||||||
ps.cond.L.Lock()
|
|
||||||
defer ps.cond.L.Unlock()
|
|
||||||
|
|
||||||
if ps.players == nil {
|
|
||||||
ps.players = map[*playerImpl]struct{}{}
|
|
||||||
}
|
|
||||||
ps.players[player] = struct{}{}
|
|
||||||
ps.cond.Signal()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ps *players) removePlayer(player *playerImpl) {
|
|
||||||
ps.cond.L.Lock()
|
|
||||||
defer ps.cond.L.Unlock()
|
|
||||||
|
|
||||||
delete(ps.players, player)
|
|
||||||
ps.cond.Signal()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ps *players) read(buf []float32) {
|
|
||||||
ps.cond.L.Lock()
|
|
||||||
players := make([]*playerImpl, 0, len(ps.players))
|
|
||||||
for p := range ps.players {
|
|
||||||
players = append(players, p)
|
|
||||||
}
|
|
||||||
ps.cond.L.Unlock()
|
|
||||||
|
|
||||||
for i := range buf {
|
|
||||||
buf[i] = 0
|
|
||||||
}
|
|
||||||
for _, p := range players {
|
|
||||||
p.readBufferAndAdd(buf)
|
|
||||||
}
|
|
||||||
ps.cond.Signal()
|
|
||||||
}
|
|
||||||
|
|
||||||
type Player struct {
|
|
||||||
p *playerImpl
|
|
||||||
}
|
|
||||||
|
|
||||||
type playerImpl struct {
|
|
||||||
context *Context
|
|
||||||
src io.Reader
|
|
||||||
volume float64
|
|
||||||
err error
|
|
||||||
state playerState
|
|
||||||
tmpbuf []byte
|
|
||||||
buf []byte
|
|
||||||
eof bool
|
|
||||||
bufferSize int
|
|
||||||
|
|
||||||
m sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func newPlayer(context *Context, src io.Reader) *Player {
|
|
||||||
p := &Player{
|
|
||||||
p: &playerImpl{
|
|
||||||
context: context,
|
|
||||||
src: src,
|
|
||||||
volume: 1,
|
|
||||||
bufferSize: context.defaultBufferSize(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
runtime.SetFinalizer(p, (*Player).Close)
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Player) Err() error {
|
|
||||||
return p.p.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *playerImpl) Err() error {
|
|
||||||
p.m.Lock()
|
|
||||||
defer p.m.Unlock()
|
|
||||||
|
|
||||||
return p.err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Player) Play() {
|
|
||||||
p.p.Play()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *playerImpl) Play() {
|
|
||||||
ch := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
p.m.Lock()
|
|
||||||
defer p.m.Unlock()
|
|
||||||
|
|
||||||
close(ch)
|
|
||||||
p.playImpl()
|
|
||||||
}()
|
|
||||||
<-ch
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Player) SetBufferSize(bufferSize int) {
|
|
||||||
p.p.setBufferSize(bufferSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *playerImpl) setBufferSize(bufferSize int) {
|
|
||||||
p.m.Lock()
|
|
||||||
defer p.m.Unlock()
|
|
||||||
|
|
||||||
orig := p.bufferSize
|
|
||||||
p.bufferSize = bufferSize
|
|
||||||
if bufferSize == 0 {
|
|
||||||
p.bufferSize = p.context.defaultBufferSize()
|
|
||||||
}
|
|
||||||
if orig != p.bufferSize {
|
|
||||||
p.tmpbuf = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *playerImpl) ensureTmpBuf() []byte {
|
|
||||||
if p.tmpbuf == nil {
|
|
||||||
p.tmpbuf = make([]byte, p.bufferSize)
|
|
||||||
}
|
|
||||||
return p.tmpbuf
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *playerImpl) playImpl() {
|
|
||||||
if p.err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if p.state != playerPaused {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !p.eof {
|
|
||||||
buf := p.ensureTmpBuf()
|
|
||||||
for len(p.buf) < p.bufferSize {
|
|
||||||
n, err := p.src.Read(buf)
|
|
||||||
if err != nil && err != io.EOF {
|
|
||||||
p.setErrorImpl(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p.buf = append(p.buf, buf[:n]...)
|
|
||||||
if err == io.EOF {
|
|
||||||
p.eof = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !p.eof || len(p.buf) > 0 {
|
|
||||||
p.state = playerPlay
|
|
||||||
}
|
|
||||||
|
|
||||||
p.m.Unlock()
|
|
||||||
p.context.players.addPlayer(p)
|
|
||||||
p.m.Lock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Player) Pause() {
|
|
||||||
p.p.Pause()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *playerImpl) Pause() {
|
|
||||||
p.m.Lock()
|
|
||||||
defer p.m.Unlock()
|
|
||||||
|
|
||||||
if p.state != playerPlay {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p.state = playerPaused
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Player) Seek(offset int64, whence int) (int64, error) {
|
|
||||||
return p.p.Seek(offset, whence)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *playerImpl) Seek(offset int64, whence int) (int64, error) {
|
|
||||||
p.m.Lock()
|
|
||||||
defer p.m.Unlock()
|
|
||||||
|
|
||||||
// If a player is playing, keep playing even after this seeking.
|
|
||||||
if p.state == playerPlay {
|
|
||||||
defer p.playImpl()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset the internal buffer.
|
|
||||||
p.resetImpl()
|
|
||||||
|
|
||||||
// Check if the source implements io.Seeker.
|
|
||||||
s, ok := p.src.(io.Seeker)
|
|
||||||
if !ok {
|
|
||||||
return 0, errors.New("cbackend: the source must implement io.Seeker")
|
|
||||||
}
|
|
||||||
return s.Seek(offset, whence)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *playerImpl) resetImpl() {
|
|
||||||
if p.state == playerClosed {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p.state = playerPaused
|
|
||||||
p.buf = p.buf[:0]
|
|
||||||
p.eof = false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Player) IsPlaying() bool {
|
|
||||||
return p.p.IsPlaying()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *playerImpl) IsPlaying() bool {
|
|
||||||
p.m.Lock()
|
|
||||||
defer p.m.Unlock()
|
|
||||||
return p.state == playerPlay
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Player) Volume() float64 {
|
|
||||||
return p.p.Volume()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *playerImpl) Volume() float64 {
|
|
||||||
p.m.Lock()
|
|
||||||
defer p.m.Unlock()
|
|
||||||
return p.volume
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Player) SetVolume(volume float64) {
|
|
||||||
p.p.SetVolume(volume)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *playerImpl) SetVolume(volume float64) {
|
|
||||||
p.m.Lock()
|
|
||||||
defer p.m.Unlock()
|
|
||||||
p.volume = volume
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Player) UnplayedBufferSize() int {
|
|
||||||
return p.p.UnplayedBufferSize()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *playerImpl) UnplayedBufferSize() int {
|
|
||||||
p.m.Lock()
|
|
||||||
defer p.m.Unlock()
|
|
||||||
return len(p.buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Player) Close() error {
|
|
||||||
runtime.SetFinalizer(p, nil)
|
|
||||||
return p.p.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *playerImpl) Close() error {
|
|
||||||
p.m.Lock()
|
|
||||||
defer p.m.Unlock()
|
|
||||||
return p.closeImpl()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *playerImpl) closeImpl() error {
|
|
||||||
p.m.Unlock()
|
|
||||||
p.context.players.removePlayer(p)
|
|
||||||
p.m.Lock()
|
|
||||||
|
|
||||||
if p.state == playerClosed {
|
|
||||||
return p.err
|
|
||||||
}
|
|
||||||
p.state = playerClosed
|
|
||||||
p.buf = nil
|
|
||||||
return p.err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *playerImpl) readBufferAndAdd(buf []float32) int {
|
|
||||||
p.m.Lock()
|
|
||||||
defer p.m.Unlock()
|
|
||||||
|
|
||||||
if p.state != playerPlay {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
bitDepthInBytes := p.context.bitDepthInBytes
|
|
||||||
n := len(p.buf) / bitDepthInBytes
|
|
||||||
if n > len(buf) {
|
|
||||||
n = len(buf)
|
|
||||||
}
|
|
||||||
volume := float32(p.volume)
|
|
||||||
src := p.buf[:n*bitDepthInBytes]
|
|
||||||
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
var v float32
|
|
||||||
switch bitDepthInBytes {
|
|
||||||
case 1:
|
|
||||||
v8 := src[i]
|
|
||||||
v = float32(v8-(1<<7)) / (1 << 7)
|
|
||||||
case 2:
|
|
||||||
v16 := int16(src[2*i]) | (int16(src[2*i+1]) << 8)
|
|
||||||
v = float32(v16) / (1 << 15)
|
|
||||||
}
|
|
||||||
buf[i] += v * volume
|
|
||||||
}
|
|
||||||
|
|
||||||
copy(p.buf, p.buf[n*bitDepthInBytes:])
|
|
||||||
p.buf = p.buf[:len(p.buf)-n*bitDepthInBytes]
|
|
||||||
|
|
||||||
if p.eof && len(p.buf) == 0 {
|
|
||||||
p.state = playerPaused
|
|
||||||
}
|
|
||||||
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *playerImpl) canReadSourceToBuffer() bool {
|
|
||||||
p.m.Lock()
|
|
||||||
defer p.m.Unlock()
|
|
||||||
|
|
||||||
if p.eof {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return len(p.buf) < p.bufferSize
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *playerImpl) readSourceToBuffer() {
|
|
||||||
p.m.Lock()
|
|
||||||
defer p.m.Unlock()
|
|
||||||
|
|
||||||
if p.err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if p.state == playerClosed {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(p.buf) >= p.bufferSize {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := p.ensureTmpBuf()
|
|
||||||
n, err := p.src.Read(buf)
|
|
||||||
|
|
||||||
if err != nil && err != io.EOF {
|
|
||||||
p.setErrorImpl(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
p.buf = append(p.buf, buf[:n]...)
|
|
||||||
if err == io.EOF {
|
|
||||||
p.eof = true
|
|
||||||
if len(p.buf) == 0 {
|
|
||||||
p.state = playerPaused
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *playerImpl) setErrorImpl(err error) {
|
|
||||||
p.err = err
|
|
||||||
p.closeImpl()
|
|
||||||
}
|
|
2
go.mod
2
go.mod
@ -8,7 +8,7 @@ require (
|
|||||||
github.com/hajimehoshi/bitmapfont/v2 v2.2.1
|
github.com/hajimehoshi/bitmapfont/v2 v2.2.1
|
||||||
github.com/hajimehoshi/file2byteslice v0.0.0-20210813153925-5340248a8f41
|
github.com/hajimehoshi/file2byteslice v0.0.0-20210813153925-5340248a8f41
|
||||||
github.com/hajimehoshi/go-mp3 v0.3.3
|
github.com/hajimehoshi/go-mp3 v0.3.3
|
||||||
github.com/hajimehoshi/oto/v2 v2.3.0-alpha.6.0.20220802151442-4f655ffe7bb4
|
github.com/hajimehoshi/oto/v2 v2.3.0-alpha.6.0.20220808142854-0d70fdd8205d
|
||||||
github.com/jakecoffman/cp v1.2.1
|
github.com/jakecoffman/cp v1.2.1
|
||||||
github.com/jezek/xgb v1.0.1
|
github.com/jezek/xgb v1.0.1
|
||||||
github.com/jfreymuth/oggvorbis v1.0.3
|
github.com/jfreymuth/oggvorbis v1.0.3
|
||||||
|
4
go.sum
4
go.sum
@ -11,8 +11,8 @@ github.com/hajimehoshi/go-mp3 v0.3.3 h1:cWnfRdpye2m9ElSoVqneYRcpt/l3ijttgjMeQh+r
|
|||||||
github.com/hajimehoshi/go-mp3 v0.3.3/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM=
|
github.com/hajimehoshi/go-mp3 v0.3.3/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM=
|
||||||
github.com/hajimehoshi/oto v0.6.1 h1:7cJz/zRQV4aJvMSSRqzN2TImoVVMpE0BCY4nrNJaDOM=
|
github.com/hajimehoshi/oto v0.6.1 h1:7cJz/zRQV4aJvMSSRqzN2TImoVVMpE0BCY4nrNJaDOM=
|
||||||
github.com/hajimehoshi/oto v0.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI=
|
github.com/hajimehoshi/oto v0.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI=
|
||||||
github.com/hajimehoshi/oto/v2 v2.3.0-alpha.6.0.20220802151442-4f655ffe7bb4 h1:tykbQMVvjFgSvX5ndg05alWI0mfD7rm/DtysW9Z757U=
|
github.com/hajimehoshi/oto/v2 v2.3.0-alpha.6.0.20220808142854-0d70fdd8205d h1:T64feTPB+qGhPnLRsyzlYNiz+jTd/pcPCqB8/VTgooY=
|
||||||
github.com/hajimehoshi/oto/v2 v2.3.0-alpha.6.0.20220802151442-4f655ffe7bb4/go.mod h1:ZH5KyijkM53TgspdS9sArUe7vv9EP7x4aNYJAmfGhvE=
|
github.com/hajimehoshi/oto/v2 v2.3.0-alpha.6.0.20220808142854-0d70fdd8205d/go.mod h1:ZH5KyijkM53TgspdS9sArUe7vv9EP7x4aNYJAmfGhvE=
|
||||||
github.com/jakecoffman/cp v1.2.1 h1:zkhc2Gpo9l4NLUZfeG3j33+3bQD7MkqPa+n5PdX+5mI=
|
github.com/jakecoffman/cp v1.2.1 h1:zkhc2Gpo9l4NLUZfeG3j33+3bQD7MkqPa+n5PdX+5mI=
|
||||||
github.com/jakecoffman/cp v1.2.1/go.mod h1:JjY/Fp6d8E1CHnu74gWNnU0+b9VzEdUVPoJxg2PsTQg=
|
github.com/jakecoffman/cp v1.2.1/go.mod h1:JjY/Fp6d8E1CHnu74gWNnU0+b9VzEdUVPoJxg2PsTQg=
|
||||||
github.com/jezek/xgb v1.0.1 h1:YUGhxps0aR7J2Xplbs23OHnV1mWaxFVcOl9b+1RQkt8=
|
github.com/jezek/xgb v1.0.1 h1:YUGhxps0aR7J2Xplbs23OHnV1mWaxFVcOl9b+1RQkt8=
|
||||||
|
Loading…
Reference in New Issue
Block a user