1package rtaudio
2
3/*
4
5#cgo CXXFLAGS: -g
6#cgo LDFLAGS: -lstdc++ -g
7
8#cgo linux CXXFLAGS: -D__LINUX_ALSA__
9#cgo linux LDFLAGS: -lm -lasound -pthread
10
11#cgo linux,pulseaudio CXXFLAGS: -D__LINUX_PULSE__
12#cgo linux,pulseaudio LDFLAGS: -lpulse -lpulse-simple
13
14#cgo jack CXXFLAGS: -D__UNIX_JACK__
15#cgo jack LDFLAGS: -ljack
16
17#cgo windows CXXFLAGS: -D__WINDOWS_WASAPI__
18#cgo windows LDFLAGS: -lm -lksuser -lmfplat -lmfuuid -lwmcodecdspuuid -lwinmm -lole32 -static
19
20#cgo darwin CXXFLAGS: -D__MACOSX_CORE__
21#cgo darwin LDFLAGS: -framework CoreAudio -framework CoreFoundation
22
23#include <stdlib.h>
24#include <stdint.h>
25#include "rtaudio_stub.h"
26
27extern int goCallback(void *out, void *in, unsigned int nFrames,
28	double stream_time, rtaudio_stream_status_t status, void *userdata);
29
30static inline void cgoRtAudioOpenStream(rtaudio_t audio,
31	rtaudio_stream_parameters_t *output_params,
32	rtaudio_stream_parameters_t *input_params,
33	rtaudio_format_t format,
34	unsigned int sample_rate,
35	unsigned int *buffer_frames,
36	int cb_id,
37	rtaudio_stream_options_t *options) {
38		rtaudio_open_stream(audio, output_params, input_params,
39			format, sample_rate, buffer_frames,
40			goCallback, (void *)(uintptr_t)cb_id, options, NULL);
41}
42*/
43import "C"
44import (
45	"errors"
46	"sync"
47	"time"
48	"unsafe"
49)
50
51// API is an enumeration of available compiled APIs. Supported API include
52// Alsa/PulseAudio/OSS, Jack, CoreAudio, WASAPI/ASIO/DS and dummy API.
53type API C.rtaudio_api_t
54
55const (
56	// APIUnspecified looks for a working compiled API.
57	APIUnspecified API = C.RTAUDIO_API_UNSPECIFIED
58	// APILinuxALSA uses the Advanced Linux Sound Architecture API.
59	APILinuxALSA = C.RTAUDIO_API_LINUX_ALSA
60	// APILinuxPulse uses the Linux PulseAudio API.
61	APILinuxPulse = C.RTAUDIO_API_LINUX_PULSE
62	// APILinuxOSS uses the Linux Open Sound System API.
63	APILinuxOSS = C.RTAUDIO_API_LINUX_OSS
64	// APIUnixJack uses the Jack Low-Latency Audio Server API.
65	APIUnixJack = C.RTAUDIO_API_UNIX_JACK
66	// APIMacOSXCore uses Macintosh OS-X Core Audio API.
67	APIMacOSXCore = C.RTAUDIO_API_MACOSX_CORE
68	// APIWindowsWASAPI uses the Microsoft WASAPI API.
69	APIWindowsWASAPI = C.RTAUDIO_API_WINDOWS_WASAPI
70	// APIWindowsASIO uses the Steinberg Audio Stream I/O API.
71	APIWindowsASIO = C.RTAUDIO_API_WINDOWS_ASIO
72	// APIWindowsDS uses the Microsoft DirectSound API.
73	APIWindowsDS = C.RTAUDIO_API_WINDOWS_DS
74	// APIDummy is a compilable but non-functional API.
75	APIDummy = C.RTAUDIO_API_DUMMY
76)
77
78func (api API) String() string {
79	switch api {
80	case APIUnspecified:
81		return "unspecified"
82	case APILinuxALSA:
83		return "alsa"
84	case APILinuxPulse:
85		return "pulse"
86	case APILinuxOSS:
87		return "oss"
88	case APIUnixJack:
89		return "jack"
90	case APIMacOSXCore:
91		return "coreaudio"
92	case APIWindowsWASAPI:
93		return "wasapi"
94	case APIWindowsASIO:
95		return "asio"
96	case APIWindowsDS:
97		return "directsound"
98	case APIDummy:
99		return "dummy"
100	}
101	return "?"
102}
103
104// StreamStatus defines over- or underflow flags in the audio callback.
105type StreamStatus C.rtaudio_stream_status_t
106
107const (
108	// StatusInputOverflow indicates that data was discarded because of an
109	// overflow condition at the driver.
110	StatusInputOverflow StreamStatus = C.RTAUDIO_STATUS_INPUT_OVERFLOW
111	// StatusOutputUnderflow indicates that the output buffer ran low, likely
112	// producing a break in the output sound.
113	StatusOutputUnderflow StreamStatus = C.RTAUDIO_STATUS_OUTPUT_UNDERFLOW
114)
115
116// Version returns current RtAudio library version string.
117func Version() string {
118	return C.GoString(C.rtaudio_version())
119}
120
121// CompiledAPI determines the available compiled audio APIs.
122func CompiledAPI() (apis []API) {
123	capis := (*[1 << 27]C.rtaudio_api_t)(unsafe.Pointer(C.rtaudio_compiled_api()))
124	for i := 0; ; i++ {
125		api := capis[i]
126		if api == C.RTAUDIO_API_UNSPECIFIED {
127			break
128		}
129		apis = append(apis, API(api))
130	}
131	return apis
132}
133
134// DeviceInfo is the public device information structure for returning queried values.
135type DeviceInfo struct {
136	Name              string
137	Probed            bool
138	NumOutputChannels int
139	NumInputChannels  int
140	NumDuplexChannels int
141	IsDefaultOutput   bool
142	IsDefaultInput    bool
143
144	//rtaudio_format_t native_formats;
145
146	PreferredSampleRate uint
147	SampleRates         []int
148}
149
150// StreamParams is the structure for specifying input or output stream parameters.
151type StreamParams struct {
152	DeviceID     uint
153	NumChannels  uint
154	FirstChannel uint
155}
156
157// StreamFlags is a set of RtAudio stream option flags.
158type StreamFlags C.rtaudio_stream_flags_t
159
160const (
161	// FlagsNoninterleaved is set to use non-interleaved buffers (default = interleaved).
162	FlagsNoninterleaved = C.RTAUDIO_FLAGS_NONINTERLEAVED
163	// FlagsMinimizeLatency when set attempts to configure stream parameters for lowest possible latency.
164	FlagsMinimizeLatency = C.RTAUDIO_FLAGS_MINIMIZE_LATENCY
165	// FlagsHogDevice when set attempts to grab device for exclusive use.
166	FlagsHogDevice = C.RTAUDIO_FLAGS_HOG_DEVICE
167	// FlagsScheduleRealtime is set in attempt to select realtime scheduling (round-robin) for the callback thread.
168	FlagsScheduleRealtime = C.RTAUDIO_FLAGS_SCHEDULE_REALTIME
169	// FlagsAlsaUseDefault is set to use the "default" PCM device (ALSA only).
170	FlagsAlsaUseDefault = C.RTAUDIO_FLAGS_ALSA_USE_DEFAULT
171)
172
173// StreamOptions is the structure for specifying stream options.
174type StreamOptions struct {
175	Flags      StreamFlags
176	NumBuffers uint
177	Priotity   int
178	Name       string
179}
180
181// RtAudio is a "controller" used to select an available audio i/o interface.
182type RtAudio interface {
183	Destroy()
184	CurrentAPI() API
185	Devices() ([]DeviceInfo, error)
186	DefaultOutputDevice() int
187	DefaultInputDevice() int
188
189	Open(out, in *StreamParams, format Format, sampleRate uint, frames uint, cb Callback, opts *StreamOptions) error
190	Close()
191	Start() error
192	Stop() error
193	Abort() error
194
195	IsOpen() bool
196	IsRunning() bool
197
198	Latency() (int, error)
199	SampleRate() (uint, error)
200	Time() (time.Duration, error)
201	SetTime(time.Duration) error
202
203	ShowWarnings(bool)
204}
205
206type rtaudio struct {
207	audio          C.rtaudio_t
208	cb             Callback
209	inputChannels  int
210	outputChannels int
211	format         Format
212}
213
214var _ RtAudio = &rtaudio{}
215
216// Create a new RtAudio instance using the given API.
217func Create(api API) (RtAudio, error) {
218	audio := C.rtaudio_create(C.rtaudio_api_t(api))
219	if C.rtaudio_error(audio) != nil {
220		return nil, errors.New(C.GoString(C.rtaudio_error(audio)))
221	}
222	return &rtaudio{audio: audio}, nil
223}
224
225func (audio *rtaudio) Destroy() {
226	C.rtaudio_destroy(audio.audio)
227}
228
229func (audio *rtaudio) CurrentAPI() API {
230	return API(C.rtaudio_current_api(audio.audio))
231}
232
233func (audio *rtaudio) DefaultInputDevice() int {
234	return int(C.rtaudio_get_default_input_device(audio.audio))
235}
236
237func (audio *rtaudio) DefaultOutputDevice() int {
238	return int(C.rtaudio_get_default_output_device(audio.audio))
239}
240
241func (audio *rtaudio) Devices() ([]DeviceInfo, error) {
242	n := C.rtaudio_device_count(audio.audio)
243	devices := []DeviceInfo{}
244	for i := C.int(0); i < n; i++ {
245		cinfo := C.rtaudio_get_device_info(audio.audio, i)
246		if C.rtaudio_error(audio.audio) != nil {
247			return nil, errors.New(C.GoString(C.rtaudio_error(audio.audio)))
248		}
249		sr := []int{}
250		for _, r := range cinfo.sample_rates {
251			if r == 0 {
252				break
253			}
254			sr = append(sr, int(r))
255		}
256		devices = append(devices, DeviceInfo{
257			Name:                C.GoString(&cinfo.name[0]),
258			Probed:              cinfo.probed != 0,
259			NumInputChannels:    int(cinfo.input_channels),
260			NumOutputChannels:   int(cinfo.output_channels),
261			NumDuplexChannels:   int(cinfo.duplex_channels),
262			IsDefaultOutput:     cinfo.is_default_output != 0,
263			IsDefaultInput:      cinfo.is_default_input != 0,
264			PreferredSampleRate: uint(cinfo.preferred_sample_rate),
265			SampleRates:         sr,
266		})
267		// TODO: formats
268	}
269	return devices, nil
270}
271
272// Format defines RtAudio data format type.
273type Format int
274
275const (
276	// FormatInt8 uses 8-bit signed integer.
277	FormatInt8 Format = C.RTAUDIO_FORMAT_SINT8
278	// FormatInt16 uses 16-bit signed integer.
279	FormatInt16 = C.RTAUDIO_FORMAT_SINT16
280	// FormatInt24 uses 24-bit signed integer.
281	FormatInt24 = C.RTAUDIO_FORMAT_SINT24
282	// FormatInt32 uses 32-bit signed integer.
283	FormatInt32 = C.RTAUDIO_FORMAT_SINT32
284	// FormatFloat32 uses 32-bit floating point values normalized between (-1..1).
285	FormatFloat32 = C.RTAUDIO_FORMAT_FLOAT32
286	// FormatFloat64 uses 64-bit floating point values normalized between (-1..1).
287	FormatFloat64 = C.RTAUDIO_FORMAT_FLOAT64
288)
289
290// Buffer is a common interface for audio buffers of various data format types.
291type Buffer interface {
292	Len() int
293	Int8() []int8
294	Int16() []int16
295	Int24() []Int24
296	Int32() []int32
297	Float32() []float32
298	Float64() []float64
299}
300
301// Int24 is a helper type to convert int32 values to int24 and back.
302type Int24 [3]byte
303
304// Set Int24 value using the least significant bytes of the given number n.
305func (i *Int24) Set(n int32) {
306	(*i)[0], (*i)[1], (*i)[2] = byte(n&0xff), byte((n&0xff00)>>8), byte((n&0xff0000)>>16)
307}
308
309// Get Int24 value as int32.
310func (i Int24) Get() int32 {
311	n := int32(i[0]) | int32(i[1])<<8 | int32(i[2])<<16
312	if n&0x800000 != 0 {
313		n |= ^0xffffff
314	}
315	return n
316}
317
318type buffer struct {
319	format      Format
320	length      int
321	numChannels int
322	ptr         unsafe.Pointer
323}
324
325func (b *buffer) Len() int {
326	if b.ptr == nil {
327		return 0
328	}
329	return b.length
330}
331
332func (b *buffer) Int8() []int8 {
333	if b.format != FormatInt8 {
334		return nil
335	}
336	if b.ptr == nil {
337		return nil
338	}
339	return (*[1 << 30]int8)(b.ptr)[:b.length*b.numChannels : b.length*b.numChannels]
340}
341
342func (b *buffer) Int16() []int16 {
343	if b.format != FormatInt16 {
344		return nil
345	}
346	if b.ptr == nil {
347		return nil
348	}
349	return (*[1 << 29]int16)(b.ptr)[:b.length*b.numChannels : b.length*b.numChannels]
350}
351
352func (b *buffer) Int24() []Int24 {
353	if b.format != FormatInt24 {
354		return nil
355	}
356	if b.ptr == nil {
357		return nil
358	}
359	return (*[1 << 28]Int24)(b.ptr)[:b.length*b.numChannels : b.length*b.numChannels]
360}
361
362func (b *buffer) Int32() []int32 {
363	if b.format != FormatInt32 {
364		return nil
365	}
366	if b.ptr == nil {
367		return nil
368	}
369	return (*[1 << 27]int32)(b.ptr)[:b.length*b.numChannels : b.length*b.numChannels]
370}
371
372func (b *buffer) Float32() []float32 {
373	if b.format != FormatFloat32 {
374		return nil
375	}
376	if b.ptr == nil {
377		return nil
378	}
379	return (*[1 << 27]float32)(b.ptr)[:b.length*b.numChannels : b.length*b.numChannels]
380}
381
382func (b *buffer) Float64() []float64 {
383	if b.format != FormatFloat64 {
384		return nil
385	}
386	if b.ptr == nil {
387		return nil
388	}
389	return (*[1 << 23]float64)(b.ptr)[:b.length*b.numChannels : b.length*b.numChannels]
390}
391
392// Callback is a client-defined function that will be invoked when input data
393// is available and/or output data is needed.
394type Callback func(out Buffer, in Buffer, dur time.Duration, status StreamStatus) int
395
396var (
397	mu     sync.Mutex
398	audios = map[int]*rtaudio{}
399)
400
401func registerAudio(a *rtaudio) int {
402	mu.Lock()
403	defer mu.Unlock()
404	for i := 0; ; i++ {
405		if _, ok := audios[i]; !ok {
406			audios[i] = a
407			return i
408		}
409	}
410}
411
412func unregisterAudio(a *rtaudio) {
413	mu.Lock()
414	defer mu.Unlock()
415	for i := 0; i < len(audios); i++ {
416		if audios[i] == a {
417			delete(audios, i)
418			return
419		}
420	}
421}
422
423func findAudio(k int) *rtaudio {
424	mu.Lock()
425	defer mu.Unlock()
426	return audios[k]
427}
428
429//export goCallback
430func goCallback(out, in unsafe.Pointer, frames C.uint, sec C.double,
431	status C.rtaudio_stream_status_t, userdata unsafe.Pointer) C.int {
432
433	k := int(uintptr(userdata))
434	audio := findAudio(k)
435	dur := time.Duration(time.Microsecond * time.Duration(sec*1000000.0))
436	inbuf := &buffer{audio.format, int(frames), audio.inputChannels, in}
437	outbuf := &buffer{audio.format, int(frames), audio.outputChannels, out}
438	return C.int(audio.cb(outbuf, inbuf, dur, StreamStatus(status)))
439}
440
441func (audio *rtaudio) Open(out, in *StreamParams, format Format, sampleRate uint,
442	frames uint, cb Callback, opts *StreamOptions) error {
443	var (
444		cInPtr   *C.rtaudio_stream_parameters_t
445		cOutPtr  *C.rtaudio_stream_parameters_t
446		cOptsPtr *C.rtaudio_stream_options_t
447		cIn      C.rtaudio_stream_parameters_t
448		cOut     C.rtaudio_stream_parameters_t
449		cOpts    C.rtaudio_stream_options_t
450	)
451
452	audio.inputChannels = 0
453	audio.outputChannels = 0
454	if out != nil {
455		audio.outputChannels = int(out.NumChannels)
456		cOut.device_id = C.uint(out.DeviceID)
457		cOut.num_channels = C.uint(out.NumChannels)
458		cOut.first_channel = C.uint(out.FirstChannel)
459		cOutPtr = &cOut
460	}
461	if in != nil {
462		audio.inputChannels = int(in.NumChannels)
463		cIn.device_id = C.uint(in.DeviceID)
464		cIn.num_channels = C.uint(in.NumChannels)
465		cIn.first_channel = C.uint(in.FirstChannel)
466		cInPtr = &cIn
467	}
468	if opts != nil {
469		cOpts.flags = C.rtaudio_stream_flags_t(opts.Flags)
470		cOpts.num_buffers = C.uint(opts.NumBuffers)
471		cOpts.priority = C.int(opts.Priotity)
472		cOptsPtr = &cOpts
473	}
474	framesCount := C.uint(frames)
475	audio.format = format
476	audio.cb = cb
477
478	k := registerAudio(audio)
479	C.cgoRtAudioOpenStream(audio.audio, cOutPtr, cInPtr,
480		C.rtaudio_format_t(format), C.uint(sampleRate), &framesCount, C.int(k), cOptsPtr)
481	if C.rtaudio_error(audio.audio) != nil {
482		return errors.New(C.GoString(C.rtaudio_error(audio.audio)))
483	}
484	return nil
485}
486
487func (audio *rtaudio) Close() {
488	unregisterAudio(audio)
489	C.rtaudio_close_stream(audio.audio)
490}
491
492func (audio *rtaudio) Start() error {
493	C.rtaudio_start_stream(audio.audio)
494	if C.rtaudio_error(audio.audio) != nil {
495		return errors.New(C.GoString(C.rtaudio_error(audio.audio)))
496	}
497	return nil
498}
499
500func (audio *rtaudio) Stop() error {
501	C.rtaudio_stop_stream(audio.audio)
502	if C.rtaudio_error(audio.audio) != nil {
503		return errors.New(C.GoString(C.rtaudio_error(audio.audio)))
504	}
505	return nil
506}
507
508func (audio *rtaudio) Abort() error {
509	C.rtaudio_abort_stream(audio.audio)
510	if C.rtaudio_error(audio.audio) != nil {
511		return errors.New(C.GoString(C.rtaudio_error(audio.audio)))
512	}
513	return nil
514}
515
516func (audio *rtaudio) IsOpen() bool {
517	return C.rtaudio_is_stream_open(audio.audio) != 0
518}
519
520func (audio *rtaudio) IsRunning() bool {
521	return C.rtaudio_is_stream_running(audio.audio) != 0
522}
523
524func (audio *rtaudio) Latency() (int, error) {
525	latency := C.rtaudio_get_stream_latency(audio.audio)
526	if C.rtaudio_error(audio.audio) != nil {
527		return 0, errors.New(C.GoString(C.rtaudio_error(audio.audio)))
528	}
529	return int(latency), nil
530}
531
532func (audio *rtaudio) SampleRate() (uint, error) {
533	sampleRate := C.rtaudio_get_stream_sample_rate(audio.audio)
534	if C.rtaudio_error(audio.audio) != nil {
535		return 0, errors.New(C.GoString(C.rtaudio_error(audio.audio)))
536	}
537	return uint(sampleRate), nil
538}
539
540func (audio *rtaudio) Time() (time.Duration, error) {
541	sec := C.rtaudio_get_stream_time(audio.audio)
542	if C.rtaudio_error(audio.audio) != nil {
543		return 0, errors.New(C.GoString(C.rtaudio_error(audio.audio)))
544	}
545	return time.Duration(time.Microsecond * time.Duration(sec*1000000.0)), nil
546}
547
548func (audio *rtaudio) SetTime(t time.Duration) error {
549	sec := float64(t) * 1000000.0 / float64(time.Microsecond)
550	C.rtaudio_set_stream_time(audio.audio, C.double(sec))
551	if C.rtaudio_error(audio.audio) != nil {
552		return errors.New(C.GoString(C.rtaudio_error(audio.audio)))
553	}
554	return nil
555}
556
557func (audio *rtaudio) ShowWarnings(show bool) {
558	if show {
559		C.rtaudio_show_warnings(audio.audio, 1)
560	} else {
561		C.rtaudio_show_warnings(audio.audio, 0)
562	}
563}
564