// 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 }