| 1 | module audio |
| 2 | |
| 3 | import sokol.memory |
| 4 | |
| 5 | $if linux { |
| 6 | // provide a nicer error for the user that does not have ALSA installed |
| 7 | #include <alsa/asoundlib.h> # Please install the `libasound2-dev` package |
| 8 | } |
| 9 | |
| 10 | $if openbsd { |
| 11 | $compile_error('sokol/audio not supported on OpenBSD') |
| 12 | } |
| 13 | |
| 14 | #flag -I @VEXEROOT/thirdparty/sokol |
| 15 | // FreeBSD requires the audio/alsa-lib to be installed |
| 16 | #flag freebsd -I/usr/local/include |
| 17 | |
| 18 | @[use_once] |
| 19 | #define SOKOL_IMPL |
| 20 | #include "sokol_audio.h" |
| 21 | #flag linux -lasound -lpthread |
| 22 | #flag darwin -framework AudioToolbox |
| 23 | #flag windows -lole32 |
| 24 | #flag freebsd -L/usr/local/lib |
| 25 | #flag freebsd -lasound |
| 26 | #flag android -laaudio |
| 27 | |
| 28 | // callback function for `stream_cb` in [[C.saudio_desc](#C.saudio_desc)] when calling [audio.setup()](#setup) |
| 29 | // |
| 30 | // sokol callback functions run in a separate thread |
| 31 | // |
| 32 | // This function will be called with a reference to the C buffer and the maximum number of frames and channels |
| 33 | // the audio backend is expecting in its buffer. |
| 34 | // |
| 35 | // Terms: |
| 36 | // - *sample* - a 32-bit floating point number from `-1.0` to `+1.0` representing the waveform amplitude at that instant |
| 37 | // - *frame* - one sample for each channel at that instant |
| 38 | // |
| 39 | // To determine the number of samples expected, do `num_frames * num_channels`. |
| 40 | // Then, write up to that many `f32` samples into `buffer` using unsafe operations. |
| 41 | // |
| 42 | // Do not write more data to the buffer than it is requesting, but you may write less. The buffer is initialized with |
| 43 | // zeroes, so unwritten data will result in audio silence. |
| 44 | // Example body: unsafe { C.memcpy(buffer, &samples, samples.len * int(sizeof(f32))) } |
| 45 | // Example body: unsafe { mut b := buffer; for i, sample in samples { b[i] = sample } } |
| 46 | pub type FNStreamingCB = fn (buffer &f32, num_frames int, num_channels int) |
| 47 | |
| 48 | // callback function for `stream_userdata_cb` to use in `C.saudio_desc` when calling [audio.setup()](#setup) |
| 49 | // This function operates the same way as [[FNStreamingCB](#FNStreamingCB)] but it passes customizable `user_data` to the |
| 50 | // callback. This is the method to use if your audio data is stored in a struct or array. Identify the |
| 51 | // `user_data` when you call `audio.setup()` and that object will be passed to the callback as the last arg. |
| 52 | // Note: Sokol's callback functions run in a separate thread. |
| 53 | // Example: previously_parsed_wavfile_bytes := [f32(0),0,0,0]; mycallback := fn (buffer &f32, num_frames int, num_channels int, mut sb []f32) {}; mut soundbuffer := []f32{}; soundbuffer << previously_parsed_wavfile_bytes; audio.setup(stream_userdata_cb: mycallback, user_data: soundbuffer.data); |
| 54 | pub type FnStreamingCBWithUserData = fn (buffer &f32, num_frames int, num_channels int, user_data voidptr) |
| 55 | |
| 56 | pub fn (x FNStreamingCB) str() string { |
| 57 | return '&FNStreamingCB{ ${ptr_str(x)} }' |
| 58 | } |
| 59 | |
| 60 | pub fn (x FnStreamingCBWithUserData) str() string { |
| 61 | return '&FnStreamingCBWithUserData{ ${ptr_str(x)} }' |
| 62 | } |
| 63 | |
| 64 | @[typedef] |
| 65 | pub struct C.saudio_allocator { |
| 66 | pub mut: |
| 67 | alloc_fn memory.FnAllocatorAlloc |
| 68 | free_fn memory.FnAllocatorFree |
| 69 | user_data voidptr |
| 70 | } |
| 71 | |
| 72 | @[typedef] |
| 73 | pub struct C.saudio_logger { |
| 74 | pub mut: |
| 75 | func memory.FnLogCb |
| 76 | user_data voidptr |
| 77 | } |
| 78 | |
| 79 | // only one of `stream_cb` or `stream_userdata_cb` should be used |
| 80 | // |
| 81 | // default values (internal to sokol C library): |
| 82 | // |
| 83 | // | variable | default | note | |
| 84 | // | :----------- | -------: | :--------- | |
| 85 | // | sample_rate | 44100 | higher sample rates take more memory but are higher quality | |
| 86 | // | num_channels | 1 | for stereo sound, this should be 2 | |
| 87 | // | buffer_frames | 2048 | buffer size in frames, larger is more latency, smaller means higher CPU | |
| 88 | // | packet_frames | 128 | push model only, number of frames that will be pushed in each packet | |
| 89 | // | num_packets | 64 | for push model only, number of packets in the backend ringbuffer | |
| 90 | @[params; typedef] |
| 91 | pub struct C.saudio_desc { |
| 92 | pub: |
| 93 | sample_rate int |
| 94 | num_channels int |
| 95 | buffer_frames int |
| 96 | packet_frames int |
| 97 | num_packets int |
| 98 | stream_cb FNStreamingCB |
| 99 | stream_userdata_cb FnStreamingCBWithUserData |
| 100 | pub mut: |
| 101 | user_data voidptr |
| 102 | allocator C.saudio_allocator |
| 103 | logger C.saudio_logger |
| 104 | } |
| 105 | |
| 106 | fn C.saudio_setup(const_desc &C.saudio_desc) |
| 107 | |
| 108 | fn C.saudio_shutdown() |
| 109 | |
| 110 | fn C.saudio_isvalid() bool |
| 111 | |
| 112 | fn C.saudio_userdata() voidptr |
| 113 | |
| 114 | fn C.saudio_query_desc() C.saudio_desc |
| 115 | |
| 116 | fn C.saudio_sample_rate() i32 |
| 117 | |
| 118 | fn C.saudio_buffer_frames() i32 |
| 119 | |
| 120 | fn C.saudio_channels() i32 |
| 121 | |
| 122 | fn C.saudio_suspended() bool |
| 123 | |
| 124 | fn C.saudio_expect() i32 |
| 125 | |
| 126 | fn C.saudio_push(const_frames &f32, num_frames i32) i32 |
| 127 | |
| 128 | // setup - setup sokol-audio |
| 129 | pub fn setup(desc &C.saudio_desc) { |
| 130 | if desc.allocator.alloc_fn == unsafe { nil } && desc.allocator.free_fn == unsafe { nil } { |
| 131 | unsafe { |
| 132 | desc.allocator.alloc_fn = memory.salloc |
| 133 | desc.allocator.free_fn = memory.sfree |
| 134 | desc.allocator.user_data = voidptr(0x100a0d10) |
| 135 | } |
| 136 | } |
| 137 | if desc.logger.func == unsafe { nil } { |
| 138 | unsafe { |
| 139 | desc.logger.func = memory.slog |
| 140 | } |
| 141 | } |
| 142 | C.saudio_setup(desc) |
| 143 | } |
| 144 | |
| 145 | // shutdown - shutdown sokol-audio |
| 146 | pub fn shutdown() { |
| 147 | C.saudio_shutdown() |
| 148 | } |
| 149 | |
| 150 | // is_valid - true after setup if audio backend was successfully initialized |
| 151 | pub fn is_valid() bool { |
| 152 | return C.saudio_isvalid() |
| 153 | } |
| 154 | |
| 155 | // userdata - return the saudio_desc.user_data pointer |
| 156 | pub fn user_data() voidptr { |
| 157 | return C.saudio_userdata() |
| 158 | } |
| 159 | |
| 160 | // query - return a copy of the original saudio_desc struct |
| 161 | pub fn query() C.saudio_desc { |
| 162 | return C.saudio_query_desc() |
| 163 | } |
| 164 | |
| 165 | // sample_rate - return the actual sample rate |
| 166 | pub fn sample_rate() int { |
| 167 | return C.saudio_sample_rate() |
| 168 | } |
| 169 | |
| 170 | // buffer_frames - return the actual backend buffer size in number of frames |
| 171 | pub fn buffer_frames() int { |
| 172 | return C.saudio_buffer_frames() |
| 173 | } |
| 174 | |
| 175 | // channels - return the actual number of channels |
| 176 | pub fn channels() int { |
| 177 | return C.saudio_channels() |
| 178 | } |
| 179 | |
| 180 | // suspended returns true if audio context is currently suspended |
| 181 | // (only in WebAudio backend, all other backends return false) |
| 182 | pub fn suspended() bool { |
| 183 | return C.saudio_suspended() |
| 184 | } |
| 185 | |
| 186 | // expect - get current number of frames to fill packet queue; use in combination with audio.push |
| 187 | pub fn expect() int { |
| 188 | return C.saudio_expect() |
| 189 | } |
| 190 | |
| 191 | // push - push sample frames from main thread, returns number of frames actually pushed |
| 192 | pub fn push(frames &f32, num_frames int) int { |
| 193 | return C.saudio_push(frames, num_frames) |
| 194 | } |
| 195 | |
| 196 | // fclamp - helper function to 'clamp' a number to a certain range |
| 197 | // Example: sample := f32(3.14); realsample := audio.fclamp(sample, -1.0, 1.0); assert realsample == 1.0 |
| 198 | @[inline] |
| 199 | pub fn fclamp(x f32, flo f32, fhi f32) f32 { |
| 200 | if x > fhi { |
| 201 | return fhi |
| 202 | } |
| 203 | if x < flo { |
| 204 | return flo |
| 205 | } |
| 206 | return x |
| 207 | } |
| 208 | |
| 209 | // min - helper function to return the smaller of two numbers |
| 210 | // |
| 211 | // NOTE: math.min returns `f32` values, this returns `int` values |
| 212 | // Example: println(audio.min(1, 5)) |
| 213 | pub fn min(x int, y int) int { |
| 214 | if x < y { |
| 215 | return x |
| 216 | } |
| 217 | return y |
| 218 | } |
| 219 | |
| 220 | // max - helper function to return the larger of two numbers |
| 221 | // |
| 222 | // NOTE: math.max returns `f32` values, this returns `int` values |
| 223 | // Example: println(audio.max(1, 5)) |
| 224 | pub fn max(x int, y int) int { |
| 225 | if x < y { |
| 226 | return y |
| 227 | } |
| 228 | return x |
| 229 | } |
| 230 | |