├── LICENSE ├── README.md ├── atomix.h ├── libs ├── README ├── miniaudio.h └── stb_vorbis.h ├── mu.ogg ├── so.ogg └── test.c /LICENSE: -------------------------------------------------------------------------------- 1 | CC0 1.0 Universal 2 | 3 | Statement of Purpose 4 | 5 | The laws of most jurisdictions throughout the world automatically confer 6 | exclusive Copyright and Related Rights (defined below) upon the creator and 7 | subsequent owner(s) (each and all, an "owner") of an original work of 8 | authorship and/or a database (each, a "Work"). 9 | 10 | Certain owners wish to permanently relinquish those rights to a Work for the 11 | purpose of contributing to a commons of creative, cultural and scientific 12 | works ("Commons") that the public can reliably and without fear of later 13 | claims of infringement build upon, modify, incorporate in other works, reuse 14 | and redistribute as freely as possible in any form whatsoever and for any 15 | purposes, including without limitation commercial purposes. These owners may 16 | contribute to the Commons to promote the ideal of a free culture and the 17 | further production of creative, cultural and scientific works, or to gain 18 | reputation or greater distribution for their Work in part through the use and 19 | efforts of others. 20 | 21 | For these and/or other purposes and motivations, and without any expectation 22 | of additional consideration or compensation, the person associating CC0 with a 23 | Work (the "Affirmer"), to the extent that he or she is an owner of Copyright 24 | and Related Rights in the Work, voluntarily elects to apply CC0 to the Work 25 | and publicly distribute the Work under its terms, with knowledge of his or her 26 | Copyright and Related Rights in the Work and the meaning and intended legal 27 | effect of CC0 on those rights. 28 | 29 | 1. Copyright and Related Rights. A Work made available under CC0 may be 30 | protected by copyright and related or neighboring rights ("Copyright and 31 | Related Rights"). Copyright and Related Rights include, but are not limited 32 | to, the following: 33 | 34 | i. the right to reproduce, adapt, distribute, perform, display, communicate, 35 | and translate a Work; 36 | 37 | ii. moral rights retained by the original author(s) and/or performer(s); 38 | 39 | iii. publicity and privacy rights pertaining to a person's image or likeness 40 | depicted in a Work; 41 | 42 | iv. rights protecting against unfair competition in regards to a Work, 43 | subject to the limitations in paragraph 4(a), below; 44 | 45 | v. rights protecting the extraction, dissemination, use and reuse of data in 46 | a Work; 47 | 48 | vi. database rights (such as those arising under Directive 96/9/EC of the 49 | European Parliament and of the Council of 11 March 1996 on the legal 50 | protection of databases, and under any national implementation thereof, 51 | including any amended or successor version of such directive); and 52 | 53 | vii. other similar, equivalent or corresponding rights throughout the world 54 | based on applicable law or treaty, and any national implementations thereof. 55 | 56 | 2. Waiver. To the greatest extent permitted by, but not in contravention of, 57 | applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and 58 | unconditionally waives, abandons, and surrenders all of Affirmer's Copyright 59 | and Related Rights and associated claims and causes of action, whether now 60 | known or unknown (including existing as well as future claims and causes of 61 | action), in the Work (i) in all territories worldwide, (ii) for the maximum 62 | duration provided by applicable law or treaty (including future time 63 | extensions), (iii) in any current or future medium and for any number of 64 | copies, and (iv) for any purpose whatsoever, including without limitation 65 | commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes 66 | the Waiver for the benefit of each member of the public at large and to the 67 | detriment of Affirmer's heirs and successors, fully intending that such Waiver 68 | shall not be subject to revocation, rescission, cancellation, termination, or 69 | any other legal or equitable action to disrupt the quiet enjoyment of the Work 70 | by the public as contemplated by Affirmer's express Statement of Purpose. 71 | 72 | 3. Public License Fallback. Should any part of the Waiver for any reason be 73 | judged legally invalid or ineffective under applicable law, then the Waiver 74 | shall be preserved to the maximum extent permitted taking into account 75 | Affirmer's express Statement of Purpose. In addition, to the extent the Waiver 76 | is so judged Affirmer hereby grants to each affected person a royalty-free, 77 | non transferable, non sublicensable, non exclusive, irrevocable and 78 | unconditional license to exercise Affirmer's Copyright and Related Rights in 79 | the Work (i) in all territories worldwide, (ii) for the maximum duration 80 | provided by applicable law or treaty (including future time extensions), (iii) 81 | in any current or future medium and for any number of copies, and (iv) for any 82 | purpose whatsoever, including without limitation commercial, advertising or 83 | promotional purposes (the "License"). The License shall be deemed effective as 84 | of the date CC0 was applied by Affirmer to the Work. Should any part of the 85 | License for any reason be judged legally invalid or ineffective under 86 | applicable law, such partial invalidity or ineffectiveness shall not 87 | invalidate the remainder of the License, and in such case Affirmer hereby 88 | affirms that he or she will not (i) exercise any of his or her remaining 89 | Copyright and Related Rights in the Work or (ii) assert any associated claims 90 | and causes of action with respect to the Work, in either case contrary to 91 | Affirmer's express Statement of Purpose. 92 | 93 | 4. Limitations and Disclaimers. 94 | 95 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 96 | surrendered, licensed or otherwise affected by this document. 97 | 98 | b. Affirmer offers the Work as-is and makes no representations or warranties 99 | of any kind concerning the Work, express, implied, statutory or otherwise, 100 | including without limitation warranties of title, merchantability, fitness 101 | for a particular purpose, non infringement, or the absence of latent or 102 | other defects, accuracy, or the present or absence of errors, whether or not 103 | discoverable, all to the greatest extent permissible under applicable law. 104 | 105 | c. Affirmer disclaims responsibility for clearing rights of other persons 106 | that may apply to the Work or any use thereof, including without limitation 107 | any person's Copyright and Related Rights in the Work. Further, Affirmer 108 | disclaims responsibility for obtaining any necessary consents, permissions 109 | or other rights required for any use of the Work. 110 | 111 | d. Affirmer understands and acknowledges that Creative Commons is not a 112 | party to this document and has no duty or obligation with respect to this 113 | CC0 or use of the Work. 114 | 115 | For more information, please see 116 | 117 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Documentation 2 | 3 | This is a header-only library, as such most of its functional documentation is contained within the "header section" of the 4 | source code in the form of comments. It is highly recommended that you read said documentation before using this library. 5 | 6 | ## Features 7 | 8 | The atomix library is a wait-free mixer making heavy use of stdatomic.h and SSE intrinsics, its features include: 9 | 10 | - No hard dependencies besides the standard library and the optional standard header stdatomic.h 11 | - Configurable memory allocation function, allowing for compatibility with custom memory managers 12 | - Pure mixer, allowing usage with the backend of your choice and mixing at any desired sampling rate 13 | - High performance, generally fast enough for real-time mixing at high sampling rates even without SSE 14 | 15 | ## Future 16 | 17 | Possible future features include: 18 | 19 | - Automatic miniaudio integration, removing the need for a boilerplate callback function 20 | - Mechanism to allow more advanced layering, coordination, and timing of sounds 21 | - Proper 3D audio support, possibly with position interpolation and doppler 22 | 23 | ## Attribution 24 | 25 | You are not required to give attribution when using this library. If you want to give attribution anyway, either link to 26 | this repository, [my website](https://www.slopegames.com/), or credit me as [BareRose](https://github.com/BareRose). 27 | If you want to support me financially, consider giving to my [Patreon](https://www.patreon.com/slopegames). 28 | 29 | ## License 30 | 31 | Licensed under CC0 aka the most lawyer-friendly way of spelling "public domain". -------------------------------------------------------------------------------- /atomix.h: -------------------------------------------------------------------------------- 1 | /* 2 | atomix.h - Portable, single-file, wait-free atomic sound mixing library utilizing SSE-accelerated mixing 3 | 4 | To the extent possible under law, the author(s) have dedicated all copyright and related and neighboring 5 | rights to this software to the public domain worldwide. This software is distributed without any warranty. 6 | You should have received a copy of the CC0 Public Domain Dedication along with this software. 7 | If not, see . 8 | */ 9 | 10 | /* 11 | atomix supports the following three configurations: 12 | #define ATOMIX_EXTERN 13 | Default, should be used when using atomix in multiple compilation units within the same project. 14 | #define ATOMIX_IMPLEMENTATION 15 | Must be defined in exactly one source file within a project for atomix to be found by the linker. 16 | #define ATOMIX_STATIC 17 | Defines all atomix functions as static, useful if atomix is only used in a single compilation unit. 18 | 19 | atomix supports the following additional options: 20 | #define ATOMIX_NO_CLIP 21 | Disables internal clipping, useful if you are using a backend that already does clipping of its own. 22 | #define ATOMIX_NO_SSE 23 | Disables all SSE optimizations, which makes atomix mix about 4 times slower but also use less memory. 24 | #define ATOMIX_LBITS 25 | Determines the number of layers as a power of 2. For example the default value of 8 means 256 layers. 26 | #define ATOMIX_ZALLOC(S) 27 | Overrides the zalloc function used by atomix with your own. This is calloc but with just 1 argument. 28 | 29 | atomix threads: 30 | Atomix is built around having one thread occasionally calling atomixMixerMix (usually in a callback) 31 | and one other thread (usually the main thread) calling the other functions to play/stop/etc sounds. 32 | Anything more than this will likely break thread safety and result in various problems and/or bugs. 33 | 34 | atomix frames: 35 | A frame refers to a number of samples equal to the number of channels, so usually two floats as one 36 | sample is a single float and atomix works primarily in stereo. Calling atomixSoundNew with a channel 37 | count of one is the one exception where a frame is a single sample as there is only one channel. 38 | 39 | atomix fading: 40 | Fading out happens automatically when a playing sound is stopped (changed to ATOMIX_STOP or ATOMIX_HALT). 41 | Fading in happens when a sound is resumed (changed to ATOMIX_PLAY or ATOMIX_LOOP) after having been halted. 42 | Fading out will not happen if the sound is close to its end, in which case it will simply play out instead. 43 | A sound started in a halted state will start fully faded out, resulting in a fade in when it is unhalted. 44 | 45 | atomix limits (SSE): 46 | The atomixMixerMix function uses a buffer on the stack which scales with the number of frames requested, 47 | therefore requesting an excessively high number of frames at once will likely result in a stack overflow. 48 | All frame numbers passed to atomix functions such as cursor positions, start and end frames, fade timers, 49 | and sound lengths are rounded to multiples of 4 in some way for reasons relating to internal alignment. 50 | 51 | atomix limits (non-SSE): 52 | Without SSE alignment requirements the atomixMixerMix no longer has to use a buffer on the stack, which 53 | removes the risk of stack overflow when mixing lots of frames at once. Memory usage is lower in general. 54 | Frame numbers passed to atomix functions are still rounded to multiples of 4 to keep the API consistent. 55 | Internally things are slightly different, as frames are now processed one-by-one instead of 4 at a time. 56 | */ 57 | 58 | //header section 59 | #ifndef ATOMIX_H 60 | #define ATOMIX_H 61 | 62 | //process configuration 63 | #ifdef ATOMIX_STATIC 64 | #define ATOMIX_IMPLEMENTATION 65 | #define ATMXDEF static 66 | #else //ATOMIX_EXTERN 67 | #define ATMXDEF extern 68 | #endif 69 | 70 | //constants 71 | #define ATOMIX_STOP 1 72 | #define ATOMIX_HALT 2 73 | #define ATOMIX_PLAY 3 74 | #define ATOMIX_LOOP 4 75 | 76 | //includes 77 | #include //integer types 78 | 79 | //structs 80 | struct atomix_mixer; //forward declaration 81 | struct atomix_sound; //forward declaration 82 | 83 | //function declarations 84 | ATMXDEF struct atomix_sound* atomixSoundNew(uint8_t, float*, int32_t); 85 | //creates a new atomix sound with given number of channels and data 86 | //length of data is in frames and rounded to multiple of 4 for alignment 87 | //given data is copied, so the buffer can safely be freed after return 88 | //returns a pointer to the new atomix sound or NULL on failure 89 | ATMXDEF int32_t atomixSoundLength(struct atomix_sound*); 90 | //returns the length of given sound in frames, always multiple of 4 91 | ATMXDEF struct atomix_mixer* atomixMixerNew(float, int32_t); 92 | //returns a new atomix mixer with given volume and fade or NULL on failure to allocate 93 | ATMXDEF uint32_t atomixMixerMix(struct atomix_mixer*, float*, uint32_t); 94 | //uses given atomix mixer to output exactly the requested number of frames to given buffer 95 | //returns the number of frames actually written to the buffer, buffer must not be NULL 96 | ATMXDEF uint32_t atomixMixerPlay(struct atomix_mixer*, struct atomix_sound*, uint8_t, float, float); 97 | //uses given atomix mixer to play given atomix sound with given initial state, gain, and pan 98 | //returns a sound handle used to reference the sound at a later point, or 0 on failure 99 | ATMXDEF uint32_t atomixMixerPlayAdv(struct atomix_mixer*, struct atomix_sound*, uint8_t, float, float, int32_t, int32_t, int32_t); 100 | //variant of atomixPlay that sets the start and end frames in the sound, positions are truncated to multiple of 4 101 | //a negative start value can be used to play a sound with a delay, a high end value can be used to loop a few times 102 | //if in the ATOMIX_LOOP state, looping will include these start/end positions, allowing for partial sounds to loop 103 | //returns a sound handle used to reference the sound at a later point, or 0 on failure 104 | ATMXDEF int atomixMixerSetGainPan(struct atomix_mixer*, uint32_t, float, float); 105 | //sets the gain and pan for the sound with given handle in given mixer 106 | //gain may be any float including negative, pan is clamped internally 107 | //returns 0 on success, non-zero if the handle is invalid 108 | ATMXDEF int atomixMixerSetCursor(struct atomix_mixer*, uint32_t, int32_t); 109 | //sets the cursor for the sound with given handle in given mixer 110 | //given cursor value is clamped and truncated to multiple of 4 111 | //returns 0 on success, non-zero if the handle is invalid 112 | ATMXDEF int atomixMixerSetState(struct atomix_mixer*, uint32_t, uint8_t); 113 | //sets the state for the sound with given handle in given mixer 114 | //given state must be one of the ATOMIX_XXX define constants 115 | //returns 0 on success, non-zero if the handle is invalid 116 | ATMXDEF void atomixMixerVolume(struct atomix_mixer*, float); 117 | //sets the global volume for given atomix mixer, may be any float including negative 118 | ATMXDEF void atomixMixerFade(struct atomix_mixer*, int32_t); 119 | //sets the global default fade value applied to all new sounds added after this command 120 | ATMXDEF void atomixMixerStopAll(struct atomix_mixer*); 121 | //stops all sounds in given mixer, invalidating any existing sound handles in that mixer 122 | ATMXDEF void atomixMixerHaltAll(struct atomix_mixer*); 123 | //halts all sounds currently playing in given mixer, allowing them to be resumed later 124 | ATMXDEF void atomixMixerPlayAll(struct atomix_mixer*); 125 | //resumes all halted sounds in given mixer, no effect on looping or stopped sounds 126 | 127 | #endif //ATOMIX_H 128 | 129 | //implementation section 130 | #ifdef ATOMIX_IMPLEMENTATION 131 | #undef ATOMIX_IMPLEMENTATION 132 | 133 | //macros 134 | #ifndef ATOMIX_ZALLOC 135 | #include //calloc 136 | #define ATOMIX_ZALLOC(S) calloc(1, S) 137 | #endif 138 | #define ATMX_STORE(A, C) atomic_store_explicit(A, C, memory_order_release) 139 | #define ATMX_LOAD(A) atomic_load_explicit(A, memory_order_acquire) 140 | #define ATMX_CSWAP(A, E, C) atomic_compare_exchange_strong_explicit(A, E, C, memory_order_acq_rel, memory_order_acquire) 141 | 142 | //constants 143 | #ifndef ATOMIX_LBITS 144 | #define ATOMIX_LBITS 8 145 | #endif 146 | #define ATMX_LAYERS (1 << ATOMIX_LBITS) 147 | #define ATMX_LMASK (ATMX_LAYERS - 1) 148 | 149 | //includes 150 | #ifndef ATOMIX_NO_SSE 151 | #include //SSE intrinsics 152 | #endif 153 | #ifndef __cplusplus 154 | #include //atomics 155 | #else 156 | using namespace std; 157 | #include //atomics 158 | #define _Atomic(X) atomic 159 | #endif 160 | #include //memcpy 161 | 162 | //structs 163 | struct atomix_sound { 164 | uint8_t cha; //channels 165 | int32_t len; //data length 166 | #ifndef ATOMIX_NO_SSE 167 | __m128* data; //aligned data 168 | #else 169 | float data[]; //float data 170 | #endif 171 | }; 172 | struct atmx_f2 { 173 | float l, r; //left/right floats 174 | }; 175 | struct atmx_layer { 176 | uint32_t id; //playing id 177 | _Atomic(uint8_t) flag; //state 178 | _Atomic(int32_t) cursor; //cursor 179 | _Atomic(struct atmx_f2) gain; //gain 180 | struct atomix_sound* snd; //sound data 181 | int32_t start, end; //start and end 182 | int32_t fade, fmax; //fading 183 | }; 184 | struct atomix_mixer { 185 | uint32_t nid; //next id 186 | _Atomic(float) volume; //global volume 187 | struct atmx_layer lays[ATMX_LAYERS]; //layers 188 | int32_t fade; //global default fade value 189 | #ifndef ATOMIX_NO_SSE 190 | uint32_t rem; //remaining frames 191 | float data[6]; //old frames 192 | #endif 193 | }; 194 | 195 | //function declarations 196 | #ifndef ATOMIX_NO_SSE 197 | static void atmxMixLayer(struct atmx_layer*, __m128, __m128*, uint32_t); 198 | static int32_t atmxMixFadeMono(struct atmx_layer*, int32_t, __m128, __m128*, uint32_t); 199 | static int32_t atmxMixFadeStereo(struct atmx_layer*, int32_t, __m128, __m128*, uint32_t); 200 | static int32_t atmxMixPlayMono(struct atmx_layer*, int, int32_t, __m128, __m128*, uint32_t); 201 | static int32_t atmxMixPlayStereo(struct atmx_layer*, int, int32_t, __m128, __m128*, uint32_t); 202 | #else 203 | static void atmxMixLayer(struct atmx_layer*, float, float*, uint32_t); 204 | static int32_t atmxMixFadeMono(struct atmx_layer*, int32_t, struct atmx_f2, float*, uint32_t); 205 | static int32_t atmxMixFadeStereo(struct atmx_layer*, int32_t, struct atmx_f2, float*, uint32_t); 206 | static int32_t atmxMixPlayMono(struct atmx_layer*, int, int32_t, struct atmx_f2, float*, uint32_t); 207 | static int32_t atmxMixPlayStereo(struct atmx_layer*, int, int32_t, struct atmx_f2, float*, uint32_t); 208 | #endif 209 | static struct atmx_f2 atmxGainf2(float, float); 210 | 211 | //public functions 212 | ATMXDEF struct atomix_sound* atomixSoundNew (uint8_t cha, float* data, int32_t len) { 213 | //validate arguments first and return NULL if invalid 214 | if ((cha < 1)||(cha > 2)||(!data)||(len < 1)) return NULL; 215 | //round length to next multiple of 4 216 | int32_t rlen = (len + 3) & ~0x03; 217 | //allocate sound struct and space for data 218 | #ifndef ATOMIX_NO_SSE 219 | struct atomix_sound* snd = (struct atomix_sound*)ATOMIX_ZALLOC(sizeof(struct atomix_sound) + rlen*cha*sizeof(float) + 15); 220 | #else 221 | struct atomix_sound* snd = (struct atomix_sound*)ATOMIX_ZALLOC(sizeof(struct atomix_sound) + rlen*cha*sizeof(float)); 222 | #endif 223 | //return if zalloc failed 224 | if (!snd) return NULL; 225 | //fill in channel and length 226 | snd->cha = cha; snd->len = rlen; 227 | //align data pointer in allocated space if SSE 228 | #ifndef ATOMIX_NO_SSE 229 | snd->data = (__m128*)(void*)(((uintptr_t)(void*)&snd[1] + 15) & ~15); 230 | #endif 231 | //copy sound data into now aligned buffer 232 | memcpy(snd->data, data, len*cha*sizeof(float)); 233 | //return 234 | return snd; 235 | } 236 | ATMXDEF int32_t atomixSoundLength (struct atomix_sound* snd) { 237 | //return length, always multiple of 4 238 | return snd->len; 239 | } 240 | ATMXDEF struct atomix_mixer* atomixMixerNew (float vol, int32_t fade) { 241 | //allocate space for the mixer filled with zero 242 | struct atomix_mixer* mix = (struct atomix_mixer*)ATOMIX_ZALLOC(sizeof(struct atomix_mixer)); 243 | //return if zalloc failed 244 | if (!mix) return NULL; 245 | //atomically set the volume 246 | ATMX_STORE(&mix->volume, vol); 247 | //set fade value 248 | mix->fade = (fade < 0) ? 0 : fade & ~3; 249 | //return 250 | return mix; 251 | } 252 | ATMXDEF uint32_t atomixMixerMix (struct atomix_mixer* mix, float* buff, uint32_t fnum) { 253 | //the mixing function differs greatly depending on whether SSE is enabled or not 254 | #ifndef ATOMIX_NO_SSE 255 | //output remaining frames in buffer before mixing new ones 256 | uint32_t rnum = fnum; 257 | //only do this if there are old frames 258 | if (mix->rem) 259 | if (rnum > mix->rem) { 260 | //rnum bigger than remaining frames (usual case) 261 | memcpy(buff, mix->data, mix->rem*2*sizeof(float)); 262 | rnum -= mix->rem; buff += mix->rem*2; mix->rem = 0; 263 | } else { 264 | //rnum smaller equal remaining frames (rare case) 265 | memcpy(buff, mix->data, rnum*2*sizeof(float)); mix->rem -= rnum; 266 | //move back remaining old frames if any 267 | if (mix->rem) memmove(mix->data, mix->data + rnum*2, (3 - rnum)*2*sizeof(float)); 268 | //return 269 | return fnum; 270 | } 271 | //asize in __m128 (__m128 = 2 frames) and multiple of 2 272 | uint32_t asize = ((rnum + 3) & ~3) >> 1; 273 | //dynamically sized aligned buffer 274 | __m128 align[asize]; 275 | //clear the aligned buffer using SSE assignment 276 | for (uint32_t i = 0; i < asize; i++) align[i] = _mm_setzero_ps(); 277 | //begin actual mixing, caching the volume first 278 | __m128 vol = _mm_set_ps1(ATMX_LOAD(&mix->volume)); 279 | for (int i = 0; i < ATMX_LAYERS; i++) atmxMixLayer(&mix->lays[i], vol, align, asize); 280 | //perform clipping using SSE min and max (unless disabled) 281 | #ifndef ATOMIX_NO_CLIP 282 | __m128 neg1 = _mm_set_ps1(-1.0f), pos1 = _mm_set_ps1(1.0f); 283 | for (uint32_t i = 0; i < asize; i++) align[i] = _mm_min_ps(_mm_max_ps(align[i], neg1), pos1); 284 | #endif 285 | //copy rnum frames, leaving possible remainder 286 | memcpy(buff, align, rnum*2*sizeof(float)); 287 | //determine remaining number of frames 288 | mix->rem = asize*2 - rnum; 289 | //copy remaining frames to buffer inside the mixer struct 290 | if (mix->rem) memcpy(mix->data, (float*)align + rnum*2, mix->rem); 291 | #else 292 | //clear the output buffer using memset 293 | memset(buff, 0, fnum*2*sizeof(float)); 294 | //begin actual mixing, caching the volume first 295 | float vol = ATMX_LOAD(&mix->volume); 296 | for (int i = 0; i < ATMX_LAYERS; i++) atmxMixLayer(&mix->lays[i], vol, buff, fnum); 297 | //perform clipping using simple ternary operators (unless disabled) 298 | #ifndef ATOMIX_NO_CLIP 299 | for (uint32_t i = 0; i < fnum*2; i++) buff[i] = (buff[i] < -1.0f) ? -1.0f : (buff[i] > 1.0f) ? 1.0f : buff[i]; 300 | #endif 301 | #endif 302 | //return 303 | return fnum; 304 | } 305 | ATMXDEF uint32_t atomixMixerPlay (struct atomix_mixer* mix, struct atomix_sound* snd, uint8_t flag, float gain, float pan) { 306 | //play with start and end equal to start and end of the sound itself 307 | return atomixMixerPlayAdv(mix, snd, flag, gain, pan, 0, snd->len, mix->fade); 308 | } 309 | ATMXDEF uint32_t atomixMixerPlayAdv (struct atomix_mixer* mix, struct atomix_sound* snd, uint8_t flag, float gain, float pan, int32_t start, int32_t end, int32_t fade) { 310 | //return failure if given flag invalid 311 | if ((flag < 1)||(flag > 4)) return 0; 312 | //return failure if start or end invalid 313 | if ((end - start < 4)||(end < 4)) return 0; 314 | //make ATMX_LAYERS attempts to find layer 315 | for (int i = 0; i < ATMX_LAYERS; i++) { 316 | //get layer for next sound handle id 317 | uint32_t id; struct atmx_layer* lay = &mix->lays[(id = mix->nid++) & ATMX_LMASK]; 318 | //check if corresponding layer is free 319 | if (ATMX_LOAD(&lay->flag) == 0) { 320 | //skip 0 as it is special 321 | if (!id) id = ATMX_LAYERS; 322 | //fill in non-atomic layer data along with truncating start and end 323 | lay->id = id; lay->snd = snd; 324 | lay->start = start & ~3; lay->end = end & ~3; 325 | lay->fmax = (fade < 0) ? 0 : fade & ~3; 326 | //set initial fade state based on flag 327 | lay->fade = (flag < 3) ? 0 : lay->fmax; 328 | //convert gain and pan to left and right gain and store it atomically 329 | ATMX_STORE(&lay->gain, atmxGainf2(gain, pan)); 330 | //atomically set cursor to start position based on given argument 331 | ATMX_STORE(&lay->cursor, lay->start); 332 | //store flag last, releasing the layer to the mixer thread 333 | ATMX_STORE(&lay->flag, flag); 334 | //return success 335 | return id; 336 | } 337 | } 338 | //return failure 339 | return 0; 340 | } 341 | ATMXDEF int atomixMixerSetGainPan (struct atomix_mixer* mix, uint32_t id, float gain, float pan) { 342 | //get layer based on the lowest bits of id 343 | struct atmx_layer* lay = &mix->lays[id & ATMX_LMASK]; 344 | //check id and state flag to make sure the id is valid 345 | if ((id == lay->id)&&(ATMX_LOAD(&lay->flag) > 1)) { 346 | //convert gain and pan to left and right gain and store it atomically 347 | ATMX_STORE(&lay->gain, atmxGainf2(gain, pan)); 348 | //return success 349 | return 1; 350 | } 351 | //return failure 352 | return 0; 353 | } 354 | ATMXDEF int atomixMixerSetCursor (struct atomix_mixer* mix, uint32_t id, int32_t cursor) { 355 | //get layer based on the lowest bits of id 356 | struct atmx_layer* lay = &mix->lays[id & ATMX_LMASK]; 357 | //check id and state flag to make sure the id is valid 358 | if ((id == lay->id)&&(ATMX_LOAD(&lay->flag) > 1)) { 359 | //clamp cursor and truncate to multiple of 4 before storing 360 | ATMX_STORE(&lay->cursor, (cursor < lay->start) ? lay->start : (cursor > lay->end) ? lay->end : cursor & ~3); 361 | //return success 362 | return 1; 363 | } 364 | //return failure 365 | return 0; 366 | } 367 | ATMXDEF int atomixMixerSetState (struct atomix_mixer* mix, uint32_t id, uint8_t flag) { 368 | //return failure if given flag invalid 369 | if ((flag < 1)||(flag > 4)) return 0; 370 | //get layer based on the lowest bits of id 371 | struct atmx_layer* lay = &mix->lays[id & ATMX_LMASK]; uint8_t prev; 372 | //check id and state flag to make sure the id is valid 373 | if ((id == lay->id)&&((prev = ATMX_LOAD(&lay->flag)) > 1)) { 374 | //return success if already in desired state 375 | if (prev == flag) return 1; 376 | //swap if flag has not changed and return if successful 377 | if (ATMX_CSWAP(&lay->flag, &prev, flag)) return 1; 378 | } 379 | //return failure 380 | return 0; 381 | } 382 | ATMXDEF void atomixMixerVolume (struct atomix_mixer* mix, float vol) { 383 | //simple atomic store of the volume 384 | ATMX_STORE(&mix->volume, vol); 385 | } 386 | ATMXDEF void atomixMixerFade (struct atomix_mixer* mix, int32_t fade) { 387 | //simple assignment of the fade value 388 | mix->fade = (fade < 0) ? 0 : fade & ~3; 389 | } 390 | ATMXDEF void atomixMixerStopAll (struct atomix_mixer* mix) { 391 | //go through all active layers and set their states to the stop state 392 | for (int i = 0; i < ATMX_LAYERS; i++) { 393 | //pointer to this layer for cleaner code 394 | struct atmx_layer* lay = &mix->lays[i]; 395 | //check if active and set to stop if true 396 | if (ATMX_LOAD(&lay->flag) > 1) ATMX_STORE(&lay->flag, (uint8_t)ATOMIX_STOP); 397 | } 398 | } 399 | ATMXDEF void atomixMixerHaltAll (struct atomix_mixer* mix) { 400 | //go through all playing layers and set their states to halt 401 | for (int i = 0; i < ATMX_LAYERS; i++) { 402 | //pointer to this layer for cleaner code 403 | struct atmx_layer* lay = &mix->lays[i]; uint8_t flag; 404 | //check if playing or looping and try to swap 405 | if ((flag = ATMX_LOAD(&lay->flag)) > 2) ATMX_CSWAP(&lay->flag, &flag, (uint8_t)ATOMIX_HALT); 406 | } 407 | } 408 | ATMXDEF void atomixMixerPlayAll (struct atomix_mixer* mix) { 409 | //go through all halted layers and set their states to play 410 | for (int i = 0; i < ATMX_LAYERS; i++) { 411 | //need to reset each time 412 | uint8_t flag = ATOMIX_HALT; 413 | //swap the flag to play if it is on halt 414 | ATMX_CSWAP(&mix->lays[i].flag, &flag, (uint8_t)ATOMIX_PLAY); 415 | } 416 | } 417 | 418 | //internal functions 419 | #ifndef ATOMIX_NO_SSE 420 | static void atmxMixLayer (struct atmx_layer* lay, __m128 vol, __m128* align, uint32_t asize) { 421 | //load flag value atomically first 422 | uint8_t flag = ATMX_LOAD(&lay->flag); 423 | //return if flag cleared 424 | if (flag == 0) return; 425 | //atomically load cursor 426 | int32_t cur = ATMX_LOAD(&lay->cursor); 427 | //atomically load left and right gain 428 | struct atmx_f2 g = ATMX_LOAD(&lay->gain); 429 | __m128 gmul = _mm_mul_ps(_mm_setr_ps(g.l, g.r, g.l, g.r), vol); 430 | //action based on flag 431 | if (flag < 3) { 432 | //ATOMIX_STOP or ATOMIX_HALT, fade out if not faded or at end 433 | if ((lay->fade > 0)&&(cur < lay->end)) 434 | if (lay->snd->cha == 1) 435 | cur = atmxMixFadeMono(lay, cur, gmul, align, asize); 436 | else 437 | cur = atmxMixFadeStereo(lay, cur, gmul, align, asize); 438 | //clear flag if ATOMIX_STOP and fully faded or at end 439 | if ((flag == ATOMIX_STOP)&&((lay->fade == 0)||(cur == lay->end))) ATMX_STORE(&lay->flag, (uint8_t)0); 440 | } else { 441 | //ATOMIX_PLAY or ATOMIX_LOOP, play including fade in 442 | if (lay->snd->cha == 1) 443 | cur = atmxMixPlayMono(lay, (flag == ATOMIX_LOOP), cur, gmul, align, asize); 444 | else 445 | cur = atmxMixPlayStereo(lay, (flag == ATOMIX_LOOP), cur, gmul, align, asize); 446 | //clear flag if ATOMIX_PLAY and the cursor has reached the end 447 | if ((flag == ATOMIX_PLAY)&&(cur == lay->end)) ATMX_CSWAP(&lay->flag, &flag, (uint8_t)0); 448 | } 449 | } 450 | static int32_t atmxMixFadeMono (struct atmx_layer* lay, int32_t cur, __m128 gmul, __m128* align, uint32_t asize) { 451 | //cache cursor 452 | int32_t old = cur; 453 | //check if enough samples left for fade out 454 | if (lay->fade < lay->end - cur) { 455 | //perform fade out 456 | for (uint32_t i = 0; i < asize; i += 2) { 457 | //quit if fully faded out 458 | if (lay->fade == 0) break; 459 | //mix if cursor within sound 460 | if (cur >= 0) { 461 | //get faded volume multiplier 462 | __m128 fmul = _mm_mul_ps(_mm_set_ps1((float)lay->fade/(float)lay->fmax), gmul); 463 | //load 4 samples from data (this is 4 frames) 464 | __m128 sam = lay->snd->data[(cur % lay->snd->len) >> 2]; 465 | //mix low samples obtained with unpacklo 466 | align[i] = _mm_add_ps(align[i], _mm_mul_ps(_mm_unpacklo_ps(sam, sam), fmul)); 467 | //mix high samples obtained with unpackhi 468 | align[i+1] = _mm_add_ps(align[i+1], _mm_mul_ps(_mm_unpackhi_ps(sam, sam), fmul)); 469 | } 470 | //advance cursor and fade 471 | lay->fade -= 4; cur += 4; 472 | } 473 | } else { 474 | //continue playback to end without fade out 475 | for (uint32_t i = 0; i < asize; i += 2) { 476 | //quit if cursor at end 477 | if (cur == lay->end) break; 478 | //mix if cursor within sound 479 | if (cur >= 0) { 480 | //load 4 samples from data (this is 4 frames) 481 | __m128 sam = lay->snd->data[(cur % lay->snd->len) >> 2]; 482 | //mix low samples obtained with unpacklo 483 | align[i] = _mm_add_ps(align[i], _mm_mul_ps(_mm_unpacklo_ps(sam, sam), gmul)); 484 | //mix high samples obtained with unpackhi 485 | align[i+1] = _mm_add_ps(align[i+1], _mm_mul_ps(_mm_unpackhi_ps(sam, sam), gmul)); 486 | } 487 | //advance cursor 488 | cur += 4; 489 | } 490 | } 491 | //swap back cursor if unchanged 492 | if (!ATMX_CSWAP(&lay->cursor, &old, cur)) cur = old; 493 | //return new cursor 494 | return cur; 495 | } 496 | static int32_t atmxMixFadeStereo (struct atmx_layer* lay, int32_t cur, __m128 gmul, __m128* align, uint32_t asize) { 497 | //cache cursor 498 | int32_t old = cur; 499 | //check if enough samples left for fade out 500 | if (lay->fade < lay->end - cur) { 501 | //perform fade out 502 | for (uint32_t i = 0; i < asize; i += 2) { 503 | //quit if fully faded out 504 | if (lay->fade == 0) break; 505 | //mix if cursor within sound 506 | if (cur >= 0) { 507 | //get faded volume multiplier 508 | __m128 fmul = _mm_mul_ps(_mm_set_ps1((float)lay->fade/(float)lay->fmax), gmul); 509 | //mod for repeating and convert to __m128 offset 510 | int32_t off = (cur % lay->snd->len) >> 1; 511 | //mix in first two frames 512 | align[i] = _mm_add_ps(align[i], _mm_mul_ps(lay->snd->data[off], fmul)); 513 | //mix in second two frames 514 | align[i+1] = _mm_add_ps(align[i+1], _mm_mul_ps(lay->snd->data[off+1], fmul)); 515 | } 516 | //advance cursor and fade 517 | lay->fade -= 4; cur += 4; 518 | } 519 | } else { 520 | //continue playback to end without fade out 521 | for (uint32_t i = 0; i < asize; i += 2) { 522 | //quit if cursor at end 523 | if (cur == lay->end) break; 524 | //mix if cursor within sound 525 | if (cur >= 0) { 526 | //mod for repeating and convert to __m128 offset 527 | int32_t off = (cur % lay->snd->len) >> 1; 528 | //mix in first two frames 529 | align[i] = _mm_add_ps(align[i], _mm_mul_ps(lay->snd->data[off], gmul)); 530 | //mix in second two frames 531 | align[i+1] = _mm_add_ps(align[i+1], _mm_mul_ps(lay->snd->data[off+1], gmul)); 532 | } 533 | //advance cursor 534 | cur += 4; 535 | } 536 | } 537 | //swap back cursor if unchanged 538 | if (!ATMX_CSWAP(&lay->cursor, &old, cur)) cur = old; 539 | //return new cursor 540 | return cur; 541 | } 542 | static int32_t atmxMixPlayMono (struct atmx_layer* lay, int loop, int32_t cur, __m128 gmul, __m128* align, uint32_t asize) { 543 | //cache cursor 544 | int32_t old = cur; 545 | //check if fully faded in yet 546 | if (lay->fade < lay->fmax) { 547 | //perform fade in 548 | for (uint32_t i = 0; i < asize; i += 2) { 549 | //check if cursor at end 550 | if (cur == lay->end) { 551 | //quit unless looping 552 | if (!loop) break; 553 | //wrap around if looping 554 | cur = lay->start; 555 | } 556 | //mix if cursor within sound 557 | if (cur >= 0) { 558 | //get faded volume multiplier 559 | __m128 fmul = _mm_mul_ps(_mm_set_ps1((float)lay->fade/(float)lay->fmax), gmul); 560 | //load 4 samples from data (this is 4 frames) 561 | __m128 sam = lay->snd->data[(cur % lay->snd->len) >> 2]; 562 | //mix low samples obtained with unpacklo 563 | align[i] = _mm_add_ps(align[i], _mm_mul_ps(_mm_unpacklo_ps(sam, sam), fmul)); 564 | //mix high samples obtained with unpackhi 565 | align[i+1] = _mm_add_ps(align[i+1], _mm_mul_ps(_mm_unpackhi_ps(sam, sam), fmul)); 566 | } 567 | //advance fade unless fully faded in 568 | if (lay->fade < lay->fmax) lay->fade += 4; 569 | //advance cursor 570 | cur += 4; 571 | } 572 | } else { 573 | //regular playback 574 | for (uint32_t i = 0; i < asize; i += 2) { 575 | //check if cursor at end 576 | if (cur == lay->end) { 577 | //quit unless looping 578 | if (!loop) break; 579 | //wrap around if looping 580 | cur = lay->start; 581 | } 582 | //mix if cursor within sound 583 | if (cur >= 0) { 584 | //load 4 samples from data (this is 4 frames) 585 | __m128 sam = lay->snd->data[(cur % lay->snd->len) >> 2]; 586 | //mix low samples obtained with unpacklo 587 | align[i] = _mm_add_ps(align[i], _mm_mul_ps(_mm_unpacklo_ps(sam, sam), gmul)); 588 | //mix high samples obtained with unpackhi 589 | align[i+1] = _mm_add_ps(align[i+1], _mm_mul_ps(_mm_unpackhi_ps(sam, sam), gmul)); 590 | } 591 | //advance cursor 592 | cur += 4; 593 | } 594 | } 595 | //swap back cursor if unchanged 596 | if (!ATMX_CSWAP(&lay->cursor, &old, cur)) cur = old; 597 | //return new cursor 598 | return cur; 599 | } 600 | static int32_t atmxMixPlayStereo (struct atmx_layer* lay, int loop, int32_t cur, __m128 gmul, __m128* align, uint32_t asize) { 601 | //cache cursor 602 | int32_t old = cur; 603 | //check if fully faded in yet 604 | if (lay->fade < lay->fmax) { 605 | //perform fade in 606 | for (uint32_t i = 0; i < asize; i += 2) { 607 | //check if cursor at end 608 | if (cur == lay->end) { 609 | //quit unless looping 610 | if (!loop) break; 611 | //wrap around if looping 612 | cur = lay->start; 613 | } 614 | //mix if cursor within sound 615 | if (cur >= 0) { 616 | //get faded volume multiplier 617 | __m128 fmul = _mm_mul_ps(_mm_set_ps1((float)lay->fade/(float)lay->fmax), gmul); 618 | //mod for repeating and convert to __m128 offset 619 | int32_t off = (cur % lay->snd->len) >> 1; 620 | //mix in first two frames 621 | align[i] = _mm_add_ps(align[i], _mm_mul_ps(lay->snd->data[off], fmul)); 622 | //mix in second two frames 623 | align[i+1] = _mm_add_ps(align[i+1], _mm_mul_ps(lay->snd->data[off+1], fmul)); 624 | } 625 | //advance fade unless fully faded in 626 | if (lay->fade < lay->fmax) lay->fade += 4; 627 | //advance cursor 628 | cur += 4; 629 | } 630 | } else { 631 | //regular playback 632 | for (uint32_t i = 0; i < asize; i += 2) { 633 | //check if cursor at end 634 | if (cur == lay->end) { 635 | //quit unless looping 636 | if (!loop) break; 637 | //wrap around if looping 638 | cur = lay->start; 639 | } 640 | //mix if cursor within sound 641 | if (cur >= 0) { 642 | //mod for repeating and convert to __m128 offset 643 | int32_t off = (cur % lay->snd->len) >> 1; 644 | //mix in first two frames 645 | align[i] = _mm_add_ps(align[i], _mm_mul_ps(lay->snd->data[off], gmul)); 646 | //mix in second two frames 647 | align[i+1] = _mm_add_ps(align[i+1], _mm_mul_ps(lay->snd->data[off+1], gmul)); 648 | } 649 | //advance cursor 650 | cur += 4; 651 | } 652 | } 653 | //swap back cursor if unchanged 654 | if (!ATMX_CSWAP(&lay->cursor, &old, cur)) cur = old; 655 | //return new cursor 656 | return cur; 657 | } 658 | #else 659 | static void atmxMixLayer (struct atmx_layer* lay, float vol, float* buff, uint32_t fnum) { 660 | //load flag value atomically first 661 | uint8_t flag = ATMX_LOAD(&lay->flag); 662 | //return if flag cleared 663 | if (flag == 0) return; 664 | //atomically load cursor 665 | int32_t cur = ATMX_LOAD(&lay->cursor); 666 | //atomically load left and right gain 667 | struct atmx_f2 g = ATMX_LOAD(&lay->gain); 668 | //multiply volume into gain 669 | g.l *= vol; g.r *= vol; 670 | //action based on flag 671 | if (flag < 3) { 672 | //ATOMIX_STOP or ATOMIX_HALT, fade out if not faded or at end 673 | if ((lay->fade > 0)&&(cur < lay->end)) 674 | if (lay->snd->cha == 1) 675 | cur = atmxMixFadeMono(lay, cur, g, buff, fnum); 676 | else 677 | cur = atmxMixFadeStereo(lay, cur, g, buff, fnum); 678 | //clear flag if ATOMIX_STOP and fully faded or at end 679 | if ((flag == ATOMIX_STOP)&&((lay->fade == 0)||(cur == lay->end))) ATMX_STORE(&lay->flag, (uint8_t)0); 680 | } else { 681 | //ATOMIX_PLAY or ATOMIX_LOOP, play including fade in 682 | if (lay->snd->cha == 1) 683 | cur = atmxMixPlayMono(lay, (flag == ATOMIX_LOOP), cur, g, buff, fnum); 684 | else 685 | cur = atmxMixPlayStereo(lay, (flag == ATOMIX_LOOP), cur, g, buff, fnum); 686 | //clear flag if ATOMIX_PLAY and the cursor has reached the end 687 | if ((flag == ATOMIX_PLAY)&&(cur == lay->end)) ATMX_CSWAP(&lay->flag, &flag, (uint8_t)0); 688 | } 689 | } 690 | static int32_t atmxMixFadeMono (struct atmx_layer* lay, int32_t cur, struct atmx_f2 g, float* buff, uint32_t fnum) { 691 | //cache cursor 692 | int32_t old = cur; 693 | //check if enough samples left for fade out 694 | if (lay->fade < lay->end - cur) { 695 | //perform fade out 696 | for (uint32_t i = 0; i < fnum*2; i += 2) { 697 | //quit if fully faded out 698 | if (lay->fade == 0) break; 699 | //mix if cursor within sound 700 | if (cur >= 0) { 701 | //get fade multiplier 702 | float fade = (float)lay->fade/(float)lay->fmax; 703 | //load 1 sample from data (this is 1 frame) 704 | float sam = lay->snd->data[cur % lay->snd->len]; 705 | //mix left sample of frame 706 | buff[i] += sam*fade*g.l; 707 | //mix right sample of frame 708 | buff[i+1] += sam*fade*g.r; 709 | } 710 | //advance cursor and fade 711 | lay->fade--; cur++; 712 | } 713 | } else { 714 | //continue playback to end without fade out 715 | for (uint32_t i = 0; i < fnum*2; i += 2) { 716 | //quit if cursor at end 717 | if (cur == lay->end) break; 718 | //mix if cursor within sound 719 | if (cur >= 0) { 720 | //load 1 sample from data (this is 1 frame) 721 | float sam = lay->snd->data[cur % lay->snd->len]; 722 | //mix left sample of frame 723 | buff[i] += sam*g.l; 724 | //mix right sample of frame 725 | buff[i+1] += sam*g.r; 726 | } 727 | //advance cursor 728 | cur++; 729 | } 730 | } 731 | //swap back cursor if unchanged 732 | if (!ATMX_CSWAP(&lay->cursor, &old, cur)) cur = old; 733 | //return new cursor 734 | return cur; 735 | } 736 | static int32_t atmxMixFadeStereo (struct atmx_layer* lay, int32_t cur, struct atmx_f2 g, float* buff, uint32_t fnum) { 737 | //cache cursor 738 | int32_t old = cur; 739 | //check if enough samples left for fade out 740 | if (lay->fade < lay->end - cur) { 741 | //perform fade out 742 | for (uint32_t i = 0; i < fnum*2; i += 2) { 743 | //quit if fully faded out 744 | if (lay->fade == 0) break; 745 | //mix if cursor within sound 746 | if (cur >= 0) { 747 | //get fade multiplier 748 | float fade = (float)lay->fade/(float)lay->fmax; 749 | //mod for repeating and convert to float offset 750 | int32_t off = (cur % lay->snd->len) << 1; 751 | //mix left sample of frame 752 | buff[i] += lay->snd->data[off]*fade*g.l; 753 | //mix right sample of frame 754 | buff[i+1] += lay->snd->data[off+1]*fade*g.r; 755 | } 756 | //advance cursor and fade 757 | lay->fade--; cur++; 758 | } 759 | } else { 760 | //continue playback to end without fade out 761 | for (uint32_t i = 0; i < fnum*2; i += 2) { 762 | //quit if cursor at end 763 | if (cur == lay->end) break; 764 | //mix if cursor within sound 765 | if (cur >= 0) { 766 | //mod for repeating and convert to float offset 767 | int32_t off = (cur % lay->snd->len) << 1; 768 | //mix left sample of frame 769 | buff[i] += lay->snd->data[off]*g.l; 770 | //mix right sample of frame 771 | buff[i+1] += lay->snd->data[off+1]*g.r; 772 | } 773 | //advance cursor 774 | cur++; 775 | } 776 | } 777 | //swap back cursor if unchanged 778 | if (!ATMX_CSWAP(&lay->cursor, &old, cur)) cur = old; 779 | //return new cursor 780 | return cur; 781 | } 782 | static int32_t atmxMixPlayMono (struct atmx_layer* lay, int loop, int32_t cur, struct atmx_f2 g, float* buff, uint32_t fnum) { 783 | //cache cursor 784 | int32_t old = cur; 785 | //check if fully faded in yet 786 | if (lay->fade < lay->fmax) { 787 | //perform fade in 788 | for (uint32_t i = 0; i < fnum*2; i += 2) { 789 | //check if cursor at end 790 | if (cur == lay->end) { 791 | //quit unless looping 792 | if (!loop) break; 793 | //wrap around if looping 794 | cur = lay->start; 795 | } 796 | //mix if cursor within sound 797 | if (cur >= 0) { 798 | //get fade multiplier 799 | float fade = (float)lay->fade/(float)lay->fmax; 800 | //load 1 sample from data (this is 1 frame) 801 | float sam = lay->snd->data[cur % lay->snd->len]; 802 | //mix left sample of frame 803 | buff[i] += sam*fade*g.l; 804 | //mix right sample of frame 805 | buff[i+1] += sam*fade*g.r; 806 | } 807 | //advance fade unless fully faded in 808 | if (lay->fade < lay->fmax) lay->fade++; 809 | //advance cursor 810 | cur++; 811 | } 812 | } else { 813 | //regular playback 814 | for (uint32_t i = 0; i < fnum*2; i += 2) { 815 | //check if cursor at end 816 | if (cur == lay->end) { 817 | //quit unless looping 818 | if (!loop) break; 819 | //wrap around if looping 820 | cur = lay->start; 821 | } 822 | //mix if cursor within sound 823 | if (cur >= 0) { 824 | //load 1 sample from data (this is 1 frame) 825 | float sam = lay->snd->data[cur % lay->snd->len]; 826 | //mix left sample of frame 827 | buff[i] += sam*g.l; 828 | //mix right sample of frame 829 | buff[i+1] += sam*g.r; 830 | } 831 | //advance cursor 832 | cur++; 833 | } 834 | } 835 | //swap back cursor if unchanged 836 | if (!ATMX_CSWAP(&lay->cursor, &old, cur)) cur = old; 837 | //return new cursor 838 | return cur; 839 | } 840 | static int32_t atmxMixPlayStereo (struct atmx_layer* lay, int loop, int32_t cur, struct atmx_f2 g, float* buff, uint32_t fnum) { 841 | //cache cursor 842 | int32_t old = cur; 843 | //check if fully faded in yet 844 | if (lay->fade < lay->fmax) { 845 | //perform fade in 846 | for (uint32_t i = 0; i < fnum*2; i += 2) { 847 | //check if cursor at end 848 | if (cur == lay->end) { 849 | //quit unless looping 850 | if (!loop) break; 851 | //wrap around if looping 852 | cur = lay->start; 853 | } 854 | //mix if cursor within sound 855 | if (cur >= 0) { 856 | //get fade multiplier 857 | float fade = (float)lay->fade/(float)lay->fmax; 858 | //mod for repeating and convert to float offset 859 | int32_t off = (cur % lay->snd->len) << 1; 860 | //mix left sample of frame 861 | buff[i] += lay->snd->data[off]*fade*g.l; 862 | //mix right sample of frame 863 | buff[i+1] += lay->snd->data[off+1]*fade*g.r; 864 | } 865 | //advance fade unless fully faded in 866 | if (lay->fade < lay->fmax) lay->fade++; 867 | //advance cursor 868 | cur++; 869 | } 870 | } else { 871 | //regular playback 872 | for (uint32_t i = 0; i < fnum*2; i += 2) { 873 | //check if cursor at end 874 | if (cur == lay->end) { 875 | //quit unless looping 876 | if (!loop) break; 877 | //wrap around if looping 878 | cur = lay->start; 879 | } 880 | //mix if cursor within sound 881 | if (cur >= 0) { 882 | //mod for repeating and convert to float offset 883 | int32_t off = (cur % lay->snd->len) << 1; 884 | //mix left sample of frame 885 | buff[i] += lay->snd->data[off]*g.l; 886 | //mix right sample of frame 887 | buff[i+1] += lay->snd->data[off+1]*g.r; 888 | } 889 | //advance cursor 890 | cur++; 891 | } 892 | } 893 | //swap back cursor if unchanged 894 | if (!ATMX_CSWAP(&lay->cursor, &old, cur)) cur = old; 895 | //return new cursor 896 | return cur; 897 | } 898 | #endif 899 | static struct atmx_f2 atmxGainf2 (float gain, float pan) { 900 | //clamp pan to its valid range of -1.0f to 1.0f inclusive 901 | pan = (pan < -1.0f) ? -1.0f : (pan > 1.0f) ? 1.0f : pan; 902 | //convert gain and pan to left and right gain and store it atomically 903 | return (struct atmx_f2){gain*(0.5f - pan/2.0f), gain*(0.5f + pan/2.0f)}; 904 | } 905 | 906 | #endif //ATOMIX_IMPLEMENTATION -------------------------------------------------------------------------------- /libs/README: -------------------------------------------------------------------------------- 1 | This folder contains 3rd party libraries for compilation and linking. They are included merely for convenience, are not considered part of the project's own source code, and are subject to their individual licenses. They are unmodified from their original unless otherwise noted. -------------------------------------------------------------------------------- /mu.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BareRose/atomix/7c75a1602e53c3e25b60b5c0d0a618112d7399d2/mu.ogg -------------------------------------------------------------------------------- /so.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BareRose/atomix/7c75a1602e53c3e25b60b5c0d0a618112d7399d2/so.ogg -------------------------------------------------------------------------------- /test.c: -------------------------------------------------------------------------------- 1 | /* 2 | atomix.h example for command line usage like "test.exe mu.ogg so.ogg" performing benchmarks and demos 3 | 4 | To the extent possible under law, the author(s) have dedicated all copyright and related and neighboring 5 | rights to this software to the public domain worldwide. This software is distributed without any warranty. 6 | You should have received a copy of the CC0 Public Domain Dedication along with this software. 7 | If not, see . 8 | */ 9 | 10 | /* 11 | Compile using "gcc -O2 test.c -o test.exe" or equivalent, then run from command line to use. 12 | Most modern compilers will have SSE enabled by default, if not you need a flag like "-msse". 13 | If you find an error in this test or discover a possible improvement, please open an issue. 14 | */ 15 | 16 | //includes 17 | #define ATOMIX_STATIC 18 | #include "atomix.h" 19 | #define STB_VORBIS_NO_INTEGER_CONVERSION 20 | #define STB_VORBIS_IMPLEMENTATION 21 | #define STB_VORBIS_STATIC 22 | #include "libs/stb_vorbis.h" 23 | #define MA_IMPLEMENTATION 24 | #include "libs/miniaudio.h" 25 | #include 26 | #include 27 | 28 | //data callback 29 | void dataCallback (ma_device* dev, void* out, const void* inp, ma_uint32 fnum) { 30 | atomixMixerMix(dev->pUserData, out, fnum); 31 | } 32 | 33 | //benchmarking 34 | #ifdef WIN32 35 | #include 36 | double getTime () { 37 | LARGE_INTEGER t, f; 38 | QueryPerformanceCounter(&t); 39 | QueryPerformanceFrequency(&f); 40 | return (double)t.QuadPart/(double)f.QuadPart; 41 | } 42 | void psleep (double s) { 43 | Sleep(s*1000.0); 44 | } 45 | #else 46 | #include 47 | double getTime () { 48 | struct timeval t; 49 | gettimeofday(&t, NULL); 50 | return t.tv_sec + t.tv_usec*1e-6; 51 | } 52 | void psleep (double s) { 53 | struct timespec t = {s, s*1e-6}; 54 | nanosleep(&t, NULL); 55 | } 56 | #endif 57 | 58 | //main function 59 | int main (int argc, char *argv[]) { 60 | //perpare variables 61 | ma_device dev; 62 | float bench_buff[1024]; 63 | void* fmus; void* fsnd; 64 | ma_uint64 fmus_size, fsnd_size; 65 | ma_decoder_config fmus_cfg, fsnd_cfg; 66 | fmus_cfg = fsnd_cfg = ma_decoder_config_init(ma_format_f32, 0, 48000); 67 | //initialize rand 68 | srand(getTime()*65536.0); 69 | //check arguments 70 | if (argc < 3) printf("Missing argument!\n"); 71 | else if (ma_decode_file(argv[1], &fmus_cfg, &fmus_size, &fmus) != MA_SUCCESS) 72 | printf("Music could not be loaded!\n"); 73 | else if (ma_decode_file(argv[2], &fsnd_cfg, &fsnd_size, &fsnd) != MA_SUCCESS) 74 | printf("Sound could not be loaded!\n"); 75 | else { 76 | //transfer audio data into atomix sounds and free temporary buffers 77 | struct atomix_sound* mus; struct atomix_sound* snd; 78 | mus = atomixSoundNew(fmus_cfg.channels, fmus, fmus_size); 79 | snd = atomixSoundNew(fsnd_cfg.channels, fsnd, fsnd_size); 80 | ma_free(fmus); ma_free(fsnd); 81 | //create atomix mixer with volume of 0.5 82 | struct atomix_mixer* mix = atomixMixerNew(0.5f, 0); 83 | //begin benchmarking 84 | printf("<>\n"); 85 | //mix 512 at a time 512 times with 256 sounds 86 | for (int i = 0; i < 256; i++) atomixMixerPlay(mix, mus, ATOMIX_LOOP, 1.0f, 0.0f); 87 | double start = getTime(); 88 | for (int i = 0; i < 512; i++) atomixMixerMix(mix, bench_buff, 512); 89 | double end = getTime(); 90 | atomixMixerStopAll(mix); //mark all layers for clearing 91 | atomixMixerMix(mix, bench_buff, 512); //make sure layers are actually cleared 92 | printf("256: %.0ff/s <- %.0ff/s (%.3fMiB/s)\n", 262144.0/(end-start), 16777216.0/(end-start), 2.0/(end-start)); 93 | //mix 512 at a time 512 times with single sound 94 | atomixMixerPlay(mix, mus, ATOMIX_LOOP, 1.0f, 0.0f); 95 | start = getTime(); 96 | for (int i = 0; i < 512; i++) atomixMixerMix(mix, bench_buff, 512); 97 | end = getTime(); 98 | atomixMixerStopAll(mix); //mark all layers for clearing 99 | atomixMixerMix(mix, bench_buff, 512); //make sure layers are actually cleared 100 | printf("One: %.0ff/s <- %.0ff/s (%.3fMiB/s)\n", 262144.0/(end-start), 262144.0/(end-start), 2.0/(end-start)); 101 | //benchmarking done 102 | printf("<>\n"); 103 | //create miniaudio playback device 104 | ma_device_config cfg = ma_device_config_init(ma_device_type_playback); 105 | cfg.playback.pDeviceID = NULL; 106 | cfg.playback.format = ma_format_f32; 107 | cfg.playback.channels = 2; 108 | cfg.sampleRate = 48000; 109 | cfg.dataCallback = dataCallback; 110 | cfg.pUserData = mix; 111 | if (ma_device_init(NULL, &cfg, &dev) != MA_SUCCESS) { 112 | //failed to initialize device 113 | printf("Failed to initialize device!\n"); 114 | //return 115 | return 1; 116 | } 117 | //start playback device 118 | ma_device_start(&dev); 119 | //set fade time to quarter of a second 120 | atomixMixerFade(mix, 12000); 121 | //begin demo of simple looping music and sound halting 122 | printf("<>\n"); 123 | atomixMixerPlay(mix, mus, ATOMIX_LOOP, 0.25f, 0.0f); 124 | uint32_t sid = atomixMixerPlay(mix, snd, ATOMIX_HALT, 1.0f, 0.0f); 125 | //loop and play sound in various ways every so often 126 | for (int i = 0; i < 8; i++) { 127 | //sleep for 0.5 second 128 | psleep(0.5); 129 | //resume sound with random panning 130 | float r = (float)rand()/(float)RAND_MAX; 131 | atomixMixerSetGainPan(mix, sid, 1.0f, 2.0f*(r - 0.5f)); 132 | atomixMixerSetState(mix, sid, ATOMIX_LOOP); 133 | //sleep for 0.5 seconds 134 | psleep(0.5); 135 | //halt sound whatever its position 136 | atomixMixerSetState(mix, sid, ATOMIX_HALT); 137 | } 138 | //end demo by stopping all sounds 139 | atomixMixerStopAll(mix); 140 | printf("<>\n"); 141 | //sleep for 0.25 seconds 142 | psleep(0.25); 143 | //uninit playback device 144 | ma_device_uninit(&dev); 145 | //free mixer and sounds 146 | free(mix); free(mus); free(snd); 147 | } 148 | //return 149 | return 0; 150 | } --------------------------------------------------------------------------------