mirror of
https://github.com/make-42/hayai.git
synced 2025-01-18 18:47:10 +01:00
145 lines
3.5 KiB
Go
145 lines
3.5 KiB
Go
// Copyright 2015 Hajime Hoshi
|
|
//
|
|
// 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.
|
|
|
|
// +build !js
|
|
|
|
package oto
|
|
|
|
import (
|
|
"errors"
|
|
"runtime"
|
|
"unsafe"
|
|
)
|
|
|
|
type header struct {
|
|
buffer []byte
|
|
waveHdr *wavehdr
|
|
}
|
|
|
|
func newHeader(waveOut uintptr, bufferSize int) (*header, error) {
|
|
h := &header{
|
|
buffer: make([]byte, bufferSize),
|
|
}
|
|
h.waveHdr = &wavehdr{
|
|
lpData: uintptr(unsafe.Pointer(&h.buffer[0])),
|
|
dwBufferLength: uint32(bufferSize),
|
|
}
|
|
if err := waveOutPrepareHeader(waveOut, h.waveHdr); err != nil {
|
|
return nil, err
|
|
}
|
|
return h, nil
|
|
}
|
|
|
|
func (h *header) Write(waveOut uintptr, data []byte) error {
|
|
if len(data) != len(h.buffer) {
|
|
return errors.New("oto: len(data) must equal to len(h.buffer)")
|
|
}
|
|
copy(h.buffer, data)
|
|
if err := waveOutWrite(waveOut, h.waveHdr); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type driver struct {
|
|
out uintptr
|
|
headers []*header
|
|
tmp []byte
|
|
bufferSize int
|
|
}
|
|
|
|
func newDriver(sampleRate, channelNum, bitDepthInBytes, bufferSizeInBytes int) (tryWriteCloser, error) {
|
|
numBlockAlign := channelNum * bitDepthInBytes
|
|
f := &waveformatex{
|
|
wFormatTag: waveFormatPCM,
|
|
nChannels: uint16(channelNum),
|
|
nSamplesPerSec: uint32(sampleRate),
|
|
nAvgBytesPerSec: uint32(sampleRate * numBlockAlign),
|
|
wBitsPerSample: uint16(bitDepthInBytes * 8),
|
|
nBlockAlign: uint16(numBlockAlign),
|
|
}
|
|
w, err := waveOutOpen(f)
|
|
const elementNotFound = 1168
|
|
if e, ok := err.(*winmmError); ok && e.errno == elementNotFound {
|
|
// No device was found. Return the dummy device.
|
|
// TODO: Retry to open the device when possible.
|
|
return newDummyDriver(sampleRate, channelNum, bitDepthInBytes), nil
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
const numBufs = 2
|
|
p := &driver{
|
|
out: w,
|
|
headers: make([]*header, numBufs),
|
|
bufferSize: bufferSizeInBytes,
|
|
}
|
|
runtime.SetFinalizer(p, (*driver).Close)
|
|
for i := range p.headers {
|
|
var err error
|
|
p.headers[i], err = newHeader(w, p.bufferSize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return p, nil
|
|
}
|
|
|
|
func (p *driver) TryWrite(data []byte) (int, error) {
|
|
n := min(len(data), max(0, p.bufferSize-len(p.tmp)))
|
|
p.tmp = append(p.tmp, data[:n]...)
|
|
if len(p.tmp) < p.bufferSize {
|
|
return n, nil
|
|
}
|
|
|
|
var headerToWrite *header
|
|
for _, h := range p.headers {
|
|
// TODO: Need to check WHDR_DONE?
|
|
if h.waveHdr.dwFlags&whdrInqueue == 0 {
|
|
headerToWrite = h
|
|
break
|
|
}
|
|
}
|
|
if headerToWrite == nil {
|
|
return n, nil
|
|
}
|
|
|
|
if err := headerToWrite.Write(p.out, p.tmp); err != nil {
|
|
// This error can happen when e.g. a new HDMI connection is detected (#51).
|
|
const errorNotFound = 1168
|
|
werr := err.(*winmmError)
|
|
if werr.fname == "waveOutWrite" && werr.errno == errorNotFound {
|
|
return 0, nil
|
|
}
|
|
return 0, err
|
|
}
|
|
|
|
p.tmp = p.tmp[:0]
|
|
return n, nil
|
|
}
|
|
|
|
func (p *driver) Close() error {
|
|
runtime.SetFinalizer(p, nil)
|
|
// TODO: Call waveOutUnprepareHeader here
|
|
if err := waveOutClose(p.out); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (d *driver) tryWriteCanReturnWithoutWaiting() bool {
|
|
return true
|
|
}
|