├── 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 | }
--------------------------------------------------------------------------------