From e0bed88b7ce18d33c1402d6e13e3a60807a176cc Mon Sep 17 00:00:00 2001 From: Sam Hocevar Date: Mon, 19 Sep 2022 15:35:00 +0200 Subject: [PATCH 1/2] Add an audio output mode for the Kinc library --- driver_darwin.go | 4 +- driver_kinc.go | 97 +++++++++++++++++++++++++++++++++++++++++++++++ driver_linux.go | 4 +- driver_macos.go | 4 +- driver_windows.go | 4 +- winmm_windows.go | 4 +- 6 files changed, 107 insertions(+), 10 deletions(-) create mode 100644 driver_kinc.go diff --git a/driver_darwin.go b/driver_darwin.go index 8294125..949c436 100644 --- a/driver_darwin.go +++ b/driver_darwin.go @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build !js -// +build !js +//go:build !js && !kinc +// +build !js && !kinc package oto diff --git a/driver_kinc.go b/driver_kinc.go new file mode 100644 index 0000000..559eb73 --- /dev/null +++ b/driver_kinc.go @@ -0,0 +1,97 @@ +// Copyright 2022 Sam Hocevar +// +// 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 kinc +// +build kinc + +package oto + +/* +#include + +typedef void (*audio_callback_t)(kinc_a2_buffer_t *buffer, int samples); +typedef void (*sample_rate_callback_t)(void); + +void audio_callback(kinc_a2_buffer_t *buffer, int samples); +void sample_rate_callback(void); +*/ +import "C" + +import ( + "unsafe" +) + +const ( + BufferLength = 4096 // Max number of samples in our audio buffer +) + +type driver struct { + buf []byte // Holds int16 data +} + +var gDriver *driver + +//export audio_callback +func audio_callback(buffer *C.kinc_a2_buffer_t, samples int32) { + // TODO: buffer.write_location and buffer.read_location may give us a good + // hint about the buffer status and decide whether we need to hurry up to + // avoid stuttering. + canRead := len(gDriver.buf) / 2 + toWrite := min(canRead, int(samples)) + for i := 0; i < toWrite; i++ { + + sample := *(*int16)(unsafe.Pointer(uintptr(unsafe.Pointer(&gDriver.buf[0])) + uintptr(i * 2))) + *(*float32)(unsafe.Pointer(uintptr(unsafe.Pointer(buffer.data)) + uintptr(buffer.write_location))) = float32(sample) / 32768. + + buffer.write_location += 4 + if buffer.write_location >= buffer.data_size { + buffer.write_location = 0 + } + } + gDriver.buf = gDriver.buf[toWrite * 2:] +} + +//export sample_rate_callback +func sample_rate_callback() { + // TODO: handle rate changes? +} + +func newDriver(sampleRate, numChans, bitDepthInBytes, bufferSizeInBytes int) (tryWriteCloser, error) { + p := &driver{ + []byte{}, + } + + C.kinc_a2_init() + C.kinc_a2_set_callback((C.audio_callback_t)(C.audio_callback)) + gDriver = p + + return p, nil +} + +func (p *driver) TryWrite(data []byte) (n int, err error) { + toAppend := min(len(data), max(0, BufferLength - len(p.buf))) + p.buf = append(p.buf, data[:toAppend]...) + return toAppend, nil +} + +func (p *driver) Close() error { + C.kinc_a2_shutdown() + gDriver = nil + + return nil +} + +func (d *driver) tryWriteCanReturnWithoutWaiting() bool { + return true +} diff --git a/driver_linux.go b/driver_linux.go index 50951d6..7fd6ec9 100644 --- a/driver_linux.go +++ b/driver_linux.go @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build !js && !android && !ios -// +build !js,!android,!ios +//go:build !js && !android && !ios && !kinc +// +build !js,!android,!ios,!kinc package oto diff --git a/driver_macos.go b/driver_macos.go index 5a2ab84..26ed0b6 100644 --- a/driver_macos.go +++ b/driver_macos.go @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build darwin && !ios && !js -// +build darwin,!ios,!js +//go:build darwin && !ios && !js && !kinc +// +build darwin,!ios,!js,!kinc package oto diff --git a/driver_windows.go b/driver_windows.go index 8f295e0..626b9ca 100644 --- a/driver_windows.go +++ b/driver_windows.go @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build !js -// +build !js +//go:build !js && !kinc +// +build !js,!kinc package oto diff --git a/winmm_windows.go b/winmm_windows.go index 73aae06..04fc8fd 100644 --- a/winmm_windows.go +++ b/winmm_windows.go @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build !js -// +build !js +//go:build !js && !kinc +// +build !js && !kinc package oto From 682ec29aec8fec30e5d5e1695dbe16fc9dccaaee Mon Sep 17 00:00:00 2001 From: Sam Hocevar Date: Sat, 1 Oct 2022 09:42:51 +0200 Subject: [PATCH 2/2] Ensure latency stays low in the Kinc driver --- driver_kinc.go | 52 +++++++++++++++++++++++++++++++++++--------------- go.mod | 2 +- 2 files changed, 38 insertions(+), 16 deletions(-) diff --git a/driver_kinc.go b/driver_kinc.go index 559eb73..394f1d3 100644 --- a/driver_kinc.go +++ b/driver_kinc.go @@ -29,37 +29,55 @@ void sample_rate_callback(void); import "C" import ( + "sync" "unsafe" ) const ( - BufferLength = 4096 // Max number of samples in our audio buffer + MaxBufferLength = 4096 // Max number of samples we want in the audio queue ) type driver struct { - buf []byte // Holds int16 data + buf []int16 + mutex sync.Mutex } var gDriver *driver +func (buffer *C.kinc_a2_buffer_t) SampleCount() int { + return int((buffer.write_location + buffer.data_size - buffer.read_location) % buffer.data_size) / 4 +} + //export audio_callback func audio_callback(buffer *C.kinc_a2_buffer_t, samples int32) { - // TODO: buffer.write_location and buffer.read_location may give us a good - // hint about the buffer status and decide whether we need to hurry up to - // avoid stuttering. - canRead := len(gDriver.buf) / 2 - toWrite := min(canRead, int(samples)) - for i := 0; i < toWrite; i++ { + gDriver.mutex.Lock() + defer gDriver.mutex.Unlock() + + // Protect against accessing gDriver.buf[0] + if len(gDriver.buf) == 0 { + return + } - sample := *(*int16)(unsafe.Pointer(uintptr(unsafe.Pointer(&gDriver.buf[0])) + uintptr(i * 2))) - *(*float32)(unsafe.Pointer(uintptr(unsafe.Pointer(buffer.data)) + uintptr(buffer.write_location))) = float32(sample) / 32768. + // Bluntly drop data if our buffer is too full + if buffer.SampleCount() >= MaxBufferLength { + gDriver.buf = gDriver.buf[:] + return + } + + dst := unsafe.Slice((*float32)(unsafe.Pointer(buffer.data)), int(buffer.data_size) / 4) + for i := 0; i < int(samples); i++ { + if i < len(gDriver.buf) { + dst[buffer.write_location / 4] = float32(gDriver.buf[i]) / 32768 + } else { + dst[buffer.write_location / 4] = 0 + } buffer.write_location += 4 if buffer.write_location >= buffer.data_size { buffer.write_location = 0 } } - gDriver.buf = gDriver.buf[toWrite * 2:] + gDriver.buf = gDriver.buf[min(len(gDriver.buf), int(samples)):] } //export sample_rate_callback @@ -69,7 +87,7 @@ func sample_rate_callback() { func newDriver(sampleRate, numChans, bitDepthInBytes, bufferSizeInBytes int) (tryWriteCloser, error) { p := &driver{ - []byte{}, + buf: []int16{}, } C.kinc_a2_init() @@ -80,9 +98,13 @@ func newDriver(sampleRate, numChans, bitDepthInBytes, bufferSizeInBytes int) (tr } func (p *driver) TryWrite(data []byte) (n int, err error) { - toAppend := min(len(data), max(0, BufferLength - len(p.buf))) - p.buf = append(p.buf, data[:toAppend]...) - return toAppend, nil + p.mutex.Lock() + defer p.mutex.Unlock() + + samples := unsafe.Slice((*int16)(unsafe.Pointer(&data[0])), len(data) / 2) + sampleCount := min(len(samples), max(0, MaxBufferLength - len(p.buf))) + p.buf = append(p.buf, samples[:sampleCount]...) + return sampleCount * 2, nil } func (p *driver) Close() error { diff --git a/go.mod b/go.mod index fff5a85..fad1887 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/hajimehoshi/oto -go 1.13 +go 1.17 require ( golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6