mirror of
https://github.com/make-42/xyosc
synced 2025-01-18 18:57:10 +01:00
189 lines
5.4 KiB
Go
189 lines
5.4 KiB
Go
package malgo
|
|
|
|
// #include "malgo.h"
|
|
import "C"
|
|
import (
|
|
"sync"
|
|
"unsafe"
|
|
)
|
|
|
|
// DataProc type.
|
|
type DataProc func(pOutputSample, pInputSamples []byte, framecount uint32)
|
|
|
|
// StopProc type.
|
|
type StopProc func()
|
|
|
|
// DeviceCallbacks contains callbacks for one initialized device.
|
|
type DeviceCallbacks struct {
|
|
// Data is called for the full duplex IO.
|
|
Data DataProc
|
|
// Stop is called when the device stopped.
|
|
Stop StopProc
|
|
}
|
|
|
|
// Device represents a streaming instance.
|
|
type Device struct {
|
|
ptr *unsafe.Pointer
|
|
}
|
|
|
|
// InitDevice initializes a device.
|
|
//
|
|
// The device ID can be nil, in which case the default device is used. Otherwise, you
|
|
// can retrieve the ID by calling Context.Devices() and use the ID from the returned data.
|
|
//
|
|
// Set device ID to nil to use the default device. Do _not_ rely on the first device ID returned
|
|
// by Context.Devices() to be the default device.
|
|
//
|
|
// The returned instance has to be cleaned up using Uninit().
|
|
func InitDevice(context Context, deviceConfig DeviceConfig, deviceCallbacks DeviceCallbacks) (*Device, error) {
|
|
ptr := C.ma_malloc(C.sizeof_ma_device, nil)
|
|
dev := Device{
|
|
ptr: &ptr,
|
|
}
|
|
if uintptr(*dev.ptr) == 0 {
|
|
return nil, ErrOutOfMemory
|
|
}
|
|
devConfigC, release := deviceConfig.toC()
|
|
defer release()
|
|
|
|
rawDevice := dev.cptr()
|
|
C.goSetDeviceConfigCallbacks(&devConfigC)
|
|
result := C.ma_device_init(context.cptr(), &devConfigC, rawDevice)
|
|
if result != 0 {
|
|
dev.free()
|
|
return nil, errorFromResult(result)
|
|
}
|
|
deviceMutex.Lock()
|
|
dataCallbacks[rawDevice] = deviceCallbacks.Data
|
|
stopCallbacks[rawDevice] = deviceCallbacks.Stop
|
|
deviceMutex.Unlock()
|
|
|
|
return &dev, nil
|
|
}
|
|
|
|
func (dev Device) cptr() *C.ma_device {
|
|
return (*C.ma_device)(*dev.ptr)
|
|
}
|
|
|
|
func (dev Device) free() {
|
|
if dev.ptr != nil {
|
|
C.ma_free(*dev.ptr, nil)
|
|
}
|
|
}
|
|
|
|
// Type returns device type.
|
|
func (dev *Device) Type() DeviceType {
|
|
return DeviceType(dev.cptr()._type)
|
|
}
|
|
|
|
// PlaybackFormat returns device playback format.
|
|
func (dev *Device) PlaybackFormat() FormatType {
|
|
return FormatType(dev.cptr().playback.format)
|
|
}
|
|
|
|
// CaptureFormat returns device capture format.
|
|
func (dev *Device) CaptureFormat() FormatType {
|
|
return FormatType(dev.cptr().capture.format)
|
|
}
|
|
|
|
// PlaybackChannels returns number of playback channels.
|
|
func (dev *Device) PlaybackChannels() uint32 {
|
|
return uint32(dev.cptr().playback.channels)
|
|
}
|
|
|
|
// CaptureChannels returns number of playback channels.
|
|
func (dev *Device) CaptureChannels() uint32 {
|
|
return uint32(dev.cptr().capture.channels)
|
|
}
|
|
|
|
// SampleRate returns sample rate.
|
|
func (dev *Device) SampleRate() uint32 {
|
|
return uint32(dev.cptr().sampleRate)
|
|
}
|
|
|
|
// Start activates the device.
|
|
// For playback devices this begins playback. For capture devices it begins recording.
|
|
//
|
|
// For a playback device, this will retrieve an initial chunk of audio data from the client before
|
|
// returning. The reason for this is to ensure there is valid audio data in the buffer, which needs
|
|
// to be done _before_ the device begins playback.
|
|
//
|
|
// This API waits until the backend device has been started for real by the worker thread. It also
|
|
// waits on a mutex for thread-safety.
|
|
func (dev *Device) Start() error {
|
|
result := C.ma_device_start(dev.cptr())
|
|
return errorFromResult(result)
|
|
}
|
|
|
|
// IsStarted determines whether or not the device is started.
|
|
func (dev *Device) IsStarted() bool {
|
|
result := C.ma_device_is_started(dev.cptr())
|
|
return result != 0
|
|
}
|
|
|
|
// Stop puts the device to sleep, but does not uninitialize it. Use Start() to start it up again.
|
|
//
|
|
// This API needs to wait on the worker thread to stop the backend device properly before returning. It
|
|
// also waits on a mutex for thread-safety. In addition, some backends need to wait for the device to
|
|
// finish playback/recording of the current fragment which can take some time (usually proportionate to
|
|
// the buffer size that was specified at initialization time).
|
|
func (dev *Device) Stop() error {
|
|
result := C.ma_device_stop(dev.cptr())
|
|
return errorFromResult(result)
|
|
}
|
|
|
|
// Uninit uninitializes a device.
|
|
//
|
|
// This will explicitly stop the device. You do not need to call Stop() beforehand, but it's
|
|
// harmless if you do.
|
|
func (dev *Device) Uninit() {
|
|
rawDevice := dev.cptr()
|
|
deviceMutex.Lock()
|
|
delete(dataCallbacks, rawDevice)
|
|
delete(stopCallbacks, rawDevice)
|
|
deviceMutex.Unlock()
|
|
|
|
C.ma_device_uninit(rawDevice)
|
|
dev.free()
|
|
}
|
|
|
|
var deviceMutex sync.Mutex
|
|
var dataCallbacks = make(map[*C.ma_device]DataProc)
|
|
var stopCallbacks = make(map[*C.ma_device]StopProc)
|
|
|
|
//export goDataCallback
|
|
func goDataCallback(pDevice *C.ma_device, pOutput, pInput unsafe.Pointer, frameCount C.ma_uint32) {
|
|
deviceMutex.Lock()
|
|
callback := dataCallbacks[pDevice]
|
|
deviceMutex.Unlock()
|
|
|
|
if callback != nil {
|
|
var inputSamples, outputSamples []byte
|
|
|
|
if pOutput != nil {
|
|
sampleCount := uint32(frameCount) * uint32(pDevice.playback.channels)
|
|
sizeInBytes := uint32(C.ma_get_bytes_per_sample(pDevice.playback.format))
|
|
outputSamples = unsafe.Slice((*byte)(pOutput), sampleCount*sizeInBytes)
|
|
}
|
|
|
|
if pInput != nil {
|
|
sampleCount := uint32(frameCount) * uint32(pDevice.capture.channels)
|
|
sizeInBytes := uint32(C.ma_get_bytes_per_sample(pDevice.capture.format))
|
|
inputSamples = unsafe.Slice((*byte)(pInput), sampleCount*sizeInBytes)
|
|
}
|
|
|
|
callback(outputSamples, inputSamples, uint32(frameCount))
|
|
}
|
|
}
|
|
|
|
//export goStopCallback
|
|
func goStopCallback(pDevice *C.ma_device) {
|
|
deviceMutex.Lock()
|
|
callback := stopCallbacks[pDevice]
|
|
deviceMutex.Unlock()
|
|
|
|
if callback != nil {
|
|
callback()
|
|
}
|
|
}
|