Add audio module and more graphics primitives
Audio module (via sokolaudio + stbvorbis): - Sound effects loaded into memory for low-latency playback - Music streaming from OGG files - Volume control, mute/unmute, pause/resume - Test app with keyboard controls
Graphics additions: - Drawing: draw-line, draw-point, draw-triangle, fill-triangle - Transform stack: push/pop, translate, rotate, scale
src/c/audio.c | 561 ++++++++++++++++-
src/c/graphics.c | 253 ++++++++
src/sigil/studio/audio.sgl | 21 +-
src/sigil/studio/graphics.sgl | 7 +
test/music.ogg | Bin 0 -> 133623 bytes
test/test-audio.sgl | 86 +++
vendor/stb/stb_vorbis.c | 5584 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
7 files changed, 6507 insertions(+), 5 deletions(-)src/c/audio.cmodified
* audio.c - Sigil Studio Audio Module * * Wraps sokol_audio.h to provide audio playback capabilities. * This is a stub for now - full implementation later. * Uses stb_vorbis for OGG decoding. * * Sound effects are loaded entirely into memory. * Music is streamed from disk via stb_vorbis. */#include "studio-internal.h"#include "sigil-internal.h"#include <stdio.h>#include <stdlib.h>#include <string.h>#include <math.h>/* Sokol headers (implementation is in sokol.c) */#include "sokol_audio.h"/* stb_vorbis for OGG decoding (implementation in stb_impl.c) */#include "stb_vorbis.c"/* ============================================================ * CONSTANTS * ============================================================ */#define MAX_SOUNDS 64#define MAX_PLAYING_SOUNDS 16#define STREAM_BUFFER_SAMPLES 4096/* ============================================================ * DATA STRUCTURES * ============================================================ *//* Sound effect - fully loaded into memory */typedef struct { float *samples; /* Interleaved stereo samples */ int num_samples; /* Total samples (frames * channels) */ int sample_rate; int channels;} StudioSound;/* Playing sound instance */typedef struct { StudioSound *sound; int position; /* Current playback position */ float volume; float pan; /* -1.0 left, 0.0 center, 1.0 right */ bool playing; bool loop;} PlayingSound;/* Music stream - decoded on the fly */typedef struct { stb_vorbis *vorbis; char *filepath; /* For reopening if looping */ float volume; bool playing; bool loop; bool paused;} MusicStream;/* ============================================================ * GLOBAL STATE * ============================================================ */static PlayingSound g_playing_sounds[MAX_PLAYING_SOUNDS];static MusicStream g_music = {0};static float g_master_volume = 1.0f;static bool g_muted = false;/* Type tags for foreign objects */static Value sound_type_tag = SIGIL_UNDEFINED;/* ============================================================ * AUDIO CALLBACK * ============================================================ */static void audio_callback(float *buffer, int num_frames, int num_channels){ /* Clear buffer */ memset(buffer, 0, num_frames * num_channels * sizeof(float)); if (g_muted) return; float master = g_master_volume; /* Mix playing sounds */ for (int i = 0; i < MAX_PLAYING_SOUNDS; i++) { PlayingSound *ps = &g_playing_sounds[i]; if (!ps->playing || !ps->sound) continue; StudioSound *snd = ps->sound; float vol = ps->volume * master; /* Calculate pan gains */ float pan = ps->pan; float left_gain = vol * (pan <= 0 ? 1.0f : 1.0f - pan); float right_gain = vol * (pan >= 0 ? 1.0f : 1.0f + pan); for (int f = 0; f < num_frames; f++) { if (ps->position >= snd->num_samples / snd->channels) { if (ps->loop) { ps->position = 0; } else { ps->playing = false; break; } } float left, right; if (snd->channels == 1) { /* Mono */ left = right = snd->samples[ps->position]; } else { /* Stereo */ left = snd->samples[ps->position * 2]; right = snd->samples[ps->position * 2 + 1]; } if (num_channels >= 2) { buffer[f * num_channels] += left * left_gain; buffer[f * num_channels + 1] += right * right_gain; } else { buffer[f] += (left + right) * 0.5f * vol; } ps->position++; } } /* Mix music stream */ if (g_music.playing && !g_music.paused && g_music.vorbis) { float vol = g_music.volume * master; float temp[STREAM_BUFFER_SAMPLES * 2]; int samples_needed = num_frames; int offset = 0; while (samples_needed > 0) { int to_decode = samples_needed < STREAM_BUFFER_SAMPLES ? samples_needed : STREAM_BUFFER_SAMPLES; int decoded = stb_vorbis_get_samples_float_interleaved( g_music.vorbis, 2, temp, to_decode * 2); if (decoded == 0) { /* End of file */ if (g_music.loop && g_music.filepath) { /* Reopen and continue */ stb_vorbis_close(g_music.vorbis); int error; g_music.vorbis = stb_vorbis_open_filename( g_music.filepath, &error, NULL); if (!g_music.vorbis) { g_music.playing = false; break; } continue; } else { g_music.playing = false; break; } } /* Mix decoded samples */ for (int f = 0; f < decoded; f++) { int buf_idx = (offset + f) * num_channels; if (num_channels >= 2) { buffer[buf_idx] += temp[f * 2] * vol; buffer[buf_idx + 1] += temp[f * 2 + 1] * vol; } else { buffer[buf_idx] += (temp[f * 2] + temp[f * 2 + 1]) * 0.5f * vol; } } samples_needed -= decoded; offset += decoded; } } /* Clamp output */ for (int i = 0; i < num_frames * num_channels; i++) { if (buffer[i] > 1.0f) buffer[i] = 1.0f; if (buffer[i] < -1.0f) buffer[i] = -1.0f; }}/* ============================================================ * HELPER FUNCTIONS * ============================================================ */static void ensure_sound_type(SigilVM *vm){ if (sigil_is_undefined(sound_type_tag)) { sound_type_tag = sigil_intern_symbol(vm, "sigil-studio-sound", 18); }}static StudioSound *get_sound(SigilVM *vm, Value v){ if (!sigil_is_foreign(v)) return NULL; ensure_sound_type(vm); if (sigil_foreign_type(v) != sound_type_tag) return NULL; return (StudioSound *)sigil_foreign_data(v);}static void sound_destructor(void *data){ StudioSound *snd = (StudioSound *)data; if (snd) { free(snd->samples); free(snd); }}static PlayingSound *find_free_slot(void){ for (int i = 0; i < MAX_PLAYING_SOUNDS; i++) { if (!g_playing_sounds[i].playing) { return &g_playing_sounds[i]; } } return NULL;}/* ============================================================ * NATIVE FUNCTIONS * NATIVE FUNCTIONS - SETUP * ============================================================ *//* return SIGIL_NIL; } saudio_desc desc = {0}; saudio_desc desc = { .stream_cb = audio_callback, .num_channels = 2, .sample_rate = 44100, .buffer_frames = 2048 }; saudio_setup(&desc); /* Clear playing sounds */ memset(g_playing_sounds, 0, sizeof(g_playing_sounds)); /* Clear music */ memset(&g_music, 0, sizeof(g_music)); g_music.volume = 1.0f; g_master_volume = 1.0f; g_muted = false; if (g_studio) { g_studio->audio_initialized = true; } (void)vm; (void)argc; (void)args; if (g_studio && g_studio->audio_initialized) { /* Stop music */ if (g_music.vorbis) { stb_vorbis_close(g_music.vorbis); g_music.vorbis = NULL; } free(g_music.filepath); g_music.filepath = NULL; saudio_shutdown(); g_studio->audio_initialized = false; } return g_studio->audio_initialized ? SIGIL_TRUE : SIGIL_FALSE;}/* ============================================================ * NATIVE FUNCTIONS - SOUNDS * ============================================================ *//* * (load-sound path) -> <sound> or #f * * Load an OGG file entirely into memory. */static Value native_load_sound(SigilVM *vm, int argc, Value *args){ if (argc < 1 || !sigil_is_string(args[0])) { sigil__vm_set_error(vm, SIGIL_ERR_TYPE, "load-sound: expected string path"); return SIGIL_FALSE; } SigilString *path_str = (SigilString *)sigil_as_ptr(args[0]); const char *path = path_str->data; int channels, sample_rate; short *raw_samples; int num_samples = stb_vorbis_decode_filename(path, &channels, &sample_rate, &raw_samples); if (num_samples < 0) { return SIGIL_FALSE; } /* Convert to float */ int total_samples = num_samples * channels; float *samples = malloc(total_samples * sizeof(float)); if (!samples) { free(raw_samples); return SIGIL_FALSE; } for (int i = 0; i < total_samples; i++) { samples[i] = raw_samples[i] / 32768.0f; } free(raw_samples); StudioSound *snd = malloc(sizeof(StudioSound)); if (!snd) { free(samples); return SIGIL_FALSE; } snd->samples = samples; snd->num_samples = total_samples; snd->sample_rate = sample_rate; snd->channels = channels; ensure_sound_type(vm); return sigil_make_foreign(vm, sound_type_tag, snd, sound_destructor, sizeof(StudioSound) + total_samples * sizeof(float));}/* * (sound? obj) -> boolean */static Value native_sound_p(SigilVM *vm, int argc, Value *args){ (void)argc; return get_sound(vm, args[0]) ? SIGIL_TRUE : SIGIL_FALSE;}/* * (play-sound sound [volume] [pan] [loop?]) -> boolean * * Play a sound effect. Returns #t if started, #f if no slots available. */static Value native_play_sound(SigilVM *vm, int argc, Value *args){ if (argc < 1) { sigil__vm_set_error(vm, SIGIL_ERR_ARITY, "play-sound: requires sound argument"); return SIGIL_FALSE; } StudioSound *snd = get_sound(vm, args[0]); if (!snd) { sigil__vm_set_error(vm, SIGIL_ERR_TYPE, "play-sound: expected sound"); return SIGIL_FALSE; } PlayingSound *ps = find_free_slot(); if (!ps) { return SIGIL_FALSE; /* No slots available */ } ps->sound = snd; ps->position = 0; ps->volume = argc > 1 ? (float)sigil_as_flonum(args[1]) : 1.0f; ps->pan = argc > 2 ? (float)sigil_as_flonum(args[2]) : 0.0f; ps->loop = argc > 3 ? sigil_is_true(args[3]) : false; ps->playing = true; return SIGIL_TRUE;}/* * (stop-all-sounds) - Stop all playing sound effects */static Value native_stop_all_sounds(SigilVM *vm, int argc, Value *args){ (void)vm; (void)argc; (void)args; for (int i = 0; i < MAX_PLAYING_SOUNDS; i++) { g_playing_sounds[i].playing = false; } return SIGIL_NIL;}/* ============================================================ * NATIVE FUNCTIONS - MUSIC * ============================================================ *//* * (play-music path [loop?]) -> boolean * * Start streaming music from an OGG file. */static Value native_play_music(SigilVM *vm, int argc, Value *args){ if (argc < 1 || !sigil_is_string(args[0])) { sigil__vm_set_error(vm, SIGIL_ERR_TYPE, "play-music: expected string path"); return SIGIL_FALSE; } /* Stop any existing music */ if (g_music.vorbis) { stb_vorbis_close(g_music.vorbis); g_music.vorbis = NULL; } free(g_music.filepath); g_music.filepath = NULL; SigilString *path_str = (SigilString *)sigil_as_ptr(args[0]); const char *path = path_str->data; int error; g_music.vorbis = stb_vorbis_open_filename(path, &error, NULL); if (!g_music.vorbis) { return SIGIL_FALSE; } g_music.filepath = strdup(path); g_music.loop = argc > 1 ? sigil_is_true(args[1]) : true; g_music.playing = true; g_music.paused = false; return SIGIL_TRUE;}/* * (stop-music) - Stop music playback */static Value native_stop_music(SigilVM *vm, int argc, Value *args){ (void)vm; (void)argc; (void)args; if (g_music.vorbis) { stb_vorbis_close(g_music.vorbis); g_music.vorbis = NULL; } free(g_music.filepath); g_music.filepath = NULL; g_music.playing = false; return SIGIL_NIL;}/* * (pause-music) - Pause music playback */static Value native_pause_music(SigilVM *vm, int argc, Value *args){ (void)vm; (void)argc; (void)args; g_music.paused = true; return SIGIL_NIL;}/* * (resume-music) - Resume music playback */static Value native_resume_music(SigilVM *vm, int argc, Value *args){ (void)vm; (void)argc; (void)args; g_music.paused = false; return SIGIL_NIL;}/* * (music-playing?) -> boolean */static Value native_music_playing(SigilVM *vm, int argc, Value *args){ (void)vm; (void)argc; (void)args; return (g_music.playing && !g_music.paused) ? SIGIL_TRUE : SIGIL_FALSE;}/* * (set-music-volume volume) - Set music volume (0.0 to 1.0) */static Value native_set_music_volume(SigilVM *vm, int argc, Value *args){ if (argc < 1) { sigil__vm_set_error(vm, SIGIL_ERR_ARITY, "set-music-volume: requires volume"); return SIGIL_NIL; } float vol = (float)sigil_as_flonum(args[0]); if (vol < 0.0f) vol = 0.0f; if (vol > 1.0f) vol = 1.0f; g_music.volume = vol; return SIGIL_NIL;}/* ============================================================ * NATIVE FUNCTIONS - GLOBAL CONTROL * ============================================================ */Showing the first 500 of 610 diff lines for this file. This diff is INCOMPLETE; read the file or clone the repository for the rest.
src/c/graphics.cmodified
return SIGIL_NIL;}/* * (draw-line x1 y1 x2 y2) - Draw a line */static Value native_draw_line(SigilVM *vm, int argc, Value *args){ if (argc < 4) { sigil__vm_error(vm, SIGIL_ERR_ARITY, "draw-line: requires x1, y1, x2, y2 arguments"); return SIGIL_UNDEFINED; } float x1 = value_to_float(args[0]); float y1 = value_to_float(args[1]); float x2 = value_to_float(args[2]); float y2 = value_to_float(args[3]); sgp_line line = {{x1, y1}, {x2, y2}}; sgp_draw_lines(&line, 1); return SIGIL_NIL;}/* * (draw-point x y) - Draw a single point */static Value native_draw_point(SigilVM *vm, int argc, Value *args){ if (argc < 2) { sigil__vm_error(vm, SIGIL_ERR_ARITY, "draw-point: requires x, y arguments"); return SIGIL_UNDEFINED; } float x = value_to_float(args[0]); float y = value_to_float(args[1]); sgp_point pt = {x, y}; sgp_draw_points(&pt, 1); return SIGIL_NIL;}/* * (draw-triangle x1 y1 x2 y2 x3 y3) - Draw a triangle outline */static Value native_draw_triangle(SigilVM *vm, int argc, Value *args){ if (argc < 6) { sigil__vm_error(vm, SIGIL_ERR_ARITY, "draw-triangle: requires x1, y1, x2, y2, x3, y3"); return SIGIL_UNDEFINED; } float x1 = value_to_float(args[0]); float y1 = value_to_float(args[1]); float x2 = value_to_float(args[2]); float y2 = value_to_float(args[3]); float x3 = value_to_float(args[4]); float y3 = value_to_float(args[5]); sgp_line lines[3] = { {{x1, y1}, {x2, y2}}, {{x2, y2}, {x3, y3}}, {{x3, y3}, {x1, y1}} }; sgp_draw_lines(lines, 3); return SIGIL_NIL;}/* * (fill-triangle x1 y1 x2 y2 x3 y3) - Draw a filled triangle */static Value native_fill_triangle(SigilVM *vm, int argc, Value *args){ if (argc < 6) { sigil__vm_error(vm, SIGIL_ERR_ARITY, "fill-triangle: requires x1, y1, x2, y2, x3, y3"); return SIGIL_UNDEFINED; } float x1 = value_to_float(args[0]); float y1 = value_to_float(args[1]); float x2 = value_to_float(args[2]); float y2 = value_to_float(args[3]); float x3 = value_to_float(args[4]); float y3 = value_to_float(args[5]); sgp_triangle tri = {{x1, y1}, {x2, y2}, {x3, y3}}; sgp_draw_filled_triangles(&tri, 1); return SIGIL_NIL;}/* ============================================================ * TRANSFORM STACK * ============================================================ *//* * (push-transform) - Save current transform state */static Value native_push_transform(SigilVM *vm, int argc, Value *args){ (void)vm; (void)argc; (void)args; sgp_push_transform(); return SIGIL_NIL;}/* * (pop-transform) - Restore previous transform state */static Value native_pop_transform(SigilVM *vm, int argc, Value *args){ (void)vm; (void)argc; (void)args; sgp_pop_transform(); return SIGIL_NIL;}/* * (reset-transform) - Reset to identity transform */static Value native_reset_transform(SigilVM *vm, int argc, Value *args){ (void)vm; (void)argc; (void)args; sgp_reset_transform(); return SIGIL_NIL;}/* * (translate x y) - Translate by (x, y) */static Value native_translate(SigilVM *vm, int argc, Value *args){ if (argc < 2) { sigil__vm_error(vm, SIGIL_ERR_ARITY, "translate: requires x, y arguments"); return SIGIL_UNDEFINED; } float x = value_to_float(args[0]); float y = value_to_float(args[1]); sgp_translate(x, y); return SIGIL_NIL;}/* * (rotate angle) - Rotate by angle (in radians) */static Value native_rotate(SigilVM *vm, int argc, Value *args){ if (argc < 1) { sigil__vm_error(vm, SIGIL_ERR_ARITY, "rotate: requires angle argument"); return SIGIL_UNDEFINED; } float angle = value_to_float(args[0]); sgp_rotate(angle); return SIGIL_NIL;}/* * (rotate-at angle x y) - Rotate around point (x, y) */static Value native_rotate_at(SigilVM *vm, int argc, Value *args){ if (argc < 3) { sigil__vm_error(vm, SIGIL_ERR_ARITY, "rotate-at: requires angle, x, y arguments"); return SIGIL_UNDEFINED; } float angle = value_to_float(args[0]); float x = value_to_float(args[1]); float y = value_to_float(args[2]); sgp_rotate_at(angle, x, y); return SIGIL_NIL;}/* * (scale sx sy) - Scale by (sx, sy) */static Value native_scale(SigilVM *vm, int argc, Value *args){ if (argc < 2) { sigil__vm_error(vm, SIGIL_ERR_ARITY, "scale: requires sx, sy arguments"); return SIGIL_UNDEFINED; } float sx = value_to_float(args[0]); float sy = value_to_float(args[1]); sgp_scale(sx, sy); return SIGIL_NIL;}/* * (scale-at sx sy x y) - Scale around point (x, y) */static Value native_scale_at(SigilVM *vm, int argc, Value *args){ if (argc < 4) { sigil__vm_error(vm, SIGIL_ERR_ARITY, "scale-at: requires sx, sy, x, y arguments"); return SIGIL_UNDEFINED; } float sx = value_to_float(args[0]); float sy = value_to_float(args[1]); float x = value_to_float(args[2]); float y = value_to_float(args[3]); sgp_scale_at(sx, sy, x, y); return SIGIL_NIL;}/* * (gfx-initialized?) -> boolean */ SIGIL_ARITY_EXACT(4), "Draw filled rectangle"); sigil_module_register_native(vm, "draw-rect", native_draw_rect, SIGIL_ARITY_EXACT(4), "Draw rectangle outline"); sigil_module_register_native(vm, "draw-line", native_draw_line, SIGIL_ARITY_EXACT(4), "Draw a line"); sigil_module_register_native(vm, "draw-point", native_draw_point, SIGIL_ARITY_EXACT(2), "Draw a point"); sigil_module_register_native(vm, "draw-triangle", native_draw_triangle, SIGIL_ARITY_EXACT(6), "Draw triangle outline"); sigil_module_register_native(vm, "fill-triangle", native_fill_triangle, SIGIL_ARITY_EXACT(6), "Draw filled triangle"); /* Transform stack */ sigil_module_register_native(vm, "push-transform", native_push_transform, SIGIL_ARITY_EXACT(0), "Save transform state"); sigil_module_register_native(vm, "pop-transform", native_pop_transform, SIGIL_ARITY_EXACT(0), "Restore transform state"); sigil_module_register_native(vm, "reset-transform", native_reset_transform, SIGIL_ARITY_EXACT(0), "Reset to identity transform"); sigil_module_register_native(vm, "translate", native_translate, SIGIL_ARITY_EXACT(2), "Translate by (x, y)"); sigil_module_register_native(vm, "rotate", native_rotate, SIGIL_ARITY_EXACT(1), "Rotate by angle (radians)"); sigil_module_register_native(vm, "rotate-at", native_rotate_at, SIGIL_ARITY_EXACT(3), "Rotate around point"); sigil_module_register_native(vm, "scale", native_scale, SIGIL_ARITY_EXACT(2), "Scale by (sx, sy)"); sigil_module_register_native(vm, "scale-at", native_scale_at, SIGIL_ARITY_EXACT(4), "Scale around point"); /* Texture functions */ sigil_module_register_native(vm, "load-texture", native_load_texture, sigil_module_export(vm, "set-color"); sigil_module_export(vm, "draw-filled-rect"); sigil_module_export(vm, "draw-rect"); sigil_module_export(vm, "draw-line"); sigil_module_export(vm, "draw-point"); sigil_module_export(vm, "draw-triangle"); sigil_module_export(vm, "fill-triangle"); sigil_module_export(vm, "push-transform"); sigil_module_export(vm, "pop-transform"); sigil_module_export(vm, "reset-transform"); sigil_module_export(vm, "translate"); sigil_module_export(vm, "rotate"); sigil_module_export(vm, "rotate-at"); sigil_module_export(vm, "scale"); sigil_module_export(vm, "scale-at"); sigil_module_export(vm, "load-texture"); sigil_module_export(vm, "texture?"); sigil_module_export(vm, "texture-width");src/sigil/studio/audio.sglmodified
;;; (sigil studio audio) - Audio Module;;;;;; Provides audio playback capabilities via sokol_audio.;;; Uses stb_vorbis for OGG audio decoding.;;;;;; Sound effects are loaded entirely into memory for low-latency playback.;;; Music is streamed from disk for memory efficiency.;;;;;; Native functions are registered by audio.c.(define-library (sigil studio audio) (export ;; Setup/shutdown audio-setup audio-shutdown audio-initialized?) audio-setup audio-shutdown audio-initialized? ;; Sound effects (loaded into memory) load-sound sound? play-sound stop-all-sounds ;; Music streaming play-music stop-music pause-music resume-music music-playing? set-music-volume ;; Global control set-master-volume mute-audio unmute-audio audio-muted?) (begin ;; Native functions are automatically available after native module init. ;; Full audio API (load-sound, play-sound, etc.) to be added later. ))src/sigil/studio/graphics.sglmodified
;; Drawing clear-screen set-color draw-filled-rect draw-rect draw-line draw-point draw-triangle fill-triangle ;; Transform stack push-transform pop-transform reset-transform translate rotate rotate-at scale scale-at ;; Textures load-texturetest/music.oggbinary
Binary. Not rendered here.
test/test-audio.sgladded
;;; test-audio.sgl - Test audio playback(import (sigil core) (sigil studio app) (sigil studio graphics) (sigil studio audio) (sigil studio font))(define *font* #f)(define *music-playing* #f)(define *volume* 1.0)(define (on-init) (display "Initializing audio...\n") (audio-setup) (set! *font* (load-font "test/Saucer.ttf" 24)) ;; Start music (display "Playing music...\n") (if (play-music "test/music.ogg" #t) (begin (display "Music started!\n") (set! *music-playing* #t)) (display "ERROR: Failed to load music!\n")))(define (on-frame) (begin-frame) (clear-screen 0.1 0.1 0.15) (when *font* (set-color 0.0 1.0 1.0) (draw-text *font* "Audio Test" 300 50) (set-color 0.8 0.8 0.8) (draw-text *font* "P - Play/Pause music" 100 150) (draw-text *font* "S - Stop music" 100 190) (draw-text *font* "M - Mute/Unmute" 100 230) (draw-text *font* "UP/DOWN - Volume" 100 270) (draw-text *font* "ESC - Quit" 100 310) (set-color 1.0 1.0 0.0) (if (music-playing?) (draw-text *font* "Status: Playing" 100 350) (draw-text *font* "Status: Stopped/Paused" 100 350)) (set-color 0.5 0.8 1.0) (draw-text *font* (string-append "Volume: " (number->string (truncate (* *volume* 100))) "%") 100 390) (if (audio-muted?) (begin (set-color 1.0 0.3 0.3) (draw-text *font* "MUTED" 300 390)))) ;; Input handling (when (key-pressed? 'p) (if (music-playing?) (pause-music) (resume-music))) (when (key-pressed? 's) (stop-music)) (when (key-pressed? 'm) (if (audio-muted?) (unmute-audio) (mute-audio))) (when (key-pressed? 'up) (set! *volume* (min 1.0 (+ *volume* 0.1))) (set-master-volume *volume*)) (when (key-pressed? 'down) (set! *volume* (max 0.0 (- *volume* 0.1))) (set-master-volume *volume*)) (when (key-pressed? 'escape) (request-quit)) (end-frame))(define (on-cleanup) (display "Cleaning up audio...\n") (stop-music) (audio-shutdown))(app-run on-init on-frame on-cleanup "Audio Test" 800 500)vendor/stb/stb_vorbis.cadded
// Ogg Vorbis audio decoder - v1.22 - public domain// http://nothings.org/stb_vorbis///// Original version written by Sean Barrett in 2007.//// Originally sponsored by RAD Game Tools. Seeking implementation// sponsored by Phillip Bennefall, Marc Andersen, Aaron Baker,// Elias Software, Aras Pranckevicius, and Sean Barrett.//// LICENSE//// See end of file for license information.//// Limitations://// - floor 0 not supported (used in old ogg vorbis files pre-2004)// - lossless sample-truncation at beginning ignored// - cannot concatenate multiple vorbis streams// - sample positions are 32-bit, limiting seekable 192Khz// files to around 6 hours (Ogg supports 64-bit)//// Feature contributors:// Dougall Johnson (sample-exact seeking)//// Bugfix/warning contributors:// Terje Mathisen Niklas Frykholm Andy Hill// Casey Muratori John Bolton Gargaj// Laurent Gomila Marc LeBlanc Ronny Chevalier// Bernhard Wodo Evan Balster github:alxprd// Tom Beaumont Ingo Leitgeb Nicolas Guillemot// Phillip Bennefall Rohit Thiago Goulart// github:manxorist Saga Musix github:infatum// Timur Gagiev Maxwell Koo Peter Waller// github:audinowho Dougall Johnson David Reid// github:Clownacy Pedro J. Estebanez Remi Verschelde// AnthoFoxo github:morlat Gabriel Ravier//// Partial history:// 1.22 - 2021-07-11 - various small fixes// 1.21 - 2021-07-02 - fix bug for files with no comments// 1.20 - 2020-07-11 - several small fixes// 1.19 - 2020-02-05 - warnings// 1.18 - 2020-02-02 - fix seek bugs; parse header comments; misc warnings etc.// 1.17 - 2019-07-08 - fix CVE-2019-13217..CVE-2019-13223 (by ForAllSecure)// 1.16 - 2019-03-04 - fix warnings// 1.15 - 2019-02-07 - explicit failure if Ogg Skeleton data is found// 1.14 - 2018-02-11 - delete bogus dealloca usage// 1.13 - 2018-01-29 - fix truncation of last frame (hopefully)// 1.12 - 2017-11-21 - limit residue begin/end to blocksize/2 to avoid large temp allocs in bad/corrupt files// 1.11 - 2017-07-23 - fix MinGW compilation// 1.10 - 2017-03-03 - more robust seeking; fix negative ilog(); clear error in open_memory// 1.09 - 2016-04-04 - back out 'truncation of last frame' fix from previous version// 1.08 - 2016-04-02 - warnings; setup memory leaks; truncation of last frame// 1.07 - 2015-01-16 - fixes for crashes on invalid files; warning fixes; const// 1.06 - 2015-08-31 - full, correct support for seeking API (Dougall Johnson)// some crash fixes when out of memory or with corrupt files// fix some inappropriately signed shifts// 1.05 - 2015-04-19 - don't define __forceinline if it's redundant// 1.04 - 2014-08-27 - fix missing const-correct case in API// 1.03 - 2014-08-07 - warning fixes// 1.02 - 2014-07-09 - declare qsort comparison as explicitly _cdecl in Windows// 1.01 - 2014-06-18 - fix stb_vorbis_get_samples_float (interleaved was correct)// 1.0 - 2014-05-26 - fix memory leaks; fix warnings; fix bugs in >2-channel;// (API change) report sample rate for decode-full-file funcs//// See end of file for full version history.////////////////////////////////////////////////////////////////////////////////// HEADER BEGINS HERE//#ifndef STB_VORBIS_INCLUDE_STB_VORBIS_H#define STB_VORBIS_INCLUDE_STB_VORBIS_H#if defined(STB_VORBIS_NO_CRT) && !defined(STB_VORBIS_NO_STDIO)#define STB_VORBIS_NO_STDIO 1#endif#ifndef STB_VORBIS_NO_STDIO#include <stdio.h>#endif#ifdef __cplusplusextern "C" {#endif/////////// THREAD SAFETY// Individual stb_vorbis* handles are not thread-safe; you cannot decode from// them from multiple threads at the same time. However, you can have multiple// stb_vorbis* handles and decode from them independently in multiple thrads./////////// MEMORY ALLOCATION// normally stb_vorbis uses malloc() to allocate memory at startup,// and alloca() to allocate temporary memory during a frame on the// stack. (Memory consumption will depend on the amount of setup// data in the file and how you set the compile flags for speed// vs. size. In my test files the maximal-size usage is ~150KB.)//// You can modify the wrapper functions in the source (setup_malloc,// setup_temp_malloc, temp_malloc) to change this behavior, or you// can use a simpler allocation model: you pass in a buffer from// which stb_vorbis will allocate _all_ its memory (including the// temp memory). "open" may fail with a VORBIS_outofmem if you// do not pass in enough data; there is no way to determine how// much you do need except to succeed (at which point you can// query get_info to find the exact amount required. yes I know// this is lame).//// If you pass in a non-NULL buffer of the type below, allocation// will occur from it as described above. Otherwise just pass NULL// to use malloc()/alloca()typedef struct{ char *alloc_buffer; int alloc_buffer_length_in_bytes;} stb_vorbis_alloc;/////////// FUNCTIONS USEABLE WITH ALL INPUT MODEStypedef struct stb_vorbis stb_vorbis;typedef struct{ unsigned int sample_rate; int channels; unsigned int setup_memory_required; unsigned int setup_temp_memory_required; unsigned int temp_memory_required; int max_frame_size;} stb_vorbis_info;typedef struct{ char *vendor; int comment_list_length; char **comment_list;} stb_vorbis_comment;// get general information about the fileextern stb_vorbis_info stb_vorbis_get_info(stb_vorbis *f);// get ogg commentsextern stb_vorbis_comment stb_vorbis_get_comment(stb_vorbis *f);// get the last error detected (clears it, too)extern int stb_vorbis_get_error(stb_vorbis *f);// close an ogg vorbis file and free all memory in useextern void stb_vorbis_close(stb_vorbis *f);// this function returns the offset (in samples) from the beginning of the// file that will be returned by the next decode, if it is known, or -1// otherwise. after a flush_pushdata() call, this may take a while before// it becomes valid again.// NOT WORKING YET after a seek with PULLDATA APIextern int stb_vorbis_get_sample_offset(stb_vorbis *f);// returns the current seek point within the file, or offset from the beginning// of the memory buffer. In pushdata mode it returns 0.extern unsigned int stb_vorbis_get_file_offset(stb_vorbis *f);/////////// PUSHDATA API#ifndef STB_VORBIS_NO_PUSHDATA_API// this API allows you to get blocks of data from any source and hand// them to stb_vorbis. you have to buffer them; stb_vorbis will tell// you how much it used, and you have to give it the rest next time;// and stb_vorbis may not have enough data to work with and you will// need to give it the same data again PLUS more. Note that the Vorbis// specification does not bound the size of an individual frame.extern stb_vorbis *stb_vorbis_open_pushdata( const unsigned char * datablock, int datablock_length_in_bytes, int *datablock_memory_consumed_in_bytes, int *error, const stb_vorbis_alloc *alloc_buffer);// create a vorbis decoder by passing in the initial data block containing// the ogg&vorbis headers (you don't need to do parse them, just provide// the first N bytes of the file--you're told if it's not enough, see below)// on success, returns an stb_vorbis *, does not set error, returns the amount of// data parsed/consumed on this call in *datablock_memory_consumed_in_bytes;// on failure, returns NULL on error and sets *error, does not change *datablock_memory_consumed// if returns NULL and *error is VORBIS_need_more_data, then the input block was// incomplete and you need to pass in a larger block from the start of the fileextern int stb_vorbis_decode_frame_pushdata( stb_vorbis *f, const unsigned char *datablock, int datablock_length_in_bytes, int *channels, // place to write number of float * buffers float ***output, // place to write float ** array of float * buffers int *samples // place to write number of output samples );// decode a frame of audio sample data if possible from the passed-in data block//// return value: number of bytes we used from datablock//// possible cases:// 0 bytes used, 0 samples output (need more data)// N bytes used, 0 samples output (resynching the stream, keep going)// N bytes used, M samples output (one frame of data)// note that after opening a file, you will ALWAYS get one N-bytes,0-sample// frame, because Vorbis always "discards" the first frame.//// Note that on resynch, stb_vorbis will rarely consume all of the buffer,// instead only datablock_length_in_bytes-3 or less. This is because it wants// to avoid missing parts of a page header if they cross a datablock boundary,// without writing state-machiney code to record a partial detection.//// The number of channels returned are stored in *channels (which can be// NULL--it is always the same as the number of channels reported by// get_info). *output will contain an array of float* buffers, one per// channel. In other words, (*output)[0][0] contains the first sample from// the first channel, and (*output)[1][0] contains the first sample from// the second channel.//// *output points into stb_vorbis's internal output buffer storage; these// buffers are owned by stb_vorbis and application code should not free// them or modify their contents. They are transient and will be overwritten// once you ask for more data to get decoded, so be sure to grab any data// you need before then.extern void stb_vorbis_flush_pushdata(stb_vorbis *f);// inform stb_vorbis that your next datablock will not be contiguous with// previous ones (e.g. you've seeked in the data); future attempts to decode// frames will cause stb_vorbis to resynchronize (as noted above), and// once it sees a valid Ogg page (typically 4-8KB, as large as 64KB), it// will begin decoding the _next_ frame.//// if you want to seek using pushdata, you need to seek in your file, then// call stb_vorbis_flush_pushdata(), then start calling decoding, then once// decoding is returning you data, call stb_vorbis_get_sample_offset, and// if you don't like the result, seek your file again and repeat.#endif////////// PULLING INPUT API#ifndef STB_VORBIS_NO_PULLDATA_API// This API assumes stb_vorbis is allowed to pull data from a source--// either a block of memory containing the _entire_ vorbis stream, or a// FILE * that you or it create, or possibly some other reading mechanism// if you go modify the source to replace the FILE * case with some kind// of callback to your code. (But if you don't support seeking, you may// just want to go ahead and use pushdata.)#if !defined(STB_VORBIS_NO_STDIO) && !defined(STB_VORBIS_NO_INTEGER_CONVERSION)extern int stb_vorbis_decode_filename(const char *filename, int *channels, int *sample_rate, short **output);#endif#if !defined(STB_VORBIS_NO_INTEGER_CONVERSION)extern int stb_vorbis_decode_memory(const unsigned char *mem, int len, int *channels, int *sample_rate, short **output);#endif// decode an entire file and output the data interleaved into a malloc()ed// buffer stored in *output. The return value is the number of samples// decoded, or -1 if the file could not be opened or was not an ogg vorbis file.// When you're done with it, just free() the pointer returned in *output.extern stb_vorbis * stb_vorbis_open_memory(const unsigned char *data, int len, int *error, const stb_vorbis_alloc *alloc_buffer);// create an ogg vorbis decoder from an ogg vorbis stream in memory (note// this must be the entire stream!). on failure, returns NULL and sets *error#ifndef STB_VORBIS_NO_STDIOextern stb_vorbis * stb_vorbis_open_filename(const char *filename, int *error, const stb_vorbis_alloc *alloc_buffer);// create an ogg vorbis decoder from a filename via fopen(). on failure,// returns NULL and sets *error (possibly to VORBIS_file_open_failure).extern stb_vorbis * stb_vorbis_open_file(FILE *f, int close_handle_on_close, int *error, const stb_vorbis_alloc *alloc_buffer);// create an ogg vorbis decoder from an open FILE *, looking for a stream at// the _current_ seek point (ftell). on failure, returns NULL and sets *error.// note that stb_vorbis must "own" this stream; if you seek it in between// calls to stb_vorbis, it will become confused. Moreover, if you attempt to// perform stb_vorbis_seek_*() operations on this file, it will assume it// owns the _entire_ rest of the file after the start point. Use the next// function, stb_vorbis_open_file_section(), to limit it.extern stb_vorbis * stb_vorbis_open_file_section(FILE *f, int close_handle_on_close, int *error, const stb_vorbis_alloc *alloc_buffer, unsigned int len);// create an ogg vorbis decoder from an open FILE *, looking for a stream at// the _current_ seek point (ftell); the stream will be of length 'len' bytes.// on failure, returns NULL and sets *error. note that stb_vorbis must "own"// this stream; if you seek it in between calls to stb_vorbis, it will become// confused.#endifextern int stb_vorbis_seek_frame(stb_vorbis *f, unsigned int sample_number);extern int stb_vorbis_seek(stb_vorbis *f, unsigned int sample_number);// these functions seek in the Vorbis file to (approximately) 'sample_number'.// after calling seek_frame(), the next call to get_frame_*() will include// the specified sample. after calling stb_vorbis_seek(), the next call to// stb_vorbis_get_samples_* will start with the specified sample. If you// do not need to seek to EXACTLY the target sample when using get_samples_*,// you can also use seek_frame().extern int stb_vorbis_seek_start(stb_vorbis *f);// this function is equivalent to stb_vorbis_seek(f,0)extern unsigned int stb_vorbis_stream_length_in_samples(stb_vorbis *f);extern float stb_vorbis_stream_length_in_seconds(stb_vorbis *f);// these functions return the total length of the vorbis streamextern int stb_vorbis_get_frame_float(stb_vorbis *f, int *channels, float ***output);// decode the next frame and return the number of samples. the number of// channels returned are stored in *channels (which can be NULL--it is always// the same as the number of channels reported by get_info). *output will// contain an array of float* buffers, one per channel. These outputs will// be overwritten on the next call to stb_vorbis_get_frame_*.//// You generally should not intermix calls to stb_vorbis_get_frame_*()// and stb_vorbis_get_samples_*(), since the latter calls the former.#ifndef STB_VORBIS_NO_INTEGER_CONVERSIONextern int stb_vorbis_get_frame_short_interleaved(stb_vorbis *f, int num_c, short *buffer, int num_shorts);extern int stb_vorbis_get_frame_short (stb_vorbis *f, int num_c, short **buffer, int num_samples);#endif// decode the next frame and return the number of *samples* per channel.// Note that for interleaved data, you pass in the number of shorts (the// size of your array), but the return value is the number of samples per// channel, not the total number of samples.//// The data is coerced to the number of channels you request according to the// channel coercion rules (see below). You must pass in the size of your// buffer(s) so that stb_vorbis will not overwrite the end of the buffer.// The maximum buffer size needed can be gotten from get_info(); however,// the Vorbis I specification implies an absolute maximum of 4096 samples// per channel.// Channel coercion rules:// Let M be the number of channels requested, and N the number of channels present,// and Cn be the nth channel; let stereo L be the sum of all L and center channels,// and stereo R be the sum of all R and center channels (channel assignment from the// vorbis spec).// M N output// 1 k sum(Ck) for all k// 2 * stereo L, stereo R// k l k > l, the first l channels, then 0s// k l k <= l, the first k channels// Note that this is not _good_ surround etc. mixing at all! It's just so// you get something useful.extern int stb_vorbis_get_samples_float_interleaved(stb_vorbis *f, int channels, float *buffer, int num_floats);extern int stb_vorbis_get_samples_float(stb_vorbis *f, int channels, float **buffer, int num_samples);// gets num_samples samples, not necessarily on a frame boundary--this requires// buffering so you have to supply the buffers. DOES NOT APPLY THE COERCION RULES.// Returns the number of samples stored per channel; it may be less than requested// at the end of the file. If there are no more samples in the file, returns 0.#ifndef STB_VORBIS_NO_INTEGER_CONVERSIONextern int stb_vorbis_get_samples_short_interleaved(stb_vorbis *f, int channels, short *buffer, int num_shorts);extern int stb_vorbis_get_samples_short(stb_vorbis *f, int channels, short **buffer, int num_samples);#endif// gets num_samples samples, not necessarily on a frame boundary--this requires// buffering so you have to supply the buffers. Applies the coercion rules above// to produce 'channels' channels. Returns the number of samples stored per channel;// it may be less than requested at the end of the file. If there are no more// samples in the file, returns 0.#endif//////// ERROR CODESenum STBVorbisError{ VORBIS__no_error, VORBIS_need_more_data=1, // not a real error VORBIS_invalid_api_mixing, // can't mix API modes VORBIS_outofmem, // not enough memory VORBIS_feature_not_supported, // uses floor 0 VORBIS_too_many_channels, // STB_VORBIS_MAX_CHANNELS is too small VORBIS_file_open_failure, // fopen() failed VORBIS_seek_without_length, // can't seek in unknown-length file VORBIS_unexpected_eof=10, // file is truncated? VORBIS_seek_invalid, // seek past EOF // decoding errors (corrupt/invalid stream) -- you probably // don't care about the exact details of these // vorbis errors: VORBIS_invalid_setup=20, VORBIS_invalid_stream, // ogg errors: VORBIS_missing_capture_pattern=30, VORBIS_invalid_stream_structure_version, VORBIS_continued_packet_flag_invalid, VORBIS_incorrect_stream_serial_number, VORBIS_invalid_first_page, VORBIS_bad_packet_type, VORBIS_cant_find_last_page, VORBIS_seek_failed, VORBIS_ogg_skeleton_not_supported};#ifdef __cplusplus}#endif#endif // STB_VORBIS_INCLUDE_STB_VORBIS_H//// HEADER ENDS HERE////////////////////////////////////////////////////////////////////////////////#ifndef STB_VORBIS_HEADER_ONLY// global configuration settings (e.g. set these in the project/makefile),// or just set them in this file at the top (although ideally the first few// should be visible when the header file is compiled too, although it's not// crucial)// STB_VORBIS_NO_PUSHDATA_API// does not compile the code for the various stb_vorbis_*_pushdata()// functions// #define STB_VORBIS_NO_PUSHDATA_API// STB_VORBIS_NO_PULLDATA_API// does not compile the code for the non-pushdata APIs// #define STB_VORBIS_NO_PULLDATA_API// STB_VORBIS_NO_STDIO// does not compile the code for the APIs that use FILE *s internally// or externally (implied by STB_VORBIS_NO_PULLDATA_API)// #define STB_VORBIS_NO_STDIO// STB_VORBIS_NO_INTEGER_CONVERSION// does not compile the code for converting audio sample data from// float to integer (implied by STB_VORBIS_NO_PULLDATA_API)// #define STB_VORBIS_NO_INTEGER_CONVERSION// STB_VORBIS_NO_FAST_SCALED_FLOAT// does not use a fast float-to-int trick to accelerate float-to-int on// most platforms which requires endianness be defined correctly.//#define STB_VORBIS_NO_FAST_SCALED_FLOAT// STB_VORBIS_MAX_CHANNELS [number]// globally define this to the maximum number of channels you need.// The spec does not put a restriction on channels except that// the count is stored in a byte, so 255 is the hard limit.// Reducing this saves about 16 bytes per value, so using 16 saves// (255-16)*16 or around 4KB. Plus anything other memory usage// I forgot to account for. Can probably go as low as 8 (7.1 audio),// 6 (5.1 audio), or 2 (stereo only).#ifndef STB_VORBIS_MAX_CHANNELS#define STB_VORBIS_MAX_CHANNELS 16 // enough for anyone?#endif// STB_VORBIS_PUSHDATA_CRC_COUNT [number]// after a flush_pushdata(), stb_vorbis begins scanning for the// next valid page, without backtracking. when it finds something// that looks like a page, it streams through it and verifies its// CRC32. Should that validation fail, it keeps scanning. But it's// possible that _while_ streaming through to check the CRC32 of// one candidate page, it sees another candidate page. This #define// determines how many "overlapping" candidate pages it can search// at once. Note that "real" pages are typically ~4KB to ~8KB, whereas// garbage pages could be as big as 64KB, but probably average ~16KB.// So don't hose ourselves by scanning an apparent 64KB page and// missing a ton of real ones in the interim; so minimum of 2#ifndef STB_VORBIS_PUSHDATA_CRC_COUNT#define STB_VORBIS_PUSHDATA_CRC_COUNT 4#endif// STB_VORBIS_FAST_HUFFMAN_LENGTH [number]// sets the log size of the huffman-acceleration table. Maximum// supported value is 24. with larger numbers, more decodings are O(1),// but the table size is larger so worse cache missing, so you'll have// to probe (and try multiple ogg vorbis files) to find the sweet spot.#ifndef STB_VORBIS_FAST_HUFFMAN_LENGTH#define STB_VORBIS_FAST_HUFFMAN_LENGTH 10#endif// STB_VORBIS_FAST_BINARY_LENGTH [number]// sets the log size of the binary-search acceleration table. this// is used in similar fashion to the fast-huffman size to set initial// parameters for the binary search// STB_VORBIS_FAST_HUFFMAN_INT// The fast huffman tables are much more efficient if they can be// stored as 16-bit results instead of 32-bit results. This restricts// the codebooks to having only 65535 possible outcomes, though.// (At least, accelerated by the huffman table.)#ifndef STB_VORBIS_FAST_HUFFMAN_INTShowing the first 500 of 5585 diff lines for this file. This diff is INCOMPLETE; read the file or clone the repository for the rest.