{"id":16138183,"url":"https://github.com/ul/chez-soundio","last_synced_at":"2026-02-11T19:02:28.815Z","repository":{"id":146516299,"uuid":"83567759","full_name":"ul/chez-soundio","owner":"ul","description":"libsoundio bindings for Chez Scheme","archived":false,"fork":false,"pushed_at":"2021-09-19T22:37:19.000Z","size":56,"stargazers_count":9,"open_issues_count":1,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2026-01-14T18:37:01.784Z","etag":null,"topics":["chez-scheme","ffi","ffi-bindings","ffi-wrapper","library","libsoundio","literate-programming","scheme"],"latest_commit_sha":null,"homepage":"","language":"Scheme","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"isc","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ul.png","metadata":{"files":{"readme":"README.org","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-03-01T15:07:52.000Z","updated_at":"2025-12-02T23:26:19.000Z","dependencies_parsed_at":null,"dependency_job_id":"576668e6-1aab-4b53-bce6-50e6ff8215d3","html_url":"https://github.com/ul/chez-soundio","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ul/chez-soundio","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ul%2Fchez-soundio","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ul%2Fchez-soundio/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ul%2Fchez-soundio/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ul%2Fchez-soundio/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ul","download_url":"https://codeload.github.com/ul/chez-soundio/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ul%2Fchez-soundio/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29341697,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-11T18:58:20.535Z","status":"ssl_error","status_checked_at":"2026-02-11T18:56:44.814Z","response_time":97,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["chez-scheme","ffi","ffi-bindings","ffi-wrapper","library","libsoundio","literate-programming","scheme"],"created_at":"2024-10-09T23:32:45.041Z","updated_at":"2026-02-11T19:02:28.798Z","avatar_url":"https://github.com/ul.png","language":"Scheme","funding_links":[],"categories":[],"sub_categories":[],"readme":"* SoundIo\n\n  [[https://github.com/andrewrk/libsoundio][libsoundio]] [[https://github.com/cisco/ChezScheme][Chez Scheme]] wrapper.\n\n  Status: FFI is complete, high-level wrappers are alpha.\n\n  Dependency versions: libsoundio 2.0.0 and Chez Scheme 9.5.5.5\n\n  NOTE: Threaded Chez Scheme crashes with SIGILL on MacOS and with SIGSEGV on\n  Linux if =write_callback= calls Scheme code. Please use provided ring buffer\n  based bridge.\n\n* TODO Usage\n\n  See [[Example]] for now.\n\n* FFI\n\n** Load Library\n\n   libsoundio shared library should be installed somewhere in the PATH, let's\n   load it depending on platform:\n\n#+NAME: load-library\n#+BEGIN_SRC scheme\n  ;; \u003cload-library\u003e\n  (define init-ffi\n    (case (machine-type)\n      [(i3nt ti3nt a6nt ta6nt) (load-shared-object \"libsoundio.dll\")]\n      [(i3osx ti3osx a6osx ta6osx tarm64osx) (load-shared-object \"libsoundio.dylib\")]\n      [(i3le ti3le a6le ta6le) (load-shared-object \"libsoundio.so\")]\n      [else (error \"soundio\"\n                   \"don't know how libsoundio shared library file is called on this machine-type\"\n                   (machine-type))]))\n  ;; \u003c/load-library\u003e\n#+END_SRC\n\n   Machine type correspondence to platform could be found in [[https://cisco.github.io/ChezScheme/release_notes/v9.4/release_notes.html][release notes]].\n\n** Data Structures\n\n   Defining foreign types (ftypes) for interaction with C code gives runtime checks and\n   more clarity. To have mutually recursive ftypes we will describe them one by\n   one and then put into the single =define-ftype=.\n\n*** TODO Auto-generate most of this from =soundio.h=\n\n*** Enums\n\n    Chez Scheme FFI has no(ftype-ref SoundIoOutStream (layout channel_count) out-stream) representation for enums, we are going to make them\n    just =int= aliases.\n\n#+NAME: ftype-enums\n#+BEGIN_SRC scheme\n  ;; \u003cftype-enums\u003e\n  [SoundIoBackend int]\n  [SoundIoChannelId int]\n  [SoundIoFormat int]\n  [SoundIoDeviceAim int]\n  ;; \u003c/ftype-enums\u003e\n#+END_SRC\n\n*** Callbacks\n\n    libsoundio has plenty of ones, defining ftypes for them instead of just\n    using =void*= would give us runtime safety and convenience.\n\n#+NAME: ftype-callbacks\n#+BEGIN_SRC scheme\n  ;; \u003cftype-callbacks\u003e\n  [OnDeviceChangeCallback (function ((* SoundIo)) void)]\n  [OnBackendDisconnectCallback (function ((* SoundIo) int) void)]\n  [OnEventsSignalCallback (function ((* SoundIo)) void)]\n  [EmitRtprioWarningCallback (function () void)]\n  [JackInfoCallback (function ((* char)) void)]\n  [JackErrorCallback (function ((* char)) void)]\n  [WriteCallback (function ((* SoundIoOutStream) int int) void)]\n  [UnderflowCallback (function ((* SoundIoOutStream)) void)]\n  [ReadCallback (function ((* SoundIoInStream) int int) void)]\n  [OverflowCallback (function ((* SoundIoInStream)) void)]\n  [ErrorCallback (function ((* SoundIoOutStream) int) void)]\n  ;; \u003c/ftype-callbacks\u003e\n#+END_SRC\n\n*** Structs\n\n#+NAME: SoundIo\n#+BEGIN_SRC scheme\n  [SoundIo\n   (struct\n    [userdata void*] ; Optional. Put whatever you want here. Defaults to NULL.\n    [on_devices_change (* OnDeviceChangeCallback)] ; Optional callback.\n    [on_backend_disconnect (* OnBackendDisconnectCallback)] ; Optional callback.\n    [on_events_signal (* OnEventsSignalCallback)] ; Optional callback.\n    [current_backend SoundIoBackend] ; Read-only.\n    [app_name (* char)] ; Optional: Application name.\n    [emit_rtprio_warning (* EmitRtprioWarningCallback)] ; Optional: Real time priority warning.\n    [jack_info_callback (* JackInfoCallback)] ; Optional: JACK info callback.\n    [jack_error_callback (* JackErrorCallback)] ; Optional: JACK error callback.\n    )]\n#+END_SRC\n\n#+NAME: SoundIoChannelArea\n#+BEGIN_SRC scheme\n  [SoundIoChannelArea\n   (struct\n    [ptr (* char)]\n    [step int])]\n  ;; Useful for defining **SoundIoChannelArea in function ftype as (* *SoundIoChannelArea)\n  ;; nested * or its alias doesn't work:\n  ;; Exception: invalid (non-base) foreign-procedure argument ftype **SoundIoChannelArea\n  [*SoundIoChannelArea (* SoundIoChannelArea)]\n#+END_SRC\n\n#+NAME: SoundIoChannelLayout\n#+BEGIN_SRC scheme\n  [SoundIoChannelLayout\n   (struct\n     [name (* char)]\n     [channel_count int]\n     ;; #define SOUNDIO_MAX_CHANNELS 24\n     ;; http://libsound.io/doc-1.1.0/soundio_8h.html#a1bf1282c5d903085916f8ed6af174bdd\n     [channels (array 24 SoundIoChannelId)])]\n#+END_SRC\n\n#+NAME: SoundIoDevice\n#+BEGIN_SRC scheme\n  [SoundIoDevice\n   (struct\n    [soundio (* SoundIo)]\n    [id (* char)]\n    [name (* char)]\n    [aim SoundIoDeviceAim]\n    [layouts (* SoundIoChannelLayout)]\n    [layout_count int]\n    [current_layout SoundIoChannelLayout]\n    [formats (* SoundIoFormat)]\n    [format_count int]\n    [current_format SoundIoFormat]\n    [sample_rates (* SoundIoSampleRateRange)]\n    [sample_rate_count int]\n    [sample_rate_current int]\n    [software_latency_min double]\n    [software_latency_max double]\n    [software_latency_current double]\n    [is_raw boolean]\n    [ref_count int]\n    [probe_error int])]\n#+END_SRC\n\n#+NAME: SoundIoInStream\n#+BEGIN_SRC scheme\n  [SoundIoInStream\n   (struct\n     [device (* SoundIoDevice)]\n     [format SoundIoFormat]\n     [sample_rate int]\n     [layout SoundIoChannelLayout]\n     [software_latency double]\n     [userdata void*]\n     [read_callback (* ReadCallback)]\n     [overflow_callback (* OverflowCallback)]\n     [error_callback (* ErrorCallback)]\n     [name (* char)]\n     [non_terminal_hint boolean]\n     [bytes_per_frame int]\n     [bytes_per_sample int]\n     [layout_error int])]\n#+END_SRC\n\n#+NAME: SoundIoOutStream\n#+BEGIN_SRC scheme\n  [SoundIoOutStream\n   (struct\n     [device (* SoundIoDevice)]\n     [format SoundIoFormat]\n     [sample_rate int]\n     [layout SoundIoChannelLayout]\n     [software_latency double]\n     [volume float]\n     [userdata void*]\n     [write_callback (* WriteCallback)]\n     [underflow_callback (* UnderflowCallback)]\n     [error_callback (* ErrorCallback)]\n     [name (* char)]\n     [non_terminal_hint boolean]\n     [bytes_per_frame int]\n     [bytes_per_sample int]\n     [layout_error int])]\n#+END_SRC\n\n#+NAME: SoundIoSampleRateRange\n#+BEGIN_SRC scheme\n  [SoundIoSampleRateRange\n   (struct\n    [min int]\n    [max int])]\n#+END_SRC\n\n#+NAME: SoundIoRingBuffer\n#+BEGIN_SRC scheme\n  [SoundIoRingBuffer\n   (struct\n    [mem SoundIoOsMirroredMemory]\n    [write_offset SoundIoAtomicLong]\n    [read_offset SoundIoAtomicLong]\n    [capacity int])]\n#+END_SRC\n\n#+NAME: SoundIoOsMirroredMemory\n#+BEGIN_SRC scheme\n  [SoundIoOsMirroredMemory\n   (struct\n    [capacity size_t]\n    [address (* char)]\n    [priv void*])]\n#+END_SRC\n\n#+NAME: SoundIoAtomicLong\n#+BEGIN_SRC scheme\n  [SoundIoAtomicLong long]\n#+END_SRC\n\n#+NAME: ftype-structs\n#+BEGIN_SRC scheme\n  ;; \u003cftype-structs\u003e\n  \u003c\u003cSoundIo\u003e\u003e\n  \u003c\u003cSoundIoChannelArea\u003e\u003e\n  \u003c\u003cSoundIoChannelLayout\u003e\u003e\n  \u003c\u003cSoundIoDevice\u003e\u003e\n  \u003c\u003cSoundIoInStream\u003e\u003e\n  \u003c\u003cSoundIoOutStream\u003e\u003e\n  \u003c\u003cSoundIoSampleRateRange\u003e\u003e\n  \u003c\u003cSoundIoOsMirroredMemory\u003e\u003e\n  \u003c\u003cSoundIoAtomicLong\u003e\u003e\n  \u003c\u003cSoundIoRingBuffer\u003e\u003e\n  ;; \u003c/ftype-structs\u003e\n#+END_SRC\n\n*** Summa\n\n#+NAME: ftypes\n#+BEGIN_SRC scheme\n  ;; \u003cftypes\u003e\n  (define-ftype\n    \u003c\u003cftype-enums\u003e\u003e\n    \u003c\u003cftype-callbacks\u003e\u003e\n    \u003c\u003cftype-structs\u003e\u003e\n  )\n  ;; \u003c/ftypes\u003e\n#+END_SRC\n\n** Procedures\n\n   We are going to keep original names while defining foreign procedures, thus\n   let's write a macro to save few keystrokes:\n\n#+NAME: define-foreign-procedure\n#+BEGIN_SRC scheme\n  (define-syntax (define-foreign-procedure stx)\n    (syntax-case stx ()\n      [(_ [name args result])\n       #`(define name\n           (foreign-procedure\n            #,(symbol-\u003estring (syntax-\u003edatum #'name))\n            args\n            result))]\n      [(_ e ...)\n       #'(begin\n           (define-foreign-procedure e)\n           ...)]))\n#+END_SRC\n\n#+NAME: foreign-procedures\n#+BEGIN_SRC scheme\n  (define-foreign-procedure\n    [soundio_backend_count ((* SoundIo)) int]\n    [soundio_backend_name (SoundIoBackend) int]\n    [soundio_best_matching_channel_layout\n     ((* SoundIoChannelLayout) ; preferred_layouts\n      int                      ; preferred_layout_count\n      (* SoundIoChannelLayout) ; available_layouts\n      int                      ; available_layout_count\n      )\n     (* SoundIoChannelLayout)]\n    [soundio_channel_layout_builtin_count () int]\n    [soundio_channel_layout_detect_builtin ((* SoundIoChannelLayout)) boolean]\n    [soundio_channel_layout_equal ((* SoundIoChannelLayout) (* SoundIoChannelLayout)) boolean]\n    [soundio_channel_layout_find_channel ((* SoundIoChannelLayout) SoundIoChannelId) int]\n    [soundio_channel_layout_get_builtin (int) (* SoundIoChannelLayout)]\n    [soundio_channel_layout_get_default (#|channel_count|# int) (* SoundIoChannelLayout)]\n    [soundio_connect ((* SoundIo)) int]\n    [soundio_connect_backend ((* SoundIo) (* SoundIoBackend)) int]\n    [soundio_create () (* SoundIo)]\n    [soundio_default_input_device_index ((* SoundIo)) int]\n    [soundio_default_output_device_index ((* SoundIo)) int]\n    [soundio_destroy ((* SoundIo)) void]\n    [soundio_device_equal ((* SoundIoDevice) (* SoundIoDevice)) boolean]\n    [soundio_device_nearest_sample_rate ((* SoundIoDevice) int) int]\n    [soundio_device_ref ((* SoundIoDevice)) void]\n    [soundio_device_sort_channel_layouts ((* SoundIoDevice)) void]\n    [soundio_device_supports_format ((* SoundIoDevice) SoundIoFormat) boolean]\n    [soundio_device_supports_layout ((* SoundIoDevice) (* SoundIoChannelLayout)) boolean]\n    [soundio_device_supports_sample_rate ((* SoundIoDevice) int) boolean]\n    [soundio_device_unref ((* SoundIoDevice)) void]\n    [soundio_disconnect ((* SoundIo)) void]\n    [soundio_flush_events ((* SoundIo)) void]\n    [soundio_force_device_scan ((* SoundIo)) void]\n    [soundio_format_string (SoundIoFormat) string]\n    [soundio_get_backend ((* SoundIo) int) SoundIoBackend]\n    ;; [soundio_get_bytes_per_frame (SoundIoFormat #|channel_count|# int) int]\n    ;; [soundio_get_bytes_per_sample (SoundIoFormat) int]\n    ;; [soundio_get_bytes_per_second (SoundIoFormat #|channel_count|# int #|sample_rate|# int) int]\n    [soundio_get_channel_name (SoundIoChannelId) string]\n    [soundio_get_input_device ((* SoundIo) int) (* SoundIoDevice)]\n    [soundio_get_output_device ((* SoundIo) int) (* SoundIoDevice)]\n    [soundio_have_backend (SoundIoBackend) boolean]\n    [soundio_input_device_count ((* SoundIo)) int]\n    [soundio_instream_begin_read ((* SoundIoInStream) (* *SoundIoChannelArea) (* int)) int]\n    [soundio_instream_create ((* SoundIoDevice)) (* SoundIoInStream)]\n    [soundio_instream_destroy ((* SoundIoInStream)) void]\n    [soundio_instream_end_read ((* SoundIoInStream)) int]\n    [soundio_instream_get_latency ((* SoundIoInStream) (* double)) int]\n    [soundio_instream_open ((* SoundIoInStream)) int]\n    [soundio_instream_pause ((* SoundIoInStream) boolean) int]\n    [soundio_instream_start ((* SoundIoInStream)) int]\n    [soundio_output_device_count ((* SoundIo)) int]\n    [soundio_outstream_begin_write ((* SoundIoOutStream) (* *SoundIoChannelArea) (* int)) int]\n    [soundio_outstream_clear_buffer ((* SoundIoOutStream)) int]\n    [soundio_outstream_create ((* SoundIoDevice)) (* SoundIoOutStream)]\n    [soundio_outstream_destroy ((* SoundIoOutStream)) void]\n    [soundio_outstream_end_write ((* SoundIoOutStream)) int]\n    [soundio_outstream_get_latency ((* SoundIoOutStream) (* double)) int]\n    [soundio_outstream_open ((* SoundIoOutStream)) int]\n    [soundio_outstream_pause ((* SoundIoOutStream) boolean) int]\n    [soundio_outstream_start ((* SoundIoOutStream)) int]\n    [soundio_parse_channel_id ((* char) int) SoundIoChannelId]\n    [soundio_ring_buffer_advance_read_ptr ((* SoundIoRingBuffer) int) void]\n    [soundio_ring_buffer_advance_write_ptr ((* SoundIoRingBuffer) int) void]\n    [soundio_ring_buffer_capacity ((* SoundIoRingBuffer)) int]\n    [soundio_ring_buffer_clear ((* SoundIoRingBuffer)) void]\n    [soundio_ring_buffer_create ((* SoundIo) int) (* SoundIoRingBuffer)]\n    [soundio_ring_buffer_destroy ((* SoundIoRingBuffer)) void]\n    [soundio_ring_buffer_fill_count ((* SoundIoRingBuffer)) int]\n    [soundio_ring_buffer_free_count ((* SoundIoRingBuffer)) int]\n    [soundio_ring_buffer_read_ptr ((* SoundIoRingBuffer)) (* char)]\n    [soundio_ring_buffer_write_ptr ((* SoundIoRingBuffer)) (* char)]\n    [soundio_sort_channel_layouts ((* SoundIoChannelLayout) int) void]\n    [soundio_strerror (int) string]\n    [soundio_version_major () int]\n    [soundio_version_minor () int]\n    [soundio_version_patch () int]\n    [soundio_version_string () string]\n    [soundio_wait_events ((* SoundIo)) void]\n    [soundio_wakeup ((* SoundIo)) void])\n#+END_SRC\n\n** Summa\n\n#+NAME: ffi\n#+BEGIN_SRC scheme\n  ;; \u003cffi\u003e\n  \u003c\u003cload-library\u003e\u003e\n  \u003c\u003cftypes\u003e\u003e\n  \u003c\u003cdefine-foreign-procedure\u003e\u003e\n  \u003c\u003cforeign-procedures\u003e\u003e\n  ;; \u003c/ffi\u003e\n#+END_SRC\n\n* Higher-level wrapping\n\n  Though library is already usable for producing sound via Scheme there is still\n  plenty of boilerplate to abstract away. It's quite hard to cover all use\n  cases, the plan is to add features one by one based on real usage feedback.\n\n  Known limitations of current wrapper:\n\n  - it designed for threaded version and uses threads; though we could imagine\n    use case for libsoundio in non-threaded Chez (non-interactive sound\n    generation), we are interested in live-coding application and lean towards\n    it\n  - at the moment only =float= sample type is supported\n\n** C Bridge\n\n   To make library work in threaded version we need to build and load our\n   =bridge.c= helper.\n\n   First, we need to define how our file is called and where Scheme's headers\n   located.\n\n#+NAME: bridge-paths\n#+BEGIN_SRC scheme\n  ;; \u003cbridge-paths\u003e\n  (define bridge-source-filename \"bridge.c\")\n  (define bridge-library-filename \"libbridge.so\")\n  (define scheme-headers-path (format \"/usr/local/lib/csv9.5.5.5/~a\" (machine-type)))\n  ;; \u003c/bridge-paths\u003e\n#+END_SRC\n\n   In case library doesn't exist try to build it automatically.\n\n#+NAME: build-bridge\n#+BEGIN_SRC scheme\n  ;; \u003cbuild-bridge\u003e\n  (case (machine-type)\n    [(i3nt ti3nt a6nt ta6nt)\n     (begin\n       (error \"init-bridge\"\n              \"don't know how to build for Windows, look at the source for template to adjust\")\n       (system (format \"cl -c -DWIN32 ~a\"\n                       bridge-source-filename))\n       (system (format \"link -dll -out:~a ~a.obj\"\n                       bridge-library-filename\n                       bridge-source-filename)))]\n    [(i3osx ti3osx a6osx ta6osx tarm64osx)\n     (system (format \"cc -O3 -dynamiclib -Wl,-undefined -Wl,dynamic_lookup -I~a -lsoundio -o ~a ~a\"\n                     scheme-headers-path\n                     bridge-library-filename\n                     bridge-source-filename))]\n    [(i3le ti3le a6le ta6le)\n     (system (format \"cc -O3 -fPIC -shared -Wl,-undefined -Wl,dynamic_lookup -I~a -lsoundio -o ~a ~a.c\"\n                     scheme-headers-path\n                     bridge-library-filename\n                     bridge-source-filename))]\n    [else (error \"init-bridge\"\n                 \"don't know how to build bridge shared library on this machine-type\"\n                 (machine-type))])\n  ;; \u003c/build-bridge\u003e\n#+END_SRC\n\n   Machine type correspondence to platform could be found in [[https://cisco.github.io/ChezScheme/release_notes/v9.4/release_notes.html][release notes]].\n\n   We need to wrap loading shared library into define to make it work inside\n   R6RS =library= construct.\n\n#+NAME: init-bridge\n#+BEGIN_SRC scheme\n  ;; \u003cbuild-bridge\u003e\n  \u003c\u003cbridge-paths\u003e\u003e\n  (define init-bridge\n    (begin\n      (unless (file-exists? bridge-library-filename)\n        \u003c\u003cbuild-bridge\u003e\u003e\n        )\n      (load-shared-object bridge-library-filename)))\n  ;; \u003c/build-bridge\u003e\n#+END_SRC\n\n*** write_callback\n\n    Heart of the bridge is custom =write_callback= which draws samples from ring\n    buffer passed to it via stream's =userdata= field. To avoid underflows we\n    fill stream with zeros if buffer has not enough data.\n\n#+NAME: write_callback\n#+BEGIN_SRC c\n  // \u003cwrite_callback\u003e\n  static void write_callback(struct SoundIoOutStream *outstream, int frame_count_min, int frame_count_max) {\n    struct SoundIoRingBuffer *ring_buffer = outstream-\u003euserdata;\n    struct SoundIoChannelArea *areas;\n    int frame_count;\n    int frames_left;\n    int err;\n\n    char *read_ptr = soundio_ring_buffer_read_ptr(ring_buffer);\n    int fill_bytes = soundio_ring_buffer_fill_count(ring_buffer);\n    int fill_count = fill_bytes / outstream-\u003ebytes_per_frame;\n\n    if (frame_count_min \u003e fill_count) {\n      \u003c\u003cfill-stream-with-zeros\u003e\u003e\n    }\n\n    \u003c\u003ccopy-samples-from-buffer\u003e\u003e\n\n    soundio_ring_buffer_advance_read_ptr(ring_buffer, read_count * outstream-\u003ebytes_per_frame);\n  }\n  // \u003c/write_callback\u003e\n#+END_SRC\n\n    libsoundio examples suggest to guard actual write to stream with checks.\n\n#+NAME: begin-write\n#+BEGIN_SRC c\n  // \u003cbegin-write\u003e\n  if ((err = soundio_outstream_begin_write(outstream, \u0026areas, \u0026frame_count))) {\n    fprintf(stderr, \"begin_write: %s\\n\", soundio_strerror(err));\n    exit(1);\n  }\n  // \u003c/begin-write\u003e\n#+END_SRC\n\n#+NAME: end-write\n#+BEGIN_SRC c\n  // \u003cend-write\u003e\n  if ((err = soundio_outstream_end_write(outstream))) {\n    fprintf(stderr, \"end_write: %s\\n\", soundio_strerror(err));\n    // REVIEW pthread_exit?\n    exit(1);\n  }\n  // \u003c/end-write\u003e\n#+END_SRC\n\n#+NAME: copy-samples-from-buffer\n#+BEGIN_SRC c\n  // \u003ccopy-samples-from-buffer\u003e\n  int read_count = frame_count_max \u003c fill_count ? frame_count_max : fill_count;\n  frames_left = read_count;\n\n  while (frames_left \u003e 0) {\n    int frame_count = frames_left;\n\n    \u003c\u003cbegin-write\u003e\u003e\n\n    if (frame_count \u003c= 0)\n      break;\n\n    for (int frame = 0; frame \u003c frame_count; frame += 1) {\n      for (int ch = 0; ch \u003c outstream-\u003elayout.channel_count; ch += 1) {\n        memcpy(areas[ch].ptr, read_ptr, outstream-\u003ebytes_per_sample);\n        areas[ch].ptr += areas[ch].step;\n        read_ptr += outstream-\u003ebytes_per_sample;\n      }\n    }\n\n    \u003c\u003cend-write\u003e\u003e\n\n    frames_left -= frame_count;\n  }\n  // \u003c/copy-samples-from-buffer\u003e\n#+END_SRC\n\n#+NAME: fill-stream-with-zeros\n#+BEGIN_SRC c\n  // \u003cfill-stream-with-zeros\u003e\n  frames_left = frame_count_min;\n  for (;;) {\n    frame_count = frames_left;\n    if (!frame_count)\n      return;\n\n    \u003c\u003cbegin-write\u003e\u003e\n\n    if (!frame_count)\n      return;\n    for (int frame = 0; frame \u003c frame_count; frame += 1) {\n      for (int ch = 0; ch \u003c outstream-\u003elayout.channel_count; ch += 1) {\n        memset(areas[ch].ptr, 0, outstream-\u003ebytes_per_sample);\n        areas[ch].ptr += areas[ch].step;\n      }\n    }\n\n    \u003c\u003cend-write\u003e\u003e\n\n    frames_left -= frame_count;\n  }\n  // \u003c/fill-stream-with-zeros\u003e\n#+END_SRC\n\n*** bridge_outstream_attach_ring_buffer\n\n    It accepts =outstream= and =buffer= and sets =buffer= and our\n    =write_callback= to =outstream=.\n\n#+NAME: bridge_outstream_attach_ring_buffer\n#+BEGIN_SRC c\n  // \u003cbridge_outstream_attach_ring_buffer\u003e\n  EXPORT void bridge_outstream_attach_ring_buffer\n  (struct SoundIoOutStream *outstream, struct SoundIoRingBuffer *buffer) {\n    outstream-\u003eformat = SoundIoFormatFloat32NE;\n    outstream-\u003euserdata = buffer;\n    outstream-\u003ewrite_callback = write_callback;\n  }\n  // \u003c/bridge_outstream_attach_ring_buffer\u003e\n#+END_SRC\n\n*** usleep\n\n    It's a microsecond resolution sleep based on calling =select= with timeout.\n    It accepts =seconds= and =microseconds= to sleep as integers. It is used to\n    wait a little when buffer is full. It is also useful if you want to\n    implement high-resolution scheduler. I found out that using Scheme's =sleep=\n    which calls =nanosleep= under the hood is quite expensive and imprecise.\n\n    I'm not sure why it's needed to wrap =select= into Scheme thread\n    deactivation, but without it attempts to call =usleep= from different\n    threads leads to stops in sound.\n\n#+NAME: usleep\n#+BEGIN_SRC c\n  // \u003cusleep\u003e\n  EXPORT void usleep (long seconds, long microseconds) {\n    struct timeval timeout;\n    timeout.tv_sec = seconds;\n    timeout.tv_usec = microseconds;\n    Sdeactivate_thread();\n    select(0, NULL, NULL, NULL, \u0026timeout);\n    Sactivate_thread();\n  }\n  // \u003c/usleep\u003e\n#+END_SRC\n\n*** Define foreign procedures in Scheme\n\n#+NAME: bridge-ffi\n#+BEGIN_SRC scheme\n  ;; \u003cbridge-ffi\u003e\n  (define-foreign-procedure\n    [bridge_outstream_attach_ring_buffer ((* SoundIoOutStream) (* SoundIoRingBuffer)) void]\n    [usleep (long #|seconds|# long #|microseconds|#) void])\n  ;; \u003c/bridge-ffi\u003e\n#+END_SRC\n\n** Scheme\n\n  Most of the time I want just fire up default output device and provide\n  per-sample-per-channel dsp callback to make noise, and eventually stop doing\n  it. It would be good to have dedicated DS which will hold a bunch of pointers\n  created on the way.\n\n#+NAME: sound-out-record\n#+BEGIN_SRC scheme\n  ;; \u003csound-out-record\u003e\n  (define-record-type sound-out\n    (fields stream\n            ring-buffer\n            (mutable write-callback)\n            (mutable write-thread)))\n  ;; \u003c/sound-out-record\u003e\n#+END_SRC\n\n  Next step is to encapsulate all initialization routines.\n\n  As an experiment, let's go from the end to the beginning. Ultimate goal of\n  initialization is to have open output audio stream on default device. The\n  stream should have =write_callback= assigned but to be not started. We want to\n  ignit sound as a separate action. Also we want to return a bunch of pointers\n  packed into =sound-out= record to have access to them later: to start and stop\n  stream and to properly close and destroy stream.\n\n  =define-record-type= produced record constructor for us, just pass fields to\n  it:\n\n#+NAME: make-sound-out\n#+BEGIN_SRC scheme\n  ;; \u003cmake-sound-out\u003e\n  (printf \"Channels:\\t~s\\r\\n\" channel-count)\n  (printf \"Sample rate:\\t~s\\r\\n\" sample-rate)\n  (printf \"Latency:\\t~s\\r\\n\" latency)\n  (printf \"Buffer:\\t\\t~s\\r\\n\" buffer-size)\n  (make-sound-out out-stream ring-buffer write-callback #f)\n  ;; \u003c/make-sound-out\u003e\n#+END_SRC\n\n  Callbacks are set before stream start. We don't want user to bother with\n  pointer arithmetic and stuff, thus we wrap callbacks. Even more, threaded Chez\n  Scheme crashes when =write_callback= calls Scheme code. Thus we are going to\n  use ring buffer to build a bridge between systems. User's =write-callback=\n  will receive =timestamp= and =channel= and should return sample value.\n  =underflow-callback= is still to be implemented, because we moved to ring\n  buffer from direct callbacks which corrupted Scheme runtime.\n\n#+NAME: attach-buffer-to-stream\n#+BEGIN_SRC scheme\n  ;; \u003cattach-buffer-to-stream\u003e\n  (let* ([frame-size (ftype-sizeof float)]\n         [channel-count (ftype-ref SoundIoOutStream (layout channel_count) out-stream)]\n         [sample-rate (ftype-ref SoundIoOutStream (sample_rate) out-stream)]\n         [latency (ftype-ref SoundIoOutStream (software_latency) out-stream)]\n         [buffer-size (exact (ceiling (* latency sample-rate)))] ; in samples\n         [buffer-capacity (* buffer-size frame-size channel-count)] ; in bytes\n         [ring-buffer (soundio_ring_buffer_create sio buffer-capacity)])\n    (when (ftype-pointer-null? ring-buffer)\n      (error \"soundio_ring_buffer_create\" \"out of memory\"))\n    (bridge_outstream_attach_ring_buffer out-stream ring-buffer)\n    \u003c\u003cmake-sound-out\u003e\u003e\n    )\n  ;; \u003c/attach-buffer-to-stream\u003e\n#+END_SRC\n\n  It makes sense to attach buffer and return =sound-out= record if opening\n  stream was successful:\n\n#+NAME: try-open-stream\n#+BEGIN_SRC scheme\n  ;; \u003ctry-open-stream\u003e\n  (let ([err (soundio_outstream_open out-stream)])\n    (when (not (zero? err))\n      (error \"soundio_outstream_open\" (soundio_strerror err)))\n    (let ([err (ftype-ref SoundIoOutStream (layout_error) out-stream)])\n      (when (not (zero? err))\n        (error \"soundio_outstream_open\" (soundio_strerror err))))\n    \u003c\u003cattach-buffer-to-stream\u003e\u003e\n    )\n  ;; \u003c/try-open-stream\u003e\n#+END_SRC\n\n  Let's create stream before setting its callbacks:\n\n#+NAME: try-create-stream\n#+BEGIN_SRC scheme\n  ;; \u003ctry-create-stream\u003e\n  (let ([out-stream (soundio_outstream_create device)])\n    (when (ftype-pointer-null? out-stream)\n      (error \"soundio_outstream_create\" \"out of memory\"))\n    \u003c\u003ctry-open-stream\u003e\u003e\n    )\n  ;; \u003c/try-create-stream\u003e\n#+END_SRC\n\n  The same story with device, we need to obtain it before use:\n\n#+NAME: try-create-device\n#+BEGIN_SRC scheme\n  ;; \u003ctry-create-device\u003e\n  (let ([idx (soundio_default_output_device_index sio)])\n    (when (\u003c idx 0)\n      (error \"soundio_default_output_device_index\" \"no output device found\"))\n    (let ([device (soundio_get_output_device sio idx)])\n      (when (ftype-pointer-null? device)\n        (error \"soundio_get_output_device\" \"out of memory\"))\n      \u003c\u003ctry-create-stream\u003e\u003e\n      ))\n  ;; \u003c/try-create-device\u003e\n#+END_SRC\n\n  And sio instance is to be created and connected before device access. Note\n  flushing events.\n\n#+NAME: try-create-connect-sio\n#+BEGIN_SRC scheme\n  ;; \u003ctry-create-connect-sio\u003e\n  (let ([sio (soundio_create)])\n    (when (ftype-pointer-null? sio)\n      (error \"soundio_create\" \"out of memory\"))\n    (let ([err (soundio_connect sio)])\n      (when (not (zero? err))\n        (error \"soundio_connect\" (soundio_strerror err)))\n      (soundio_flush_events sio)\n      \u003c\u003ctry-create-device\u003e\u003e\n      ))\n  ;; \u003c/try-create-connect-sio\u003e\n#+END_SRC\n\n  Now just give it a name =)\n\n#+NAME: open-default-out-stream\n#+BEGIN_SRC scheme\n  ;; \u003copen-default-out-stream\u003e\n  (define (open-default-out-stream write-callback)\n    \u003c\u003ctry-create-connect-sio\u003e\u003e\n    )\n  ;; \u003c/open-default-out-stream\u003e\n#+END_SRC\n\n  Now we need to be able start stream, stop stream and teardown our audio\n  subsytem. Starting and stopping stream require managing thread responsible\n  for calling our dsp function and filling ring buffer.\n\n#+NAME: start-out-stream\n#+BEGIN_SRC scheme\n  ;; \u003cstart-out-stream\u003e\n  (define (start-out-stream sound-out)\n    (let* ([frame-size (ftype-sizeof float)]\n           [out-stream (sound-out-stream sound-out)]\n           [channel-count (ftype-ref SoundIoOutStream (layout channel_count) out-stream)]\n           [sample-rate (ftype-ref SoundIoOutStream (sample_rate) out-stream)]\n           [seconds-per-sample (inexact (/ sample-rate))]\n           [ring-buffer (sound-out-ring-buffer sound-out)]\n           [polling-microseconds 1000]\n           [sample-number 0])\n      (sound-out-write-thread-set! sound-out (get-thread-id))\n      (fork-thread\n       (lambda ()\n         (let loop ()\n           (let ([write-callback (sound-out-write-callback sound-out)])\n             (when (sound-out-write-thread sound-out)\n               (let ([free-count (soundio_ring_buffer_free_count ring-buffer)])\n                 (if (zero? free-count)\n                     (begin\n                       (usleep 0 polling-microseconds)\n                       (loop))\n                     (let ([free-frames (/ free-count frame-size channel-count)]\n                           [write-ptr (ftype-pointer-address (soundio_ring_buffer_write_ptr ring-buffer))])\n                       (do ([frame 0 (+ frame 1)])\n                           ((= frame free-frames) 0)\n                         (let* ([sample-number (+ sample-number frame)]\n                                [time (fl* (fixnum-\u003eflonum sample-number) seconds-per-sample)])\n                           (do ([channel 0 (+ channel 1)])\n                               ((= channel channel-count) 0)\n                             (foreign-set!\n                              'float\n                              write-ptr\n                              (* (+ (* frame channel-count) channel) frame-size)\n                              (write-callback time channel))\n                             )))\n                       (soundio_ring_buffer_advance_write_ptr ring-buffer free-count)\n                       (set! sample-number (+ sample-number free-frames))\n                       (loop))\n                     )))))))\n      (soundio_outstream_start out-stream)))\n  ;; \u003c/start-out-stream\u003e\n#+END_SRC\n\n#+NAME: stop-out-stream\n#+BEGIN_SRC scheme\n  ;; \u003cstop-out-stream\u003e\n  (define (stop-out-stream sound-out)\n    (sound-out-write-thread-set! sound-out #f)\n    (soundio_outstream_pause (sound-out-stream sound-out) #t))\n  ;; \u003c/stop-out-stream\u003e\n#+END_SRC\n\n  Unmounting entire system require more actions. We are to destroy stream, unref\n  device, destroy sio and ring buffer.\n\n#+NAME: teardown-out-stream\n#+BEGIN_SRC scheme\n  ;; \u003cteardown-out-stream\u003e\n  (define (teardown-out-stream sound-out)\n    (let* ([stream (sound-out-stream sound-out)]\n           [ring-buffer (sound-out-ring-buffer sound-out)]\n           [device (ftype-ref SoundIoOutStream (device) stream)]\n           [soundio (ftype-ref SoundIoDevice (soundio) device)])\n      (soundio_outstream_destroy stream)\n      (soundio_ring_buffer_destroy ring-buffer)\n      (soundio_device_unref device)\n      (soundio_destroy soundio)))\n  ;; \u003c/teardown-out-stream\u003e\n#+END_SRC\n\n#+NAME: channel-count\n#+BEGIN_SRC scheme\n  ;; \u003cchannel-count\u003e\n  (define (channel-count sound-out)\n    (ftype-ref SoundIoOutStream\n               (layout channel_count)\n               (sound-out-stream sound-out)))\n  ;; \u003c/channel-count\u003e\n#+END_SRC\n\n#+NAME: sample-rate\n#+BEGIN_SRC scheme\n  ;; \u003csample-rate\u003e\n  (define (sample-rate sound-out)\n    (ftype-ref SoundIoOutStream\n               (sample_rate)\n               (sound-out-stream sound-out)))\n  ;; \u003c/sample-rate\u003e\n#+END_SRC\n\n** Summa\n\n#+NAME: high-level-wrapper\n#+BEGIN_SRC scheme\n  ;; \u003chigh-level-wrapper\u003e\n  \u003c\u003cinit-bridge\u003e\u003e\n  \u003c\u003cbridge-ffi\u003e\u003e\n  \u003c\u003csound-out-record\u003e\u003e\n  \u003c\u003copen-default-out-stream\u003e\u003e\n  \u003c\u003cstart-out-stream\u003e\u003e\n  \u003c\u003cstop-out-stream\u003e\u003e\n  \u003c\u003cteardown-out-stream\u003e\u003e\n  \u003c\u003cchannel-count\u003e\u003e\n  \u003c\u003csample-rate\u003e\u003e\n  ;; \u003c/high-level-wrapper\u003e\n#+END_SRC\n\n* Helpers\n\n  =make-ftype-pointer= locks object as pointed [[https://cisco.github.io/ChezScheme/csug9.4/foreign.html][here]], and its manual unlocking is\n  required to prevent memory leaks. It's done by 3 levels deep call of core\n  functions, thus we are going to define a dedicated function for it.\n\n#+NAME: unlock-ftype-pointer\n#+BEGIN_SRC scheme\n  ;; \u003cunlock-ftype-pointer\u003e\n  (define (unlock-ftype-pointer fptr)\n    (unlock-object\n     (foreign-callable-code-object\n      (ftype-pointer-address fptr))))\n  ;; \u003c/unlock-ftype-pointer\u003e\n#+END_SRC\n\n** Summa\n\n#+NAME: helpers\n#+BEGIN_SRC scheme\n  ;; \u003chelpers\u003e\n  \u003c\u003cunlock-ftype-pointer\u003e\u003e\n  ;; \u003c/helpers\u003e\n#+END_SRC\n\n* Example\n\n  Let's play a bunch of sine waves (and test performance on the way).\n\n#+NAME: sine-example.ss\n#+BEGIN_SRC scheme :tangle sine-example.ss :noweb yes :mkdirp yes :paddle no\n  (import (prefix (soundio) soundio:))\n\n  (define pi 3.1415926535)\n\n  (define two-pi (* 2 pi))\n\n  (define sine (lambda (time freq)\n                 (sin (* two-pi freq time))))\n\n  (define square (lambda (time freq)\n                   (let ([ft (* two-pi freq time)])\n                     (+ (- (* 2 (floor ft))\n                           (floor (* 2 ft)))\n                        1))))\n\n  (define write-callback (lambda (time channel)\n                           (let ([k 100]\n                                 [sample 0.0])\n                             (do ([i 0 (+ i 1)]\n                                  [sample 0.0 (+ sample (sine time (+ 440.0 i)))])\n                                 ((= i k) (/ sample k))))))\n\n  (define square-callback (lambda (time channel)\n                            (let ([k 20]\n                                  [sample 0.0])\n                              (do ([i 0 (+ i 1)]\n                                   [sample 0.0 (+ sample (square time (+ 440.0 i)))])\n                                  ((= i k) (/ sample k 2))))))\n\n  (define my-out (soundio:open-default-out-stream write-callback))\n\n  (soundio:start-out-stream my-out)\n#+END_SRC\n\n* License and Contribution\n\n  Contribution is more than welcome in any form. If you don't want to bother\n  youself dealing with org-mode (though it worth trying!), just patch generated\n  files included in repo and make PR. I'll incorporate changes into org file\n  then.\n\n#+BEGIN_SRC text :tangle LICENSE\nISC License\n\nCopyright (c) 2017, Ruslan Prokopchuk\n\nPermission to use, copy, modify, and/or distribute this software for any\npurpose with or without fee is hereby granted, provided that the above\ncopyright notice and this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE\nOR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\nPERFORMANCE OF THIS SOFTWARE.\n#+END_SRC\n\n* Files :noexport:\n#+BEGIN_SRC scheme :tangle soundio-ffi.ss :noweb yes :mkdirp yes :paddle no\n  \u003c\u003chelpers\u003e\u003e\n  \u003c\u003cffi\u003e\u003e\n#+END_SRC\n\n#+BEGIN_SRC scheme :tangle soundio.ss :noweb yes :mkdirp yes :paddle no\n  (library (soundio (1))\n    (export open-default-out-stream\n            start-out-stream\n            stop-out-stream\n            teardown-out-stream\n            sample-rate\n            channel-count\n            usleep)\n    (import (chezscheme))\n    (include \"soundio-ffi.ss\")\n    \u003c\u003chigh-level-wrapper\u003e\u003e\n  )\n#+END_SRC\n\n#+NAME: bridge.c\n#+BEGIN_SRC C :tangle bridge.c :noweb yes :mkdirp yes :paddle no\n  #ifdef WIN32\n  #define EXPORT extern __declspec (dllexport)\n  #else\n  #define EXPORT extern\n  #endif\n\n  #include \u003csys/select.h\u003e\n  #include \u003csoundio/soundio.h\u003e\n  #include \u003cstdio.h\u003e\n  #include \u003cstdlib.h\u003e\n  #include \u003cstring.h\u003e\n  #include \u003cmath.h\u003e\n\n  #include \"scheme.h\"\n\n  \u003c\u003cwrite_callback\u003e\u003e\n  \u003c\u003cbridge_outstream_attach_ring_buffer\u003e\u003e\n  \u003c\u003cusleep\u003e\u003e\n#+END_SRC\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ful%2Fchez-soundio","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ful%2Fchez-soundio","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ful%2Fchez-soundio/lists"}