├── .editorconfig ├── README.md ├── LICENSE ├── THIRDPARTY.LICENSE ├── include └── tinymixer │ └── mixer.h └── src └── mixer.cpp /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | charset = utf-8 7 | indent_style = tab 8 | indent_size = 4 9 | 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | tinymixer 2 | ========= 3 | 4 | minimal audio mixer designed for game-like projects 5 | 6 | Contact 7 | ------- 8 | [@MatthewEndsley](https://twitter.com/#!/MatthewEndsley) 9 | 10 | 11 | License 12 | ------- 13 | Copyright 2011-2013 Matthew Endsley 14 | 15 | This project is governed by the BSD 2-clause license. For details see the file 16 | titled LICENSE in the project root folder. 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2011-2013 Matthew Endsley 2 | All rights reserved 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted providing that the following conditions 6 | are met: 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 14 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 17 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 19 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 20 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 21 | STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 22 | IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 23 | POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /THIRDPARTY.LICENSE: -------------------------------------------------------------------------------- 1 | stb_vorbis 2 | ========== 3 | This software is available under 2 licenses -- choose whichever you prefer. 4 | ------------------------------------------------------------------------------ 5 | ALTERNATIVE A - MIT License 6 | Copyright (c) 2017 Sean Barrett 7 | Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | this software and associated documentation files (the "Software"), to deal in 9 | the Software without restriction, including without limitation the rights to 10 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 11 | of the Software, and to permit persons to whom the Software is furnished to do 12 | so, subject to the following conditions: 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | ------------------------------------------------------------------------------ 23 | ALTERNATIVE B - Public Domain (www.unlicense.org) 24 | This is free and unencumbered software released into the public domain. 25 | Anyone is free to copy, modify, publish, use, compile, sell, or distribute this 26 | software, either in source code form or as a compiled binary, for any purpose, 27 | commercial or non-commercial, and by any means. 28 | In jurisdictions that recognize copyright laws, the author or authors of this 29 | software dedicate any and all copyright interest in the software to the public 30 | domain. We make this dedication for the benefit of the public at large and to 31 | the detriment of our heirs and successors. We intend this dedication to be an 32 | overt act of relinquishment in perpetuity of all present and future rights to 33 | this software under copyright law. 34 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 35 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 36 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 37 | AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 38 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 39 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 40 | -------------------------------------------------------------------------------- /include/tinymixer/mixer.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright 2011-2013 Matthew Endsley 3 | * All rights reserved 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted providing that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 15 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 18 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 22 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 23 | * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | * POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | #ifndef TINYMIXER_MIXER_H 28 | #define TINYMIXER_MIXER_H 29 | 30 | #include 31 | 32 | struct tinymixer_buffer; 33 | struct tinymixer_channel; 34 | 35 | struct tinymixer_callbacks 36 | { 37 | // user context. tinymixer will not modify or interpret this value. It is 38 | // simply passed as-is to the callback functions 39 | void* opaque = nullptr; 40 | 41 | // allocate and free memory. If null, tinymixer will use malloc/free 42 | void* (*allocate)(void* opaque, int bytes) = nullptr; 43 | void (*free)(void* opaque, void* pointer) = nullptr; 44 | 45 | // allow the client to add additional audio into the mix before applying 46 | // the effects pass 47 | void (*pre_effects)(void* opaque, float* samples, int nsamples, float gain) = nullptr; 48 | 49 | // called when a channel stops playing 50 | void (*channel_complete)(void* opaque, void* channel_opaque, tinymixer_channel channel) = nullptr; 51 | }; 52 | 53 | struct tinymixer_buffer_callbacks 54 | { 55 | void (*on_destroy)(void* buffer_opqaue); 56 | 57 | void* (*start_source)(void* buffer_opaque); 58 | void (*end_source)(void* buffer_opaque, void* source_opaque); 59 | int (*request_samples)(void* buffer_opaque, void* source_opaque, const float** left, const float** right, int nsamples); 60 | }; 61 | 62 | void tinymixer_init(tinymixer_callbacks callbacks, int sample_rate); 63 | void tinymixer_getsamples(float* samples, int nsamples); 64 | void tinymixer_set_mastergain(float gain); 65 | 66 | void tinymixer_create_buffer_interleaved_s16le(int channels, const int16_t* pcm_data, int pcm_data_size 67 | , const tinymixer_buffer** handle); 68 | void tinymixer_create_buffer_interleaved_float(int channels, const float* pcm_data, int pcm_data_size 69 | , const tinymixer_buffer** handle); 70 | void tinymixer_create_buffer_vorbis_stream(const void* data, int ndata 71 | , void* opaque, void (*closed)(void*), const tinymixer_buffer** handle); 72 | void tinymixer_create_buffer_custom_stream(void* opaque, tinymixer_buffer_callbacks callbacks, const tinymixer_buffer** buffer); 73 | int tinymixer_get_buffer_size(const tinymixer_buffer* handle); 74 | void tinymixer_release_buffer(const tinymixer_buffer* handle); 75 | 76 | void tinymixer_update_listener(const float* position); 77 | void tinymixer_set_base_gain(int index, float gain); 78 | void tinymixer_set_callback_gain(float gain); 79 | void tinymixer_effects_compressor(const float thresholds[2], const float multipliers[2] 80 | , float attack_seconds, float release_seconds); 81 | 82 | bool tinymixer_add(const tinymixer_buffer* handle, int gain_index, float gain, float pitch 83 | , tinymixer_channel* channel); 84 | bool tinymixer_add(const tinymixer_buffer* handle, int gain_index, float gain, float pitch, const float* position 85 | , float distance_min, float distance_max, tinymixer_channel* channel); 86 | 87 | bool tinymixer_add_loop(const tinymixer_buffer* handle, int gain_index, float gain, float pitch 88 | , tinymixer_channel* channel); 89 | bool tinymixer_add_loop(const tinymixer_buffer* handle, int gain_index, float gain, float pitch 90 | , const float* position, float distance_min, float distance_max, tinymixer_channel* channel); 91 | 92 | void tinymixer_channel_set_opaque(tinymixer_channel channel, void* opaque); 93 | void tinymixer_channel_stop(tinymixer_channel channel); 94 | void tinymixer_channel_set_position(tinymixer_channel channel, const float* position); 95 | void tinymixer_channel_fadeout(tinymixer_channel channel, float seconds); 96 | void tinymixer_channel_set_gain(tinymixer_channel channel, float gain); 97 | void tinymixer_channel_set_frequency(tinymixer_channel channel, float frequency); 98 | 99 | float tinymixer_channel_get_gain(tinymixer_channel channel); 100 | void tinymixer_stop_all_sources(); 101 | 102 | struct tinymixer_channel 103 | { 104 | int index = 0; 105 | }; 106 | 107 | static inline bool tinymixer_channel_isvalid(tinymixer_channel channel) { return channel.index != 0; } 108 | static inline bool operator==(tinymixer_channel lhs, tinymixer_channel rhs) { return lhs.index == rhs.index; } 109 | 110 | struct tinymixer_resampler 111 | { 112 | float ideal_rate; 113 | float prev_samples[2]; 114 | }; 115 | 116 | void tinymixer_resampler_init(tinymixer_resampler* resampler, int input_sample_rate, int output_sample_rate); 117 | void tinymixer_resampler_init_rate(tinymixer_resampler* resampler, float ideal_rate); 118 | int tinymixer_resampler_calculate_input_samples(const tinymixer_resampler* resampler, int output_samples); 119 | int tinymixer_resampler_calculate_output_samples(const tinymixer_resampler* resampler, int input_samples); 120 | void tinymixer_resample_stereo(tinymixer_resampler* resampler, const float* input, int num_input_samples, float* output, int num_output_samples); 121 | void tinymixer_resample_mono(tinymixer_resampler* resampler, const float* input, int num_input_samples, float* output, int num_output_samples); 122 | 123 | struct tinymixer_lowpass_filter 124 | { 125 | float cutoff_frequency; // 1250.0 is a decent starting point 126 | float sample_rate; 127 | 128 | float channel_history[2]; 129 | }; 130 | 131 | void tinymixer_lowpass_filter_init(tinymixer_lowpass_filter* filter, float cutoff_frequency, float sample_rate); 132 | void tinymixer_lowpass_filter_apply(tinymixer_lowpass_filter* filter, float* output, float* input, int num_samples, int num_channels); 133 | 134 | #endif 135 | -------------------------------------------------------------------------------- /src/mixer.cpp: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright 2011-2013 Matthew Endsley 3 | * All rights reserved 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted providing that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 15 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 18 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 22 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 23 | * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | * POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #define STB_VORBIS_HEADER_ONLY 36 | #include "vorbis.cpp" 37 | #undef STB_VORBIS_HEADER_ONLY 38 | 39 | namespace { 40 | struct SourceFlags { 41 | enum Enum { 42 | Playing = 1 << 0, 43 | Positional = 1 << 1, 44 | Looping = 1 << 2, 45 | FadeOut = 1 << 3, 46 | Frequency = 1 << 4, 47 | }; 48 | }; 49 | 50 | struct Buffer; 51 | struct Source; 52 | 53 | struct buffer_functions { 54 | void (*on_destroy)(Buffer* buffer); 55 | 56 | void (*start_source)(Source* source); 57 | void (*end_source)(Source* source); 58 | int (*request_samples)(Source* source, const float** left, const float** right, int nsamples); 59 | 60 | int (*get_buffer_size)(const Buffer* buffer); 61 | }; 62 | 63 | struct Buffer { 64 | const buffer_functions *funcs; 65 | std::atomic refcnt; 66 | }; 67 | 68 | struct static_source_data { 69 | int32_t sample_pos; 70 | }; 71 | 72 | struct vorbis_stream_data { 73 | stb_vorbis* v; 74 | const float* outputs[2]; 75 | int noutputs; 76 | }; 77 | 78 | struct custom_stream_data { 79 | void* opaque; 80 | }; 81 | 82 | struct Source { 83 | const Buffer* buffer; 84 | union { 85 | static_source_data static_source; 86 | vorbis_stream_data vorbis_stream; 87 | custom_stream_data custom_stream; 88 | } instance_data; 89 | float position[3]; 90 | float fadeout_per_sample; 91 | float gain_base; 92 | float distance_min; 93 | float distance_difference; 94 | float frequency; 95 | uint16_t frame_age; 96 | uint8_t flags; 97 | uint8_t gain_base_index; 98 | tinymixer_resampler resampler; 99 | void* opaque; 100 | }; 101 | 102 | static const int c_ngaintypes = 8; 103 | static const int c_nsources = 32; 104 | static const int c_nsamples = 2048; 105 | static const float c_fnsamples = (float)c_nsamples; 106 | static float c_speakerdist = 0.17677669529663688110021109052621f; // 1/(4 *sqrtf(2)) 107 | 108 | struct Mixer { 109 | std::mutex lock; 110 | tinymixer_callbacks callbacks; 111 | float position[3]; 112 | float gain_master; 113 | float gain_base[c_ngaintypes]; 114 | float gain_callback; 115 | int32_t sample_rate; 116 | float compressor_last_samples[2]; 117 | float compressor_thresholds[2]; 118 | float compressor_multipliers[2]; 119 | float compressor_factor; 120 | float compressor_attack_per1ksamples; 121 | float compressor_release_per1ksamples; 122 | int32_t samples_remaining; 123 | Source sources[c_nsources]; 124 | float buffer[2*c_nsamples]; 125 | float scratch[2*c_nsamples]; 126 | }; 127 | } 128 | 129 | static Mixer g_mixer; 130 | 131 | static inline float mixer_clamp(float v, float min, float max) { return min > v ? min : (v > max ? max : v); } 132 | static inline int mixer_min(int a, int b) { return (a < b) ? a : b; } 133 | static inline float mixer_dist(const float* a, const float* b) { 134 | const float distsq = (a[0] - b[0])*(a[0] - b[0]) + (a[1] - b[1])*(a[1] - b[1]) + (a[2] - b[2])*(a[2] - b[2]); 135 | return sqrtf(distsq); 136 | } 137 | static inline void mixer_vcopy(float* v, const float* a) { v[0] = a[0], v[1] = a[1], v[2] = a[2]; } 138 | 139 | static void addref(Buffer* buffer) { 140 | ++buffer->refcnt; 141 | } 142 | 143 | static void decref(Buffer* buffer) { 144 | if (1 == buffer->refcnt.fetch_sub(1)) { 145 | buffer->funcs->on_destroy(buffer); 146 | g_mixer.callbacks.free(g_mixer.callbacks.opaque, buffer); 147 | } 148 | } 149 | 150 | static void kill_source(Source* source) { 151 | 152 | if (g_mixer.callbacks.channel_complete) { 153 | tinymixer_channel channel; 154 | channel.index = (int)(source - g_mixer.sources) + 1; 155 | 156 | // unlock the mutex while in the user callback 157 | g_mixer.lock.unlock(); 158 | g_mixer.callbacks.channel_complete(g_mixer.callbacks.opaque, source->opaque, channel); 159 | g_mixer.lock.lock(); 160 | } 161 | 162 | if (source->buffer) { 163 | source->buffer->funcs->end_source(source); 164 | decref((Buffer*)source->buffer); 165 | } 166 | 167 | source->buffer = 0; 168 | source->flags = 0; 169 | } 170 | 171 | static Source *find_source() { 172 | Source* best_source = 0; 173 | uint16_t best_age = 0; 174 | 175 | for (int ii = 0; ii < c_nsources; ++ii) { 176 | Source* source = &g_mixer.sources[ii]; 177 | if (!source->buffer) 178 | return source; 179 | 180 | if (0 == (source->flags & SourceFlags::Looping)) { 181 | const uint16_t age = source->frame_age; 182 | if (age >= best_age) { 183 | best_source = source; 184 | best_age = age; 185 | } 186 | } 187 | } 188 | 189 | if (NULL != best_source) { 190 | if (best_source->buffer) { 191 | kill_source(best_source); 192 | } 193 | } 194 | return best_source; 195 | } 196 | 197 | static void render(Source* source, float* buffer, const float gain[2]) { 198 | 199 | float* left = buffer; 200 | float* right = buffer + c_nsamples; 201 | 202 | int remaining = c_nsamples; 203 | while (remaining > 0) { 204 | int samples_read = remaining; 205 | int samples_written = samples_read; 206 | 207 | const float* srcleft; 208 | const float* srcright; 209 | 210 | // source has a non-1.0f frequency shift 211 | if (source->flags & SourceFlags::Frequency) { 212 | samples_read = tinymixer_resampler_calculate_input_samples(&source->resampler, samples_read); 213 | samples_read = source->buffer->funcs->request_samples(source, &srcleft, &srcright, samples_read); 214 | if (samples_read == 0) { 215 | // source is no longer playing 216 | source->flags &= ~SourceFlags::Playing; 217 | return; 218 | } 219 | 220 | samples_written = tinymixer_resampler_calculate_output_samples(&source->resampler, samples_read); 221 | tinymixer_resample_mono(&source->resampler, srcleft, samples_read, g_mixer.scratch, samples_written); 222 | 223 | if (srcleft == srcright) { 224 | srcleft = g_mixer.scratch; 225 | srcright = g_mixer.scratch; 226 | } else { 227 | // resmample the second streo channel 228 | tinymixer_resample_mono(&source->resampler, srcright, samples_read, g_mixer.scratch + samples_written, samples_written); 229 | 230 | srcleft = g_mixer.scratch; 231 | srcright = g_mixer.scratch + samples_written; 232 | } 233 | } else { 234 | samples_read = source->buffer->funcs->request_samples(source, &srcleft, &srcright, samples_read); 235 | if (samples_read == 0) { 236 | // source is no longer playing 237 | source->flags &= ~SourceFlags::Playing; 238 | return; 239 | } 240 | samples_written = samples_read; 241 | } 242 | 243 | // render the source to the output mix 244 | for (int ii = 0; ii < samples_written; ++ii) 245 | *left++ += gain[0] * srcleft[ii]; 246 | for (int ii = 0; ii < samples_written; ++ii) 247 | *right++ += gain[1] * srcright[ii]; 248 | 249 | remaining -= samples_written; 250 | } 251 | } 252 | 253 | static void render_effects(float* buffer) { 254 | float compressor_factor = g_mixer.compressor_factor; 255 | 256 | // get maximum absolute power level from the rendered buffer, and adjust the compressor factor 257 | float max_power = 0; 258 | for (int ii = 0; ii < c_nsamples; ++ii) { 259 | const float power = fabsf(buffer[ii]); 260 | if (power > max_power) 261 | max_power = power; 262 | } 263 | 264 | float target_compressor_factor = 1.0f; 265 | if (max_power > g_mixer.compressor_thresholds[1]) 266 | target_compressor_factor = g_mixer.compressor_multipliers[1]; 267 | else if (max_power > g_mixer.compressor_thresholds[0]) 268 | target_compressor_factor = g_mixer.compressor_multipliers[0]; 269 | 270 | float attack_release = 1.0f; 271 | if (target_compressor_factor < compressor_factor) 272 | attack_release = g_mixer.compressor_attack_per1ksamples; 273 | else if (target_compressor_factor > compressor_factor) 274 | attack_release = g_mixer.compressor_release_per1ksamples; 275 | 276 | // linearly interp compressor_factor toward the target compressor value 277 | const float interp = attack_release * c_fnsamples; 278 | compressor_factor = compressor_factor + interp*(target_compressor_factor - compressor_factor); 279 | compressor_factor = mixer_clamp(compressor_factor, g_mixer.compressor_multipliers[1], 1.0f); 280 | 281 | // 2-pass compressor to limit dynamic range of audio clipping levels 282 | if (compressor_factor < 1.0f) { 283 | for (int cc = 0; cc < 2; ++cc) { 284 | float prev_sample = g_mixer.compressor_last_samples[cc]; 285 | 286 | float sample = 0; 287 | float* channel = buffer + cc*c_nsamples; 288 | for (int ii = 0; ii < c_nsamples; ++ii) { 289 | float sample = channel[ii]; 290 | 291 | // uhhh... linear space? really?? 292 | float diff = sample - prev_sample; 293 | sample = prev_sample + compressor_factor*diff; 294 | channel[ii] = sample; 295 | } 296 | 297 | g_mixer.compressor_last_samples[cc] = sample; 298 | } 299 | } 300 | 301 | g_mixer.compressor_factor = compressor_factor; 302 | } 303 | 304 | static void mix(float* buffer) { 305 | int nplaying = 0; 306 | int playing[c_nsources]; 307 | float gain[c_nsources][2]; 308 | 309 | // build active sounds 310 | for (int ii = 0; ii < c_nsources; ++ii) { 311 | const Source* source = &g_mixer.sources[ii]; 312 | if (source->flags & SourceFlags::Playing) { 313 | playing[nplaying] = ii; 314 | ++nplaying; 315 | } 316 | } 317 | 318 | // Update source gains 319 | for (int ii = 0; ii < nplaying; ++ii) { 320 | const Source* source = &g_mixer.sources[playing[ii]]; 321 | 322 | const float gain_base = g_mixer.gain_master * g_mixer.gain_base[source->gain_base_index] * source->gain_base; 323 | gain[ii][0] = gain_base; 324 | gain[ii][1] = gain_base; 325 | 326 | if (source->flags & SourceFlags::Positional) { 327 | const float dist = mixer_dist(g_mixer.position, source->position); 328 | if (dist > 1.0e-8f) { 329 | // attenuation factor due to distance 330 | const float gain_distance = 1.0f - (dist - source->distance_min) / source->distance_difference; 331 | 332 | // attenuation factor due to panning (position audio) 333 | const float gain_panning = (source->position[0] - g_mixer.position[0]) / dist; 334 | 335 | gain[ii][0] *= gain_distance * (1.0f + gain_panning * -c_speakerdist); 336 | gain[ii][1] *= gain_distance * (1.0f + gain_panning * +c_speakerdist); 337 | } 338 | } 339 | 340 | // clamp gains 341 | gain[ii][0] = mixer_clamp(gain[ii][0], 0.0f, 1.0f); 342 | gain[ii][1] = mixer_clamp(gain[ii][1], 0.0f, 1.0f); 343 | } 344 | 345 | memset(buffer, 0, sizeof(float)*2*c_nsamples); 346 | 347 | // render playing sources 348 | for (int ii = 0; ii < nplaying; ++ii) { 349 | Source* source = &g_mixer.sources[playing[ii]]; 350 | render(source, buffer, gain[ii]); 351 | } 352 | 353 | // allow application to apply a premixed track (such as music) 354 | if (g_mixer.callbacks.pre_effects) 355 | (g_mixer.callbacks.pre_effects)(g_mixer.callbacks.opaque, buffer, c_nsamples, g_mixer.gain_callback); 356 | 357 | // render effects 358 | render_effects(buffer); 359 | 360 | // perform source-level post processing 361 | for (int ii = 0; ii < nplaying; ++ii) { 362 | Source* source = &g_mixer.sources[playing[ii]]; 363 | ++source->frame_age; 364 | 365 | // handle fadeout->stop 366 | if (source->flags & SourceFlags::FadeOut) { 367 | source->gain_base -= source->fadeout_per_sample * c_nsamples; 368 | if (source->gain_base <= 0.0f) { 369 | source->flags = 0; 370 | } 371 | } 372 | } 373 | 374 | // cleanup dead sources 375 | for (int ii = 0; ii < nplaying; ++ii) { 376 | Source* source = &g_mixer.sources[playing[ii]]; 377 | if (0 == (source->flags & SourceFlags::Playing)) { 378 | kill_source(source); 379 | } 380 | } 381 | } 382 | 383 | void tinymixer_getsamples(float* samples, int nsamples) { 384 | std::lock_guard lock(g_mixer.lock); 385 | 386 | // was data leftover after the previous call to getsamples? Copy that out here 387 | while (nsamples && g_mixer.samples_remaining) { 388 | const int samples_to_mix = mixer_min(nsamples, g_mixer.samples_remaining); 389 | const int offset = c_nsamples - g_mixer.samples_remaining; 390 | 391 | // clip and interleave 392 | for (int cc = 0; cc < 2; ++cc) 393 | for (int ii = 0; ii < samples_to_mix; ++ii) 394 | samples[cc + 2*ii] = mixer_clamp(g_mixer.buffer[cc*c_nsamples + offset + ii], -1.0f, 1.0f); 395 | 396 | g_mixer.samples_remaining -= samples_to_mix; 397 | samples += (2*samples_to_mix); 398 | nsamples -= samples_to_mix; 399 | } 400 | 401 | // Copy out samples 402 | while (nsamples) { 403 | const int samples_to_mix = mixer_min(nsamples, c_nsamples); 404 | mix(g_mixer.buffer); 405 | g_mixer.samples_remaining = c_nsamples; 406 | 407 | // clip and interleave 408 | for (int cc = 0; cc < 2; ++cc) 409 | for (int ii = 0; ii < samples_to_mix; ++ii) 410 | samples[cc + 2*ii] = mixer_clamp(g_mixer.buffer[cc*c_nsamples + ii], -1.0f, 1.0f); 411 | 412 | 413 | g_mixer.samples_remaining -= samples_to_mix; 414 | samples += (2*samples_to_mix); 415 | nsamples -= samples_to_mix; 416 | } 417 | } 418 | 419 | void tinymixer_set_mastergain(float gain) { 420 | std::lock_guard lock(g_mixer.lock); 421 | g_mixer.gain_master = gain; 422 | } 423 | 424 | static Source* add(const tinymixer_buffer* handle, int gain_index, float gain, float pitch) { 425 | Source* source = find_source(); 426 | if (!source) 427 | return 0; 428 | 429 | source->buffer = (const Buffer*)handle; 430 | source->gain_base = gain; 431 | source->gain_base_index = (uint8_t)gain_index; 432 | source->frame_age = 0; 433 | 434 | const float diff = pitch - 1.0f; 435 | if (diff*diff < 1.0e-8f) { 436 | source->flags &= ~SourceFlags::Frequency; 437 | } else { 438 | source->flags |= SourceFlags::Frequency; 439 | tinymixer_resampler_init_rate(&source->resampler, pitch); 440 | } 441 | 442 | source->buffer->funcs->start_source(source); 443 | 444 | addref((Buffer*)handle); 445 | return source; 446 | } 447 | 448 | static void play(Source* source) { 449 | source->flags |= SourceFlags::Playing; 450 | } 451 | 452 | namespace { 453 | struct StaticSampleBuffer { 454 | Buffer buffer; 455 | int32_t nsamples; 456 | uint8_t nchannels; 457 | // float smaples[nsamples*nschannels]; 458 | }; 459 | } 460 | 461 | static void static_sample_buffer_on_destroy(Buffer* buffer) { 462 | } 463 | 464 | static void static_sample_buffer_start_source(Source* source) { 465 | source->instance_data.static_source.sample_pos = 0; 466 | } 467 | 468 | static void static_sample_buffer_end_source(Source*source) { 469 | } 470 | 471 | static int static_sample_buffer_request_samples(Source* source, const float** left, const float** right, int nsamples) { 472 | const StaticSampleBuffer* buffer = (const StaticSampleBuffer*)source->buffer; 473 | 474 | int sample_pos = source->instance_data.static_source.sample_pos; 475 | 476 | // handle looping sources 477 | if (sample_pos == buffer->nsamples) { 478 | if (source->flags & SourceFlags::Looping) { 479 | sample_pos = 0; 480 | } else { 481 | return 0; 482 | } 483 | } 484 | 485 | nsamples = mixer_min(buffer->nsamples - sample_pos, nsamples); 486 | const float* srcleft = (float*)(buffer + 1) + sample_pos; 487 | 488 | *left = srcleft; 489 | if (buffer->nchannels == 1) 490 | *right = srcleft; 491 | else { 492 | *right = srcleft + buffer->nsamples; 493 | } 494 | 495 | source->instance_data.static_source.sample_pos = sample_pos + nsamples; 496 | return nsamples; 497 | } 498 | 499 | static int static_sample_buffer_get_buffer_size(const Buffer* buffer) { 500 | const StaticSampleBuffer* sbuffer = (const StaticSampleBuffer*)buffer; 501 | return sizeof(StaticSampleBuffer) + sizeof(float)*sbuffer->nchannels*sbuffer->nsamples; 502 | } 503 | 504 | static buffer_functions static_sample_functions = { 505 | static_sample_buffer_on_destroy, 506 | static_sample_buffer_start_source, 507 | static_sample_buffer_end_source, 508 | static_sample_buffer_request_samples, 509 | static_sample_buffer_get_buffer_size, 510 | }; 511 | 512 | void tinymixer_create_buffer_interleaved_s16le(int channels, const int16_t* pcm_data, int pcm_data_size, const tinymixer_buffer** handle) { 513 | const int nsamples = pcm_data_size/sizeof(uint16_t)/channels; 514 | 515 | StaticSampleBuffer* buffer = (StaticSampleBuffer*)g_mixer.callbacks.allocate(g_mixer.callbacks.opaque, sizeof(Buffer) + nsamples*channels*sizeof(float)); 516 | buffer->buffer.funcs = &static_sample_functions; 517 | buffer->buffer.refcnt = 1; 518 | buffer->nchannels = (uint8_t)channels; 519 | buffer->nsamples = nsamples; 520 | 521 | // copy samples 522 | const int16_t* source = (const int16_t*)pcm_data; 523 | float* dest = (float*)(buffer + 1); 524 | for (int cc = 0; cc < channels; ++cc) { 525 | for (int ii = 0; ii < nsamples; ++ii) 526 | *dest++ = (float)source[channels*ii + cc] / (float)0x8000; 527 | } 528 | 529 | *handle = (tinymixer_buffer*)buffer; 530 | } 531 | 532 | void tinymixer_create_buffer_interleaved_float(int channels, const float* pcm_data, int pcm_data_size, const tinymixer_buffer** handle) { 533 | const int nsamples = pcm_data_size/sizeof(float)/channels; 534 | 535 | StaticSampleBuffer* buffer = (StaticSampleBuffer*)g_mixer.callbacks.allocate(g_mixer.callbacks.opaque, sizeof(Buffer) + nsamples*channels*sizeof(float)); 536 | buffer->buffer.funcs = &static_sample_functions; 537 | buffer->buffer.refcnt = 1; 538 | buffer->nchannels = (uint8_t)channels; 539 | buffer->nsamples = nsamples; 540 | 541 | // copy samples 542 | const float* source = (const float*)pcm_data; 543 | float* dest = (float*)(buffer + 1); 544 | for (int cc = 0; cc < channels; ++cc) { 545 | for (int ii = 0; ii < nsamples; ++ii) 546 | *dest++ = source[channels*ii + cc];//(int16_t)mixer_clamp((int32_t)(source[channels*ii + cc] * (float)0x8000), (int16_t)0x8000, 0x7fff); 547 | } 548 | 549 | *handle = (tinymixer_buffer*)buffer; 550 | } 551 | 552 | namespace { 553 | struct VorbisStreamBuffer { 554 | Buffer buffer; 555 | void* opaque; 556 | void (*closed)(void*); 557 | int ndata; 558 | // uint8_t vorbis_data[ndata] 559 | }; 560 | } 561 | 562 | static void vorbis_stream_on_destroy(Buffer* buffer) { 563 | VorbisStreamBuffer* vbuffer = (VorbisStreamBuffer*)buffer; 564 | if (vbuffer->closed) 565 | vbuffer->closed(vbuffer->opaque); 566 | } 567 | 568 | static void vorbis_stream_start_source(Source* source) { 569 | const VorbisStreamBuffer* vbuffer = (const VorbisStreamBuffer*)source->buffer; 570 | vorbis_stream_data* vsd = &source->instance_data.vorbis_stream; 571 | 572 | // open the vorbis stream 573 | unsigned char* data = (unsigned char*)(vbuffer + 1); 574 | vsd->v = stb_vorbis_open_memory(data, vbuffer->ndata, NULL, NULL); 575 | vsd->noutputs = 0; 576 | } 577 | 578 | static void vorbis_stream_end_source(Source* source) { 579 | stb_vorbis* v = source->instance_data.vorbis_stream.v; 580 | if (v) 581 | stb_vorbis_close(v); 582 | 583 | source->instance_data.vorbis_stream.v = 0; 584 | } 585 | 586 | static int vorbis_stream_request_samples(Source* source, const float** left, const float** right, int nsamples) { 587 | vorbis_stream_data* vsd = &source->instance_data.vorbis_stream; 588 | 589 | // no steam? 590 | if (!vsd->v) 591 | return 0; 592 | 593 | if (vsd->noutputs == 0) { 594 | int channels; 595 | float** outputs; 596 | vsd->noutputs = stb_vorbis_get_frame_float(vsd->v, &channels, &outputs); 597 | 598 | // if we're looping and have reached the end, seek to the start and try again 599 | if (vsd->noutputs == 0 && source->flags & SourceFlags::Looping) { 600 | stb_vorbis_seek_start(vsd->v); 601 | vsd->noutputs = stb_vorbis_get_frame_float(vsd->v, &channels, &outputs); 602 | } 603 | 604 | // handle mono streams 605 | if (vsd->noutputs) { 606 | vsd->outputs[0] = outputs[0]; 607 | vsd->outputs[1] = (channels == 1) ? outputs[0] : outputs[1]; 608 | } 609 | } 610 | 611 | nsamples = mixer_min(nsamples, vsd->noutputs); 612 | 613 | *left = vsd->outputs[0]; 614 | *right = vsd->outputs[1]; 615 | 616 | vsd->noutputs -= nsamples; 617 | vsd->outputs[0] += nsamples; 618 | vsd->outputs[1] += nsamples; 619 | return nsamples; 620 | } 621 | 622 | static int vorbis_stream_get_buffer_size(const Buffer* buffer) { 623 | const VorbisStreamBuffer* vbuffer = (const VorbisStreamBuffer*)buffer; 624 | return sizeof(VorbisStreamBuffer) + vbuffer->ndata; 625 | } 626 | 627 | static buffer_functions vorbis_stream_buffer_funcs = { 628 | vorbis_stream_on_destroy, 629 | vorbis_stream_start_source, 630 | vorbis_stream_end_source, 631 | vorbis_stream_request_samples, 632 | vorbis_stream_get_buffer_size, 633 | }; 634 | 635 | void tinymixer_create_buffer_vorbis_stream(const void* data, int ndata, void* opaque, void (*closed)(void*), const tinymixer_buffer** handle) { 636 | std::lock_guard lock(g_mixer.lock); 637 | VorbisStreamBuffer* buffer = (VorbisStreamBuffer*)g_mixer.callbacks.allocate(g_mixer.callbacks.opaque, sizeof(VorbisStreamBuffer) + ndata); 638 | buffer->buffer.funcs = &vorbis_stream_buffer_funcs; 639 | buffer->buffer.refcnt = 1; 640 | buffer->opaque = opaque; 641 | buffer->closed = closed; 642 | buffer->ndata = ndata; 643 | 644 | // copy vorbis data 645 | memcpy(buffer + 1, data, ndata); 646 | *handle = (tinymixer_buffer*)buffer; 647 | } 648 | 649 | namespace { 650 | struct CustomStreamBuffer { 651 | Buffer buffer; 652 | void* opaque; 653 | tinymixer_buffer_callbacks callbacks; 654 | }; 655 | } 656 | 657 | static void custom_stream_on_destroy(Buffer* buffer) { 658 | CustomStreamBuffer* cbuffer = (CustomStreamBuffer*)buffer; 659 | cbuffer->callbacks.on_destroy(cbuffer->opaque); 660 | } 661 | 662 | static void custom_stream_start_source(Source* source) { 663 | CustomStreamBuffer* cbuffer = (CustomStreamBuffer*)source->buffer; 664 | source->instance_data.custom_stream.opaque = cbuffer->callbacks.start_source(cbuffer->opaque); 665 | } 666 | 667 | static void custom_stream_end_source(Source* source) { 668 | CustomStreamBuffer* cbuffer = (CustomStreamBuffer*)source->buffer; 669 | cbuffer->callbacks.end_source(cbuffer->opaque, source->instance_data.custom_stream.opaque); 670 | } 671 | 672 | static int custom_stream_request_samples(Source* source, const float** left, const float** right, int nsamples) { 673 | CustomStreamBuffer* cbuffer = (CustomStreamBuffer*)source->buffer; 674 | return cbuffer->callbacks.request_samples(cbuffer->opaque, source->instance_data.custom_stream.opaque, left, right, nsamples); 675 | } 676 | 677 | static int custom_stream_get_buffer_size(const Buffer* buffer) { 678 | return sizeof(CustomStreamBuffer); 679 | } 680 | 681 | static buffer_functions custom_stream_buffer_funcs = { 682 | custom_stream_on_destroy, 683 | custom_stream_start_source, 684 | custom_stream_end_source, 685 | custom_stream_request_samples, 686 | custom_stream_get_buffer_size, 687 | }; 688 | 689 | void tinymixer_create_buffer_custom_stream(void* opaque, tinymixer_buffer_callbacks callbacks, const tinymixer_buffer** handle) { 690 | std::lock_guard lock(g_mixer.lock); 691 | 692 | CustomStreamBuffer* buffer = (CustomStreamBuffer*)g_mixer.callbacks.allocate(g_mixer.callbacks.opaque, sizeof(CustomStreamBuffer)); 693 | buffer->buffer.funcs = &custom_stream_buffer_funcs; 694 | buffer->buffer.refcnt = 1; 695 | buffer->opaque = opaque; 696 | buffer->callbacks = callbacks; 697 | 698 | *handle = (tinymixer_buffer*)buffer; 699 | } 700 | 701 | int tinymixer_get_buffer_size(const tinymixer_buffer* handle) { 702 | std::lock_guard lock(g_mixer.lock); 703 | const Buffer* buffer = (const Buffer*)handle; 704 | return buffer->funcs->get_buffer_size(buffer); 705 | } 706 | 707 | void tinymixer_release_buffer(const tinymixer_buffer* handle) { 708 | std::lock_guard lock(g_mixer.lock); 709 | decref((Buffer*)handle); 710 | } 711 | 712 | bool tinymixer_add(const tinymixer_buffer* handle, int gain_index, float gain, float pitch, tinymixer_channel* channel) { 713 | std::lock_guard lock(g_mixer.lock); 714 | Source* source = add(handle, gain_index, gain, pitch); 715 | if (source) { 716 | play(source); 717 | channel->index = (int)(source - g_mixer.sources) + 1; 718 | return true; 719 | } 720 | 721 | return false; 722 | } 723 | 724 | bool tinymixer_add(const tinymixer_buffer* handle, int gain_index, float gain, float pitch, const float* position, float distance_min, float distance_max, tinymixer_channel* channel) { 725 | std::lock_guard lock(g_mixer.lock); 726 | Source *source = add(handle, gain_index, gain, pitch); 727 | if (source) { 728 | mixer_vcopy(source->position, position); 729 | source->flags |= SourceFlags::Positional; 730 | source->distance_min = distance_min; 731 | source->distance_difference = (distance_max - distance_min); 732 | play(source); 733 | channel->index = (int)(source - g_mixer.sources) + 1; 734 | 735 | return true; 736 | } 737 | 738 | return false; 739 | } 740 | 741 | static Source* add_loop(const tinymixer_buffer* handle, int gain_index, float gain, float pitch, tinymixer_channel* channel) { 742 | Source* source = add(handle, gain_index, gain, pitch); 743 | if (!source) { 744 | channel->index = 0; 745 | return 0; 746 | } 747 | 748 | source->flags |= SourceFlags::Looping; 749 | channel->index = (int)(source - g_mixer.sources) + 1; 750 | return source; 751 | } 752 | 753 | bool tinymixer_add_loop(const tinymixer_buffer* handle, int gain_index, float gain, float pitch, tinymixer_channel* channel) { 754 | std::lock_guard lock(g_mixer.lock); 755 | Source* source = add_loop(handle, gain_index, gain, pitch, channel); 756 | if (source) 757 | { 758 | play(source); 759 | 760 | return true; 761 | } 762 | 763 | return false; 764 | } 765 | 766 | bool tinymixer_add_loop(const tinymixer_buffer* handle, int gain_index, float gain, float pitch, const float* position, float distance_min, float distance_max, tinymixer_channel* channel) { 767 | std::lock_guard lock(g_mixer.lock); 768 | Source* source = add_loop(handle, gain_index, gain, pitch, channel); 769 | if (source) { 770 | mixer_vcopy(source->position, position); 771 | source->flags |= SourceFlags::Positional; 772 | source->distance_min = distance_min; 773 | source->distance_difference = (distance_max - distance_min); 774 | 775 | play(source); 776 | 777 | return true; 778 | } 779 | 780 | return false; 781 | } 782 | 783 | void tinymixer_channel_set_opaque(tinymixer_channel channel, void* opaque) { 784 | std::lock_guard lock(g_mixer.lock); 785 | Source* source = &g_mixer.sources[channel.index - 1]; 786 | source->opaque = opaque; 787 | } 788 | 789 | void tinymixer_channel_stop(tinymixer_channel channel) { 790 | std::lock_guard lock(g_mixer.lock); 791 | Source* source = &g_mixer.sources[channel.index - 1]; 792 | kill_source(source); 793 | } 794 | 795 | void tinymixer_channel_set_position(tinymixer_channel channel, const float* position) { 796 | std::lock_guard lock(g_mixer.lock); 797 | Source* source = &g_mixer.sources[channel.index - 1]; 798 | mixer_vcopy(source->position, position); 799 | } 800 | 801 | void tinymixer_channel_fadeout(tinymixer_channel channel, float seconds) { 802 | std::lock_guard lock(g_mixer.lock); 803 | Source* source = &g_mixer.sources[channel.index - 1]; 804 | source->fadeout_per_sample = 1.0f / (seconds * g_mixer.sample_rate); 805 | source->flags |= SourceFlags::FadeOut; 806 | } 807 | 808 | void tinymixer_channel_set_gain(tinymixer_channel channel, float gain) { 809 | std::lock_guard lock(g_mixer.lock); 810 | Source* source = &g_mixer.sources[channel.index - 1]; 811 | source->gain_base = gain; 812 | source->flags &= ~SourceFlags::FadeOut; 813 | } 814 | 815 | float tinymixer_channel_get_gain(tinymixer_channel channel) { 816 | std::lock_guard lock(g_mixer.lock); 817 | Source* source = &g_mixer.sources[channel.index - 1]; 818 | return source->gain_base; 819 | } 820 | 821 | void tinymixer_channel_set_frequency(tinymixer_channel channel, float frequency) { 822 | std::lock_guard lock(g_mixer.lock); 823 | Source* source = &g_mixer.sources[channel.index - 1]; 824 | 825 | // clear frequency shift if ~0.0f 826 | const float diff = frequency - 1.0f; 827 | if (diff*diff < 1.0e-8f) { 828 | source->flags &= ~SourceFlags::Frequency; 829 | } else { 830 | source->flags |= SourceFlags::Frequency; 831 | source->resampler.ideal_rate = frequency; 832 | } 833 | } 834 | 835 | void tinymixer_init(tinymixer_callbacks callbacks, int sample_rate) { 836 | // setup default callbacks where needed 837 | if (!callbacks.allocate) { 838 | callbacks.allocate = [](void* opaque, int bytes) { 839 | return malloc(bytes); 840 | }; 841 | } 842 | if (!callbacks.free) { 843 | callbacks.free = [](void* opaque, void* pointer) { 844 | free(pointer); 845 | }; 846 | } 847 | 848 | g_mixer.gain_master = 1.0f; 849 | for (int ii = 0; ii < c_ngaintypes; ++ii) 850 | g_mixer.gain_base[ii] = 1.0f; 851 | 852 | g_mixer.sample_rate = sample_rate; 853 | g_mixer.callbacks = callbacks; 854 | g_mixer.samples_remaining = 0; 855 | 856 | const float default_thresholds[2] = {1.0f, 1.0f}; 857 | const float default_multipliers[2] = {1.0f, 1.0f}; 858 | const float default_attack = 0.0f; 859 | const float default_release = 0.0f; 860 | tinymixer_effects_compressor(default_thresholds, default_multipliers, default_attack, default_release); 861 | g_mixer.compressor_factor = 1.0f; 862 | g_mixer.compressor_last_samples[0] = g_mixer.compressor_last_samples[1] = 0; 863 | } 864 | 865 | void tinymixer_update_listener(const float* position) { 866 | std::lock_guard lock(g_mixer.lock); 867 | mixer_vcopy(g_mixer.position, position); 868 | } 869 | 870 | void tinymixer_set_base_gain(int index, float gain) { 871 | std::lock_guard lock(g_mixer.lock); 872 | g_mixer.gain_base[index] = gain; 873 | } 874 | 875 | void tinymixer_set_callback_gain(float gain) { 876 | std::lock_guard lock(g_mixer.lock); 877 | g_mixer.gain_callback = gain; 878 | } 879 | 880 | void tinymixer_effects_compressor(const float thresholds[2], const float multipliers[2], float attack_seconds, float release_seconds) { 881 | std::lock_guard lock(g_mixer.lock); 882 | g_mixer.compressor_thresholds[0] = mixer_clamp(thresholds[0], 0.0f, 1.0f); 883 | g_mixer.compressor_thresholds[1] = mixer_clamp(thresholds[1], 0.0f, 1.0f); 884 | g_mixer.compressor_multipliers[0] = mixer_clamp(multipliers[0], 0.0f, 1.0f); 885 | g_mixer.compressor_multipliers[1] = mixer_clamp(multipliers[1], 0.0f, 1.0f); 886 | 887 | float attackSampleRate = (attack_seconds * (float)g_mixer.sample_rate); 888 | g_mixer.compressor_attack_per1ksamples = (attackSampleRate > 0.0f) ? (1.0f / attackSampleRate) : 1.0f; 889 | 890 | float releaseSampleRate = (release_seconds * (float)g_mixer.sample_rate); 891 | g_mixer.compressor_release_per1ksamples = (releaseSampleRate > 0.0f) ? (1.0f / releaseSampleRate) : 1.0f; 892 | } 893 | 894 | void tinymixer_stop_all_sources() { 895 | std::lock_guard lock(g_mixer.lock); 896 | for (int ii = 0; ii < c_nsources; ++ii) { 897 | Source* source = &g_mixer.sources[ii]; 898 | if (source->buffer) { 899 | kill_source(source); 900 | } 901 | } 902 | } 903 | 904 | void tinymixer_resampler_init(tinymixer_resampler* resampler, int input_sample_rate, int output_sample_rate) { 905 | const float ideal_rate = (float)input_sample_rate / (float)output_sample_rate; 906 | tinymixer_resampler_init_rate(resampler, ideal_rate); 907 | } 908 | 909 | void tinymixer_resampler_init_rate(tinymixer_resampler* resampler, float ideal_rate) { 910 | resampler->ideal_rate = ideal_rate; 911 | resampler->prev_samples[0] = 0.0f; 912 | resampler->prev_samples[1] = 0.0f; 913 | } 914 | 915 | int tinymixer_resampler_calculate_input_samples(const tinymixer_resampler* resampler, int output_samples) { 916 | const float input_samples = ceilf(resampler->ideal_rate * (float)output_samples); 917 | return (int)input_samples; 918 | } 919 | 920 | int tinymixer_resampler_calculate_output_samples(const tinymixer_resampler* resampler, int input_samples) { 921 | const float output_samples = ceilf((float)input_samples / resampler->ideal_rate); 922 | return (int)output_samples; 923 | } 924 | 925 | void tinymixer_resample_stereo(tinymixer_resampler* resampler, const float* input, int num_input_samples, float* output, int num_output_samples) { 926 | float* output_end = output + (2 * num_output_samples) - 2; 927 | 928 | float pos = resampler->ideal_rate; 929 | while (pos < 1.0f) { 930 | output[0] = resampler->prev_samples[0] + pos * (input[0] - resampler->prev_samples[0]); 931 | output[1] = resampler->prev_samples[1] + pos * (input[1] - resampler->prev_samples[1]); 932 | 933 | output += 2; 934 | pos += resampler->ideal_rate; 935 | } 936 | 937 | while (output < output_end) { 938 | const float pos_floor = floorf(pos); 939 | const int index = 2 * (int)pos_floor; 940 | output[0] = input[index - 2] + (input[index + 0] - input[index - 2]) * (pos - pos_floor); 941 | output[1] = input[index - 1] + (input[index + 1] - input[index - 1]) * (pos - pos_floor); 942 | 943 | output += 2; 944 | pos += resampler->ideal_rate; 945 | } 946 | 947 | output[0] = input[2 * num_input_samples - 2]; 948 | output[1] = input[2 * num_input_samples - 1]; 949 | 950 | resampler->prev_samples[0] = output[0]; 951 | resampler->prev_samples[1] = output[1]; 952 | } 953 | 954 | void tinymixer_resample_mono(tinymixer_resampler* resampler, const float* input, int num_input_samples, float* output, int num_output_samples) { 955 | float* output_end = output + num_output_samples - 1; 956 | 957 | float pos = resampler->ideal_rate; 958 | while (pos < 1.0f) { 959 | output[0] = resampler->prev_samples[0] + pos * (input[0] - resampler->prev_samples[0]); 960 | 961 | ++output; 962 | pos += resampler->ideal_rate; 963 | } 964 | 965 | while (output < output_end) { 966 | const float pos_floor = floorf(pos); 967 | const int index = (int)pos_floor; 968 | output[0] = input[index - 1] + (input[index] - input[index - 1]) * (pos - pos_floor); 969 | 970 | ++output; 971 | pos += resampler->ideal_rate; 972 | } 973 | 974 | output[0] = input[num_input_samples - 1]; 975 | 976 | resampler->prev_samples[0] = output[0]; 977 | } 978 | 979 | void* tinymixer_vorbis_malloc(size_t sz) { 980 | return g_mixer.callbacks.allocate(g_mixer.callbacks.opaque, (int)sz); 981 | } 982 | 983 | void tinymixer_vorbis_free(void* ptr) { 984 | return g_mixer.callbacks.free(g_mixer.callbacks.opaque, ptr); 985 | } 986 | 987 | void* tinymixer_vorbis_temp_malloc(size_t sz) { 988 | return tinymixer_vorbis_malloc((int)sz); 989 | } 990 | 991 | void tinymixer_vorbis_temp_free(void* ptr) { 992 | tinymixer_vorbis_free(ptr); 993 | } 994 | 995 | void tinymixer_lowpass_filter_init(tinymixer_lowpass_filter* filter, float cutoff_frequency, float sample_rate) { 996 | filter->cutoff_frequency = cutoff_frequency; 997 | filter->sample_rate = sample_rate; 998 | 999 | memset(filter->channel_history, 0, sizeof(filter->channel_history)); 1000 | } 1001 | 1002 | void tinymixer_lowpass_filter_apply(tinymixer_lowpass_filter* filter, float* output, float* input, int num_samples, int num_channels) { 1003 | 1004 | float yk[2] = { 1005 | filter->channel_history[0] 1006 | , filter->channel_history[1] 1007 | }; 1008 | 1009 | const float alpha = filter->cutoff_frequency / filter->sample_rate; 1010 | 1011 | for (int ii = 0; ii != num_samples; ++ii) { 1012 | for (int channel = 0; channel != num_channels; ++channel) { 1013 | yk[channel] += alpha * (input[ii*num_channels + channel] - yk[channel]); 1014 | output[ii*num_channels + channel] = yk[channel]; 1015 | } 1016 | } 1017 | 1018 | filter->channel_history[0] = yk[0]; 1019 | filter->channel_history[1] = yk[1]; 1020 | } 1021 | --------------------------------------------------------------------------------