libsoundio Chez Scheme wrapper.
Status: FFI is complete, high-level wrappers are alpha.
Dependency versions: libsoundio 2.0.0 and Chez Scheme 9.5.5.5
NOTE: Threaded Chez Scheme crashes with SIGILL on MacOS and with SIGSEGV on
Linux if write_callback
calls Scheme code. Please use provided ring buffer
based bridge.
See Example for now.
libsoundio shared library should be installed somewhere in the PATH, let’s load it depending on platform:
;; <load-library>
(define init-ffi
(case (machine-type)
[(i3nt ti3nt a6nt ta6nt) (load-shared-object "libsoundio.dll")]
[(i3osx ti3osx a6osx ta6osx tarm64osx) (load-shared-object "libsoundio.dylib")]
[(i3le ti3le a6le ta6le) (load-shared-object "libsoundio.so")]
[else (error "soundio"
"don't know how libsoundio shared library file is called on this machine-type"
(machine-type))]))
;; </load-library>
Machine type correspondence to platform could be found in release notes.
Defining foreign types (ftypes) for interaction with C code gives runtime checks and
more clarity. To have mutually recursive ftypes we will describe them one by
one and then put into the single define-ftype
.
Chez Scheme FFI has no(ftype-ref SoundIoOutStream (layout channel_count) out-stream) representation for enums, we are going to make them
just int
aliases.
;; <ftype-enums>
[SoundIoBackend int]
[SoundIoChannelId int]
[SoundIoFormat int]
[SoundIoDeviceAim int]
;; </ftype-enums>
libsoundio has plenty of ones, defining ftypes for them instead of just
using void*
would give us runtime safety and convenience.
;; <ftype-callbacks>
[OnDeviceChangeCallback (function ((* SoundIo)) void)]
[OnBackendDisconnectCallback (function ((* SoundIo) int) void)]
[OnEventsSignalCallback (function ((* SoundIo)) void)]
[EmitRtprioWarningCallback (function () void)]
[JackInfoCallback (function ((* char)) void)]
[JackErrorCallback (function ((* char)) void)]
[WriteCallback (function ((* SoundIoOutStream) int int) void)]
[UnderflowCallback (function ((* SoundIoOutStream)) void)]
[ReadCallback (function ((* SoundIoInStream) int int) void)]
[OverflowCallback (function ((* SoundIoInStream)) void)]
[ErrorCallback (function ((* SoundIoOutStream) int) void)]
;; </ftype-callbacks>
[SoundIo
(struct
[userdata void*] ; Optional. Put whatever you want here. Defaults to NULL.
[on_devices_change (* OnDeviceChangeCallback)] ; Optional callback.
[on_backend_disconnect (* OnBackendDisconnectCallback)] ; Optional callback.
[on_events_signal (* OnEventsSignalCallback)] ; Optional callback.
[current_backend SoundIoBackend] ; Read-only.
[app_name (* char)] ; Optional: Application name.
[emit_rtprio_warning (* EmitRtprioWarningCallback)] ; Optional: Real time priority warning.
[jack_info_callback (* JackInfoCallback)] ; Optional: JACK info callback.
[jack_error_callback (* JackErrorCallback)] ; Optional: JACK error callback.
)]
[SoundIoChannelArea
(struct
[ptr (* char)]
[step int])]
;; Useful for defining **SoundIoChannelArea in function ftype as (* *SoundIoChannelArea)
;; nested * or its alias doesn't work:
;; Exception: invalid (non-base) foreign-procedure argument ftype **SoundIoChannelArea
[*SoundIoChannelArea (* SoundIoChannelArea)]
[SoundIoChannelLayout
(struct
[name (* char)]
[channel_count int]
;; #define SOUNDIO_MAX_CHANNELS 24
;; http://libsound.io/doc-1.1.0/soundio_8h.html#a1bf1282c5d903085916f8ed6af174bdd
[channels (array 24 SoundIoChannelId)])]
[SoundIoDevice
(struct
[soundio (* SoundIo)]
[id (* char)]
[name (* char)]
[aim SoundIoDeviceAim]
[layouts (* SoundIoChannelLayout)]
[layout_count int]
[current_layout SoundIoChannelLayout]
[formats (* SoundIoFormat)]
[format_count int]
[current_format SoundIoFormat]
[sample_rates (* SoundIoSampleRateRange)]
[sample_rate_count int]
[sample_rate_current int]
[software_latency_min double]
[software_latency_max double]
[software_latency_current double]
[is_raw boolean]
[ref_count int]
[probe_error int])]
[SoundIoInStream
(struct
[device (* SoundIoDevice)]
[format SoundIoFormat]
[sample_rate int]
[layout SoundIoChannelLayout]
[software_latency double]
[userdata void*]
[read_callback (* ReadCallback)]
[overflow_callback (* OverflowCallback)]
[error_callback (* ErrorCallback)]
[name (* char)]
[non_terminal_hint boolean]
[bytes_per_frame int]
[bytes_per_sample int]
[layout_error int])]
[SoundIoOutStream
(struct
[device (* SoundIoDevice)]
[format SoundIoFormat]
[sample_rate int]
[layout SoundIoChannelLayout]
[software_latency double]
[volume float]
[userdata void*]
[write_callback (* WriteCallback)]
[underflow_callback (* UnderflowCallback)]
[error_callback (* ErrorCallback)]
[name (* char)]
[non_terminal_hint boolean]
[bytes_per_frame int]
[bytes_per_sample int]
[layout_error int])]
[SoundIoSampleRateRange
(struct
[min int]
[max int])]
[SoundIoRingBuffer
(struct
[mem SoundIoOsMirroredMemory]
[write_offset SoundIoAtomicLong]
[read_offset SoundIoAtomicLong]
[capacity int])]
[SoundIoOsMirroredMemory
(struct
[capacity size_t]
[address (* char)]
[priv void*])]
[SoundIoAtomicLong long]
;; <ftype-structs>
<<SoundIo>>
<<SoundIoChannelArea>>
<<SoundIoChannelLayout>>
<<SoundIoDevice>>
<<SoundIoInStream>>
<<SoundIoOutStream>>
<<SoundIoSampleRateRange>>
<<SoundIoOsMirroredMemory>>
<<SoundIoAtomicLong>>
<<SoundIoRingBuffer>>
;; </ftype-structs>
;; <ftypes>
(define-ftype
<<ftype-enums>>
<<ftype-callbacks>>
<<ftype-structs>>
)
;; </ftypes>
We are going to keep original names while defining foreign procedures, thus let’s write a macro to save few keystrokes:
(define-syntax (define-foreign-procedure stx)
(syntax-case stx ()
[(_ [name args result])
#`(define name
(foreign-procedure
#,(symbol->string (syntax->datum #'name))
args
result))]
[(_ e ...)
#'(begin
(define-foreign-procedure e)
...)]))
(define-foreign-procedure
[soundio_backend_count ((* SoundIo)) int]
[soundio_backend_name (SoundIoBackend) int]
[soundio_best_matching_channel_layout
((* SoundIoChannelLayout) ; preferred_layouts
int ; preferred_layout_count
(* SoundIoChannelLayout) ; available_layouts
int ; available_layout_count
)
(* SoundIoChannelLayout)]
[soundio_channel_layout_builtin_count () int]
[soundio_channel_layout_detect_builtin ((* SoundIoChannelLayout)) boolean]
[soundio_channel_layout_equal ((* SoundIoChannelLayout) (* SoundIoChannelLayout)) boolean]
[soundio_channel_layout_find_channel ((* SoundIoChannelLayout) SoundIoChannelId) int]
[soundio_channel_layout_get_builtin (int) (* SoundIoChannelLayout)]
[soundio_channel_layout_get_default (#|channel_count|# int) (* SoundIoChannelLayout)]
[soundio_connect ((* SoundIo)) int]
[soundio_connect_backend ((* SoundIo) (* SoundIoBackend)) int]
[soundio_create () (* SoundIo)]
[soundio_default_input_device_index ((* SoundIo)) int]
[soundio_default_output_device_index ((* SoundIo)) int]
[soundio_destroy ((* SoundIo)) void]
[soundio_device_equal ((* SoundIoDevice) (* SoundIoDevice)) boolean]
[soundio_device_nearest_sample_rate ((* SoundIoDevice) int) int]
[soundio_device_ref ((* SoundIoDevice)) void]
[soundio_device_sort_channel_layouts ((* SoundIoDevice)) void]
[soundio_device_supports_format ((* SoundIoDevice) SoundIoFormat) boolean]
[soundio_device_supports_layout ((* SoundIoDevice) (* SoundIoChannelLayout)) boolean]
[soundio_device_supports_sample_rate ((* SoundIoDevice) int) boolean]
[soundio_device_unref ((* SoundIoDevice)) void]
[soundio_disconnect ((* SoundIo)) void]
[soundio_flush_events ((* SoundIo)) void]
[soundio_force_device_scan ((* SoundIo)) void]
[soundio_format_string (SoundIoFormat) string]
[soundio_get_backend ((* SoundIo) int) SoundIoBackend]
;; [soundio_get_bytes_per_frame (SoundIoFormat #|channel_count|# int) int]
;; [soundio_get_bytes_per_sample (SoundIoFormat) int]
;; [soundio_get_bytes_per_second (SoundIoFormat #|channel_count|# int #|sample_rate|# int) int]
[soundio_get_channel_name (SoundIoChannelId) string]
[soundio_get_input_device ((* SoundIo) int) (* SoundIoDevice)]
[soundio_get_output_device ((* SoundIo) int) (* SoundIoDevice)]
[soundio_have_backend (SoundIoBackend) boolean]
[soundio_input_device_count ((* SoundIo)) int]
[soundio_instream_begin_read ((* SoundIoInStream) (* *SoundIoChannelArea) (* int)) int]
[soundio_instream_create ((* SoundIoDevice)) (* SoundIoInStream)]
[soundio_instream_destroy ((* SoundIoInStream)) void]
[soundio_instream_end_read ((* SoundIoInStream)) int]
[soundio_instream_get_latency ((* SoundIoInStream) (* double)) int]
[soundio_instream_open ((* SoundIoInStream)) int]
[soundio_instream_pause ((* SoundIoInStream) boolean) int]
[soundio_instream_start ((* SoundIoInStream)) int]
[soundio_output_device_count ((* SoundIo)) int]
[soundio_outstream_begin_write ((* SoundIoOutStream) (* *SoundIoChannelArea) (* int)) int]
[soundio_outstream_clear_buffer ((* SoundIoOutStream)) int]
[soundio_outstream_create ((* SoundIoDevice)) (* SoundIoOutStream)]
[soundio_outstream_destroy ((* SoundIoOutStream)) void]
[soundio_outstream_end_write ((* SoundIoOutStream)) int]
[soundio_outstream_get_latency ((* SoundIoOutStream) (* double)) int]
[soundio_outstream_open ((* SoundIoOutStream)) int]
[soundio_outstream_pause ((* SoundIoOutStream) boolean) int]
[soundio_outstream_start ((* SoundIoOutStream)) int]
[soundio_parse_channel_id ((* char) int) SoundIoChannelId]
[soundio_ring_buffer_advance_read_ptr ((* SoundIoRingBuffer) int) void]
[soundio_ring_buffer_advance_write_ptr ((* SoundIoRingBuffer) int) void]
[soundio_ring_buffer_capacity ((* SoundIoRingBuffer)) int]
[soundio_ring_buffer_clear ((* SoundIoRingBuffer)) void]
[soundio_ring_buffer_create ((* SoundIo) int) (* SoundIoRingBuffer)]
[soundio_ring_buffer_destroy ((* SoundIoRingBuffer)) void]
[soundio_ring_buffer_fill_count ((* SoundIoRingBuffer)) int]
[soundio_ring_buffer_free_count ((* SoundIoRingBuffer)) int]
[soundio_ring_buffer_read_ptr ((* SoundIoRingBuffer)) (* char)]
[soundio_ring_buffer_write_ptr ((* SoundIoRingBuffer)) (* char)]
[soundio_sort_channel_layouts ((* SoundIoChannelLayout) int) void]
[soundio_strerror (int) string]
[soundio_version_major () int]
[soundio_version_minor () int]
[soundio_version_patch () int]
[soundio_version_string () string]
[soundio_wait_events ((* SoundIo)) void]
[soundio_wakeup ((* SoundIo)) void])
;; <ffi>
<<load-library>>
<<ftypes>>
<<define-foreign-procedure>>
<<foreign-procedures>>
;; </ffi>
Though library is already usable for producing sound via Scheme there is still plenty of boilerplate to abstract away. It’s quite hard to cover all use cases, the plan is to add features one by one based on real usage feedback.
Known limitations of current wrapper:
- it designed for threaded version and uses threads; though we could imagine use case for libsoundio in non-threaded Chez (non-interactive sound generation), we are interested in live-coding application and lean towards it
- at the moment only
float
sample type is supported
To make library work in threaded version we need to build and load our
bridge.c
helper.
First, we need to define how our file is called and where Scheme’s headers located.
;; <bridge-paths>
(define bridge-source-filename "bridge.c")
(define bridge-library-filename "libbridge.so")
(define scheme-headers-path (format "/usr/local/lib/csv9.5.5.5/~a" (machine-type)))
;; </bridge-paths>
In case library doesn’t exist try to build it automatically.
;; <build-bridge>
(case (machine-type)
[(i3nt ti3nt a6nt ta6nt)
(begin
(error "init-bridge"
"don't know how to build for Windows, look at the source for template to adjust")
(system (format "cl -c -DWIN32 ~a"
bridge-source-filename))
(system (format "link -dll -out:~a ~a.obj"
bridge-library-filename
bridge-source-filename)))]
[(i3osx ti3osx a6osx ta6osx tarm64osx)
(system (format "cc -O3 -dynamiclib -Wl,-undefined -Wl,dynamic_lookup -I~a -lsoundio -o ~a ~a"
scheme-headers-path
bridge-library-filename
bridge-source-filename))]
[(i3le ti3le a6le ta6le)
(system (format "cc -O3 -fPIC -shared -Wl,-undefined -Wl,dynamic_lookup -I~a -lsoundio -o ~a ~a.c"
scheme-headers-path
bridge-library-filename
bridge-source-filename))]
[else (error "init-bridge"
"don't know how to build bridge shared library on this machine-type"
(machine-type))])
;; </build-bridge>
Machine type correspondence to platform could be found in release notes.
We need to wrap loading shared library into define to make it work inside
R6RS library
construct.
;; <build-bridge>
<<bridge-paths>>
(define init-bridge
(begin
(unless (file-exists? bridge-library-filename)
<<build-bridge>>
)
(load-shared-object bridge-library-filename)))
;; </build-bridge>
Heart of the bridge is custom write_callback
which draws samples from ring
buffer passed to it via stream’s userdata
field. To avoid underflows we
fill stream with zeros if buffer has not enough data.
// <write_callback>
static void write_callback(struct SoundIoOutStream *outstream, int frame_count_min, int frame_count_max) {
struct SoundIoRingBuffer *ring_buffer = outstream->userdata;
struct SoundIoChannelArea *areas;
int frame_count;
int frames_left;
int err;
char *read_ptr = soundio_ring_buffer_read_ptr(ring_buffer);
int fill_bytes = soundio_ring_buffer_fill_count(ring_buffer);
int fill_count = fill_bytes / outstream->bytes_per_frame;
if (frame_count_min > fill_count) {
<<fill-stream-with-zeros>>
}
<<copy-samples-from-buffer>>
soundio_ring_buffer_advance_read_ptr(ring_buffer, read_count * outstream->bytes_per_frame);
}
// </write_callback>
libsoundio examples suggest to guard actual write to stream with checks.
// <begin-write>
if ((err = soundio_outstream_begin_write(outstream, &areas, &frame_count))) {
fprintf(stderr, "begin_write: %s\n", soundio_strerror(err));
exit(1);
}
// </begin-write>
// <end-write>
if ((err = soundio_outstream_end_write(outstream))) {
fprintf(stderr, "end_write: %s\n", soundio_strerror(err));
// REVIEW pthread_exit?
exit(1);
}
// </end-write>
// <copy-samples-from-buffer>
int read_count = frame_count_max < fill_count ? frame_count_max : fill_count;
frames_left = read_count;
while (frames_left > 0) {
int frame_count = frames_left;
<<begin-write>>
if (frame_count <= 0)
break;
for (int frame = 0; frame < frame_count; frame += 1) {
for (int ch = 0; ch < outstream->layout.channel_count; ch += 1) {
memcpy(areas[ch].ptr, read_ptr, outstream->bytes_per_sample);
areas[ch].ptr += areas[ch].step;
read_ptr += outstream->bytes_per_sample;
}
}
<<end-write>>
frames_left -= frame_count;
}
// </copy-samples-from-buffer>
// <fill-stream-with-zeros>
frames_left = frame_count_min;
for (;;) {
frame_count = frames_left;
if (!frame_count)
return;
<<begin-write>>
if (!frame_count)
return;
for (int frame = 0; frame < frame_count; frame += 1) {
for (int ch = 0; ch < outstream->layout.channel_count; ch += 1) {
memset(areas[ch].ptr, 0, outstream->bytes_per_sample);
areas[ch].ptr += areas[ch].step;
}
}
<<end-write>>
frames_left -= frame_count;
}
// </fill-stream-with-zeros>
It accepts outstream
and buffer
and sets buffer
and our
write_callback
to outstream
.
// <bridge_outstream_attach_ring_buffer>
EXPORT void bridge_outstream_attach_ring_buffer
(struct SoundIoOutStream *outstream, struct SoundIoRingBuffer *buffer) {
outstream->format = SoundIoFormatFloat32NE;
outstream->userdata = buffer;
outstream->write_callback = write_callback;
}
// </bridge_outstream_attach_ring_buffer>
It’s a microsecond resolution sleep based on calling select
with timeout.
It accepts seconds
and microseconds
to sleep as integers. It is used to
wait a little when buffer is full. It is also useful if you want to
implement high-resolution scheduler. I found out that using Scheme’s sleep
which calls nanosleep
under the hood is quite expensive and imprecise.
I’m not sure why it’s needed to wrap select
into Scheme thread
deactivation, but without it attempts to call usleep
from different
threads leads to stops in sound.
// <usleep>
EXPORT void usleep (long seconds, long microseconds) {
struct timeval timeout;
timeout.tv_sec = seconds;
timeout.tv_usec = microseconds;
Sdeactivate_thread();
select(0, NULL, NULL, NULL, &timeout);
Sactivate_thread();
}
// </usleep>
;; <bridge-ffi>
(define-foreign-procedure
[bridge_outstream_attach_ring_buffer ((* SoundIoOutStream) (* SoundIoRingBuffer)) void]
[usleep (long #|seconds|# long #|microseconds|#) void])
;; </bridge-ffi>
Most of the time I want just fire up default output device and provide per-sample-per-channel dsp callback to make noise, and eventually stop doing it. It would be good to have dedicated DS which will hold a bunch of pointers created on the way.
;; <sound-out-record>
(define-record-type sound-out
(fields stream
ring-buffer
(mutable write-callback)
(mutable write-thread)))
;; </sound-out-record>
Next step is to encapsulate all initialization routines.
As an experiment, let’s go from the end to the beginning. Ultimate goal of
initialization is to have open output audio stream on default device. The
stream should have write_callback
assigned but to be not started. We want to
ignit sound as a separate action. Also we want to return a bunch of pointers
packed into sound-out
record to have access to them later: to start and stop
stream and to properly close and destroy stream.
define-record-type
produced record constructor for us, just pass fields to
it:
;; <make-sound-out>
(printf "Channels:\t~s\r\n" channel-count)
(printf "Sample rate:\t~s\r\n" sample-rate)
(printf "Latency:\t~s\r\n" latency)
(printf "Buffer:\t\t~s\r\n" buffer-size)
(make-sound-out out-stream ring-buffer write-callback #f)
;; </make-sound-out>
Callbacks are set before stream start. We don’t want user to bother with
pointer arithmetic and stuff, thus we wrap callbacks. Even more, threaded Chez
Scheme crashes when write_callback
calls Scheme code. Thus we are going to
use ring buffer to build a bridge between systems. User’s write-callback
will receive timestamp
and channel
and should return sample value.
underflow-callback
is still to be implemented, because we moved to ring
buffer from direct callbacks which corrupted Scheme runtime.
;; <attach-buffer-to-stream>
(let* ([frame-size (ftype-sizeof float)]
[channel-count (ftype-ref SoundIoOutStream (layout channel_count) out-stream)]
[sample-rate (ftype-ref SoundIoOutStream (sample_rate) out-stream)]
[latency (ftype-ref SoundIoOutStream (software_latency) out-stream)]
[buffer-size (exact (ceiling (* latency sample-rate)))] ; in samples
[buffer-capacity (* buffer-size frame-size channel-count)] ; in bytes
[ring-buffer (soundio_ring_buffer_create sio buffer-capacity)])
(when (ftype-pointer-null? ring-buffer)
(error "soundio_ring_buffer_create" "out of memory"))
(bridge_outstream_attach_ring_buffer out-stream ring-buffer)
<<make-sound-out>>
)
;; </attach-buffer-to-stream>
It makes sense to attach buffer and return sound-out
record if opening
stream was successful:
;; <try-open-stream>
(let ([err (soundio_outstream_open out-stream)])
(when (not (zero? err))
(error "soundio_outstream_open" (soundio_strerror err)))
(let ([err (ftype-ref SoundIoOutStream (layout_error) out-stream)])
(when (not (zero? err))
(error "soundio_outstream_open" (soundio_strerror err))))
<<attach-buffer-to-stream>>
)
;; </try-open-stream>
Let’s create stream before setting its callbacks:
;; <try-create-stream>
(let ([out-stream (soundio_outstream_create device)])
(when (ftype-pointer-null? out-stream)
(error "soundio_outstream_create" "out of memory"))
<<try-open-stream>>
)
;; </try-create-stream>
The same story with device, we need to obtain it before use:
;; <try-create-device>
(let ([idx (soundio_default_output_device_index sio)])
(when (< idx 0)
(error "soundio_default_output_device_index" "no output device found"))
(let ([device (soundio_get_output_device sio idx)])
(when (ftype-pointer-null? device)
(error "soundio_get_output_device" "out of memory"))
<<try-create-stream>>
))
;; </try-create-device>
And sio instance is to be created and connected before device access. Note flushing events.
;; <try-create-connect-sio>
(let ([sio (soundio_create)])
(when (ftype-pointer-null? sio)
(error "soundio_create" "out of memory"))
(let ([err (soundio_connect sio)])
(when (not (zero? err))
(error "soundio_connect" (soundio_strerror err)))
(soundio_flush_events sio)
<<try-create-device>>
))
;; </try-create-connect-sio>
Now just give it a name =)
;; <open-default-out-stream>
(define (open-default-out-stream write-callback)
<<try-create-connect-sio>>
)
;; </open-default-out-stream>
Now we need to be able start stream, stop stream and teardown our audio subsytem. Starting and stopping stream require managing thread responsible for calling our dsp function and filling ring buffer.
;; <start-out-stream>
(define (start-out-stream sound-out)
(let* ([frame-size (ftype-sizeof float)]
[out-stream (sound-out-stream sound-out)]
[channel-count (ftype-ref SoundIoOutStream (layout channel_count) out-stream)]
[sample-rate (ftype-ref SoundIoOutStream (sample_rate) out-stream)]
[seconds-per-sample (inexact (/ sample-rate))]
[ring-buffer (sound-out-ring-buffer sound-out)]
[polling-microseconds 1000]
[sample-number 0])
(sound-out-write-thread-set! sound-out (get-thread-id))
(fork-thread
(lambda ()
(let loop ()
(let ([write-callback (sound-out-write-callback sound-out)])
(when (sound-out-write-thread sound-out)
(let ([free-count (soundio_ring_buffer_free_count ring-buffer)])
(if (zero? free-count)
(begin
(usleep 0 polling-microseconds)
(loop))
(let ([free-frames (/ free-count frame-size channel-count)]
[write-ptr (ftype-pointer-address (soundio_ring_buffer_write_ptr ring-buffer))])
(do ([frame 0 (+ frame 1)])
((= frame free-frames) 0)
(let* ([sample-number (+ sample-number frame)]
[time (fl* (fixnum->flonum sample-number) seconds-per-sample)])
(do ([channel 0 (+ channel 1)])
((= channel channel-count) 0)
(foreign-set!
'float
write-ptr
(* (+ (* frame channel-count) channel) frame-size)
(write-callback time channel))
)))
(soundio_ring_buffer_advance_write_ptr ring-buffer free-count)
(set! sample-number (+ sample-number free-frames))
(loop))
)))))))
(soundio_outstream_start out-stream)))
;; </start-out-stream>
;; <stop-out-stream>
(define (stop-out-stream sound-out)
(sound-out-write-thread-set! sound-out #f)
(soundio_outstream_pause (sound-out-stream sound-out) #t))
;; </stop-out-stream>
Unmounting entire system require more actions. We are to destroy stream, unref device, destroy sio and ring buffer.
;; <teardown-out-stream>
(define (teardown-out-stream sound-out)
(let* ([stream (sound-out-stream sound-out)]
[ring-buffer (sound-out-ring-buffer sound-out)]
[device (ftype-ref SoundIoOutStream (device) stream)]
[soundio (ftype-ref SoundIoDevice (soundio) device)])
(soundio_outstream_destroy stream)
(soundio_ring_buffer_destroy ring-buffer)
(soundio_device_unref device)
(soundio_destroy soundio)))
;; </teardown-out-stream>
;; <channel-count>
(define (channel-count sound-out)
(ftype-ref SoundIoOutStream
(layout channel_count)
(sound-out-stream sound-out)))
;; </channel-count>
;; <sample-rate>
(define (sample-rate sound-out)
(ftype-ref SoundIoOutStream
(sample_rate)
(sound-out-stream sound-out)))
;; </sample-rate>
;; <high-level-wrapper>
<<init-bridge>>
<<bridge-ffi>>
<<sound-out-record>>
<<open-default-out-stream>>
<<start-out-stream>>
<<stop-out-stream>>
<<teardown-out-stream>>
<<channel-count>>
<<sample-rate>>
;; </high-level-wrapper>
make-ftype-pointer
locks object as pointed here, and its manual unlocking is
required to prevent memory leaks. It’s done by 3 levels deep call of core
functions, thus we are going to define a dedicated function for it.
;; <unlock-ftype-pointer>
(define (unlock-ftype-pointer fptr)
(unlock-object
(foreign-callable-code-object
(ftype-pointer-address fptr))))
;; </unlock-ftype-pointer>
;; <helpers>
<<unlock-ftype-pointer>>
;; </helpers>
Let’s play a bunch of sine waves (and test performance on the way).
(import (prefix (soundio) soundio:))
(define pi 3.1415926535)
(define two-pi (* 2 pi))
(define sine (lambda (time freq)
(sin (* two-pi freq time))))
(define square (lambda (time freq)
(let ([ft (* two-pi freq time)])
(+ (- (* 2 (floor ft))
(floor (* 2 ft)))
1))))
(define write-callback (lambda (time channel)
(let ([k 100]
[sample 0.0])
(do ([i 0 (+ i 1)]
[sample 0.0 (+ sample (sine time (+ 440.0 i)))])
((= i k) (/ sample k))))))
(define square-callback (lambda (time channel)
(let ([k 20]
[sample 0.0])
(do ([i 0 (+ i 1)]
[sample 0.0 (+ sample (square time (+ 440.0 i)))])
((= i k) (/ sample k 2))))))
(define my-out (soundio:open-default-out-stream write-callback))
(soundio:start-out-stream my-out)
Contribution is more than welcome in any form. If you don’t want to bother youself dealing with org-mode (though it worth trying!), just patch generated files included in repo and make PR. I’ll incorporate changes into org file then.
ISC License
Copyright (c) 2017, Ruslan Prokopchuk
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
<<helpers>>
<<ffi>>
(library (soundio (1))
(export open-default-out-stream
start-out-stream
stop-out-stream
teardown-out-stream
sample-rate
channel-count
usleep)
(import (chezscheme))
(include "soundio-ffi.ss")
<<high-level-wrapper>>
)
#ifdef WIN32
#define EXPORT extern __declspec (dllexport)
#else
#define EXPORT extern
#endif
#include <sys/select.h>
#include <soundio/soundio.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "scheme.h"
<<write_callback>>
<<bridge_outstream_attach_ring_buffer>>
<<usleep>>