2021-05-14 19:33:45 +02:00
|
|
|
// 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.
|
|
|
|
|
|
|
|
package readerdriver
|
|
|
|
|
|
|
|
import (
|
2021-07-21 15:44:43 +02:00
|
|
|
"fmt"
|
2021-05-14 19:33:45 +02:00
|
|
|
"unsafe"
|
|
|
|
|
|
|
|
"golang.org/x/sys/windows"
|
|
|
|
)
|
|
|
|
|
2021-08-18 18:44:04 +02:00
|
|
|
// Avoid goroutines on Windows (#1768).
|
|
|
|
// Apparently, switching contexts might take longer than other platforms.
|
|
|
|
|
|
|
|
const headerBufferSize = 4096
|
2021-05-14 19:33:45 +02:00
|
|
|
|
|
|
|
type header struct {
|
|
|
|
waveOut uintptr
|
2021-07-21 15:44:43 +02:00
|
|
|
buffer []float32
|
2021-05-14 19:33:45 +02:00
|
|
|
waveHdr *wavehdr
|
|
|
|
}
|
|
|
|
|
2021-07-21 15:44:43 +02:00
|
|
|
func newHeader(waveOut uintptr, bufferSizeInBytes int) (*header, error) {
|
2021-05-14 19:33:45 +02:00
|
|
|
h := &header{
|
|
|
|
waveOut: waveOut,
|
2021-07-21 15:44:43 +02:00
|
|
|
buffer: make([]float32, bufferSizeInBytes/4),
|
2021-05-14 19:33:45 +02:00
|
|
|
}
|
|
|
|
h.waveHdr = &wavehdr{
|
|
|
|
lpData: uintptr(unsafe.Pointer(&h.buffer[0])),
|
2021-07-21 15:44:43 +02:00
|
|
|
dwBufferLength: uint32(bufferSizeInBytes),
|
2021-05-14 19:33:45 +02:00
|
|
|
}
|
|
|
|
if err := waveOutPrepareHeader(waveOut, h.waveHdr); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return h, nil
|
|
|
|
}
|
|
|
|
|
2021-07-21 15:44:43 +02:00
|
|
|
func (h *header) Write(data []float32) error {
|
2021-05-14 19:33:45 +02:00
|
|
|
copy(h.buffer, data)
|
2021-05-28 15:27:18 +02:00
|
|
|
if err := waveOutWrite(h.waveOut, h.waveHdr); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
2021-05-14 19:33:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (h *header) IsQueued() bool {
|
|
|
|
return h.waveHdr.dwFlags&whdrInqueue != 0
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *header) Close() error {
|
|
|
|
return waveOutUnprepareHeader(h.waveOut, h.waveHdr)
|
|
|
|
}
|
|
|
|
|
|
|
|
type context struct {
|
|
|
|
sampleRate int
|
|
|
|
channelNum int
|
|
|
|
bitDepthInBytes int
|
2021-07-21 15:44:43 +02:00
|
|
|
|
|
|
|
waveOut uintptr
|
|
|
|
headers []*header
|
|
|
|
|
2021-08-18 18:44:04 +02:00
|
|
|
buf32 []float32
|
2021-07-21 15:44:43 +02:00
|
|
|
|
|
|
|
players *players
|
2021-05-14 19:33:45 +02:00
|
|
|
}
|
|
|
|
|
2021-07-21 15:44:43 +02:00
|
|
|
var theContext *context
|
|
|
|
|
2021-05-14 19:33:45 +02:00
|
|
|
func NewContext(sampleRate, channelNum, bitDepthInBytes int) (Context, chan struct{}, error) {
|
|
|
|
ready := make(chan struct{})
|
|
|
|
close(ready)
|
|
|
|
|
|
|
|
c := &context{
|
|
|
|
sampleRate: sampleRate,
|
|
|
|
channelNum: channelNum,
|
|
|
|
bitDepthInBytes: bitDepthInBytes,
|
2021-07-21 15:44:43 +02:00
|
|
|
players: newPlayers(),
|
|
|
|
}
|
|
|
|
theContext = c
|
|
|
|
|
|
|
|
const bitsPerSample = 32
|
|
|
|
nBlockAlign := c.channelNum * bitsPerSample / 8
|
|
|
|
f := &waveformatex{
|
|
|
|
wFormatTag: waveFormatIEEEFloat,
|
|
|
|
nChannels: uint16(c.channelNum),
|
|
|
|
nSamplesPerSec: uint32(c.sampleRate),
|
|
|
|
nAvgBytesPerSec: uint32(c.sampleRate * nBlockAlign),
|
|
|
|
wBitsPerSample: bitsPerSample,
|
|
|
|
nBlockAlign: uint16(nBlockAlign),
|
|
|
|
}
|
|
|
|
|
|
|
|
// TOOD: What about using an event instead of a callback? PortAudio and other libraries do that.
|
|
|
|
w, err := waveOutOpen(f, waveOutOpenCallback)
|
|
|
|
const elementNotFound = 1168
|
|
|
|
if e, ok := err.(*winmmError); ok && e.errno == elementNotFound {
|
|
|
|
// TODO: No device was found. Return the dummy device (hajimehoshi/oto#77).
|
|
|
|
// TODO: Retry to open the device when possible.
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
c.waveOut = w
|
2021-08-18 18:44:04 +02:00
|
|
|
c.headers = make([]*header, 0, 6)
|
2021-07-21 15:44:43 +02:00
|
|
|
for len(c.headers) < cap(c.headers) {
|
|
|
|
h, err := newHeader(c.waveOut, headerBufferSize)
|
2021-05-28 15:27:18 +02:00
|
|
|
if err != nil {
|
2021-07-21 15:44:43 +02:00
|
|
|
return nil, nil, err
|
2021-05-28 15:27:18 +02:00
|
|
|
}
|
2021-07-21 15:44:43 +02:00
|
|
|
c.headers = append(c.headers, h)
|
2021-05-28 15:27:18 +02:00
|
|
|
}
|
|
|
|
|
2021-08-18 18:44:04 +02:00
|
|
|
c.buf32 = make([]float32, headerBufferSize/4)
|
|
|
|
for range c.headers {
|
|
|
|
c.appendBuffers()
|
|
|
|
}
|
2021-05-14 19:33:45 +02:00
|
|
|
|
2021-07-21 15:44:43 +02:00
|
|
|
return c, ready, nil
|
2021-05-14 19:33:45 +02:00
|
|
|
}
|
|
|
|
|
2021-07-21 15:44:43 +02:00
|
|
|
func (c *context) Suspend() error {
|
|
|
|
if err := waveOutPause(c.waveOut); err != nil {
|
|
|
|
return err
|
2021-05-28 15:27:18 +02:00
|
|
|
}
|
2021-07-21 15:44:43 +02:00
|
|
|
return nil
|
2021-05-14 19:33:45 +02:00
|
|
|
}
|
|
|
|
|
2021-07-21 15:44:43 +02:00
|
|
|
func (c *context) Resume() error {
|
|
|
|
// TODO: Ensure at least one header is queued?
|
2021-05-28 15:27:18 +02:00
|
|
|
|
2021-07-21 15:44:43 +02:00
|
|
|
if err := waveOutRestart(c.waveOut); err != nil {
|
|
|
|
return err
|
2021-05-28 15:27:18 +02:00
|
|
|
}
|
2021-07-21 15:44:43 +02:00
|
|
|
return nil
|
2021-05-14 19:33:45 +02:00
|
|
|
}
|
|
|
|
|
2021-07-21 15:44:43 +02:00
|
|
|
func (c *context) isHeaderAvailable() bool {
|
|
|
|
for _, h := range c.headers {
|
|
|
|
if !h.IsQueued() {
|
|
|
|
return true
|
2021-05-28 15:27:18 +02:00
|
|
|
}
|
2021-05-14 19:33:45 +02:00
|
|
|
}
|
2021-07-21 15:44:43 +02:00
|
|
|
return false
|
2021-05-14 19:33:45 +02:00
|
|
|
}
|
|
|
|
|
2021-05-28 15:27:18 +02:00
|
|
|
var waveOutOpenCallback = windows.NewCallbackCDecl(func(hwo, uMsg, dwInstance, dwParam1, dwParam2 uintptr) uintptr {
|
|
|
|
const womDone = 0x3bd
|
|
|
|
if uMsg != womDone {
|
|
|
|
return 0
|
|
|
|
}
|
2021-08-18 18:44:04 +02:00
|
|
|
theContext.appendBuffers()
|
2021-05-28 15:27:18 +02:00
|
|
|
return 0
|
|
|
|
})
|
2021-05-14 19:33:45 +02:00
|
|
|
|
2021-08-18 18:44:04 +02:00
|
|
|
func (c *context) appendBuffers() {
|
|
|
|
for i := range c.buf32 {
|
|
|
|
c.buf32[i] = 0
|
2021-05-14 19:33:45 +02:00
|
|
|
}
|
2021-08-18 18:44:04 +02:00
|
|
|
c.players.read(c.buf32)
|
2021-05-14 19:33:45 +02:00
|
|
|
|
2021-07-21 15:44:43 +02:00
|
|
|
for _, h := range c.headers {
|
2021-05-28 20:43:05 +02:00
|
|
|
if h.IsQueued() {
|
|
|
|
continue
|
|
|
|
}
|
2021-05-28 15:27:18 +02:00
|
|
|
|
2021-08-18 18:44:04 +02:00
|
|
|
if err := h.Write(c.buf32); err != nil {
|
2021-05-28 20:43:05 +02:00
|
|
|
// This error can happen when e.g. a new HDMI connection is detected (hajimehoshi/oto#51).
|
|
|
|
const errorNotFound = 1168
|
|
|
|
if werr := err.(*winmmError); werr.fname == "waveOutWrite" {
|
|
|
|
switch {
|
|
|
|
case werr.mmresult == mmsyserrNomem:
|
|
|
|
continue
|
|
|
|
case werr.errno == errorNotFound:
|
|
|
|
// TODO: Retry later.
|
2021-05-28 15:27:18 +02:00
|
|
|
}
|
|
|
|
}
|
2021-07-21 15:44:43 +02:00
|
|
|
// TODO: Treat the error corretly
|
|
|
|
panic(fmt.Errorf("readerdriver: Queueing the header failed: %v", err))
|
2021-05-28 15:27:18 +02:00
|
|
|
}
|
|
|
|
}
|
2021-05-14 19:33:45 +02:00
|
|
|
}
|