1 /*
2 EXPERIMENTAL
3 ============
4 Everything in this file is experimental and subject to change. Some stuff isn't yet implemented, in particular spatialization. I've noted some ideas that are
5 basically straight off the top of my head - many of these are probably outright wrong or just generally bad ideas.
6 
7 Very simple APIs for spatialization are declared by not yet implemented. They're just placeholders to give myself an idea on some of the API design.
8 
9 The idea is that you have an `ma_engine` object - one per listener. Decoupled from that is the `ma_resource_manager` object. You can have one `ma_resource_manager`
10 object to many `ma_engine` objects. This will allow you to share resources between each listener. The `ma_engine` is responsible for the playback of audio from a
11 list of data sources. The `ma_resource_manager` is responsible for the actual loading, caching and unloading of those data sources. This decoupling is
12 something that I'm really liking right now and will likely stay in place for the final version.
13 
14 You create "sounds" from the engine which represent a sound/voice in the world. You first need to create a sound, and then you need to start it. Sounds do not
15 start by default. You can use `ma_engine_play_sound()` to "fire and forget" sounds. Sounds can have an effect (`ma_effect`) applied to it which can be set with
16 `ma_sound_set_effect()`.
17 
18 Sounds can be allocated to groups called `ma_sound_group`. The creation and deletion of groups is not thread safe and should usually happen at initialization
19 time. Groups are how you handle submixing. In many games you will see settings to control the master volume in addition to groups, usually called SFX, Music
20 and Voices. The `ma_sound_group` object is how you would achieve this via the `ma_engine` API. When a sound is created you need to specify the group it should
21 be associated with. The sound's group cannot be changed after it has been created.
22 
23 The creation and deletion of sounds should, hopefully, be thread safe. I have not yet done thorough testing on this, so there's a good chance there may be some
24 subtle bugs there.
25 
26 The best resource to use when understanding the API is the function declarations for `ma_engine`. I expect you should be able to figure it out! :)
27 */
28 
29 /*
30 Memory Allocation Types
31 =======================
32 When allocating memory you may want to optimize your custom allocators based on what it is miniaudio is actually allocating. Normally the context in which you
33 are using the allocator is enough to optimize allocations, however there are high-level APIs that perform many different types of allocations and it can be
34 useful to be told exactly what it being allocated so you can optimize your allocations appropriately.
35 */
36 #define MA_ALLOCATION_TYPE_GENERAL                              0x00000001  /* A general memory allocation. */
37 #define MA_ALLOCATION_TYPE_CONTEXT                              0x00000002  /* A ma_context allocation. */
38 #define MA_ALLOCATION_TYPE_DEVICE                               0x00000003  /* A ma_device allocation. */
39 #define MA_ALLOCATION_TYPE_DECODER                              0x00000004  /* A ma_decoder allocation. */
40 #define MA_ALLOCATION_TYPE_AUDIO_BUFFER                         0x00000005  /* A ma_audio_buffer allocation. */
41 #define MA_ALLOCATION_TYPE_ENCODED_BUFFER                       0x00000006  /* Allocation for encoded audio data containing the raw file data of a sound file. */
42 #define MA_ALLOCATION_TYPE_DECODED_BUFFER                       0x00000007  /* Allocation for decoded audio data from a sound file. */
43 #define MA_ALLOCATION_TYPE_RESOURCE_MANAGER_DATA_BUFFER_NODE    0x00000010  /* A ma_resource_manager_data_buffer_node object. */
44 #define MA_ALLOCATION_TYPE_RESOURCE_MANAGER_DATA_BUFFER         0x00000011  /* A ma_resource_manager_data_buffer_node object. */
45 #define MA_ALLOCATION_TYPE_RESOURCE_MANAGER_DATA_STREAM         0x00000012  /* A ma_resource_manager_data_stream object. */
46 #define MA_ALLOCATION_TYPE_RESOURCE_MANAGER_DATA_SOURCE         0x00000013  /* A ma_resource_manager_data_source object. */
47 
48 /*
49 Resource Management
50 ===================
51 Many programs will want to manage sound resources for things such as reference counting and streaming. This is supported by miniaudio via the
52 `ma_resource_manager` API.
53 
54 The resource manager is mainly responsible for the following:
55 
56     1) Loading of sound files into memory with reference counting.
57     2) Streaming of sound data
58 
59 When loading a sound file, the resource manager will give you back a data source compatible object called `ma_resource_manager_data_source`. This object can be
60 passed into any `ma_data_source` API which is how you can read and seek audio data. When loading a sound file, you specify whether or not you want the sound to
61 be fully loaded into memory (and optionally pre-decoded) or streamed. When loading into memory, you can also specify whether or not you want the data to be
62 loaded asynchronously.
63 
64 The example below is how you can initialize a resource manager using it's default configuration:
65 
66     ```c
67     ma_resource_manager_config config;
68     ma_resource_manager resourceManager;
69 
70     config = ma_resource_manager_config_init();
71     result = ma_resource_manager_init(&config, &resourceManager);
72     if (result != MA_SUCCESS) {
73         ma_device_uninit(&device);
74         printf("Failed to initialize the resource manager.");
75         return -1;
76     }
77     ```
78 
79 You can configure the format, channels and sample rate of the decoded audio data. By default it will use the file's native data format, but you can configure
80 it to use a consistent format. This is useful for offloading the cost of data conversion to load time rather than dynamically converting a mixing time. To do
81 this, you configure the decoded format, channels and sample rate like the code below:
82 
83     ```c
84     config = ma_resource_manager_config_init();
85     config.decodedFormat     = device.playback.format;
86     config.decodedChannels   = device.playback.channels;
87     config.decodedSampleRate = device.sampleRate;
88     ```
89 
90 In the code above, the resource manager will be configured so that any decoded audio data will be pre-converted at load time to the device's native data
91 format. If instead you used defaults and the data format of the file did not match the device's data format, you would need to convert the data at mixing time
92 which may be prohibitive in high-performance and large scale scenarios like games.
93 
94 Asynchronicity is achieved via a job system. When an operation needs to be performed, such as the decoding of a page, a job will be posted to a queue which
95 will then be processed by a job thread. By default there will be only one job thread running, but this can be configured, like so:
96 
97     ```c
98     config = ma_resource_manager_config_init();
99     config.jobThreadCount = MY_JOB_THREAD_COUNT;
100     ```
101 
102 By default job threads are managed internally by the resource manager, however you can also self-manage your job threads if, for example, you want to integrate
103 the job processing into your existing job infrastructure, or if you simply don't like the way the resource manager does it. To do this, just set the job thread
104 count to 0 and process jobs manually. To process jobs, you first need to retrieve a job using `ma_resource_manager_next_job()` and then process it using
105 `ma_resource_manager_process_job()`:
106 
107     ```c
108     config = ma_resource_manager_config_init();
109     config.jobThreadCount = 0;                            // Don't manage any job threads internally.
110     config.flags = MA_RESOURCE_MANAGER_FLAG_NON_BLOCKING; // Optional. Makes `ma_resource_manager_next_job()` non-blocking.
111 
112     // ... Initialize your custom job threads ...
113 
114     void my_custom_job_thread(...)
115     {
116         for (;;) {
117             ma_job job;
118             ma_result result = ma_resource_manager_next_job(pMyResourceManager, &job);
119             if (result != MA_SUCCESS) {
120                 if (result == MA_NOT_DATA_AVAILABLE) {
121                     // No jobs are available. Keep going. Will only get this if the resource manager was initialized
122                     // with MA_RESOURCE_MANAGER_FLAG_NON_BLOCKING.
123                     continue;
124                 } else if (result == MA_CANCELLED) {
125                     // MA_JOB_QUIT was posted. Exit.
126                     break;
127                 } else {
128                     // Some other error occurred.
129                     break;
130                 }
131             }
132 
133             ma_resource_manager_process_job(pMyResourceManager, &job);
134         }
135     }
136     ```
137 
138 In the example above, the MA_JOB_QUIT event is the used as the termination indicator. You can instead use whatever variable you would like to terminate the
139 thread. The call to `ma_resource_manager_next_job()` is blocking by default, by can be configured to be non-blocking by initializing the resource manager
140 with the MA_RESOURCE_MANAGER_FLAG_NON_BLOCKING configuration flag.
141 
142 When loading a file, it's sometimes convenient to be able to customize how files are opened and read. This can be done by setting `pVFS` member of the
143 resource manager's config:
144 
145     ```c
146     // Initialize your custom VFS object. See documentation for VFS for information on how to do this.
147     my_custom_vfs vfs = my_custom_vfs_init();
148 
149     config = ma_resource_manager_config_init();
150     config.pVFS = &vfs;
151     ```
152 
153 If you do not specify a custom VFS, the resource manager will use the operating system's normal file operations. This is default.
154 
155 To load a sound file and create a data source, call `ma_resource_manager_data_source_init()`. When loading a sound you need to specify the file path and
156 options for how the sounds should be loaded. By default a sound will be loaded synchronously. The returned data source is owned by the caller which means the
157 caller is responsible for the allocation and freeing of the data source. Below is an example for initializing a data source:
158 
159     ```c
160     ma_resource_manager_data_source dataSource;
161     ma_result result = ma_resource_manager_data_source_init(pResourceManager, pFilePath, flags, &dataSource);
162     if (result != MA_SUCCESS) {
163         // Error.
164     }
165 
166     // ...
167 
168     // A ma_resource_manager_data_source object is compatible with the `ma_data_source` API. To read data, just call
169     // the `ma_data_source_read_pcm_frames()` like you would with any normal data source.
170     result = ma_data_source_read_pcm_frames(&dataSource, pDecodedData, frameCount, &framesRead);
171     if (result != MA_SUCCESS) {
172         // Failed to read PCM frames.
173     }
174 
175     // ...
176 
177     ma_resource_manager_data_source_uninit(pResourceManager, &dataSource);
178     ```
179 
180 The `flags` parameter specifies how you want to perform loading of the sound file. It can be a combination of the following flags:
181 
182     ```
183     MA_DATA_SOURCE_STREAM
184     MA_DATA_SOURCE_DECODE
185     MA_DATA_SOURCE_ASYNC
186     ```
187 
188 When no flags are specified (set to 0), the sound will be fully loaded into memory, but not decoded, meaning the raw file data will be stored in memory, and
189 then dynamically decoded when `ma_data_source_read_pcm_frames()` is called. To instead decode the audio data before storing it in memory, use the
190 `MA_DATA_SOURCE_DECODE` flag. By default, the sound file will be loaded synchronously, meaning `ma_resource_manager_data_source_init()` will only return after
191 the entire file has been loaded. This is good for simplicity, but can be prohibitively slow. You can instead load the sound asynchronously using the
192 `MA_DATA_SOURCE_ASYNC` flag. This will result in `ma_resource_manager_data_source_init()` returning quickly, but no data will be returned by
193 `ma_data_source_read_pcm_frames()` until some data is available. When no data is available because the asynchronous decoding hasn't caught up, MA_BUSY will be
194 returned by `ma_data_source_read_pcm_frames()`.
195 
196 For large sounds, it's often prohibitive to store the entire file in memory. To mitigate this, you can instead stream audio data which you can do by specifying
197 the `MA_DATA_SOURCE_STREAM` flag. When streaming, data will be decoded in 1 second pages. When a new page needs to be decoded, a job will be posted to the job
198 queue and then subsequently processed in a job thread.
199 
200 When loading asynchronously, it can be useful to poll whether or not loading has finished. Use `ma_resource_manager_data_source_result()` to determine this.
201 For in-memory sounds, this will return `MA_SUCCESS` when the file has been *entirely* decoded. If the sound is still being decoded, `MA_BUSY` will be returned.
202 Otherwise, some other error code will be returned if the sound failed to load. For streaming data sources, `MA_SUCCESS` will be returned when the first page
203 has been decoded and the sound is ready to be played. If the first page is still being decoded, `MA_BUSY` will be returned. Otherwise, some other error code
204 will be returned if the sound failed to load.
205 
206 For in-memory sounds, reference counting is used to ensure the data is loaded only once. This means multiple calls to `ma_resource_manager_data_source_init()`
207 with the same file path will result in the file data only being loaded once. Each call to `ma_resource_manager_data_source_init()` must be matched up with a
208 call to `ma_resource_manager_data_source_uninit()`. Sometimes it can be useful for a program to register self-managed raw audio data and associate it with a
209 file path. Use `ma_resource_manager_register_decoded_data()`, `ma_resource_manager_register_encoded_data()` and `ma_resource_manager_unregister_data()` to do
210 this. `ma_resource_manager_register_decoded_data()` is used to associate a pointer to raw, self-managed decoded audio data in the specified data format with
211 the specified name. Likewise, `ma_resource_manager_register_encoded_data()` is used to associate a pointer to raw self-managed encoded audio data (the raw
212 file data) with the specified name. Note that these names need not be actual file paths. When `ma_resource_manager_data_source_init()` is called (without the
213 `MA_DATA_SOURCE_STREAM` flag), the resource manager will look for these explicitly registered data buffers and, if found, will use it as the backing data for
214 the data source. Note that the resource manager does *not* make a copy of this data so it is up to the caller to ensure the pointer stays valid for it's
215 lifetime. Use `ma_resource_manager_unregister_data()` to unregister the self-managed data. It does not make sense to use the `MA_DATA_SOURCE_STREAM` flag with
216 a self-managed data pointer. When `MA_DATA_SOURCE_STREAM` is specified, it will try loading the file data through the VFS.
217 
218 
219 Resource Manager Implementation Details
220 ---------------------------------------
221 Resources are managed in two main ways:
222 
223     1) By storing the entire sound inside an in-memory buffer (referred to as a data buffer - `ma_resource_manager_data_buffer_node`)
224     2) By streaming audio data on the fly (referred to as a data stream - `ma_resource_manager_data_stream`)
225 
226 A resource managed data source (`ma_resource_manager_data_source`) encapsulates a data buffer or data stream, depending on whether or not the data source was
227 initialized with the `MA_DATA_SOURCE_FLAG_STREAM` flag. If so, it will make use of a `ma_resource_manager_data_stream` object. Otherwise it will use a
228 `ma_resource_manager_data_buffer_node` object.
229 
230 Another major feature of the resource manager is the ability to asynchronously decode audio files. This relieves the audio thread of time-consuming decoding
231 which can negatively affect scalability due to the audio thread needing to complete it's work extremely quickly to avoid glitching. Asynchronous decoding is
232 achieved through a job system. There is a central multi-producer, multi-consumer, lock-free, fixed-capacity job queue. When some asynchronous work needs to be
233 done, a job is posted to the queue which is then read by a job thread. The number of job threads can be configured for improved scalability, and job threads
234 can all run in parallel without needing to worry about the order of execution (how this is achieved is explained below).
235 
236 When a sound is being loaded asynchronously, playback can begin before the sound has been fully decoded. This enables the application to start playback of the
237 sound quickly, while at the same time allowing to resource manager to keep loading in the background. Since there may be less threads than the number of sounds
238 being loaded at a given time, a simple scheduling system is used to keep decoding time fair. The resource manager solves this by splitting decoding into chunks
239 called pages. By default, each page is 1 second long. When a page has been decoded, the a new job will be posted to start decoding the next page. By dividing
240 up decoding into pages, an individual sound shouldn't ever delay every other sound from having their first page decoded. Of course, when loading many sounds at
241 the same time, there will always be an amount of time required to process jobs in the queue so in heavy load situations there will still be some delay. To
242 determine if a data source is ready to have some frames read, use `ma_resource_manager_data_source_get_available_frames()`. This will return the number of
243 frames available starting from the current position.
244 
245 
246 Data Buffers
247 ------------
248 When the `MA_DATA_SOURCE_FLAG_STREAM` flag is not specified at initialization time, the resource manager will try to load the data into an in-memory data
249 buffer. Before doing so, however, it will first check if the specified file has already been loaded. If so, it will increment a reference counter and just use
250 the already loaded data. This saves both time and memory. A binary search tree (BST) is used for storing data buffers as it has good balance between efficiency
251 and simplicity. The key of the BST is a 64-bit hash of the file path that was passed into `ma_resource_manager_data_source_init()`. The advantage of using a
252 hash is that it saves memory over storing the entire path, has faster comparisons, and results in a mostly balanced BST due to the random nature of the hash.
253 The disadvantage is that file names are case-sensitive. If this is an issue, you should normalize your file names to upper- or lower-case before initializing
254 your data sources.
255 
256 When a sound file has not already been loaded and the `MA_DATA_SOURCE_ASYNC` is not specified, the file will be decoded synchronously by the calling thread.
257 There are two options for controlling how the audio is stored in the data buffer - encoded or decoded. When the `MA_DATA_SOURCE_DECODE` option is not
258 specified, the raw file data will be stored in memory. Otherwise the sound will be decoded before storing it in memory. Synchronous loading is a very simple
259 and standard process of simply adding an item to the BST, allocating a block of memory and then decoding (if `MA_DATA_SOURCE_DECODE` is specified).
260 
261 When the `MA_DATA_SOURCE_ASYNC` flag is specified, loading of the data buffer is done asynchronously. In this case, a job is posted to the queue to start
262 loading and then the function instantly returns, setting an internal result code to `MA_BUSY`. This result code is returned when the program calls
263 `ma_resource_manager_data_source_result()`. When decoding has fully completed, `MA_RESULT` will be returned. This can be used to know if loading has fully
264 completed.
265 
266 When loading asynchronously, a single job is posted to the queue of the type `MA_JOB_LOAD_DATA_BUFFER`. This involves making a copy of the file path and
267 associating it with job. When the job is processed by the job thread, it will first load the file using the VFS associated with the resource manager. When
268 using a custom VFS, it's important that it be completely thread-safe because it will be used from one or more job threads at the same time. Individual files
269 should only ever be accessed by one thread at a time, however. After opening the file via the VFS, the job will determine whether or not the file is being
270 decoded. If not, it simply allocates a block of memory and loads the raw file contents into it and returns. On the other hand, when the file is being decoded,
271 it will first allocate a decoder on the heap and initialize it. Then it will check if the length of the file is known. If so it will allocate a block of memory
272 to store the decoded output and initialize it to silence. If the size is unknown, it will allocate room for one page. After memory has been allocated, the
273 first page will be decoded. If the sound is shorter than a page, the result code will be set to `MA_SUCCESS` and the completion event will be signalled and
274 loading is now complete. If, however, there is store more to decode, a job with the code `MA_JOB_PAGE_DATA_BUFFER` is posted. This job will decode the next
275 page and perform the same process if it reaches the end. If there is more to decode, the job will post another `MA_JOB_PAGE_DATA_BUFFER` job which will keep on
276 happening until the sound has been fully decoded. For sounds of an unknown length, the buffer will be dynamically expanded as necessary, and then shrunk with a
277 final realloc() when the end of the file has been reached.
278 
279 
280 Data Streams
281 ------------
282 Data streams only ever store two pages worth of data for each sound. They are most useful for large sounds like music tracks in games which would consume too
283 much memory if fully decoded in memory. Only two pages of audio data are stored in memory at a time for each data stream. After every frame from a page has
284 been read, a job will be posted to load the next page which is done from the VFS.
285 
286 For data streams, the `MA_DATA_SOURCE_FLAG_ASYNC` flag will determine whether or not initialization of the data source waits until the two pages have been
287 decoded. When unset, `ma_resource_manager_data_source()` will wait until the two pages have been loaded, otherwise it will return immediately.
288 
289 When frames are read from a data stream using `ma_resource_manager_data_source_read_pcm_frames()`, `MA_BUSY` will be returned if there are no frames available.
290 If there are some frames available, but less than the number requested, `MA_SUCCESS` will be returned, but the actual number of frames read will be less than
291 the number requested. Due to the asymchronous nature of data streams, seeking is also asynchronous. If the data stream is in the middle of a seek, `MA_BUSY`
292 will be returned when trying to read frames.
293 
294 When `ma_resource_manager_data_source_read_pcm_frames()` results in a page getting fully consumed, a job is posted to load the next page. This will be posted
295 from the same thread that called `ma_resource_manager_data_source_read_pcm_frames()` which should be lock-free.
296 
297 Data streams are uninitialized by posting a job to the queue, but the function won't return until that job has been processed. The reason for this is that the
298 caller owns the data stream object and therefore we need to ensure everything completes before handing back control to the caller. Also, if the data stream is
299 uninitialized while pages are in the middle of decoding, they must complete before destroying any underlying object and the job system handles this cleanly.
300 
301 
302 Job Queue
303 ---------
304 The resource manager uses a job queue which is multi-producer, multi-consumer, lock-free and fixed-capacity. The lock-free property of the queue is achieved
305 using the algorithm described by Michael and Scott: Nonblocking Algorithms and Preemption-Safe Locking on Multiprogrammed Shared Memory Multiprocessors. In
306 order for this to work, only a fixed number of jobs can be allocated and inserted into the queue which is done through a lock-free data structure for
307 allocating an index into a fixed sized array, with reference counting for mitigation of the ABA problem. The reference count is 32-bit.
308 
309 For many types of jobs it's important that they execute in a specific order. In these cases, jobs are executed serially. The way in which each type of job
310 handles this is specific to the job type. For the resource manager, serial execution of jobs is only required on a per-object basis (per data buffer or per
311 data stream). Each of these objects stores an execution counter. When a job is posted it is associated with an execution counter. When the job is processed, it
312 checks if the execution counter of the job equals the execution counter of the owning object and if so, processes the job. If the counters are not equal, the
313 job will be posted back onto the job queue for later processing. When the job finishes processing the execution order of the main object is incremented. This
314 system means the no matter how many job threads are executing, decoding of an individual sound will always get processed serially. The advantage to having
315 multiple threads comes into play when loading multiple sounds at the time time.
316 */
317 #ifndef miniaudio_engine_h
318 #define miniaudio_engine_h
319 
320 #ifdef __cplusplus
321 extern "C" {
322 #endif
323 
324 
325 /*
326 Effects
327 =======
328 The `ma_effect` API is a mid-level API for chaining together effects. This is a wrapper around lower level APIs which you can continue to use by themselves if
329 this API does not work for you.
330 
331 Effects can be linked together as a chain, with one input and one output. When processing audio data through an effect, it starts at the top of the chain and
332 works it's way down.
333 
334 
335 Usage
336 -----
337 To apply the effect to some audio data, do something like the following:
338 
339     ```c
340     ma_uint64 framesToProcessIn  = availableInputFrameCount;
341     ma_uint64 framesToProcessOut = frameCountOut;
342     ma_result result = ma_effect_process_pcm_frames(pEffect, pFramesIn, &framesToProcessIn, pFramesOut, &framesToProcessOut);
343     if (result != MA_SUCCESS) {
344         // Error.
345     }
346 
347     // At this point framesToProcessIn contains the number of input frames that were consumed and framesToProcessOut contains the number of output frames that
348     // were processed.
349     ```
350 
351 Some effects can change the sample rate, which means the number of output frames may be different to the number of input frames consumed. Therefore they both
352 need to be specified when processing a chunk of audio data.
353 */
354 typedef void ma_effect;
355 
356 typedef struct ma_effect_base ma_effect_base;
357 struct ma_effect_base
358 {
359     ma_result (* onProcessPCMFrames)(ma_effect* pEffect, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut);
360     ma_uint64 (* onGetRequiredInputFrameCount)(ma_effect* pEffect, ma_uint64 outputFrameCount);
361     ma_uint64 (* onGetExpectedOutputFrameCount)(ma_effect* pEffect, ma_uint64 inputFrameCount);
362     ma_result (* onGetInputDataFormat)(ma_effect* pEffect, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate);
363     ma_result (* onGetOutputDataFormat)(ma_effect* pEffect, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate);
364     ma_effect_base* pPrev;
365     ma_effect_base* pNext;
366 };
367 
368 MA_API ma_result ma_effect_process_pcm_frames(ma_effect* pEffect, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut);
369 MA_API ma_result ma_effect_process_pcm_frames_ex(ma_effect* pEffect, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut, ma_format formatIn, ma_uint32 channelsIn, ma_format formatOut, ma_uint32 channelsOut);
370 MA_API ma_uint64 ma_effect_get_required_input_frame_count(ma_effect* pEffect, ma_uint64 outputFrameCount);
371 MA_API ma_uint64 ma_effect_get_expected_output_frame_count(ma_effect* pEffect, ma_uint64 inputFrameCount);
372 MA_API ma_result ma_effect_append(ma_effect* pEffect, ma_effect* pParent);
373 MA_API ma_result ma_effect_prepend(ma_effect* pEffect, ma_effect* pChild);
374 MA_API ma_result ma_effect_detach(ma_effect* pEffect);
375 MA_API ma_result ma_effect_get_output_data_format(ma_effect* pEffect, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate);
376 MA_API ma_result ma_effect_get_input_data_format(ma_effect* pEffect, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate);
377 
378 
379 
380 /*
381 Mixing
382 ======
383 Mixing is done via the ma_mixer API. You can use this if you want to mix multiple sources of audio together and play them all at the same time, layered on top
384 of each other. This is a mid-level procedural API. Do not confuse this with a high-level data-driven API. You do not "attach" and "detach" sounds, but instead
385 write raw audio data directly into an accumulation buffer procedurally. High-level data-driven APIs will be coming at a later date.
386 
387 Below are the features of the ma_mixer API:
388 
389     * Mixing to and from any data format with seamless conversion when necessary.
390       * Initialize the `ma_mixer` object using whatever format is convenient, and then mix audio in any other format with seamless data conversion.
391     * Submixing (mix one `ma_mixer` directly into another `ma_mixer`, with volume and effect control).
392     * Volume control.
393     * Effects (via the `ma_effect` API).
394     * Mix directly from raw audio data in addition to `ma_decoder`, `ma_waveform`, `ma_noise`, `ma_pcm_rb` and `ma_rb` objects.
395 
396 Mixing sounds together is as simple as summing their samples. As samples are summed together they are stored in a buffer called the accumulation buffer. In
397 order to ensure there is enough precision to store the intermediary results, the accumulation buffer needs to be at a higher bit depth than the sample format
398 being mixed, with the exception of floating point. Below is a mapping of the sample format and the data type of the accumulation buffer:
399 
400     +---------------+------------------------+
401     | Sample Format | Accumulation Data Type |
402     +---------------+------------------------+
403     | ma_format_u8  | ma_int16               |
404     | ma_format_s16 | ma_int32               |
405     | ma_format_s24 | ma_int64               |
406     | ma_format_s32 | ma_int64               |
407     | ma_format_f32 | float                  |
408     +---------------+------------------------+
409 
410 The size of the accumulation buffer is fixed and must be specified at initialization time. When you initialize a mixer you need to also specify a sample format
411 which will be the format of the returned data after mixing. The format is also what's used to determine the bit depth to use for the accumulation buffer and
412 how to interpret the data contained within it. You must also specify a channel count in order to support interleaved multi-channel data. The sample rate is not
413 required by the mixer as it only cares about raw sample data.
414 
415 The mixing process involves three main steps:
416 
417     1) Clearing the accumulation buffer to zero
418          ma_mixer_begin()
419 
420     2) Accumulating all audio sources
421          ma_mixer_mix_pcm_frames()
422          ma_mixer_mix_data_source()
423          ma_mixer_mix_rb()
424          ma_mixer_mix_pcm_rb()
425 
426     3) Volume, clipping, effects and final output
427          ma_mixer_end()
428 
429 At the beginning of mixing the accumulation buffer will be cleared to zero. When you begin mixing you need to specify the number of PCM frames you want to
430 output at the end of mixing. If the requested number of output frames exceeds the capacity of the internal accumulation buffer, it will be clamped and returned
431 back to the caller. An effect can be applied at the end of mixing (after volume and clipping). Effects can do resampling which means the number of input frames
432 required to generate the requested number of output frames may be different. Therefore, another parameter is required which will receive the input frame count.
433 When mixing audio sources, you must do so based on the input frame count, not the output frame count (usage examples are in the next section).
434 
435 After the accumulation buffer has been cleared to zero (the first step), you can start mixing audio data. When you mix audio data you should do so based on the
436 required number of input frames returned by ma_mixer_begin() or ma_mixer_begin_submix(). You can specify audio data in any data format in which case the data
437 will be automatically converted to the format required by the accumulation buffer. Input data can be specified in multiple ways:
438 
439     - A pointer to raw PCM data
440     - A data source (ma_data_source, ma_decoder, ma_audio_buffer, ma_waveform, ma_noise)
441     - A ring buffer (ma_rb, ma_pcm_rb)
442 
443 Once you've finished accumulating all of your audio sources you need to perform a post process step which performs the final volume adjustment, clipping,
444 effects and copying to the specified output buffer in the format specified when the mixer was initialized. Volume is applied before clipping, which is applied
445 before the effect, which is done before final output. In between these steps is all of the necessary data conversion, so for performance it's important to be
446 mindful of where and when data will be converted.
447 
448 The mixing API in miniaudio supports seamless data conversion at all stages of the mixing pipeline. If you're not mindful about the data formats used by each
449 of the different stages of the mixing pipeline you may introduce unnecessary inefficiency. For maximum performance you should use a consistent sample format,
450 channel count and sample rate for as much of the mixing pipeline as possible. As soon as you introduce a different format, the mixing pipeline will perform the
451 necessary data conversion.
452 
453 
454 
455 Usage
456 -----
457 Initialize a mixer like the following:
458 
459     ```c
460     ma_mixer_config config = ma_mixer_config_init(ma_format_f32, 2, 1024, NULL);
461 
462     ma_mixer mixer;
463     result = ma_mixer_init(&config, &mixer);
464     if (result != MA_SUCCESS) {
465         // An error occurred.
466     }
467     ```
468 
469 Before you can initialize a mixer you need to specify it's configuration via a `ma_mixer_config` object. This can be created with `ma_mixer_config_init()`
470 which requires the mixing format, channel count, size of the intermediary buffer in PCM frames and an optional pointer to a pre-allocated accumulation buffer.
471 Once you have the configuration set up, you can call `ma_mixer_init()` to initialize the mixer. If you passed in NULL for the pre-allocated accumulation buffer
472 this will allocate it on the stack for you, using custom allocation callbacks specified in the `allocationCallbacks` member of the mixer config.
473 
474 Below is an example for mixing two decoders together:
475 
476     ```c
477     ma_uint64 frameCountIn;
478     ma_uint64 frameCountOut = desiredOutputFrameCount;
479     ma_mixer_begin(&mixer, NULL, &frameCountOut, &frameCountIn);
480     {
481         // At this point, frameCountIn contains the number of frames we should be mixing in this iteration, whereas frameCountOut contains the number of output
482         // frames we'll be outputting in ma_mixer_end().
483         ma_mixer_mix_data_source(&mixer, &decoder1, 0, frameCountIn, isLooping1);
484         ma_mixer_mix_data_source(&mixer, &decoder2, 0, frameCountIn, isLooping2);
485     }
486     ma_mixer_end(&mixer, NULL, pFinalMix, 0); // pFinalMix must be large enough to store frameCountOut frames in the mixer's format (specified at initialization time).
487     ```
488 
489 When you want to mix sounds together, you need to specify how many output frames you would like to end up with by the end. This depends on the size of the
490 accumulation buffer, however, which is of a fixed size. Therefore, the number of output frames you ask for is not necessarily what you'll get. In addition, an
491 effect can be applied at the end of mixing, and since that may perform resampling, the number of input frames required to generate the desired number of output
492 frames may differ which means you must also specify a pointer to a variable which will receive the required input frame count. In order to avoid glitching you
493 should write all of these input frames if they're available.
494 
495 The ma_mixer API uses a sort of "immediate mode" design. The idea is that you "begin" and "end" mixing. When you begin mixing a number of frames you need to
496 call `ma_mixer_begin()`. This will initialize the accumulation buffer to zero (silence) in preparation for mixing. Next, you can start mixing audio data which
497 can be done in several ways, depending on the source of the audio data. In the example above we are using a `ma_decoder` as the input data source. This will
498 automatically convert the input data to an appropriate format for mixing.
499 
500 Each call to ma_mixer_mix_*() accumulates from the beginning of the accumulation buffer.
501 
502 Once all of your input data has been mixed you need to call `ma_mixer_end()`. This is where the data in the accumulation buffer has volume applied, is clipped
503 and has the effect applied, in that order. Finally, the data is output to the specified buffer in the format specified when the mixer was first initialized,
504 overwriting anything that was previously contained within the buffer, unless it's a submix in which case it will be mixed with the parent mixer. See section
505 below for more details.
506 
507 The mixing API also supports submixing. This is where the final output of one mixer is mixed directly into the accumulation buffer of another mixer. A common
508 example is a game with a music submix and an effects submix, which are then combined to form the master mix. Example:
509 
510     ```c
511     ma_uint64 frameCountIn;
512     ma_uint64 frameCountOut = desiredOutputFrameCount;  // <-- Must be set to the desired number of output frames. Upon returning, will contain the actual number of output frames.
513     ma_mixer_begin(&masterMixer, NULL, &frameCountOut, &frameCountIn);
514     {
515         ma_uint64 submixFrameCountIn;
516         ma_uint64 submixFrameCountOut;  // <-- No pre-initialization required for a submix as it's derived from the parent mix's input frame count.
517 
518         // Music submix.
519         ma_mixer_begin(&musicMixer, &masterMixer, &submixFrameCountIn, &submixFrameCountOut);
520         {
521             ma_mixer_mix_data_source(&musicMixer, &musicDecoder, 0, submixFrameCountIn, isMusicLooping);
522         }
523         ma_mixer_end(&musicMixer, &masterMixer, NULL, 0);
524 
525         // Effects submix.
526         ma_mixer_begin(&effectsMixer, &masterMixer, &submixFrameCountIn, &submixFrameCountOut);
527         {
528             ma_mixer_mix_data_source(&effectsMixer, &decoder1, 0, frameCountIn, isLooping1);
529             ma_mixer_mix_data_source(&effectsMixer, &decoder2, 0, frameCountIn, isLooping2);
530         }
531         ma_mixer_end(&effectsMixer, &masterMixer, NULL, 0);
532     }
533     ma_mixer_end(&masterMixer, NULL, pFinalMix); // pFinalMix must be large enough to store frameCountOut frames in the mixer's format (specified at initialization time).
534     ```
535 
536 If you want to use submixing, you need to ensure the accumulation buffers of each mixer is large enough to accomodate each other. That is, the accumulation
537 buffer of the sub-mixer needs to be large enough to store the required number of input frames returned by the parent call to `ma_mixer_begin()`. If you are not
538 doing any resampling you can just make the accumulation buffers the same size and you will fine. If you want to submix, you can only call `ma_mixer_begin()`
539 between the begin and end pairs of the parent mixer, which can be a master mix or another submix.
540 
541 
542 
543 Implementation Details and Performance Guidelines
544 -------------------------------------------------
545 There are two main factors which affect mixing performance: data conversion and data movement. This section will detail the implementation of the ma_mixer API
546 and hopefully give you a picture on how best to identify and avoid potential performance pitfalls.
547 
548 TODO: Write me.
549 
550 Below a summary of some things to keep in mind for high performance mixing:
551 
552     * Choose a sample format at compile time and use it for everything. Optimized pipelines will be implemented for ma_format_s16 and ma_format_f32. The most
553       common format is ma_format_f32 which will work in almost all cases. If you're building a game, ma_format_s16 may also work. Professional audio work will
554       likely require ma_format_f32 for the added precision for authoring work. Do not use ma_format_s24 if you have high performance requirements as it is not
555       nicely aligned and thus requires an inefficient conversion to 32-bit.
556 
557     * If you're building a game, try to use a consistent sample format, channel count and sample rate for all of your audio files, or at least all of your
558       audio files for a specific category (same format for all sfx, same format for all music, same format for all voices, etc.)
559 
560     * Be mindful of when you perform resampling. Most desktop platforms output at a sample rate of 48000Hz or 44100Hz. If your input data is, for example,
561       22050Hz, consider doing your mixing at 22050Hz, and then doing a final resample to the playback device's output format. In this example, resampling all
562       of your data sources to 48000Hz before mixing may be unnecessarily inefficient because it'll need to perform mixing on a greater number of samples.
563 */
564 
565 MA_API size_t ma_get_accumulation_bytes_per_sample(ma_format format);
566 MA_API size_t ma_get_accumulation_bytes_per_frame(ma_format format, ma_uint32 channels);
567 
568 typedef struct
569 {
570     ma_format format;
571     ma_uint32 channels;
572     ma_uint64 accumulationBufferSizeInFrames;
573     void* pPreAllocatedAccumulationBuffer;
574     ma_allocation_callbacks allocationCallbacks;
575     float volume;
576 } ma_mixer_config;
577 
578 MA_API ma_mixer_config ma_mixer_config_init(ma_format format, ma_uint32 channels, ma_uint64 accumulationBufferSizeInFrames, void* pPreAllocatedAccumulationBuffer, const ma_allocation_callbacks* pAllocationCallbacks);
579 
580 
581 typedef struct
582 {
583     ma_format format;   /* This will be the format output by ma_mixer_end(). */
584     ma_uint32 channels;
585     ma_uint64 accumulationBufferSizeInFrames;
586     void* pAccumulationBuffer;  /* In the accumulation format. */
587     ma_allocation_callbacks allocationCallbacks;
588     ma_bool32 ownsAccumulationBuffer;
589     float volume;
590     ma_effect* pEffect; /* The effect to apply after mixing input sources. */
591     struct
592     {
593         ma_uint64 frameCountIn;
594         ma_uint64 frameCountOut;
595         ma_bool32 isInsideBeginEnd;
596     } mixingState;
597 } ma_mixer;
598 
599 /*
600 Initialize a mixer.
601 
602 A mixer is used to mix/layer/blend sounds together.
603 
604 
605 Parameters
606 ----------
607 pConfig (in)
608     A pointer to the mixer's configuration. Cannot be NULL. See remarks.
609 
610 pMixer (out)
611     A pointer to the mixer object being initialized.
612 
613 
614 Return Value
615 ------------
616 MA_SUCCESS if successful; any other error code otherwise.
617 
618 
619 Thread Safety
620 -------------
621 Unsafe. You should not be trying to initialize a mixer from one thread, while at the same time trying to use it on another.
622 
623 
624 Callback Safety
625 ---------------
626 This is safe to call in the data callback, but do if you do so, keep in mind that if you do not supply a pre-allocated accumulation buffer it will allocate
627 memory on the heap for you.
628 
629 
630 Remarks
631 -------
632 The mixer can be configured via the `pConfig` argument. The config object is initialized with `ma_mixer_config_init()`. Individual configuration settings can
633 then be set directly on the structure. Below are the members of the `ma_mixer_config` object.
634 
635     format
636         The sample format to use for mixing. This is the format that will be output by `ma_mixer_end()`.
637 
638     channels
639         The channel count to use for mixing. This is the number of channels that will be output by `ma_mixer_end()`.
640 
641     accumulationBufferSizeInFrames
642         A mixer uses a fixed sized buffer for it's entire life time. This specifies the size in PCM frames of the accumulation buffer. When calling
643         `ma_mixer_begin()`, the requested output frame count will be clamped based on the value of this property. You should not use this propertry to
644         determine how many frames to mix at a time with `ma_mixer_mix_*()` - use the value returned by `ma_mixer_begin()`.
645 
646     pPreAllocatedAccumulationBuffer
647         A pointer to a pre-allocated buffer to use for the accumulation buffer. This can be null in which case a buffer will be allocated for you using the
648         specified allocation callbacks, if any. You can calculate the size in bytes of the accumulation buffer like so:
649 
650             ```c
651             sizeInBytes = config.accumulationBufferSizeInFrames * ma_get_accumulation_bytes_per_frame(config.format, config.channels)
652             ```
653 
654         Note that you should _not_ use `ma_get_bytes_per_frame()` when calculating the size of the buffer because the accumulation buffer requires a higher bit
655         depth for accumulation in order to avoid wrapping.
656 
657     allocationCallbacks
658         Memory allocation callbacks to use for allocating memory for the accumulation buffer. If all callbacks in this object are NULL, `MA_MALLOC()` and
659         `MA_FREE()` will be used.
660 
661     volume
662         The default output volume in linear scale. Defaults to 1. This can be changed after initialization with `ma_mixer_set_volume()`.
663 */
664 MA_API ma_result ma_mixer_init(ma_mixer_config* pConfig, ma_mixer* pMixer);
665 
666 /*
667 Uninitializes a mixer.
668 
669 
670 Parameters:
671 -----------
672 pMixer (in)
673     A pointer to the mixer being unintialized.
674 
675 
676 Thread Safety
677 -------------
678 Unsafe. You should not be uninitializing a mixer while using it on another thread.
679 
680 
681 Callback Safety
682 ---------------
683 If you did not specify a pre-allocated accumulation buffer, this will free it.
684 
685 
686 Remarks
687 -------
688 If you specified a pre-allocated buffer it will be left as-is. Otherwise it will be freed using the allocation callbacks specified in the config when the mixer
689 was initialized.
690 */
691 MA_API void ma_mixer_uninit(ma_mixer* pMixer);
692 
693 /*
694 Marks the beginning of a mix of a specified number of frames.
695 
696 When you begin mixing, you must specify how many frames you want to mix. You specify the number of output frames you want, and upon returning you will receive
697 the number of output frames you'll actually get. When an effect is attached, there may be a chance that the number of input frames required to output the given
698 output frame count differs. The input frame count is also returned, and this is number of frames you must use with the `ma_mixer_mix_*()` APIs, provided that
699 number of input frames are available to you at mixing time.
700 
701 Each call to `ma_mixer_begin()` must be matched with a call to `ma_mixer_end()`. In between these you mix audio data using the `ma_mixer_mix_*()` APIs. When
702 you call `ma_mixer_end()`, the number of frames that are output will be equal to the output frame count. When you call `ma_mixer_mix_*()`, you specify a frame
703 count based on the input frame count.
704 
705 
706 Parameters
707 ----------
708 pMixer (in)
709     A pointer to the relevant mixer.
710 
711 pParentMixer (in, optional)
712     A pointer to the parent mixer. Set this to non-NULL if you want the output of `pMixer` to be mixed with `pParentMixer`. Otherwise, if you want to output
713     directly to a buffer, set this to NULL. You would set this to NULL for a master mixer, and non-NULL for a submix. See remarks.
714 
715 pFrameCountOut (in, out)
716     On input, specifies the desired number of output frames to mix in this iteration. The requested number of output frames may not be able to fit in the
717     internal accumulation buffer which means on output this variable will receive the actual number of output frames. On input, this will be ignored if
718     `pParentMixer` is non-NULL because the output frame count of a submix must be compatible with the parent mixer.
719 
720 pFramesCountIn (out)
721     A pointer to the variable that will receive the number of input frames to mix with each call to `ma_mixer_mix_*()`. This will usually always equal the
722     output frame count, but will be different if an effect is applied and that effect performs resampling. See remarks.
723 
724 
725 Return Value
726 ------------
727 MA_SUCCESS if successful; any other error code otherwise.
728 
729 
730 Thread Safety
731 -------------
732 This can be called from any thread so long as you perform your own synchronization against the `pMixer` and `pParentMixer` object.
733 
734 
735 Callback Safety
736 ---------------
737 Safe.
738 
739 
740 Remarks
741 -------
742 When you call `ma_mixer_begin()`, you need to specify how many output frames you want. The number of input frames required to generate those output frames can
743 differ, however. This will only happen if you have an effect attached (see `ma_mixer_set_effect()`) and if one of the effects in the chain performs resampling.
744 The input frame count will be returned by the `pFrameCountIn` parameter, and this is how many frames should be used when mixing with `ma_mixer_mix_*()`. See
745 examples below.
746 
747 The mixer API supports the concept of submixing which is where the output of one mixer is mixed with that of another. A common example from a game:
748 
749     Master
750         SFX
751         Music
752         Voices
753 
754 In the example above, "Master" is the master mix and "SFX", "Music" and "Voices" are submixes. When you call `ma_mixer_begin()` for the "Master" mix, you would
755 set `pParentMixer` to NULL. For the "SFX", "Music" and "Voices" you would set it to a pointer to the master mixer, and you must call `ma_mixer_begin()` and
756 `ma_mixer_end()` between the begin and end pairs of the parent mixer. If you want to perform submixing, you need to pass the same parent mixer (`pParentMixer`)
757 to `ma_mixer_end()`. See example 2 for an example on how to do submixing.
758 
759 
760 Example 1
761 ---------
762 This example shows a basic mixer without any submixing.
763 
764 ```c
765 ma_uint64 frameCountIn;
766 ma_uint64 frameCountOut = desiredFrameCount;    // <-- On input specifies what you want, on output receives what you actually got.
767 ma_mixer_begin(&mixer, NULL, &frameCountOut, &frameCountIn);
768 {
769     ma_mixer_mix_data_source(&mixer, &decoder1, frameCountIn, isLooping1);
770     ma_mixer_mix_data_source(&mixer, &decoder2, frameCountIn, isLooping2);
771 }
772 ma_mixer_end(&mixer, NULL, pFramesOut); // <-- pFramesOut must be large enough to receive frameCountOut frames in mixer.format/mixer.channels format.
773 ```
774 
775 
776 Example 2
777 ---------
778 This example shows how you can do submixing.
779 
780 ```c
781 ma_uint64 frameCountIn;
782 ma_uint64 frameCountOut = desiredFrameCount;    // <-- On input specifies what you want, on output receives what you actually got.
783 ma_mixer_begin(&masterMixer, NULL, &frameCountOut, &frameCountIn);
784 {
785     ma_uint64 submixFrameCountIn;
786 
787     // SFX submix.
788     ma_mixer_begin(&sfxMixer, &masterMixer, &submixFrameCountIn, NULL);     // Output frame count not required for submixing.
789     {
790         ma_mixer_mix_data_source(&sfxMixer, &sfxDecoder1, 0, submixFrameCountIn, isSFXLooping1);
791         ma_mixer_mix_data_source(&sfxMixer, &sfxDecoder2, 0, submixFrameCountIn, isSFXLooping2);
792     }
793     ma_mixer_end(&sfxMixer, &masterMixer, NULL, 0);
794 
795     // Voice submix.
796     ma_mixer_begin(&voiceMixer, &masterMixer, &submixFrameCountIn, NULL);
797     {
798         ma_mixer_mix_data_source(&voiceMixer, &voiceDecoder1, 0, submixFrameCountIn, isVoiceLooping1);
799     }
800     ma_mixer_end(&voiceMixer, &masterMixer, NULL, 0);
801 
802     // Music submix.
803     ma_mixer_begin(&musicMixer, &masterMixer, &submixFrameCountIn, NULL);
804     {
805         ma_mixer_mix_data_source(&musicMixer, &musicDecoder1, 0, submixFrameCountIn, isMusicLooping1);
806     }
807     ma_mixer_end(&musicMixer, &masterMixer, NULL, 0);
808 }
809 ma_mixer_end(&masterMixer, NULL, pFramesOut, 0); // <-- pFramesOut must be large enough to receive frameCountOut frames in mixer.format/mixer.channels format.
810 ```
811 
812 
813 See Also
814 --------
815 ma_mixer_end()
816 ma_mixer_set_effect()
817 ma_mixer_get_effect()
818 */
819 MA_API ma_result ma_mixer_begin(ma_mixer* pMixer, ma_mixer* pParentMixer, ma_uint64* pFrameCountOut, ma_uint64* pFrameCountIn);
820 
821 /*
822 Applies volume, performs clipping, applies the effect (if any) and outputs the final mix to the specified output buffer or mixed with another mixer.
823 
824 
825 Parameters
826 ----------
827 pMixer (in)
828     A pointer to the mixer.
829 
830 pParentMixer (in, optional)
831     A pointer to the parent mixer. If this is non-NULL, the output of `pMixer` will be mixed with `pParentMixer`. It is an error for `pParentMixer` and
832     `pFramesOut` to both be non-NULL. If this is non-NULL, it must have also been specified as the parent mixer in the prior call to `ma_mixer_begin()`.
833 
834 pFramesOut (in, optional)
835     A pointer to the buffer that will receive the final mixed output. The output buffer must be in the format specified by the mixer's configuration that was
836     used to initialized it. The required size in frames is defined by the output frame count returned by `ma_mixer_begin()`. It is an error for `pFramesOut`
837     and `pParentMixer` to both be non-NULL.
838 
839 outputOffsetInFrames (in)
840     The offset in frames to start writing the output data to the destination buffer.
841 
842 
843 Return Value
844 ------------
845 MA_SUCCESS if successful; any other error code otherwise.
846 
847 
848 Remarks
849 -------
850 It is an error both both `pParentMixer` and `pFramesOut` to both be NULL or non-NULL. You must specify one or the other.
851 
852 When outputting to a parent mixer (`pParentMixer` is non-NULL), the output is mixed with the parent mixer. Otherwise (`pFramesOut` is non-NULL), the output
853 will overwrite anything already in the output buffer.
854 
855 When calculating the final output, the volume will be applied before clipping, which is done before applying the effect (if any).
856 
857 See documentation for `ma_mixer_begin()` for an example on how to use `ma_mixer_end()`.
858 
859 
860 See Also
861 --------
862 ma_mixer_begin()
863 ma_mixer_set_volume()
864 ma_mixer_get_volume()
865 ma_mixer_set_effect()
866 ma_mixer_get_effect()
867 */
868 MA_API ma_result ma_mixer_end(ma_mixer* pMixer, ma_mixer* pParentMixer, void* pFramesOut, ma_uint64 outputOffsetInFrames);
869 
870 
871 /*
872 Mixes audio data from a buffer containing raw PCM data.
873 
874 
875 Parameters
876 ----------
877 pMixer (in)
878     A pointer to the mixer.
879 
880 pFramesIn (in)
881     A pointer to the buffer containing the raw PCM data to mix with the mixer. The data contained within this buffer is assumed to be of the same format as the
882     mixer, which was specified when the mixer was initialized. Use `ma_mixer_mix_pcm_frames_ex()` to mix data of a different format.
883 
884 frameCountIn (in)
885     The number of frames to mix. This cannot exceed the number of input frames returned by `ma_mixer_begin()`. If it does, an error will be returned. If it is
886     less, silence will be mixed to make up the excess.
887 
888 formatIn (in)
889     The sample format of the input data.
890 
891 channelsIn (in)
892     The channel count of the input data.
893 
894 
895 Return Value
896 ------------
897 MA_SUCCESS if successful; any other error code otherwise.
898 
899 
900 Remarks
901 -------
902 Each call to this function will start mixing from the start of the internal accumulation buffer.
903 
904 This will automatically convert the data to the mixer's native format. The sample format will be converted without dithering. Channels will be converted based
905 on the default channel map.
906 
907 
908 See Also
909 --------
910 ma_mixer_mix_pcm_frames()
911 ma_mixer_begin()
912 ma_mixer_end()
913 */
914 MA_API ma_result ma_mixer_mix_pcm_frames(ma_mixer* pMixer, const void* pFramesIn, ma_uint64 offsetInFrames, ma_uint64 frameCountIn, float volume, ma_format formatIn, ma_uint32 channelsIn);
915 
916 /*
917 Mixes audio data from a data source
918 
919 
920 Parameters
921 ----------
922 pMixer (in)
923     A pointer to the mixer.
924 
925 pDataSource (in)
926     A pointer to the data source to read input data from.
927 
928 frameCountIn (in)
929     The number of frames to mix. This cannot exceed the number of input frames returned by `ma_mixer_begin()`. If it does, an error will be returned. If it is
930     less, silence will be mixed to make up the excess.
931 
932 pFrameCountOut (out)
933     Receives the number of frames that were processed from the data source.
934 
935 formatIn (in)
936     The sample format of the input data.
937 
938 channelsIn (in)
939     The channel count of the input data.
940 
941 
942 Return Value
943 ------------
944 MA_SUCCESS if successful; any other error code otherwise.
945 
946 
947 Remarks
948 -------
949 Each call to this function will start mixing from the start of the internal accumulation buffer.
950 
951 This will automatically convert the data to the mixer's native format. The sample format will be converted without dithering. Channels will be converted based
952 on the default channel map.
953 
954 
955 See Also
956 --------
957 ma_mixer_begin()
958 ma_mixer_end()
959 */
960 MA_API ma_result ma_mixer_mix_data_source(ma_mixer* pMixer, ma_data_source* pDataSource, ma_uint64 offsetInFrames, ma_uint64 frameCountIn, ma_uint64* pFrameCountOut, float volume, ma_effect* pEffect, ma_bool32 loop);
961 MA_API ma_result ma_mixer_mix_rb(ma_mixer* pMixer, ma_rb* pRB, ma_uint64 offsetInFrames, ma_uint64 frameCountIn, ma_uint64* pFrameCountOut, float volume, ma_effect* pEffect, ma_format formatIn, ma_uint32 channelsIn); /* Caller is the consumer. */
962 MA_API ma_result ma_mixer_mix_pcm_rb(ma_mixer* pMixer, ma_pcm_rb* pRB, ma_uint64 offsetInFrames, ma_uint64 frameCountIn, ma_uint64* pFrameCountOut, float volume, ma_effect* pEffect);                                   /* Caller is the consumer. */
963 
964 MA_API ma_result ma_mixer_set_volume(ma_mixer* pMixer, float volume);
965 MA_API ma_result ma_mixer_get_volume(ma_mixer* pMixer, float* pVolume);
966 MA_API ma_result ma_mixer_set_gain_db(ma_mixer* pMixer, float gainDB);
967 MA_API ma_result ma_mixer_get_gain_db(ma_mixer* pMixer, float* pGainDB);
968 MA_API ma_result ma_mixer_set_effect(ma_mixer* pMixer, ma_effect* pEffect);
969 MA_API ma_result ma_mixer_get_effect(ma_mixer* pMixer, ma_effect** ppEffect);
970 MA_API ma_result ma_mixer_get_output_data_format(ma_mixer* pMixer, ma_format* pFormat, ma_uint32* pChannels);
971 MA_API ma_result ma_mixer_get_input_data_format(ma_mixer* pMixer, ma_format* pFormat, ma_uint32* pChannels);
972 
973 
974 
975 /*
976 Resource Manager Data Source Flags
977 ==================================
978 The flags below are used for controlling how the resource manager should handle the loading and caching of data sources.
979 */
980 #define MA_DATA_SOURCE_FLAG_STREAM  0x00000001  /* When set, does not load the entire data source in memory. Disk I/O will happen on job threads. */
981 #define MA_DATA_SOURCE_FLAG_DECODE  0x00000002  /* Decode data before storing in memory. When set, decoding is done at the resource manager level rather than the mixing thread. Results in faster mixing, but higher memory usage. */
982 #define MA_DATA_SOURCE_FLAG_ASYNC   0x00000004  /* When set, the resource manager will load the data source asynchronously. */
983 
984 
985 typedef enum
986 {
987     ma_resource_manager_data_buffer_encoding_encoded,
988     ma_resource_manager_data_buffer_encoding_decoded
989 } ma_resource_manager_data_buffer_encoding;
990 
991 /* The type of object that's used to connect a data buffer to a data source. */
992 typedef enum
993 {
994     ma_resource_manager_data_buffer_connector_unknown,
995     ma_resource_manager_data_buffer_connector_decoder,  /* ma_decoder */
996     ma_resource_manager_data_buffer_connector_buffer    /* ma_audio_buffer */
997 } ma_resource_manager_data_buffer_connector;
998 
999 
1000 typedef struct ma_resource_manager                  ma_resource_manager;
1001 typedef struct ma_resource_manager_data_buffer_node ma_resource_manager_data_buffer_node;
1002 typedef struct ma_resource_manager_data_buffer      ma_resource_manager_data_buffer;
1003 typedef struct ma_resource_manager_data_stream      ma_resource_manager_data_stream;
1004 typedef struct ma_resource_manager_data_source      ma_resource_manager_data_source;
1005 
1006 
1007 
1008 #ifndef MA_RESOURCE_MANAGER_JOB_QUEUE_CAPACITY
1009 #define MA_RESOURCE_MANAGER_JOB_QUEUE_CAPACITY  1024
1010 #endif
1011 
1012 #define MA_JOB_QUIT                 0x00000000
1013 #define MA_JOB_LOAD_DATA_BUFFER     0x00000001
1014 #define MA_JOB_FREE_DATA_BUFFER     0x00000002
1015 #define MA_JOB_PAGE_DATA_BUFFER     0x00000003
1016 #define MA_JOB_LOAD_DATA_STREAM     0x00000004
1017 #define MA_JOB_FREE_DATA_STREAM     0x00000005
1018 #define MA_JOB_PAGE_DATA_STREAM     0x00000006
1019 #define MA_JOB_SEEK_DATA_STREAM     0x00000007
1020 #define MA_JOB_CUSTOM               0x000000FF  /* Number your custom job codes as (MA_JOB_CUSTOM + 0), (MA_JOB_CUSTOM + 1), etc. */
1021 
1022 
1023 /*
1024 The idea of the slot allocator is for it to be used in conjunction with a fixed sized buffer. You use the slot allocator to allocator an index that can be used
1025 as the insertion point for an object.
1026 
1027 Slots are reference counted to help mitigate the ABA problem in the lock-free queue we use for tracking jobs.
1028 
1029 The slot index is stored in the low 32 bits. The reference counter is stored in the high 32 bits:
1030 
1031     +-----------------+-----------------+
1032     | 32 Bits         | 32 Bits         |
1033     +-----------------+-----------------+
1034     | Reference Count | Slot Index      |
1035     +-----------------+-----------------+
1036 */
1037 typedef struct
1038 {
1039     volatile struct
1040     {
1041         ma_uint32 bitfield;
1042     } groups[MA_RESOURCE_MANAGER_JOB_QUEUE_CAPACITY/32];
1043     ma_uint32 slots[MA_RESOURCE_MANAGER_JOB_QUEUE_CAPACITY];    /* 32 bits for reference counting for ABA mitigation. */
1044     ma_uint32 count;    /* Allocation count. */
1045 } ma_slot_allocator;
1046 
1047 MA_API ma_result ma_slot_allocator_init(ma_slot_allocator* pAllocator);
1048 MA_API ma_result ma_slot_allocator_alloc(ma_slot_allocator* pAllocator, ma_uint64* pSlot);
1049 MA_API ma_result ma_slot_allocator_free(ma_slot_allocator* pAllocator, ma_uint64 slot);
1050 
1051 
1052 /* Notification codes for ma_async_notification. Used to allow some granularity for notification callbacks. */
1053 #define MA_NOTIFICATION_COMPLETE    0   /* Operation has fully completed. */
1054 #define MA_NOTIFICATION_INIT        1   /* Object has been initialized, but operation not fully completed yet. */
1055 #define MA_NOTIFICATION_FAILED      2   /* Failed to initialize. */
1056 
1057 
1058 /*
1059 Notification callback for asynchronous operations.
1060 */
1061 typedef void ma_async_notification;
1062 
1063 typedef struct
1064 {
1065     void (* onSignal)(ma_async_notification* pNotification, int code);
1066 } ma_async_notification_callbacks;
1067 
1068 MA_API ma_result ma_async_notification_signal(ma_async_notification* pNotification, int code);
1069 
1070 
1071 /*
1072 Event Notification
1073 
1074 This notification signals an event internally on the MA_NOTIFICATION_COMPLETE and MA_NOTIFICATION_FAILED codes. All other codes are ignored.
1075 */
1076 typedef struct
1077 {
1078     ma_async_notification_callbacks cb;
1079     ma_event e;
1080 } ma_async_notification_event;
1081 
1082 MA_API ma_result ma_async_notification_event_init(ma_async_notification_event* pNotificationEvent);
1083 MA_API ma_result ma_async_notification_event_uninit(ma_async_notification_event* pNotificationEvent);
1084 MA_API ma_result ma_async_notification_event_wait(ma_async_notification_event* pNotificationEvent);
1085 MA_API ma_result ma_async_notification_event_signal(ma_async_notification_event* pNotificationEvent);
1086 
1087 
1088 typedef struct
1089 {
1090     union
1091     {
1092         struct
1093         {
1094             ma_uint16 code;
1095             ma_uint16 slot;
1096             ma_uint32 refcount;
1097         };
1098         ma_uint64 allocation;
1099     } toc;  /* 8 bytes. We encode the job code into the slot allocation data to save space. */
1100     ma_uint64 next;     /* refcount + slot for the next item. Does not include the job code. */
1101     ma_uint32 order;    /* Execution order. Used to create a data dependency and ensure a job is executed in order. Usage is contextual depending on the job type. */
1102 
1103     union
1104     {
1105         /* Resource Managemer Jobs */
1106         struct
1107         {
1108             ma_resource_manager_data_buffer* pDataBuffer;
1109             char* pFilePath;
1110             ma_async_notification* pNotification;   /* Signalled when the data buffer has been fully decoded. */
1111         } loadDataBuffer;
1112         struct
1113         {
1114             ma_resource_manager_data_buffer* pDataBuffer;
1115             ma_async_notification* pNotification;
1116         } freeDataBuffer;
1117         struct
1118         {
1119             ma_resource_manager_data_buffer* pDataBuffer;
1120             ma_decoder* pDecoder;
1121             ma_async_notification* pCompletedNotification;  /* Signalled when the data buffer has been fully decoded. */
1122             void* pData;
1123             size_t dataSizeInBytes;
1124             ma_uint64 decodedFrameCount;
1125             ma_bool32 isUnknownLength;              /* When set to true does not update the running frame count of the data buffer nor the data pointer until the last page has been decoded. */
1126         } pageDataBuffer;
1127 
1128         struct
1129         {
1130             ma_resource_manager_data_stream* pDataStream;
1131             char* pFilePath;                        /* Allocated when the job is posted, freed by the job thread after loading. */
1132             ma_async_notification* pNotification;   /* Signalled after the first two pages have been decoded and frames can be read from the stream. */
1133         } loadDataStream;
1134         struct
1135         {
1136             ma_resource_manager_data_stream* pDataStream;
1137             ma_async_notification* pNotification;
1138         } freeDataStream;
1139         struct
1140         {
1141             ma_resource_manager_data_stream* pDataStream;
1142             ma_uint32 pageIndex;                    /* The index of the page to decode into. */
1143         } pageDataStream;
1144         struct
1145         {
1146             ma_resource_manager_data_stream* pDataStream;
1147             ma_uint64 frameIndex;
1148         } seekDataStream;
1149 
1150         /* Others. */
1151         struct
1152         {
1153             ma_uintptr data0;
1154             ma_uintptr data1;
1155         } custom;
1156     };
1157 } ma_job;
1158 
1159 MA_API ma_job ma_job_init(ma_uint16 code);
1160 
1161 
1162 #define MA_JOB_QUEUE_FLAG_NON_BLOCKING  0x00000001  /* When set, ma_job_queue_next() will not wait and no semaphore will be signaled in ma_job_queue_post(). ma_job_queue_next() will return MA_NO_DATA_AVAILABLE if nothing is available. */
1163 
1164 typedef struct
1165 {
1166     ma_uint32 flags;    /* Flags passed in at initialization time. */
1167     ma_uint64 head;     /* The first item in the list. Required for removing from the top of the list. */
1168     ma_uint64 tail;     /* The last item in the list. Required for appending to the end of the list. */
1169     ma_semaphore sem;   /* Only used when MA_JOB_QUEUE_ASYNC is unset. */
1170     ma_slot_allocator allocator;
1171     ma_job jobs[MA_RESOURCE_MANAGER_JOB_QUEUE_CAPACITY];
1172 } ma_job_queue;
1173 
1174 MA_API ma_result ma_job_queue_init(ma_uint32 flags, ma_job_queue* pQueue);
1175 MA_API ma_result ma_job_queue_uninit(ma_job_queue* pQueue);
1176 MA_API ma_result ma_job_queue_post(ma_job_queue* pQueue, const ma_job* pJob);
1177 MA_API ma_result ma_job_queue_next(ma_job_queue* pQueue, ma_job* pJob); /* Returns MA_CANCELLED if the next job is a quit job. */
1178 
1179 
1180 /* Maximum job thread count will be restricted to this, but this may be removed later and replaced with a heap allocation thereby removing any limitation. */
1181 #ifndef MA_RESOURCE_MANAGER_MAX_JOB_THREAD_COUNT
1182 #define MA_RESOURCE_MANAGER_MAX_JOB_THREAD_COUNT    64
1183 #endif
1184 
1185 #define MA_RESOURCE_MANAGER_FLAG_NON_BLOCKING       0x00000001  /* Indicates ma_resource_manager_next_job() should not block. Only valid with MA_RESOURCE_MANAGER_NO_JOB_THREAD. */
1186 
1187 typedef struct
1188 {
1189     const void* pData;
1190     ma_uint64 frameCount;           /* The total number of PCM frames making up the decoded data. */
1191     ma_uint64 decodedFrameCount;    /* For async decoding. Keeps track of how many frames are *currently* decoded. */
1192     ma_format format;
1193     ma_uint32 channels;
1194     ma_uint32 sampleRate;
1195 } ma_decoded_data;
1196 
1197 typedef struct
1198 {
1199     const void* pData;
1200     size_t sizeInBytes;
1201 } ma_encoded_data;
1202 
1203 typedef struct
1204 {
1205     ma_resource_manager_data_buffer_encoding type;
1206     union
1207     {
1208         ma_decoded_data decoded;
1209         ma_encoded_data encoded;
1210     };
1211 } ma_resource_manager_memory_buffer;
1212 
1213 struct ma_resource_manager_data_buffer_node
1214 {
1215     ma_uint32 hashedName32;                         /* The hashed name. This is the key. */
1216     ma_uint32 refCount;
1217     ma_result result;                               /* Result from asynchronous loading. When loading set to MA_BUSY. When fully loaded set to MA_SUCCESS. When deleting set to MA_UNAVAILABLE. */
1218     ma_uint32 executionCounter;                     /* For allocating execution orders for jobs. */
1219     ma_uint32 executionPointer;                     /* For managing the order of execution for asynchronous jobs relating to this object. Incremented as jobs complete processing. */
1220     ma_bool32 isDataOwnedByResourceManager;         /* Set to true when the underlying data buffer was allocated the resource manager. Set to false if it is owned by the application (via ma_resource_manager_register_*()). */
1221     ma_resource_manager_memory_buffer data;
1222     ma_resource_manager_data_buffer_node* pParent;
1223     ma_resource_manager_data_buffer_node* pChildLo;
1224     ma_resource_manager_data_buffer_node* pChildHi;
1225 };
1226 
1227 struct ma_resource_manager_data_buffer
1228 {
1229     ma_data_source_callbacks ds;                    /* Data source callbacks. A data buffer is a data source. */
1230     ma_resource_manager* pResourceManager;          /* A pointer to the resource manager that owns this buffer. */
1231     ma_uint32 flags;                                /* The flags that were passed used to initialize the buffer. */
1232     ma_resource_manager_data_buffer_node* pNode;    /* The data node. This is reference counted. */
1233     ma_uint64 cursorInPCMFrames;                    /* Only updated by the public API. Never written nor read from the job thread. */
1234     ma_uint64 lengthInPCMFrames;                    /* The total length of the sound in PCM frames. This is set at load time. */
1235     ma_bool32 seekToCursorOnNextRead;               /* On the next read we need to seek to the frame cursor. */
1236     ma_bool32 isLooping;
1237     ma_resource_manager_data_buffer_connector connectorType;
1238     union
1239     {
1240         ma_decoder decoder;
1241         ma_audio_buffer buffer;
1242     } connector;
1243 };
1244 
1245 struct ma_resource_manager_data_stream
1246 {
1247     ma_data_source_callbacks ds;            /* Data source callbacks. A data stream is a data source. */
1248     ma_resource_manager* pResourceManager;  /* A pointer to the resource manager that owns this data stream. */
1249     ma_uint32 flags;                        /* The flags that were passed used to initialize the stream. */
1250     ma_decoder decoder;                     /* Used for filling pages with data. This is only ever accessed by the job thread. The public API should never touch this. */
1251     ma_bool32 isDecoderInitialized;         /* Required for determining whether or not the decoder should be uninitialized in MA_JOB_FREE_DATA_STREAM. */
1252     ma_uint64 totalLengthInPCMFrames;       /* This is calculated when first loaded by the MA_JOB_LOAD_DATA_STREAM. */
1253     ma_uint32 relativeCursor;               /* The playback cursor, relative to the current page. Only ever accessed by the public API. Never accessed by the job thread. */
1254     ma_uint64 absoluteCursor;               /* The playback cursor, in absolute position starting from the start of the file. */
1255     ma_uint32 currentPageIndex;             /* Toggles between 0 and 1. Index 0 is the first half of pPageData. Index 1 is the second half. Only ever accessed by the public API. Never accessed by the job thread. */
1256     ma_uint32 executionCounter;             /* For allocating execution orders for jobs. */
1257     ma_uint32 executionPointer;             /* For managing the order of execution for asynchronous jobs relating to this object. Incremented as jobs complete processing. */
1258 
1259     /* Written by the public API, read by the job thread. */
1260     ma_bool32 isLooping;                    /* Whether or not the stream is looping. It's important to set the looping flag at the data stream level for smooth loop transitions. */
1261 
1262     /* Written by the job thread, read by the public API. */
1263     void* pPageData;                        /* Buffer containing the decoded data of each page. Allocated once at initialization time. */
1264     ma_uint32 pageFrameCount[2];            /* The number of valid PCM frames in each page. Used to determine the last valid frame. */
1265 
1266     /* Written and read by both the public API and the job thread. */
1267     ma_result result;                       /* Result from asynchronous loading. When loading set to MA_BUSY. When initialized set to MA_SUCCESS. When deleting set to MA_UNAVAILABLE. If an error occurs when loading, set to an error code. */
1268     ma_bool32 isDecoderAtEnd;               /* Whether or not the decoder has reached the end. */
1269     ma_bool32 isPageValid[2];               /* Booleans to indicate whether or not a page is valid. Set to false by the public API, set to true by the job thread. Set to false as the pages are consumed, true when they are filled. */
1270     ma_bool32 seekCounter;                  /* When 0, no seeking is being performed. When > 0, a seek is being performed and reading should be delayed with MA_BUSY. */
1271 };
1272 
1273 struct ma_resource_manager_data_source
1274 {
1275     union
1276     {
1277         ma_resource_manager_data_buffer buffer;
1278         ma_resource_manager_data_stream stream;
1279     };  /* Must be the first item because we need the first item to be the data source callbacks for the buffer or stream. */
1280 
1281     ma_uint32 flags;                /* The flags that were passed in to ma_resource_manager_data_source_init(). */
1282     ma_uint32 executionCounter;     /* For allocating execution orders for jobs. */
1283     ma_uint32 executionPointer;     /* For managing the order of execution for asynchronous jobs relating to this object. Incremented as jobs complete processing. */
1284 };
1285 
1286 typedef struct
1287 {
1288     ma_allocation_callbacks allocationCallbacks;
1289     ma_format decodedFormat;        /* The decoded format to use. Set to ma_format_unknown (default) to use the file's native format. */
1290     ma_uint32 decodedChannels;      /* The decoded channel count to use. Set to 0 (default) to use the file's native channel count. */
1291     ma_uint32 decodedSampleRate;    /* the decoded sample rate to use. Set to 0 (default) to use the file's native sample rate. */
1292     ma_uint32 jobThreadCount;       /* Set to 0 if you want to self-manage your job threads. Defaults to 1. */
1293     ma_uint32 flags;
1294     ma_vfs* pVFS;                   /* Can be NULL in which case defaults will be used. */
1295 } ma_resource_manager_config;
1296 
1297 MA_API ma_resource_manager_config ma_resource_manager_config_init();
1298 
1299 struct ma_resource_manager
1300 {
1301     ma_resource_manager_config config;
1302     ma_resource_manager_data_buffer_node* pRootDataBufferNode;      /* The root buffer in the binary tree. */
1303     ma_mutex dataBufferLock;                                        /* For synchronizing access to the data buffer binary tree. */
1304     ma_thread jobThreads[MA_RESOURCE_MANAGER_MAX_JOB_THREAD_COUNT]; /* The thread for executing jobs. Will probably turn this into an array. */
1305     ma_job_queue jobQueue;                                          /* Lock-free multi-consumer, multi-producer job queue for managing jobs for asynchronous decoding and streaming. */
1306     ma_default_vfs defaultVFS;                                      /* Only used if a custom VFS is not specified. */
1307 };
1308 
1309 /* Init. */
1310 MA_API ma_result ma_resource_manager_init(const ma_resource_manager_config* pConfig, ma_resource_manager* pResourceManager);
1311 MA_API void ma_resource_manager_uninit(ma_resource_manager* pResourceManager);
1312 
1313 /* Registration. */
1314 MA_API ma_result ma_resource_manager_register_decoded_data(ma_resource_manager* pResourceManager, const char* pName, const void* pData, ma_uint64 frameCount, ma_format format, ma_uint32 channels, ma_uint32 sampleRate);  /* Does not copy. Increments the reference count if already exists and returns MA_SUCCESS. */
1315 MA_API ma_result ma_resource_manager_register_encoded_data(ma_resource_manager* pResourceManager, const char* pName, const void* pData, size_t sizeInBytes);    /* Does not copy. Increments the reference count if already exists and returns MA_SUCCESS. */
1316 MA_API ma_result ma_resource_manager_unregister_data(ma_resource_manager* pResourceManager, const char* pName);
1317 
1318 /* Data Buffers. */
1319 MA_API ma_result ma_resource_manager_data_buffer_init(ma_resource_manager* pResourceManager, const char* pFilePath, ma_uint32 flags, ma_async_notification* pNotification, ma_resource_manager_data_buffer* pDataBuffer);
1320 MA_API ma_result ma_resource_manager_data_buffer_uninit(ma_resource_manager_data_buffer* pDataBuffer);
1321 MA_API ma_result ma_resource_manager_data_buffer_read_pcm_frames(ma_resource_manager_data_buffer* pDataBuffer, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead);
1322 MA_API ma_result ma_resource_manager_data_buffer_seek_to_pcm_frame(ma_resource_manager_data_buffer* pDataBuffer, ma_uint64 frameIndex);
1323 MA_API ma_result ma_resource_manager_data_buffer_map(ma_resource_manager_data_buffer* pDataBuffer, void** ppFramesOut, ma_uint64* pFrameCount);
1324 MA_API ma_result ma_resource_manager_data_buffer_unmap(ma_resource_manager_data_buffer* pDataBuffer, ma_uint64 frameCount);
1325 MA_API ma_result ma_resource_manager_data_buffer_get_data_format(ma_resource_manager_data_buffer* pDataBuffer, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate);
1326 MA_API ma_result ma_resource_manager_data_buffer_get_cursor_in_pcm_frames(ma_resource_manager_data_buffer* pDataBuffer, ma_uint64* pCursor);
1327 MA_API ma_result ma_resource_manager_data_buffer_get_length_in_pcm_frames(ma_resource_manager_data_buffer* pDataBuffer, ma_uint64* pLength);
1328 MA_API ma_result ma_resource_manager_data_buffer_result(const ma_resource_manager_data_buffer* pDataBuffer);
1329 MA_API ma_result ma_resource_manager_data_buffer_set_looping(ma_resource_manager_data_buffer* pDataBuffer, ma_bool32 isLooping);
1330 MA_API ma_result ma_resource_manager_data_buffer_get_looping(const ma_resource_manager_data_buffer* pDataBuffer, ma_bool32* pIsLooping);
1331 MA_API ma_result ma_resource_manager_data_buffer_get_available_frames(ma_resource_manager_data_buffer* pDataBuffer, ma_uint64* pAvailableFrames);
1332 
1333 /* Data Streams. */
1334 MA_API ma_result ma_resource_manager_data_stream_init(ma_resource_manager* pResourceManager, const char* pFilePath, ma_uint32 flags, ma_async_notification* pNotification, ma_resource_manager_data_stream* pDataStream);
1335 MA_API ma_result ma_resource_manager_data_stream_uninit(ma_resource_manager_data_stream* pDataStream);
1336 MA_API ma_result ma_resource_manager_data_stream_read_pcm_frames(ma_resource_manager_data_stream* pDataStream, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead);
1337 MA_API ma_result ma_resource_manager_data_stream_seek_to_pcm_frame(ma_resource_manager_data_stream* pDataStream, ma_uint64 frameIndex);
1338 MA_API ma_result ma_resource_manager_data_stream_map(ma_resource_manager_data_stream* pDataStream, void** ppFramesOut, ma_uint64* pFrameCount);
1339 MA_API ma_result ma_resource_manager_data_stream_unmap(ma_resource_manager_data_stream* pDataStream, ma_uint64 frameCount);
1340 MA_API ma_result ma_resource_manager_data_stream_get_data_format(ma_resource_manager_data_stream* pDataStream, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate);
1341 MA_API ma_result ma_resource_manager_data_stream_get_cursor_in_pcm_frames(ma_resource_manager_data_stream* pDataStream, ma_uint64* pCursor);
1342 MA_API ma_result ma_resource_manager_data_stream_get_length_in_pcm_frames(ma_resource_manager_data_stream* pDataStream, ma_uint64* pLength);
1343 MA_API ma_result ma_resource_manager_data_stream_result(const ma_resource_manager_data_stream* pDataStream);
1344 MA_API ma_result ma_resource_manager_data_stream_set_looping(ma_resource_manager_data_stream* pDataStream, ma_bool32 isLooping);
1345 MA_API ma_result ma_resource_manager_data_stream_get_looping(const ma_resource_manager_data_stream* pDataStream, ma_bool32* pIsLooping);
1346 MA_API ma_result ma_resource_manager_data_stream_get_available_frames(ma_resource_manager_data_stream* pDataStream, ma_uint64* pAvailableFrames);
1347 
1348 /* Data Sources. */
1349 MA_API ma_result ma_resource_manager_data_source_init(ma_resource_manager* pResourceManager, const char* pName, ma_uint32 flags, ma_async_notification* pNotification, ma_resource_manager_data_source* pDataSource);
1350 MA_API ma_result ma_resource_manager_data_source_uninit(ma_resource_manager_data_source* pDataSource);
1351 MA_API ma_result ma_resource_manager_data_source_read_pcm_frames(ma_resource_manager_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead);
1352 MA_API ma_result ma_resource_manager_data_source_seek_to_pcm_frame(ma_resource_manager_data_source* pDataSource, ma_uint64 frameIndex);
1353 MA_API ma_result ma_resource_manager_data_source_map(ma_resource_manager_data_source* pDataSource, void** ppFramesOut, ma_uint64* pFrameCount);
1354 MA_API ma_result ma_resource_manager_data_source_unmap(ma_resource_manager_data_source* pDataSource, ma_uint64 frameCount);
1355 MA_API ma_result ma_resource_manager_data_source_get_data_format(ma_resource_manager_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate);
1356 MA_API ma_result ma_resource_manager_data_source_get_cursor_in_pcm_frames(ma_resource_manager_data_source* pDataSource, ma_uint64* pCursor);
1357 MA_API ma_result ma_resource_manager_data_source_get_length_in_pcm_frames(ma_resource_manager_data_source* pDataSource, ma_uint64* pLength);
1358 MA_API ma_result ma_resource_manager_data_source_result(const ma_resource_manager_data_source* pDataSource);
1359 MA_API ma_result ma_resource_manager_data_source_set_looping(ma_resource_manager_data_source* pDataSource, ma_bool32 isLooping);
1360 MA_API ma_result ma_resource_manager_data_source_get_looping(const ma_resource_manager_data_source* pDataSource, ma_bool32* pIsLooping);
1361 MA_API ma_result ma_resource_manager_data_source_get_available_frames(ma_resource_manager_data_source* pDataSource, ma_uint64* pAvailableFrames);
1362 
1363 /* Job management. */
1364 MA_API ma_result ma_resource_manager_post_job(ma_resource_manager* pResourceManager, const ma_job* pJob);
1365 MA_API ma_result ma_resource_manager_post_job_quit(ma_resource_manager* pResourceManager);  /* Helper for posting a quit job. */
1366 MA_API ma_result ma_resource_manager_next_job(ma_resource_manager* pResourceManager, ma_job* pJob);
1367 MA_API ma_result ma_resource_manager_process_job(ma_resource_manager* pResourceManager, ma_job* pJob);
1368 MA_API ma_result ma_resource_manager_process_next_job(ma_resource_manager* pResourceManager);   /* Returns MA_CANCELLED if a MA_JOB_QUIT job is found. In non-blocking mode, returns MA_NO_DATA_AVAILABLE if no jobs are available. */
1369 
1370 
1371 /*
1372 Engine
1373 ======
1374 The `ma_engine` API is a high-level API for audio playback. Internally it contains sounds (`ma_sound`) with resources managed via a resource manager
1375 (`ma_resource_manager`).
1376 
1377 Within the world there is the concept of a "listener". Each `ma_engine` instances has a single listener, but you can instantiate multiple `ma_engine` instances
1378 if you need more than one listener. In this case you will want to share a resource manager which you can do by initializing one manually and passing it into
1379 `ma_engine_config`. Using this method will require your application to manage groups and sounds on a per `ma_engine` basis.
1380 */
1381 typedef struct ma_engine      ma_engine;
1382 typedef struct ma_sound       ma_sound;
1383 typedef struct ma_sound_group ma_sound_group;
1384 typedef struct ma_listener    ma_listener;
1385 
1386 typedef struct
1387 {
1388     float x;
1389     float y;
1390     float z;
1391 } ma_vec3;
1392 
ma_vec3f(float x,float y,float z)1393 static MA_INLINE ma_vec3 ma_vec3f(float x, float y, float z)
1394 {
1395     ma_vec3 v;
1396     v.x = x;
1397     v.y = y;
1398     v.z = z;
1399     return v;
1400 }
1401 
1402 
1403 typedef struct
1404 {
1405     float x;
1406     float y;
1407     float z;
1408     float w;
1409 } ma_quat;
1410 
ma_quatf(float x,float y,float z,float w)1411 static MA_INLINE ma_quat ma_quatf(float x, float y, float z, float w)
1412 {
1413     ma_quat q;
1414     q.x = x;
1415     q.y = y;
1416     q.z = z;
1417     q.w = w;
1418     return q;
1419 }
1420 
1421 
1422 /* Stereo panner. */
1423 typedef enum
1424 {
1425     ma_pan_mode_balance = 0,    /* Does not blend one side with the other. Technically just a balance. Compatible with other popular audio engines and therefore the default. */
1426     ma_pan_mode_pan             /* A true pan. The sound from one side will "move" to the other side and blend with it. */
1427 } ma_pan_mode;
1428 
1429 typedef struct
1430 {
1431     ma_format format;
1432     ma_uint32 channels;
1433     ma_pan_mode mode;
1434     float pan;
1435 } ma_panner_config;
1436 
1437 MA_API ma_panner_config ma_panner_config_init(ma_format format, ma_uint32 channels);
1438 
1439 
1440 typedef struct
1441 {
1442     ma_effect_base effect;
1443     ma_format format;
1444     ma_uint32 channels;
1445     ma_pan_mode mode;
1446     float pan;  /* -1..1 where 0 is no pan, -1 is left side, +1 is right side. Defaults to 0. */
1447 } ma_panner;
1448 
1449 MA_API ma_result ma_panner_init(const ma_panner_config* pConfig, ma_panner* pPanner);
1450 MA_API ma_result ma_panner_process_pcm_frames(ma_panner* pPanner, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount);
1451 MA_API ma_result ma_panner_set_mode(ma_panner* pPanner, ma_pan_mode mode);
1452 MA_API ma_result ma_panner_set_pan(ma_panner* pPanner, float pan);
1453 
1454 
1455 
1456 /* Spatializer. */
1457 typedef struct
1458 {
1459     ma_engine* pEngine;
1460     ma_format format;
1461     ma_uint32 channels;
1462     ma_vec3 position;
1463     ma_quat rotation;
1464 } ma_spatializer_config;
1465 
1466 MA_API ma_spatializer_config ma_spatializer_config_init(ma_engine* pEngine, ma_format format, ma_uint32 channels);
1467 
1468 
1469 typedef struct
1470 {
1471     ma_effect_base effect;
1472     ma_engine* pEngine;             /* For accessing global, per-engine data such as the listener position and environmental information. */
1473     ma_format format;
1474     ma_uint32 channels;
1475     ma_vec3 position;
1476     ma_quat rotation;
1477 } ma_spatializer;
1478 
1479 MA_API ma_result ma_spatializer_init(const ma_spatializer_config* pConfig, ma_spatializer* pSpatializer);
1480 MA_API ma_result ma_spatializer_process_pcm_frames(ma_spatializer* pSpatializer, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount);
1481 MA_API ma_result ma_spatializer_set_position(ma_spatializer* pSpatializer, ma_vec3 position);
1482 MA_API ma_result ma_spatializer_set_rotation(ma_spatializer* pSpatializer, ma_quat rotation);
1483 
1484 
1485 
1486 /* Dual Fader. Used for separating fading in and fading out. */
1487 typedef struct
1488 {
1489     ma_format format;
1490     ma_uint32 channels;
1491     ma_uint32 sampleRate;
1492     struct
1493     {
1494         float volumeBeg;
1495         float volumeEnd;
1496         ma_uint64 timeInFramesBeg;
1497         ma_uint64 timeInFramesEnd;
1498         ma_bool32 autoReset;        /* Controls whether or not the fade point should automatically reset once the end of the fade point has been reached. */
1499     } state[2];
1500 } ma_dual_fader_config;
1501 
1502 MA_API ma_dual_fader_config ma_dual_fader_config_init(ma_format format, ma_uint32 channels, ma_uint32 sampleRate);
1503 
1504 typedef struct
1505 {
1506     ma_effect_base effect;
1507     ma_dual_fader_config config;
1508     ma_uint64 timeInFramesCur;  /* The current time in frames. Incremented by ma_fader_process_pcm_frames(). */
1509 } ma_dual_fader;
1510 
1511 MA_API ma_result ma_dual_fader_init(const ma_dual_fader_config* pConfig, ma_dual_fader* pFader);
1512 MA_API ma_result ma_dual_fader_process_pcm_frames(ma_dual_fader* pFader, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount);
1513 MA_API ma_result ma_dual_fader_get_data_format(const ma_dual_fader* pFader, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate);
1514 MA_API ma_result ma_dual_fader_set_fade(ma_dual_fader* pFader, ma_uint32 index, float volumeBeg, float volumeEnd, ma_uint64 timeInFramesBeg, ma_uint64 timeInFramesEnd);
1515 MA_API ma_result ma_dual_fader_set_time(ma_dual_fader* pFader, ma_uint64 currentTimeInFrames);
1516 MA_API ma_result ma_dual_fader_get_time(const ma_dual_fader* pFader, ma_uint64* pCurrentTimeInFrames);
1517 MA_API ma_bool32 ma_dual_fader_is_time_past_fade(const ma_dual_fader* pFader, ma_uint32 index);
1518 MA_API ma_bool32 ma_dual_fader_is_time_past_both_fades(const ma_dual_fader* pFader);
1519 MA_API ma_bool32 ma_dual_fader_is_in_fade(const ma_dual_fader* pFader, ma_uint32 index);
1520 MA_API ma_result ma_dual_fader_reset_fade(ma_dual_fader* pFader, ma_uint32 index);      /* Essentially disables fading for one of the sub-fades. To enable again, call ma_dual_fader_set_fade(). */
1521 MA_API ma_result ma_dual_fader_set_auto_reset(ma_dual_fader* pFader, ma_uint32 index, ma_bool32 autoReset);
1522 
1523 
1524 /* All of the proprties supported by the engine are handled via an effect. */
1525 typedef struct
1526 {
1527     ma_effect_base baseEffect;
1528     ma_engine* pEngine;             /* For accessing global, per-engine data such as the listener position and environmental information. */
1529     ma_effect* pPreEffect;          /* The application-defined effect that will be applied before spationalization, etc. */
1530     ma_panner panner;
1531     ma_spatializer spatializer;
1532     ma_dual_fader fader;            /* For fading in and out when starting and stopping. */
1533     float pitch;
1534     float oldPitch;                 /* For determining whether or not the resampler needs to be updated to reflect the new pitch. The resampler will be updated on the mixing thread. */
1535     ma_data_converter converter;    /* For pitch shift. May change this to ma_linear_resampler later. */
1536     ma_uint64 timeInFrames;         /* The running time in input frames. */
1537     ma_bool32 isSpatial;            /* Set the false by default. When set to false, will not have spatialisation applied. */
1538 } ma_engine_effect;
1539 
1540 struct ma_sound
1541 {
1542     ma_engine* pEngine;                         /* A pointer to the object that owns this sound. */
1543     ma_data_source* pDataSource;
1544     ma_sound_group* pGroup;                     /* The group the sound is attached to. */
1545     ma_sound* pPrevSoundInGroup;
1546     ma_sound* pNextSoundInGroup;
1547     ma_engine_effect effect;                    /* The effect containing all of the information for spatialization, pitching, etc. */
1548     float volume;
1549     ma_uint64 seekTarget;                       /* The PCM frame index to seek to in the mixing thread. Set to (~(ma_uint64)0) to not perform any seeking. */
1550     ma_uint64 runningTimeInEngineFrames;        /* The amount of time the sound has been running in engine frames, including start delays. */
1551     ma_uint64 startDelayInEngineFrames;         /* In the engine's sample rate. */
1552     ma_uint64 stopDelayInEngineFrames;          /* In the engine's sample rate. */
1553     ma_uint64 stopDelayInEngineFramesRemaining; /* The number of frames relative to the engine's clock before the sound is stopped. */
1554     ma_bool32 isPlaying;                        /* False by default. Sounds need to be explicitly started with ma_sound_start() and stopped with ma_sound_stop(). */
1555     ma_bool32 isMixing;
1556     ma_bool32 isLooping;                        /* False by default. */
1557     ma_bool32 atEnd;
1558     ma_bool32 ownsDataSource;
1559     ma_bool32 _isInternal;                      /* A marker to indicate the sound is managed entirely by the engine. This will be set to true when the sound is created internally by ma_engine_play_sound(). */
1560     ma_resource_manager_data_source resourceManagerDataSource;
1561 };
1562 
1563 struct ma_sound_group
1564 {
1565     ma_engine* pEngine;                         /* A pointer to the engine that owns this sound group. */
1566     ma_sound_group* pParent;
1567     ma_sound_group* pFirstChild;
1568     ma_sound_group* pPrevSibling;
1569     ma_sound_group* pNextSibling;
1570     ma_sound* pFirstSoundInGroup;
1571     ma_engine_effect effect;                    /* The main effect for panning, etc. This is set on the mixer at initialisation time. */
1572     ma_mixer mixer;
1573     ma_mutex lock;                              /* Only used by ma_sound_init_*() and ma_sound_uninit(). Not used in the mixing thread. */
1574     ma_uint64 runningTimeInEngineFrames;        /* The amount of time the sound has been running in engine frames, including start delays. */
1575     ma_uint64 startDelayInEngineFrames;
1576     ma_uint64 stopDelayInEngineFrames;          /* In the engine's sample rate. */
1577     ma_uint64 stopDelayInEngineFramesRemaining; /* The number of frames relative to the engine's clock before the sound is stopped. */
1578     ma_bool32 isPlaying;                        /* True by default. Sound groups can be stopped with ma_sound_stop() and resumed with ma_sound_start(). Also affects children. */
1579 };
1580 
1581 struct ma_listener
1582 {
1583     ma_vec3 position;
1584     ma_quat rotation;
1585 };
1586 
1587 
1588 typedef struct
1589 {
1590     ma_resource_manager* pResourceManager;  /* Can be null in which case a resource manager will be created for you. */
1591     ma_context* pContext;
1592     ma_device* pDevice;                     /* If set, the caller is responsible for calling ma_engine_data_callback() in the device's data callback. */
1593     ma_format format;                       /* The format to use when mixing and spatializing. When set to 0 will use the native format of the device. */
1594     ma_uint32 channels;                     /* The number of channels to use when mixing and spatializing. When set to 0, will use the native channel count of the device. */
1595     ma_uint32 sampleRate;                   /* The sample rate. When set to 0 will use the native channel count of the device. */
1596     ma_uint32 periodSizeInFrames;           /* If set to something other than 0, updates will always be exactly this size. The underlying device may be a different size, but from the perspective of the mixer that won't matter.*/
1597     ma_uint32 periodSizeInMilliseconds;     /* Used if periodSizeInFrames is unset. */
1598     ma_device_id* pPlaybackDeviceID;        /* The ID of the playback device to use with the default listener. */
1599     ma_allocation_callbacks allocationCallbacks;
1600     ma_bool32 noAutoStart;                  /* When set to true, requires an explicit call to ma_engine_start(). This is false by default, meaning the engine will be started automatically in ma_engine_init(). */
1601     ma_vfs* pResourceManagerVFS;            /* A pointer to a pre-allocated VFS object to use with the resource manager. This is ignored if pResourceManager is not NULL. */
1602 } ma_engine_config;
1603 
1604 MA_API ma_engine_config ma_engine_config_init_default(void);
1605 
1606 
1607 struct ma_engine
1608 {
1609     ma_resource_manager* pResourceManager;
1610     ma_device* pDevice;                     /* Optionally set via the config, otherwise allocated by the engine in ma_engine_init(). */
1611     ma_pcm_rb fixedRB;                      /* The intermediary ring buffer for helping with fixed sized updates. */
1612     ma_listener listener;
1613     ma_sound_group masterSoundGroup;        /* Sounds are associated with this group by default. */
1614     ma_format format;
1615     ma_uint32 channels;
1616     ma_uint32 sampleRate;
1617     ma_uint32 periodSizeInFrames;
1618     ma_uint32 periodSizeInMilliseconds;
1619     ma_allocation_callbacks allocationCallbacks;
1620     ma_bool32 ownsResourceManager : 1;
1621     ma_bool32 ownsDevice          : 1;
1622 };
1623 
1624 MA_API ma_result ma_engine_init(const ma_engine_config* pConfig, ma_engine* pEngine);
1625 MA_API void ma_engine_uninit(ma_engine* pEngine);
1626 MA_API void ma_engine_data_callback(ma_engine* pEngine, void* pOutput, const void* pInput, ma_uint32 frameCount);
1627 
1628 MA_API ma_result ma_engine_start(ma_engine* pEngine);
1629 MA_API ma_result ma_engine_stop(ma_engine* pEngine);
1630 MA_API ma_result ma_engine_set_volume(ma_engine* pEngine, float volume);
1631 MA_API ma_result ma_engine_set_gain_db(ma_engine* pEngine, float gainDB);
1632 
1633 MA_API ma_sound_group* ma_engine_get_master_sound_group(ma_engine* pEngine);
1634 
1635 MA_API ma_result ma_engine_listener_set_position(ma_engine* pEngine, ma_vec3 position);
1636 MA_API ma_result ma_engine_listener_set_rotation(ma_engine* pEngine, ma_quat rotation);
1637 
1638 MA_API ma_result ma_engine_play_sound(ma_engine* pEngine, const char* pFilePath, ma_sound_group* pGroup);   /* Fire and forget. */
1639 
1640 
1641 #ifndef MA_NO_RESOURCE_MANAGER
1642 MA_API ma_result ma_sound_init_from_file(ma_engine* pEngine, const char* pFilePath, ma_uint32 flags, ma_async_notification* pNotification, ma_sound_group* pGroup, ma_sound* pSound);
1643 #endif
1644 MA_API ma_result ma_sound_init_from_data_source(ma_engine* pEngine, ma_data_source* pDataSource, ma_uint32 flags, ma_sound_group* pGroup, ma_sound* pSound);
1645 MA_API void ma_sound_uninit(ma_sound* pSound);
1646 MA_API ma_result ma_sound_start(ma_sound* pSound);
1647 MA_API ma_result ma_sound_stop(ma_sound* pSound);
1648 MA_API ma_result ma_sound_set_volume(ma_sound* pSound, float volume);
1649 MA_API ma_result ma_sound_set_gain_db(ma_sound* pSound, float gainDB);
1650 MA_API ma_result ma_sound_set_effect(ma_sound* pSound, ma_effect* pEffect);
1651 MA_API ma_result ma_sound_set_pan(ma_sound* pSound, float pan);
1652 MA_API ma_result ma_sound_set_pitch(ma_sound* pSound, float pitch);
1653 MA_API ma_result ma_sound_set_position(ma_sound* pSound, ma_vec3 position);
1654 MA_API ma_result ma_sound_set_rotation(ma_sound* pSound, ma_quat rotation);
1655 MA_API ma_result ma_sound_set_looping(ma_sound* pSound, ma_bool32 isLooping);
1656 MA_API ma_result ma_sound_set_fade_point_in_frames(ma_sound* pSound, ma_uint32 fadePointIndex, float volumeBeg, float volumeEnd, ma_uint64 timeInFramesBeg, ma_uint64 timeInFramesEnd);
1657 MA_API ma_result ma_sound_set_fade_point_in_milliseconds(ma_sound* pSound, ma_uint32 fadePointIndex, float volumeBeg, float volumeEnd, ma_uint64 timeInMillisecondsBeg, ma_uint64 timeInMillisecondsEnd);
1658 MA_API ma_result ma_sound_set_fade_point_auto_reset(ma_sound* pSound, ma_uint32 fadePointIndex, ma_bool32 autoReset);
1659 MA_API ma_result ma_sound_set_start_delay(ma_sound* pSound, ma_uint64 delayInMilliseconds);
1660 MA_API ma_result ma_sound_set_stop_delay(ma_sound* pSound, ma_uint64 delayInMilliseconds);
1661 MA_API ma_bool32 ma_sound_is_playing(const ma_sound* pSound);
1662 MA_API ma_bool32 ma_sound_at_end(const ma_sound* pSound);
1663 MA_API ma_result ma_sound_get_time_in_frames(const ma_sound* pSound, ma_uint64* pTimeInFrames);
1664 MA_API ma_result ma_sound_seek_to_pcm_frame(ma_sound* pSound, ma_uint64 frameIndex); /* Just a wrapper around ma_data_source_seek_to_pcm_frame(). */
1665 MA_API ma_result ma_sound_get_data_format(ma_sound* pSound, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate);
1666 MA_API ma_result ma_sound_get_cursor_in_pcm_frames(ma_sound* pSound, ma_uint64* pCursor);
1667 MA_API ma_result ma_sound_get_length_in_pcm_frames(ma_sound* pSound, ma_uint64* pLength);
1668 
1669 MA_API ma_result ma_sound_group_init(ma_engine* pEngine, ma_sound_group* pParentGroup, ma_sound_group* pGroup);  /* Parent must be set at initialization time and cannot be changed. Not thread-safe. */
1670 MA_API void ma_sound_group_uninit(ma_sound_group* pGroup);   /* Not thread-safe. */
1671 MA_API ma_result ma_sound_group_start(ma_sound_group* pGroup);
1672 MA_API ma_result ma_sound_group_stop(ma_sound_group* pGroup);
1673 MA_API ma_result ma_sound_group_set_volume(ma_sound_group* pGroup, float volume);
1674 MA_API ma_result ma_sound_group_set_gain_db(ma_sound_group* pGroup, float gainDB);
1675 MA_API ma_result ma_sound_group_set_effect(ma_sound_group* pGroup, ma_effect* pEffect);
1676 MA_API ma_result ma_sound_group_set_pan(ma_sound_group* pGroup, float pan);
1677 MA_API ma_result ma_sound_group_set_pitch(ma_sound_group* pGroup, float pitch);
1678 MA_API ma_result ma_sound_group_set_fade_point_in_frames(ma_sound_group* pGroup, ma_uint32 fadePointIndex, float volumeBeg, float volumeEnd, ma_uint64 timeInFramesBeg, ma_uint64 timeInFramesEnd);
1679 MA_API ma_result ma_sound_group_set_fade_point_in_milliseconds(ma_sound_group* pGroup, ma_uint32 fadePointIndex, float volumeBeg, float volumeEnd, ma_uint64 timeInMillisecondsBeg, ma_uint64 timeInMillisecondsEnd);
1680 MA_API ma_result ma_sound_group_set_fade_point_auto_reset(ma_sound_group* pGroup, ma_uint32 fadePointIndex, ma_bool32 autoReset);
1681 MA_API ma_result ma_sound_group_set_start_delay(ma_sound_group* pGroup, ma_uint64 delayInMilliseconds);
1682 MA_API ma_result ma_sound_group_set_stop_delay(ma_sound_group* pGroup, ma_uint64 delayInMilliseconds);
1683 MA_API ma_bool32 ma_sound_group_is_playing(const ma_sound_group* pGroup);
1684 MA_API ma_result ma_sound_group_get_time_in_frames(const ma_sound_group* pGroup, ma_uint64* pTimeInFrames);
1685 
1686 #ifdef __cplusplus
1687 }
1688 #endif
1689 #endif  /* miniaudio_engine_h */
1690 
1691 
1692 #if defined(MA_IMPLEMENTATION) || defined(MINIAUDIO_IMPLEMENTATION)
1693 
1694 #ifndef MA_RESOURCE_MANAGER_PAGE_SIZE_IN_MILLISECONDS
1695 #define MA_RESOURCE_MANAGER_PAGE_SIZE_IN_MILLISECONDS   1000
1696 #endif
1697 
1698 
ma_ffs_32(ma_uint32 x)1699 static ma_uint32 ma_ffs_32(ma_uint32 x)
1700 {
1701     ma_uint32 i;
1702 
1703     /* Just a naive implementation just to get things working for now. Will optimize this later. */
1704     for (i = 0; i < 32; i += 1) {
1705         if ((x & (1 << i)) != 0) {
1706             return i;
1707         }
1708     }
1709 
1710     return i;
1711 }
1712 
1713 
1714 
ma_convert_pcm_frames_format_and_channels(void * pDst,ma_format formatOut,ma_uint32 channelsOut,const void * pSrc,ma_format formatIn,ma_uint32 channelsIn,ma_uint64 frameCount,ma_dither_mode ditherMode)1715 static void ma_convert_pcm_frames_format_and_channels(void* pDst, ma_format formatOut, ma_uint32 channelsOut, const void* pSrc, ma_format formatIn, ma_uint32 channelsIn, ma_uint64 frameCount, ma_dither_mode ditherMode)
1716 {
1717     MA_ASSERT(pDst != NULL);
1718     MA_ASSERT(pSrc != NULL);
1719 
1720     if (channelsOut == channelsIn) {
1721         /* Only format conversion required. */
1722         if (formatOut == formatIn) {
1723             /* No data conversion required at all - just copy. */
1724             if (pDst == pSrc) {
1725                 /* No-op. */
1726             } else {
1727                 ma_copy_pcm_frames(pDst, pSrc, frameCount, formatOut, channelsOut);
1728             }
1729         } else {
1730             /* Simple format conversion. */
1731             ma_convert_pcm_frames_format(pDst, formatOut, pSrc, formatIn, frameCount, channelsOut, ditherMode);
1732         }
1733     } else {
1734         /* Getting here means we require a channel converter. We do channel conversion in the input format, and then format convert as a post process step if required. */
1735         ma_result result;
1736         ma_channel_converter_config channelConverterConfig;
1737         ma_channel_converter channelConverter;
1738 
1739         channelConverterConfig = ma_channel_converter_config_init(formatIn, channelsIn, NULL, channelsOut, NULL, ma_channel_mix_mode_default);
1740         result = ma_channel_converter_init(&channelConverterConfig, &channelConverter);
1741         if (result != MA_SUCCESS) {
1742             return; /* Failed to initialize channel converter for some reason. Should never fail. */
1743         }
1744 
1745         /* If we don't require any format conversion we can output straight into the output buffer. Otherwise we need to use an intermediary. */
1746         if (formatOut == formatIn) {
1747             /* No format conversion required. Output straight to the output buffer. */
1748             ma_channel_converter_process_pcm_frames(&channelConverter, pDst, pSrc, frameCount);
1749         } else {
1750             /* Format conversion required. We need to use an intermediary buffer. */
1751             ma_uint8  buffer[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; /* formatIn, channelsOut */
1752             ma_uint32 bufferCap = sizeof(buffer) / ma_get_bytes_per_frame(formatIn, channelsOut);
1753             ma_uint64 totalFramesProcessed = 0;
1754 
1755             while (totalFramesProcessed < frameCount) {
1756                 ma_uint64 framesToProcess = frameCount - totalFramesProcessed;
1757                 if (framesToProcess > bufferCap) {
1758                     framesToProcess = bufferCap;
1759                 }
1760 
1761                 result = ma_channel_converter_process_pcm_frames(&channelConverter, buffer, ma_offset_ptr(pSrc, totalFramesProcessed * ma_get_bytes_per_frame(formatIn, channelsIn)), framesToProcess);
1762                 if (result != MA_SUCCESS) {
1763                     break;
1764                 }
1765 
1766                 /* Channel conversion is done, now format conversion straight into the output buffer. */
1767                 ma_convert_pcm_frames_format(ma_offset_ptr(pDst, totalFramesProcessed * ma_get_bytes_per_frame(formatOut, channelsOut)), formatOut, buffer, formatIn, framesToProcess, channelsOut, ditherMode);
1768 
1769                 totalFramesProcessed += framesToProcess;
1770             }
1771         }
1772     }
1773 }
1774 
1775 
ma_effect_get_root(ma_effect * pEffect)1776 static ma_effect_base* ma_effect_get_root(ma_effect* pEffect)
1777 {
1778     ma_effect_base* pRootEffect;
1779 
1780     if (pEffect == NULL) {
1781         return NULL;
1782     }
1783 
1784     pRootEffect = (ma_effect_base*)pEffect;
1785     for (;;) {
1786         if (pRootEffect->pPrev == NULL) {
1787             return pRootEffect;
1788         } else {
1789             pRootEffect = pRootEffect->pPrev;
1790         }
1791     }
1792 
1793     /* Should never hit this. */
1794     /*return NULL;*/
1795 }
1796 
ma_effect_process_pcm_frames(ma_effect * pEffect,const void * pFramesIn,ma_uint64 * pFrameCountIn,void * pFramesOut,ma_uint64 * pFrameCountOut)1797 MA_API ma_result ma_effect_process_pcm_frames(ma_effect* pEffect, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut)
1798 {
1799     ma_result result = MA_SUCCESS;
1800     ma_effect_base* pBase = (ma_effect_base*)pEffect;
1801     ma_effect_base* pFirstEffect;
1802     ma_effect_base* pRunningEffect;
1803     ma_uint32 iTempBuffer = 0;
1804     ma_uint8  tempFrames[2][MA_DATA_CONVERTER_STACK_BUFFER_SIZE];
1805     ma_uint64 tempFrameCount[2];
1806     ma_uint64 frameCountIn;
1807     ma_uint64 frameCountInConsumed;
1808     ma_uint64 frameCountOut;
1809     ma_uint64 frameCountOutConsumed;
1810 
1811     if (pEffect == NULL || pBase->onProcessPCMFrames == NULL) {
1812         return MA_INVALID_ARGS;
1813     }
1814 
1815     /* We need to start at the top and work our way down. */
1816     pFirstEffect = (ma_effect_base*)pEffect;
1817     while (pFirstEffect->pPrev != NULL) {
1818         pFirstEffect = pFirstEffect->pPrev;
1819     }
1820 
1821     pRunningEffect = pFirstEffect;
1822 
1823     /* Optimized path if this is the only effect in the chain. */
1824     if (pFirstEffect == pBase) {
1825         return pBase->onProcessPCMFrames(pRunningEffect, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut);
1826     }
1827 
1828     frameCountIn          = *pFrameCountIn;
1829     frameCountInConsumed  = 0;
1830     frameCountOut         = *pFrameCountOut;
1831     frameCountOutConsumed = 0;
1832 
1833     /*
1834     We need to output into a temp buffer which will become our new input buffer. We will allocate this on the stack which means we will need to do
1835     several iterations to process as much data as possible available in the input buffer, or can fit in the output buffer.
1836     */
1837     while (frameCountIn < frameCountInConsumed && frameCountOut < frameCountOutConsumed) {
1838         for (;;) {
1839             MA_ASSERT(pRunningEffect != NULL);
1840 
1841             const void* pRunningFramesIn;
1842             /* */ void* pRunningFramesOut;
1843             ma_uint64 frameCountInThisIteration;
1844             ma_uint64 frameCountOutThisIteration;
1845             ma_format runningEffectFormatIn;
1846             ma_uint32 runningEffectChannelsIn;
1847 
1848             if (pRunningEffect->onGetInputDataFormat == NULL) {
1849                 result = MA_INVALID_ARGS;   /* Don't have a way to retrieve the input format. */
1850                 break;
1851             }
1852 
1853             result = pRunningEffect->onGetInputDataFormat(pRunningEffect, &runningEffectFormatIn, &runningEffectChannelsIn, NULL);
1854             if (result != MA_SUCCESS) {
1855                 return result;  /* Failed to retrieve the input data format. Abort. */
1856             }
1857 
1858 
1859             if (pRunningEffect == pFirstEffect) {
1860                 /* It's the first effect which means we need to read directly from the input buffer. */
1861                 pRunningFramesIn = ma_offset_ptr(pFramesIn, frameCountInConsumed * ma_get_bytes_per_frame(runningEffectFormatIn, runningEffectChannelsIn));
1862                 frameCountInThisIteration = frameCountIn - frameCountInConsumed;
1863             } else {
1864                 /* It's not the first item. We need to read from a temp buffer. */
1865                 pRunningFramesIn = tempFrames[iTempBuffer];
1866                 frameCountInThisIteration = tempFrameCount[iTempBuffer];
1867                 iTempBuffer = (iTempBuffer + 1) & 0x01; /* Toggle between 0 and 1. */
1868             }
1869 
1870             if (pRunningEffect == pEffect) {
1871                 /* It's the last item in the chain so we need to output directly to the output buffer. */
1872                 pRunningFramesOut = ma_offset_ptr(pFramesOut, frameCountOutConsumed * ma_get_bytes_per_frame(runningEffectFormatIn, runningEffectChannelsIn));
1873                 frameCountOutThisIteration = frameCountOut - frameCountOutConsumed;
1874             } else {
1875                 /* It's not the last item in the chain. We need to output to a temp buffer so that it becomes our input buffer in the next iteration. */
1876                 pRunningFramesOut = tempFrames[iTempBuffer];
1877                 frameCountOutThisIteration = sizeof(tempFrames[iTempBuffer]) / ma_get_bytes_per_frame(runningEffectFormatIn, runningEffectChannelsIn);
1878             }
1879 
1880             result = pRunningEffect->onProcessPCMFrames(pRunningEffect, pRunningFramesIn, &frameCountInThisIteration, pRunningFramesOut, &frameCountOutThisIteration);
1881             if (result != MA_SUCCESS) {
1882                 break;
1883             }
1884 
1885             /*
1886             We need to increment our input and output frame counters. This depends on whether or not we read directly from the input buffer or wrote directly
1887             to the output buffer.
1888             */
1889             if (pRunningEffect == pFirstEffect) {
1890                 frameCountInConsumed += frameCountInThisIteration;
1891             }
1892             if (pRunningEffect == pBase) {
1893                 frameCountOutConsumed += frameCountOutThisIteration;
1894             }
1895 
1896             tempFrameCount[iTempBuffer] = frameCountOutThisIteration;
1897 
1898 
1899             /* If we just processed the input effect we need to abort. */
1900             if (pRunningEffect == pBase) {
1901                 break;
1902             }
1903 
1904             pRunningEffect = pRunningEffect->pNext;
1905         }
1906     }
1907 
1908 
1909     if (pFrameCountIn != NULL) {
1910         *pFrameCountIn = frameCountInConsumed;
1911     }
1912     if (pFrameCountOut != NULL) {
1913         *pFrameCountOut = frameCountOutConsumed;
1914     }
1915 
1916     return result;
1917 }
1918 
ma_effect_process_pcm_frames_ex(ma_effect * pEffect,const void * pFramesIn,ma_uint64 * pFrameCountIn,void * pFramesOut,ma_uint64 * pFrameCountOut,ma_format formatIn,ma_uint32 channelsIn,ma_format formatOut,ma_uint32 channelsOut)1919 MA_API ma_result ma_effect_process_pcm_frames_ex(ma_effect* pEffect, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut, ma_format formatIn, ma_uint32 channelsIn, ma_format formatOut, ma_uint32 channelsOut)
1920 {
1921     ma_result result;
1922     ma_format effectFormatIn;
1923     ma_uint32 effectChannelsIn;
1924     ma_format effectFormatOut;
1925     ma_uint32 effectChannelsOut;
1926 
1927     /* First thing to retrieve the effect's input and output format to determine if conversion is necessary. */
1928     result = ma_effect_get_input_data_format(pEffect, &effectFormatIn, &effectChannelsIn, NULL);
1929     if (result != MA_SUCCESS) {
1930         return result;
1931     }
1932 
1933     result = ma_effect_get_output_data_format(pEffect, &effectFormatOut, &effectChannelsOut, NULL);
1934     if (result != MA_SUCCESS) {
1935         return result;
1936     }
1937 
1938     if (effectFormatIn == formatIn && effectChannelsIn == channelsIn && effectFormatOut == formatOut && effectChannelsOut == channelsOut) {
1939         /* Fast path. No need for any data conversion. */
1940         return ma_effect_process_pcm_frames(pEffect, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut);
1941     } else {
1942         /* Slow path. Getting here means we need to do pre- and/or post-data conversion. */
1943         ma_uint8  effectInBuffer[MA_DATA_CONVERTER_STACK_BUFFER_SIZE];
1944         ma_uint32 effectInBufferCap = sizeof(effectInBuffer) / ma_get_bytes_per_frame(effectFormatIn, effectChannelsIn);
1945         ma_uint8  effectOutBuffer[MA_DATA_CONVERTER_STACK_BUFFER_SIZE];
1946         ma_uint32 effectOutBufferCap = sizeof(effectOutBuffer) / ma_get_bytes_per_frame(effectFormatOut, effectChannelsOut);
1947         ma_uint64 totalFramesProcessedIn  = 0;
1948         ma_uint64 totalFramesProcessedOut = 0;
1949         ma_uint64 frameCountIn  = *pFrameCountIn;
1950         ma_uint64 frameCountOut = *pFrameCountOut;
1951 
1952         while (totalFramesProcessedIn < frameCountIn && totalFramesProcessedOut < frameCountOut) {
1953             ma_uint64 framesToProcessThisIterationIn;
1954             ma_uint64 framesToProcessThisIterationOut;
1955             const void* pRunningFramesIn  = ma_offset_ptr(pFramesIn,  ma_get_bytes_per_frame(formatIn,  channelsIn ));
1956             /* */ void* pRunningFramesOut = ma_offset_ptr(pFramesOut, ma_get_bytes_per_frame(formatOut, channelsOut));
1957 
1958             framesToProcessThisIterationOut = frameCountOut - totalFramesProcessedOut;
1959             if (framesToProcessThisIterationOut > effectOutBufferCap) {
1960                 framesToProcessThisIterationOut = effectOutBufferCap;
1961             }
1962 
1963             framesToProcessThisIterationIn = ma_effect_get_required_input_frame_count(pEffect, framesToProcessThisIterationOut);
1964             if (framesToProcessThisIterationIn > (frameCountIn - totalFramesProcessedIn)) {
1965                 framesToProcessThisIterationIn = (frameCountIn - totalFramesProcessedIn);
1966             }
1967             if (framesToProcessThisIterationIn > effectInBufferCap) {
1968                 framesToProcessThisIterationIn = effectInBufferCap;
1969             }
1970 
1971             /* At this point our input data has been converted to the effect's input format, so now we need to run the effect, making sure we output to the intermediary buffer. */
1972             if (effectFormatIn == formatIn && effectChannelsIn == channelsIn) {
1973                 /* Fast path. No input conversion required. */
1974                 if (effectFormatOut == formatOut && effectChannelsOut == channelsOut) {
1975                     /* Fast path. Neither input nor output data conversion required. */
1976                     ma_effect_process_pcm_frames(pEffect, pRunningFramesIn, &framesToProcessThisIterationIn, pRunningFramesOut, &framesToProcessThisIterationOut);
1977                 } else {
1978                     /* Slow path. Output conversion required. */
1979                     ma_effect_process_pcm_frames(pEffect, pRunningFramesIn, &framesToProcessThisIterationIn, effectOutBuffer, &framesToProcessThisIterationOut);
1980                     ma_convert_pcm_frames_format_and_channels(pRunningFramesOut, formatOut, channelsOut, effectOutBuffer, effectFormatOut, effectChannelsOut, framesToProcessThisIterationOut, ma_dither_mode_none);
1981                 }
1982             } else {
1983                 /* Slow path. Input conversion required. */
1984                 ma_convert_pcm_frames_format_and_channels(effectInBuffer, effectFormatIn, effectChannelsIn, pRunningFramesIn, formatIn, channelsIn, framesToProcessThisIterationIn, ma_dither_mode_none);
1985 
1986                 if (effectFormatOut == formatOut && effectChannelsOut == channelsOut) {
1987                     /* Fast path. No output format conversion required. */
1988                     ma_effect_process_pcm_frames(pEffect, effectInBuffer, &framesToProcessThisIterationIn, pRunningFramesOut, &framesToProcessThisIterationOut);
1989                 } else {
1990                     /* Slow path. Output format conversion required. */
1991                     ma_effect_process_pcm_frames(pEffect, effectInBuffer, &framesToProcessThisIterationIn, effectOutBuffer, &framesToProcessThisIterationOut);
1992                     ma_convert_pcm_frames_format_and_channels(pRunningFramesOut, formatOut, channelsOut, effectOutBuffer, effectFormatOut, effectChannelsOut, framesToProcessThisIterationOut, ma_dither_mode_none);
1993                 }
1994             }
1995 
1996             totalFramesProcessedIn  += framesToProcessThisIterationIn;
1997             totalFramesProcessedOut += framesToProcessThisIterationOut;
1998         }
1999     }
2000 
2001     return MA_SUCCESS;
2002 }
2003 
ma_effect_get_required_input_frame_count_local(ma_effect * pEffect,ma_uint64 outputFrameCount)2004 static ma_uint64 ma_effect_get_required_input_frame_count_local(ma_effect* pEffect, ma_uint64 outputFrameCount)
2005 {
2006     ma_effect_base* pBase = (ma_effect_base*)pEffect;
2007 
2008     MA_ASSERT(pEffect != NULL);
2009 
2010     if (pBase->onGetRequiredInputFrameCount) {
2011         return pBase->onGetRequiredInputFrameCount(pEffect, outputFrameCount);
2012     } else {
2013         /* If there is no callback, assume a 1:1 mapping. */
2014         return outputFrameCount;
2015     }
2016 }
2017 
ma_effect_get_required_input_frame_count(ma_effect * pEffect,ma_uint64 outputFrameCount)2018 MA_API ma_uint64 ma_effect_get_required_input_frame_count(ma_effect* pEffect, ma_uint64 outputFrameCount)
2019 {
2020     ma_effect_base* pBase = (ma_effect_base*)pEffect;
2021     ma_uint64 localInputFrameCount;
2022 
2023     if (pEffect == NULL) {
2024         return 0;
2025     }
2026 
2027     localInputFrameCount = ma_effect_get_required_input_frame_count_local(pEffect, outputFrameCount);
2028 
2029     if (pBase->pPrev == NULL) {
2030         return localInputFrameCount;
2031     } else {
2032         ma_uint64 parentInputFrameCount = ma_effect_get_required_input_frame_count(pBase->pPrev, outputFrameCount);
2033         if (parentInputFrameCount > localInputFrameCount) {
2034             return parentInputFrameCount;
2035         } else {
2036             return localInputFrameCount;
2037         }
2038     }
2039 }
2040 
ma_effect_get_expected_output_frame_count_local(ma_effect * pEffect,ma_uint64 inputFrameCount)2041 static ma_uint64 ma_effect_get_expected_output_frame_count_local(ma_effect* pEffect, ma_uint64 inputFrameCount)
2042 {
2043     ma_effect_base* pBase = (ma_effect_base*)pEffect;
2044 
2045     MA_ASSERT(pEffect != NULL);
2046 
2047     if (pBase->onGetExpectedOutputFrameCount) {
2048         return pBase->onGetExpectedOutputFrameCount(pEffect, inputFrameCount);
2049     } else {
2050         /* If there is no callback, assume a 1:1 mapping. */
2051         return inputFrameCount;
2052     }
2053 }
2054 
ma_effect_get_expected_output_frame_count(ma_effect * pEffect,ma_uint64 inputFrameCount)2055 MA_API ma_uint64 ma_effect_get_expected_output_frame_count(ma_effect* pEffect, ma_uint64 inputFrameCount)
2056 {
2057     ma_effect_base* pBase = (ma_effect_base*)pEffect;
2058     ma_uint64 localOutputFrameCount;
2059 
2060     if (pEffect == NULL) {
2061         return 0;
2062     }
2063 
2064     localOutputFrameCount = ma_effect_get_expected_output_frame_count_local(pEffect, inputFrameCount);
2065 
2066     if (pBase->pPrev == NULL) {
2067         return localOutputFrameCount;
2068     } else {
2069         ma_uint64 parentOutputFrameCount = ma_effect_get_expected_output_frame_count(pBase->pPrev, inputFrameCount);
2070         if (parentOutputFrameCount < localOutputFrameCount) {
2071             return parentOutputFrameCount;
2072         } else {
2073             return localOutputFrameCount;
2074         }
2075     }
2076 }
2077 
ma_effect_append(ma_effect * pEffect,ma_effect * pParent)2078 ma_result ma_effect_append(ma_effect* pEffect, ma_effect* pParent)
2079 {
2080     ma_effect_base* pBaseEffect = (ma_effect_base*)pEffect;
2081     ma_effect_base* pBaseParent = (ma_effect_base*)pParent;
2082 
2083     if (pEffect == NULL || pParent == NULL || pEffect == pParent) {
2084         return MA_INVALID_ARGS;
2085     }
2086 
2087     /* The effect must be detached before reinserting into the list. */
2088     if (pBaseEffect->pPrev != NULL || pBaseEffect->pNext != NULL) {
2089         return MA_INVALID_OPERATION;
2090     }
2091 
2092     MA_ASSERT(pBaseEffect->pPrev == NULL);
2093     MA_ASSERT(pBaseEffect->pNext == NULL);
2094 
2095     /* Update the effect first. */
2096     pBaseEffect->pPrev = pBaseParent;
2097     pBaseEffect->pNext = pBaseParent->pNext;
2098 
2099     /* Now update the parent. Slot the effect between the parent and the parent's next item, if it has one. */
2100     if (pBaseParent->pNext != NULL) {
2101         pBaseParent->pNext->pPrev = (ma_effect_base*)pEffect;
2102     }
2103     pBaseParent->pNext = (ma_effect_base*)pEffect;
2104 
2105     return MA_SUCCESS;
2106 }
2107 
ma_effect_prepend(ma_effect * pEffect,ma_effect * pChild)2108 ma_result ma_effect_prepend(ma_effect* pEffect, ma_effect* pChild)
2109 {
2110     ma_effect_base* pBaseEffect = (ma_effect_base*)pEffect;
2111     ma_effect_base* pBaseChild  = (ma_effect_base*)pChild;
2112 
2113     if (pChild == NULL || pChild == NULL || pEffect == pChild) {
2114         return MA_INVALID_ARGS;
2115     }
2116 
2117     /* The effect must be detached before reinserting into the list. */
2118     if (pBaseEffect->pPrev != NULL || pBaseEffect->pNext != NULL) {
2119         return MA_INVALID_OPERATION;
2120     }
2121 
2122     MA_ASSERT(pBaseEffect->pPrev == NULL);
2123     MA_ASSERT(pBaseEffect->pNext == NULL);
2124 
2125     /* Update the effect first. */
2126     pBaseEffect->pNext = pBaseChild;
2127     pBaseEffect->pPrev = pBaseChild->pPrev;
2128 
2129     /* Now update the child. Slot the effect between the child and the child's previous item, if it has one. */
2130     if (pBaseChild->pPrev != NULL) {
2131         pBaseChild->pPrev->pNext = (ma_effect_base*)pEffect;
2132     }
2133     pBaseChild->pPrev = (ma_effect_base*)pEffect;
2134 
2135     return MA_SUCCESS;
2136 }
2137 
ma_effect_detach(ma_effect * pEffect)2138 ma_result ma_effect_detach(ma_effect* pEffect)
2139 {
2140     ma_effect_base* pBaseEffect = (ma_effect_base*)pEffect;
2141 
2142     if (pBaseEffect == NULL) {
2143         return MA_INVALID_ARGS;
2144     }
2145 
2146     if (pBaseEffect->pPrev != NULL) {
2147         pBaseEffect->pPrev->pNext = pBaseEffect->pNext;
2148         pBaseEffect->pPrev = NULL;
2149     }
2150 
2151     if (pBaseEffect->pNext != NULL) {
2152         pBaseEffect->pNext->pPrev = pBaseEffect->pPrev;
2153         pBaseEffect->pNext = NULL;
2154     }
2155 
2156     return MA_SUCCESS;
2157 }
2158 
ma_effect_get_output_data_format(ma_effect * pEffect,ma_format * pFormat,ma_uint32 * pChannels,ma_uint32 * pSampleRate)2159 MA_API ma_result ma_effect_get_output_data_format(ma_effect* pEffect, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate)
2160 {
2161     ma_effect_base* pBaseEffect = (ma_effect_base*)pEffect;
2162     ma_result result;
2163     ma_format format;
2164     ma_uint32 channels;
2165     ma_uint32 sampleRate;
2166 
2167     if (pFormat != NULL) {
2168         *pFormat = ma_format_unknown;
2169     }
2170     if (pChannels != NULL) {
2171         *pChannels = 0;
2172     }
2173     if (pSampleRate != NULL) {
2174         *pSampleRate = 0;
2175     }
2176 
2177     if (pBaseEffect == NULL || pBaseEffect->onGetOutputDataFormat == NULL) {
2178         return MA_INVALID_ARGS;
2179     }
2180 
2181     result = pBaseEffect->onGetOutputDataFormat(pEffect, &format, &channels, &sampleRate);
2182     if (result != MA_SUCCESS) {
2183         return result;
2184     }
2185 
2186     if (pFormat != NULL) {
2187         *pFormat = format;
2188     }
2189     if (pChannels != NULL) {
2190         *pChannels = channels;
2191     }
2192     if (pSampleRate != NULL) {
2193         *pSampleRate = sampleRate;
2194     }
2195 
2196     return MA_SUCCESS;
2197 }
2198 
ma_effect_get_input_data_format(ma_effect * pEffect,ma_format * pFormat,ma_uint32 * pChannels,ma_uint32 * pSampleRate)2199 MA_API ma_result ma_effect_get_input_data_format(ma_effect* pEffect, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate)
2200 {
2201     ma_effect_base* pRootEffect;
2202     ma_result result;
2203     ma_format format;
2204     ma_uint32 channels;
2205     ma_uint32 sampleRate;
2206 
2207     if (pFormat != NULL) {
2208         *pFormat = ma_format_unknown;
2209     }
2210     if (pChannels != NULL) {
2211         *pChannels = 0;
2212     }
2213     if (pSampleRate != NULL) {
2214         *pSampleRate = 0;
2215     }
2216 
2217     pRootEffect = ma_effect_get_root(pEffect);
2218     if (pRootEffect == NULL || pRootEffect->onGetInputDataFormat == NULL) {
2219         return MA_INVALID_ARGS;
2220     }
2221 
2222     result = pRootEffect->onGetInputDataFormat(pRootEffect, &format, &channels, &sampleRate);
2223     if (result != MA_SUCCESS) {
2224         return result;
2225     }
2226 
2227     if (pFormat != NULL) {
2228         *pFormat = format;
2229     }
2230     if (pChannels != NULL) {
2231         *pChannels = channels;
2232     }
2233     if (pSampleRate != NULL) {
2234         *pSampleRate = sampleRate;
2235     }
2236 
2237     return MA_SUCCESS;
2238 }
2239 
2240 
2241 
ma_get_accumulation_bytes_per_sample(ma_format format)2242 MA_API size_t ma_get_accumulation_bytes_per_sample(ma_format format)
2243 {
2244     size_t bytesPerSample[ma_format_count] = {
2245         0,                  /* ma_format_unknown */
2246         sizeof(ma_int16),   /* ma_format_u8  */
2247         sizeof(ma_int32),   /* ma_format_s16 */
2248         sizeof(ma_int64),   /* ma_format_s24 */
2249         sizeof(ma_int64),   /* ma_format_s32 */
2250         sizeof(float)       /* ma_format_f32 */
2251     };
2252 
2253     return bytesPerSample[format];
2254 }
2255 
ma_get_accumulation_bytes_per_frame(ma_format format,ma_uint32 channels)2256 MA_API size_t ma_get_accumulation_bytes_per_frame(ma_format format, ma_uint32 channels)
2257 {
2258     return ma_get_accumulation_bytes_per_sample(format) * channels;
2259 }
2260 
2261 
2262 
ma_float_to_fixed_16(float x)2263 static MA_INLINE ma_int16 ma_float_to_fixed_16(float x)
2264 {
2265     return (ma_int16)(x * (1 << 8));
2266 }
2267 
2268 
2269 
ma_apply_volume_unclipped_u8(ma_int16 x,ma_int16 volume)2270 static MA_INLINE ma_int16 ma_apply_volume_unclipped_u8(ma_int16 x, ma_int16 volume)
2271 {
2272     return (ma_int16)(((ma_int32)x * (ma_int32)volume) >> 8);
2273 }
2274 
ma_apply_volume_unclipped_s16(ma_int32 x,ma_int16 volume)2275 static MA_INLINE ma_int32 ma_apply_volume_unclipped_s16(ma_int32 x, ma_int16 volume)
2276 {
2277     return (ma_int32)((x * volume) >> 8);
2278 }
2279 
ma_apply_volume_unclipped_s24(ma_int64 x,ma_int16 volume)2280 static MA_INLINE ma_int64 ma_apply_volume_unclipped_s24(ma_int64 x, ma_int16 volume)
2281 {
2282     return (ma_int64)((x * volume) >> 8);
2283 }
2284 
ma_apply_volume_unclipped_s32(ma_int64 x,ma_int16 volume)2285 static MA_INLINE ma_int64 ma_apply_volume_unclipped_s32(ma_int64 x, ma_int16 volume)
2286 {
2287     return (ma_int64)((x * volume) >> 8);
2288 }
2289 
ma_apply_volume_unclipped_f32(float x,float volume)2290 static MA_INLINE float ma_apply_volume_unclipped_f32(float x, float volume)
2291 {
2292     return x * volume;
2293 }
2294 
2295 
2296 #if 0
2297 static void ma_accumulate_and_clip_u8(ma_uint8* pDst, const ma_int16* pSrc, ma_uint64 count)
2298 {
2299     ma_uint64 iSample;
2300 
2301     MA_ASSERT(pDst != NULL);
2302     MA_ASSERT(pSrc != NULL);
2303 
2304     for (iSample = 0; iSample < count; iSample += 1) {
2305         pDst[iSample] = ma_clip_u8(ma_pcm_sample_u8_to_s16_no_scale(pDst[iSample]) + pSrc[iSample]);
2306     }
2307 }
2308 
2309 static void ma_accumulate_and_clip_s16(ma_int16* pDst, const ma_int32* pSrc, ma_uint64 count)
2310 {
2311     ma_uint64 iSample;
2312 
2313     MA_ASSERT(pDst != NULL);
2314     MA_ASSERT(pSrc != NULL);
2315 
2316     for (iSample = 0; iSample < count; iSample += 1) {
2317         pDst[iSample] = ma_clip_s16(pDst[iSample] + pSrc[iSample]);
2318     }
2319 }
2320 
2321 static void ma_accumulate_and_clip_s24(ma_uint8* pDst, const ma_int64* pSrc, ma_uint64 count)
2322 {
2323     ma_uint64 iSample;
2324 
2325     MA_ASSERT(pDst != NULL);
2326     MA_ASSERT(pSrc != NULL);
2327 
2328     for (iSample = 0; iSample < count; iSample += 1) {
2329         ma_int64 s = ma_clip_s24(ma_pcm_sample_s24_to_s32_no_scale(&pDst[iSample*3]) + pSrc[iSample]);
2330         pDst[iSample*3 + 0] = (ma_uint8)((s & 0x000000FF) >>  0);
2331         pDst[iSample*3 + 1] = (ma_uint8)((s & 0x0000FF00) >>  8);
2332         pDst[iSample*3 + 2] = (ma_uint8)((s & 0x00FF0000) >> 16);
2333     }
2334 }
2335 
2336 static void ma_accumulate_and_clip_s32(ma_int32* pDst, const ma_int64* pSrc, ma_uint64 count)
2337 {
2338     ma_uint64 iSample;
2339 
2340     MA_ASSERT(pDst != NULL);
2341     MA_ASSERT(pSrc != NULL);
2342 
2343     for (iSample = 0; iSample < count; iSample += 1) {
2344         pDst[iSample] = ma_clip_s32(pDst[iSample] + pSrc[iSample]);
2345     }
2346 }
2347 
2348 static void ma_accumulate_and_clip_f32(float* pDst, const float* pSrc, ma_uint64 count)
2349 {
2350     ma_uint64 iSample;
2351 
2352     MA_ASSERT(pDst != NULL);
2353     MA_ASSERT(pSrc != NULL);
2354 
2355     for (iSample = 0; iSample < count; iSample += 1) {
2356         pDst[iSample] = ma_clip_f32(pDst[iSample] + pSrc[iSample]);
2357     }
2358 }
2359 #endif
2360 
2361 
ma_clip_samples_u8(ma_uint8 * pDst,const ma_int16 * pSrc,ma_uint64 count)2362 static void ma_clip_samples_u8(ma_uint8* pDst, const ma_int16* pSrc, ma_uint64 count)
2363 {
2364     ma_uint64 iSample;
2365 
2366     MA_ASSERT(pDst != NULL);
2367     MA_ASSERT(pSrc != NULL);
2368 
2369     for (iSample = 0; iSample < count; iSample += 1) {
2370         pDst[iSample] = ma_clip_u8(pSrc[iSample]);
2371     }
2372 }
2373 
ma_clip_samples_s16(ma_int16 * pDst,const ma_int32 * pSrc,ma_uint64 count)2374 static void ma_clip_samples_s16(ma_int16* pDst, const ma_int32* pSrc, ma_uint64 count)
2375 {
2376     ma_uint64 iSample;
2377 
2378     MA_ASSERT(pDst != NULL);
2379     MA_ASSERT(pSrc != NULL);
2380 
2381     for (iSample = 0; iSample < count; iSample += 1) {
2382         pDst[iSample] = ma_clip_s16(pSrc[iSample]);
2383     }
2384 }
2385 
ma_clip_samples_s24(ma_uint8 * pDst,const ma_int64 * pSrc,ma_uint64 count)2386 static void ma_clip_samples_s24(ma_uint8* pDst, const ma_int64* pSrc, ma_uint64 count)
2387 {
2388     ma_uint64 iSample;
2389 
2390     MA_ASSERT(pDst != NULL);
2391     MA_ASSERT(pSrc != NULL);
2392 
2393     for (iSample = 0; iSample < count; iSample += 1) {
2394         ma_int64 s = ma_clip_s24(pSrc[iSample]);
2395         pDst[iSample*3 + 0] = (ma_uint8)((s & 0x000000FF) >>  0);
2396         pDst[iSample*3 + 1] = (ma_uint8)((s & 0x0000FF00) >>  8);
2397         pDst[iSample*3 + 2] = (ma_uint8)((s & 0x00FF0000) >> 16);
2398     }
2399 }
2400 
ma_clip_samples_s32(ma_int32 * pDst,const ma_int64 * pSrc,ma_uint64 count)2401 static void ma_clip_samples_s32(ma_int32* pDst, const ma_int64* pSrc, ma_uint64 count)
2402 {
2403     ma_uint64 iSample;
2404 
2405     MA_ASSERT(pDst != NULL);
2406     MA_ASSERT(pSrc != NULL);
2407 
2408     for (iSample = 0; iSample < count; iSample += 1) {
2409         pDst[iSample] = ma_clip_s32(pSrc[iSample]);
2410     }
2411 }
2412 
ma_clip_samples_f32_ex(float * pDst,const float * pSrc,ma_uint64 count)2413 static void ma_clip_samples_f32_ex(float* pDst, const float* pSrc, ma_uint64 count)
2414 {
2415     ma_uint64 iSample;
2416 
2417     MA_ASSERT(pDst != NULL);
2418     MA_ASSERT(pSrc != NULL);
2419 
2420     for (iSample = 0; iSample < count; iSample += 1) {
2421         pDst[iSample] = ma_clip_f32(pSrc[iSample]);
2422     }
2423 }
2424 
2425 
2426 
ma_volume_and_clip_samples_u8(ma_uint8 * pDst,const ma_int16 * pSrc,ma_uint64 count,float volume)2427 static void ma_volume_and_clip_samples_u8(ma_uint8* pDst, const ma_int16* pSrc, ma_uint64 count, float volume)
2428 {
2429     ma_uint64 iSample;
2430     ma_int16  volumeFixed;
2431 
2432     MA_ASSERT(pDst != NULL);
2433     MA_ASSERT(pSrc != NULL);
2434 
2435     volumeFixed = ma_float_to_fixed_16(volume);
2436 
2437     for (iSample = 0; iSample < count; iSample += 1) {
2438         pDst[iSample] = ma_clip_u8(ma_apply_volume_unclipped_u8(pSrc[iSample], volumeFixed));
2439     }
2440 }
2441 
ma_volume_and_clip_samples_s16(ma_int16 * pDst,const ma_int32 * pSrc,ma_uint64 count,float volume)2442 static void ma_volume_and_clip_samples_s16(ma_int16* pDst, const ma_int32* pSrc, ma_uint64 count, float volume)
2443 {
2444     ma_uint64 iSample;
2445     ma_int16  volumeFixed;
2446 
2447     MA_ASSERT(pDst != NULL);
2448     MA_ASSERT(pSrc != NULL);
2449 
2450     volumeFixed = ma_float_to_fixed_16(volume);
2451 
2452     for (iSample = 0; iSample < count; iSample += 1) {
2453         pDst[iSample] = ma_clip_s16(ma_apply_volume_unclipped_s16(pSrc[iSample], volumeFixed));
2454     }
2455 }
2456 
ma_volume_and_clip_samples_s24(ma_uint8 * pDst,const ma_int64 * pSrc,ma_uint64 count,float volume)2457 static void ma_volume_and_clip_samples_s24(ma_uint8* pDst, const ma_int64* pSrc, ma_uint64 count, float volume)
2458 {
2459     ma_uint64 iSample;
2460     ma_int16  volumeFixed;
2461 
2462     MA_ASSERT(pDst != NULL);
2463     MA_ASSERT(pSrc != NULL);
2464 
2465     volumeFixed = ma_float_to_fixed_16(volume);
2466 
2467     for (iSample = 0; iSample < count; iSample += 1) {
2468         ma_int64 s = ma_clip_s24(ma_apply_volume_unclipped_s24(pSrc[iSample], volumeFixed));
2469         pDst[iSample*3 + 0] = (ma_uint8)((s & 0x000000FF) >>  0);
2470         pDst[iSample*3 + 1] = (ma_uint8)((s & 0x0000FF00) >>  8);
2471         pDst[iSample*3 + 2] = (ma_uint8)((s & 0x00FF0000) >> 16);
2472     }
2473 }
2474 
ma_volume_and_clip_samples_s32(ma_int32 * pDst,const ma_int64 * pSrc,ma_uint64 count,float volume)2475 static void ma_volume_and_clip_samples_s32(ma_int32* pDst, const ma_int64* pSrc, ma_uint64 count, float volume)
2476 {
2477     ma_uint64 iSample;
2478     ma_int16  volumeFixed;
2479 
2480     MA_ASSERT(pDst != NULL);
2481     MA_ASSERT(pSrc != NULL);
2482 
2483     volumeFixed = ma_float_to_fixed_16(volume);
2484 
2485     for (iSample = 0; iSample < count; iSample += 1) {
2486         pDst[iSample] = ma_clip_s32(ma_apply_volume_unclipped_s32(pSrc[iSample], volumeFixed));
2487     }
2488 }
2489 
ma_volume_and_clip_samples_f32(float * pDst,const float * pSrc,ma_uint64 count,float volume)2490 static void ma_volume_and_clip_samples_f32(float* pDst, const float* pSrc, ma_uint64 count, float volume)
2491 {
2492     ma_uint64 iSample;
2493 
2494     MA_ASSERT(pDst != NULL);
2495     MA_ASSERT(pSrc != NULL);
2496 
2497     /* For the f32 case we need to make sure this supports in-place processing where the input and output buffers are the same. */
2498 
2499     for (iSample = 0; iSample < count; iSample += 1) {
2500         pDst[iSample] = ma_clip_f32(ma_apply_volume_unclipped_f32(pSrc[iSample], volume));
2501     }
2502 }
2503 
ma_clip_pcm_frames(void * pDst,const void * pSrc,ma_uint64 frameCount,ma_format format,ma_uint32 channels)2504 static void ma_clip_pcm_frames(void* pDst, const void* pSrc, ma_uint64 frameCount, ma_format format, ma_uint32 channels)
2505 {
2506     ma_uint64 sampleCount;
2507 
2508     MA_ASSERT(pDst != NULL);
2509     MA_ASSERT(pSrc != NULL);
2510 
2511     sampleCount = frameCount * channels;
2512 
2513     switch (format) {
2514         case ma_format_u8:  ma_clip_samples_u8( (ma_uint8*)pDst, (const ma_int16*)pSrc, sampleCount); break;
2515         case ma_format_s16: ma_clip_samples_s16((ma_int16*)pDst, (const ma_int32*)pSrc, sampleCount); break;
2516         case ma_format_s24: ma_clip_samples_s24((ma_uint8*)pDst, (const ma_int64*)pSrc, sampleCount); break;
2517         case ma_format_s32: ma_clip_samples_s32((ma_int32*)pDst, (const ma_int64*)pSrc, sampleCount); break;
2518         case ma_format_f32: ma_clip_samples_f32_ex((float*)pDst, (const    float*)pSrc, sampleCount); break;
2519 
2520         /* Do nothing if we don't know the format. We're including these here to silence a compiler warning about enums not being handled by the switch. */
2521         case ma_format_unknown:
2522         case ma_format_count:
2523             break;
2524     }
2525 }
2526 
ma_volume_and_clip_pcm_frames(void * pDst,const void * pSrc,ma_uint64 frameCount,ma_format format,ma_uint32 channels,float volume)2527 static void ma_volume_and_clip_pcm_frames(void* pDst, const void* pSrc, ma_uint64 frameCount, ma_format format, ma_uint32 channels, float volume)
2528 {
2529     MA_ASSERT(pDst != NULL);
2530     MA_ASSERT(pSrc != NULL);
2531 
2532     if (volume == 1) {
2533         ma_clip_pcm_frames(pDst, pSrc, frameCount, format, channels);   /* Optimized case for volume = 1. */
2534     } else if (volume == 0) {
2535         ma_silence_pcm_frames(pDst, frameCount, format, channels);      /* Optimized case for volume = 0. */
2536     } else {
2537         ma_uint64 sampleCount = frameCount * channels;
2538 
2539         switch (format) {
2540             case ma_format_u8:  ma_volume_and_clip_samples_u8( (ma_uint8*)pDst, (const ma_int16*)pSrc, sampleCount, volume); break;
2541             case ma_format_s16: ma_volume_and_clip_samples_s16((ma_int16*)pDst, (const ma_int32*)pSrc, sampleCount, volume); break;
2542             case ma_format_s24: ma_volume_and_clip_samples_s24((ma_uint8*)pDst, (const ma_int64*)pSrc, sampleCount, volume); break;
2543             case ma_format_s32: ma_volume_and_clip_samples_s32((ma_int32*)pDst, (const ma_int64*)pSrc, sampleCount, volume); break;
2544             case ma_format_f32: ma_volume_and_clip_samples_f32((   float*)pDst, (const    float*)pSrc, sampleCount, volume); break;
2545 
2546             /* Do nothing if we don't know the format. We're including these here to silence a compiler warning about enums not being handled by the switch. */
2547             case ma_format_unknown:
2548             case ma_format_count:
2549                 break;
2550         }
2551     }
2552 }
2553 
2554 
ma_clipped_accumulate_u8(ma_uint8 * pDst,const ma_uint8 * pSrc,ma_uint64 sampleCount)2555 static void ma_clipped_accumulate_u8(ma_uint8* pDst, const ma_uint8* pSrc, ma_uint64 sampleCount)
2556 {
2557     ma_uint64 iSample;
2558 
2559     MA_ASSERT(pDst != NULL);
2560     MA_ASSERT(pSrc != NULL);
2561 
2562     for (iSample = 0; iSample < sampleCount; iSample += 1) {
2563         pDst[iSample] = ma_clip_u8(ma_pcm_sample_u8_to_s16_no_scale(pDst[iSample]) + ma_pcm_sample_u8_to_s16_no_scale(pSrc[iSample]));
2564     }
2565 }
2566 
ma_clipped_accumulate_s16(ma_int16 * pDst,const ma_int16 * pSrc,ma_uint64 sampleCount)2567 static void ma_clipped_accumulate_s16(ma_int16* pDst, const ma_int16* pSrc, ma_uint64 sampleCount)
2568 {
2569     ma_uint64 iSample;
2570 
2571     MA_ASSERT(pDst != NULL);
2572     MA_ASSERT(pSrc != NULL);
2573 
2574     for (iSample = 0; iSample < sampleCount; iSample += 1) {
2575         pDst[iSample] = ma_clip_s16((ma_int32)pDst[iSample] + (ma_int32)pSrc[iSample]);
2576     }
2577 }
2578 
ma_clipped_accumulate_s24(ma_uint8 * pDst,const ma_uint8 * pSrc,ma_uint64 sampleCount)2579 static void ma_clipped_accumulate_s24(ma_uint8* pDst, const ma_uint8* pSrc, ma_uint64 sampleCount)
2580 {
2581     ma_uint64 iSample;
2582 
2583     MA_ASSERT(pDst != NULL);
2584     MA_ASSERT(pSrc != NULL);
2585 
2586     for (iSample = 0; iSample < sampleCount; iSample += 1) {
2587         ma_int64 s = ma_clip_s24(ma_pcm_sample_s24_to_s32_no_scale(&pDst[iSample*3]) + ma_pcm_sample_s24_to_s32_no_scale(&pSrc[iSample*3]));
2588         pDst[iSample*3 + 0] = (ma_uint8)((s & 0x000000FF) >>  0);
2589         pDst[iSample*3 + 1] = (ma_uint8)((s & 0x0000FF00) >>  8);
2590         pDst[iSample*3 + 2] = (ma_uint8)((s & 0x00FF0000) >> 16);
2591     }
2592 }
2593 
ma_clipped_accumulate_s32(ma_int32 * pDst,const ma_int32 * pSrc,ma_uint64 sampleCount)2594 static void ma_clipped_accumulate_s32(ma_int32* pDst, const ma_int32* pSrc, ma_uint64 sampleCount)
2595 {
2596     ma_uint64 iSample;
2597 
2598     MA_ASSERT(pDst != NULL);
2599     MA_ASSERT(pSrc != NULL);
2600 
2601     for (iSample = 0; iSample < sampleCount; iSample += 1) {
2602         pDst[iSample] = ma_clip_s32((ma_int64)pDst[iSample] + (ma_int64)pSrc[iSample]);
2603     }
2604 }
2605 
ma_clipped_accumulate_f32(float * pDst,const float * pSrc,ma_uint64 sampleCount)2606 static void ma_clipped_accumulate_f32(float* pDst, const float* pSrc, ma_uint64 sampleCount)
2607 {
2608     ma_uint64 iSample;
2609 
2610     MA_ASSERT(pDst != NULL);
2611     MA_ASSERT(pSrc != NULL);
2612 
2613     for (iSample = 0; iSample < sampleCount; iSample += 1) {
2614         pDst[iSample] = ma_clip_f32(pDst[iSample] + pSrc[iSample]);
2615     }
2616 }
2617 
ma_clipped_accumulate_pcm_frames(void * pDst,const void * pSrc,ma_uint64 frameCount,ma_format format,ma_uint32 channels)2618 static void ma_clipped_accumulate_pcm_frames(void* pDst, const void* pSrc, ma_uint64 frameCount, ma_format format, ma_uint32 channels)
2619 {
2620     ma_uint64 sampleCount;
2621 
2622     MA_ASSERT(pDst != NULL);
2623     MA_ASSERT(pSrc != NULL);
2624 
2625     sampleCount = frameCount * channels;
2626 
2627     switch (format) {
2628         case ma_format_u8:  ma_clipped_accumulate_u8( (ma_uint8*)pDst, (const ma_uint8*)pSrc, sampleCount); break;
2629         case ma_format_s16: ma_clipped_accumulate_s16((ma_int16*)pDst, (const ma_int16*)pSrc, sampleCount); break;
2630         case ma_format_s24: ma_clipped_accumulate_s24((ma_uint8*)pDst, (const ma_uint8*)pSrc, sampleCount); break;
2631         case ma_format_s32: ma_clipped_accumulate_s32((ma_int32*)pDst, (const ma_int32*)pSrc, sampleCount); break;
2632         case ma_format_f32: ma_clipped_accumulate_f32((   float*)pDst, (const    float*)pSrc, sampleCount); break;
2633 
2634         /* Do nothing if we don't know the format. We're including these here to silence a compiler warning about enums not being handled by the switch. */
2635         case ma_format_unknown:
2636         case ma_format_count:
2637             break;
2638     }
2639 }
2640 
2641 
2642 
ma_unclipped_accumulate_u8(ma_int16 * pDst,const ma_uint8 * pSrc,ma_uint64 sampleCount)2643 static void ma_unclipped_accumulate_u8(ma_int16* pDst, const ma_uint8* pSrc, ma_uint64 sampleCount)
2644 {
2645     ma_uint64 iSample;
2646 
2647     MA_ASSERT(pDst != NULL);
2648     MA_ASSERT(pSrc != NULL);
2649 
2650     for (iSample = 0; iSample < sampleCount; iSample += 1) {
2651         pDst[iSample] = pDst[iSample] + ma_pcm_sample_u8_to_s16_no_scale(pSrc[iSample]);
2652     }
2653 }
2654 
ma_unclipped_accumulate_s16(ma_int32 * pDst,const ma_int16 * pSrc,ma_uint64 sampleCount)2655 static void ma_unclipped_accumulate_s16(ma_int32* pDst, const ma_int16* pSrc, ma_uint64 sampleCount)
2656 {
2657     ma_uint64 iSample;
2658 
2659     MA_ASSERT(pDst != NULL);
2660     MA_ASSERT(pSrc != NULL);
2661 
2662     for (iSample = 0; iSample < sampleCount; iSample += 1) {
2663         pDst[iSample] = (ma_int32)pDst[iSample] + (ma_int32)pSrc[iSample];
2664     }
2665 }
2666 
ma_unclipped_accumulate_s24(ma_int64 * pDst,const ma_uint8 * pSrc,ma_uint64 sampleCount)2667 static void ma_unclipped_accumulate_s24(ma_int64* pDst, const ma_uint8* pSrc, ma_uint64 sampleCount)
2668 {
2669     ma_uint64 iSample;
2670 
2671     MA_ASSERT(pDst != NULL);
2672     MA_ASSERT(pSrc != NULL);
2673 
2674     for (iSample = 0; iSample < sampleCount; iSample += 1) {
2675         pDst[iSample] = pDst[iSample] + ma_pcm_sample_s24_to_s32_no_scale(&pSrc[iSample*3]);
2676     }
2677 }
2678 
ma_unclipped_accumulate_s32(ma_int64 * pDst,const ma_int32 * pSrc,ma_uint64 sampleCount)2679 static void ma_unclipped_accumulate_s32(ma_int64* pDst, const ma_int32* pSrc, ma_uint64 sampleCount)
2680 {
2681     ma_uint64 iSample;
2682 
2683     MA_ASSERT(pDst != NULL);
2684     MA_ASSERT(pSrc != NULL);
2685 
2686     for (iSample = 0; iSample < sampleCount; iSample += 1) {
2687         pDst[iSample] = (ma_int64)pDst[iSample] + (ma_int64)pSrc[iSample];
2688     }
2689 }
2690 
ma_unclipped_accumulate_f32(float * pDst,const float * pSrc,ma_uint64 sampleCount)2691 static void ma_unclipped_accumulate_f32(float* pDst, const float* pSrc, ma_uint64 sampleCount)
2692 {
2693     ma_uint64 iSample;
2694 
2695     MA_ASSERT(pDst != NULL);
2696     MA_ASSERT(pSrc != NULL);
2697 
2698     for (iSample = 0; iSample < sampleCount; iSample += 1) {
2699         pDst[iSample] = pDst[iSample] + pSrc[iSample];
2700     }
2701 }
2702 
ma_unclipped_accumulate_pcm_frames(void * pDst,const void * pSrc,ma_uint64 frameCount,ma_format format,ma_uint32 channels)2703 static void ma_unclipped_accumulate_pcm_frames(void* pDst, const void* pSrc, ma_uint64 frameCount, ma_format format, ma_uint32 channels)
2704 {
2705     ma_uint64 sampleCount;
2706 
2707     MA_ASSERT(pDst != NULL);
2708     MA_ASSERT(pSrc != NULL);
2709 
2710     sampleCount = frameCount * channels;
2711 
2712     switch (format) {
2713         case ma_format_u8:  ma_unclipped_accumulate_u8( (ma_int16*)pDst, (const ma_uint8*)pSrc, sampleCount); break;
2714         case ma_format_s16: ma_unclipped_accumulate_s16((ma_int32*)pDst, (const ma_int16*)pSrc, sampleCount); break;
2715         case ma_format_s24: ma_unclipped_accumulate_s24((ma_int64*)pDst, (const ma_uint8*)pSrc, sampleCount); break;
2716         case ma_format_s32: ma_unclipped_accumulate_s32((ma_int64*)pDst, (const ma_int32*)pSrc, sampleCount); break;
2717         case ma_format_f32: ma_unclipped_accumulate_f32((   float*)pDst, (const    float*)pSrc, sampleCount); break;
2718 
2719         /* Do nothing if we don't know the format. We're including these here to silence a compiler warning about enums not being handled by the switch. */
2720         case ma_format_unknown:
2721         case ma_format_count:
2722             break;
2723     }
2724 }
2725 
2726 
2727 #if 0
2728 static void ma_volume_and_accumulate_and_clip_u8(ma_uint8* pDst, const ma_int16* pSrc, ma_uint64 count, float volume)
2729 {
2730     ma_uint64 iSample;
2731     ma_int16  volumeFixed;
2732 
2733     MA_ASSERT(pDst != NULL);
2734     MA_ASSERT(pSrc != NULL);
2735 
2736     volumeFixed = ma_float_to_fixed_16(volume);
2737 
2738     for (iSample = 0; iSample < count; iSample += 1) {
2739         pDst[iSample] = ma_clip_u8(ma_pcm_sample_u8_to_s16_no_scale(pDst[iSample]) + ma_apply_volume_unclipped_u8(pSrc[iSample], volumeFixed));
2740     }
2741 }
2742 
2743 static void ma_volume_and_accumulate_and_clip_s16(ma_int16* pDst, const ma_int32* pSrc, ma_uint64 count, float volume)
2744 {
2745     ma_uint64 iSample;
2746     ma_int16  volumeFixed;
2747 
2748     MA_ASSERT(pDst != NULL);
2749     MA_ASSERT(pSrc != NULL);
2750 
2751     volumeFixed = ma_float_to_fixed_16(volume);
2752 
2753     for (iSample = 0; iSample < count; iSample += 1) {
2754         pDst[iSample] = ma_clip_s16(pDst[iSample] + ma_apply_volume_unclipped_s16(pSrc[iSample], volumeFixed));
2755     }
2756 }
2757 
2758 static void ma_volume_and_accumulate_and_clip_s24(ma_uint8* pDst, const ma_int64* pSrc, ma_uint64 count, float volume)
2759 {
2760     ma_uint64 iSample;
2761     ma_int16  volumeFixed;
2762 
2763     MA_ASSERT(pDst != NULL);
2764     MA_ASSERT(pSrc != NULL);
2765 
2766     volumeFixed = ma_float_to_fixed_16(volume);
2767 
2768     for (iSample = 0; iSample < count; iSample += 1) {
2769         ma_int64 s = ma_clip_s24(ma_pcm_sample_s24_to_s32_no_scale(&pDst[iSample*3]) + ma_apply_volume_unclipped_s24(pSrc[iSample], volumeFixed));
2770         pDst[iSample*3 + 0] = (ma_uint8)((s & 0x000000FF) >>  0);
2771         pDst[iSample*3 + 1] = (ma_uint8)((s & 0x0000FF00) >>  8);
2772         pDst[iSample*3 + 2] = (ma_uint8)((s & 0x00FF0000) >> 16);
2773     }
2774 }
2775 
2776 static void ma_volume_and_accumulate_and_clip_s32(ma_int32* dst, const ma_int64* src, ma_uint64 count, float volume)
2777 {
2778     ma_uint64 iSample;
2779     ma_int16  volumeFixed;
2780 
2781     MA_ASSERT(dst != NULL);
2782     MA_ASSERT(src != NULL);
2783 
2784     volumeFixed = ma_float_to_fixed_16(volume);
2785 
2786     for (iSample = 0; iSample < count; iSample += 1) {
2787         dst[iSample] = ma_clip_s32(dst[iSample] + ma_apply_volume_unclipped_s32(src[iSample], volumeFixed));
2788     }
2789 }
2790 
2791 static void ma_volume_and_accumulate_and_clip_f32(float* pDst, const float* pSrc, ma_uint64 count, float volume)
2792 {
2793     ma_uint64 iSample;
2794 
2795     MA_ASSERT(pDst != NULL);
2796     MA_ASSERT(pSrc != NULL);
2797 
2798     for (iSample = 0; iSample < count; iSample += 1) {
2799         pDst[iSample] = ma_clip_f32(pDst[iSample] + ma_apply_volume_unclipped_f32(pSrc[iSample], volume));
2800     }
2801 }
2802 
2803 static ma_result ma_volume_and_accumulate_and_clip_pcm_frames(void* pDst, const void* pSrc, ma_uint64 frameCount, ma_format format, ma_uint32 channels, float volume)
2804 {
2805     ma_uint64 sampleCount;
2806 
2807     if (pDst == NULL || pSrc == NULL) {
2808         return MA_INVALID_ARGS;
2809     }
2810 
2811     /* The output buffer cannot be the same as the accumulation buffer. */
2812     if (pDst == pSrc) {
2813         return MA_INVALID_OPERATION;
2814     }
2815 
2816     /* No-op if there's no volume. */
2817     if (volume == 0) {
2818         return MA_SUCCESS;
2819     }
2820 
2821     sampleCount = frameCount * channels;
2822 
2823     /* No need for volume control if the volume is 1. */
2824     if (volume == 1) {
2825         switch (format) {
2826             case ma_format_u8:  ma_accumulate_and_clip_u8( pDst, pSrc, sampleCount); break;
2827             case ma_format_s16: ma_accumulate_and_clip_s16(pDst, pSrc, sampleCount); break;
2828             case ma_format_s24: ma_accumulate_and_clip_s24(pDst, pSrc, sampleCount); break;
2829             case ma_format_s32: ma_accumulate_and_clip_s32(pDst, pSrc, sampleCount); break;
2830             case ma_format_f32: ma_accumulate_and_clip_f32(pDst, pSrc, sampleCount); break;
2831             default: return MA_INVALID_ARGS;    /* Unknown format. */
2832         }
2833     } else {
2834         /* Getting here means the volume is not 0 nor 1. */
2835         MA_ASSERT(volume != 0 && volume != 1);
2836 
2837         switch (format) {
2838             case ma_format_u8:  ma_volume_and_accumulate_and_clip_u8( pDst, pSrc, sampleCount, volume); break;
2839             case ma_format_s16: ma_volume_and_accumulate_and_clip_s16(pDst, pSrc, sampleCount, volume); break;
2840             case ma_format_s24: ma_volume_and_accumulate_and_clip_s24(pDst, pSrc, sampleCount, volume); break;
2841             case ma_format_s32: ma_volume_and_accumulate_and_clip_s32(pDst, pSrc, sampleCount, volume); break;
2842             case ma_format_f32: ma_volume_and_accumulate_and_clip_f32(pDst, pSrc, sampleCount, volume); break;
2843             default: return MA_INVALID_ARGS;        /* Unknown format. */
2844         }
2845     }
2846 
2847     return MA_SUCCESS;
2848 }
2849 #endif
2850 
ma_volume_and_clip_and_effect_pcm_frames(void * pDst,ma_format formatOut,ma_uint32 channelsOut,ma_uint64 frameCountOut,const void * pSrc,ma_format formatIn,ma_uint32 channelsIn,ma_uint64 frameCountIn,float volume,ma_effect * pEffect,ma_bool32 isAccumulation)2851 static ma_result ma_volume_and_clip_and_effect_pcm_frames(void* pDst, ma_format formatOut, ma_uint32 channelsOut, ma_uint64 frameCountOut, const void* pSrc, ma_format formatIn, ma_uint32 channelsIn, ma_uint64 frameCountIn, float volume, ma_effect* pEffect, ma_bool32 isAccumulation)
2852 {
2853     ma_result result;
2854     ma_uint8  effectBufferIn[MA_DATA_CONVERTER_STACK_BUFFER_SIZE];
2855     ma_uint32 effectBufferInCapInFrames;
2856     ma_uint8  effectBufferOut[MA_DATA_CONVERTER_STACK_BUFFER_SIZE];
2857     ma_uint32 effectBufferOutCapInFrames;
2858     ma_format effectFormatIn;
2859     ma_uint32 effectChannelsIn;
2860     ma_format effectFormatOut;
2861     ma_uint32 effectChannelsOut;
2862     ma_uint64 totalFramesProcessedOut = 0;
2863     ma_uint64 totalFramesProcessedIn  = 0;
2864     /* */ void* pRunningDst = pDst;
2865     const void* pRunningSrc = pSrc;
2866 
2867     if (pDst == NULL || pSrc == NULL || pEffect == NULL) {
2868         return MA_INVALID_ARGS;
2869     }
2870 
2871     /* No op if silent. */
2872     if (volume == 0) {
2873         return MA_SUCCESS;
2874     }
2875 
2876     /* We need to know the effect's input and output formats so we can do pre- and post-effect data conversion if necessary. */
2877     ma_effect_get_input_data_format( pEffect, &effectFormatIn,  &effectChannelsIn,  NULL);
2878     ma_effect_get_output_data_format(pEffect, &effectFormatOut, &effectChannelsOut, NULL);
2879 
2880     effectBufferInCapInFrames  = sizeof(effectBufferIn ) / ma_get_bytes_per_frame(effectFormatIn,  effectChannelsIn );
2881     effectBufferOutCapInFrames = sizeof(effectBufferOut) / ma_get_bytes_per_frame(effectFormatOut, effectChannelsOut);
2882 
2883     while (totalFramesProcessedOut < frameCountOut && totalFramesProcessedIn < frameCountIn) {
2884         ma_uint64 effectFrameCountIn;
2885         ma_uint64 effectFrameCountOut;
2886 
2887         effectFrameCountOut = frameCountOut - totalFramesProcessedOut;
2888         if (effectFrameCountOut > effectBufferOutCapInFrames) {
2889             effectFrameCountOut = effectBufferOutCapInFrames;
2890         }
2891 
2892         effectFrameCountIn = ma_effect_get_required_input_frame_count(pEffect, effectFrameCountOut);
2893         if (effectFrameCountIn > frameCountIn - totalFramesProcessedIn) {
2894             effectFrameCountIn = frameCountIn - totalFramesProcessedIn;
2895         }
2896         if (effectFrameCountIn > effectBufferInCapInFrames) {
2897             effectFrameCountIn = effectBufferInCapInFrames;
2898         }
2899 
2900         /*
2901         The first step is to get the data ready for the effect. If the effect's input format and channels are the same as the source buffer, we just
2902         clip the accumulation buffer straight input the effect's input buffer. Otherwise need to do a conversion.
2903         */
2904         if (effectFormatIn == formatIn && effectChannelsIn == channelsIn) {
2905             /* Fast path. No data conversion required for the input data except clipping. */
2906             ma_volume_and_clip_pcm_frames(effectBufferIn, pRunningSrc, effectFrameCountIn, formatIn, channelsIn, volume);
2907         } else {
2908             /* Slow path. Data conversion required between the input data and the effect input data. */
2909             ma_uint8  clippedSrcBuffer[MA_DATA_CONVERTER_STACK_BUFFER_SIZE];
2910             ma_uint32 clippedSrcBufferCapInFrames = sizeof(clippedSrcBuffer) / ma_get_bytes_per_frame(formatIn, channelsIn);
2911 
2912             if (effectFrameCountIn > clippedSrcBufferCapInFrames) {
2913                 effectFrameCountIn = clippedSrcBufferCapInFrames;
2914             }
2915 
2916             ma_volume_and_clip_pcm_frames(clippedSrcBuffer, pRunningSrc, effectFrameCountIn, formatIn, channelsIn, volume);
2917 
2918             /* At this point the input data has had volume and clipping applied. We can now convert this to the effect's input format. */
2919             ma_convert_pcm_frames_format_and_channels(effectBufferIn, effectFormatIn, effectChannelsIn, clippedSrcBuffer, formatIn, channelsIn, effectFrameCountIn, ma_dither_mode_none);
2920         }
2921 
2922         /* At this point we have our input data in the effect's input format and we can now apply it. */
2923         result = ma_effect_process_pcm_frames(pEffect, effectBufferIn, &effectFrameCountIn, effectBufferOut, &effectFrameCountOut);
2924         if (result != MA_SUCCESS) {
2925             return result;  /* Failed to process the effect. */
2926         }
2927 
2928         /*
2929         The effect has been applied. If the effect's output format is the same as the final output we can just accumulate straight into the output buffer,
2930         otherwise we need to convert.
2931         */
2932         if (effectFormatOut == formatOut && effectChannelsOut == channelsOut) {
2933             /* Fast path. No data conversion required for output data. Just accumulate or overwrite. */
2934             if (isAccumulation) {
2935                 ma_unclipped_accumulate_pcm_frames(pRunningDst, effectBufferOut, effectFrameCountOut, effectFormatOut, effectChannelsOut);
2936             } else {
2937                 ma_clip_pcm_frames(pRunningDst, effectBufferOut, effectFrameCountOut, effectFormatOut, effectChannelsOut);
2938             }
2939         } else {
2940             /* Slow path. Data conversion required before accumulating. */
2941             ma_uint8  accumulationInBuffer[MA_DATA_CONVERTER_STACK_BUFFER_SIZE];
2942             ma_uint32 accumulationInBufferCapInFrames = sizeof(accumulationInBuffer) / ma_get_bytes_per_frame(formatOut, channelsOut);
2943             ma_uint64 totalFramesAccumulated = 0;
2944             ma_uint8* pRunningEffectBufferOut = effectBufferOut;
2945 
2946             while (totalFramesAccumulated < effectFrameCountOut) {
2947                 ma_uint64 framesToAccumulate = effectFrameCountOut - totalFramesAccumulated;
2948                 if (framesToAccumulate > accumulationInBufferCapInFrames) {
2949                     framesToAccumulate = accumulationInBufferCapInFrames;
2950                 }
2951 
2952                 /* We know how many frames to process in this iteration, so first of all do the conversion from the effect's output to the final output format.*/
2953                 ma_convert_pcm_frames_format_and_channels(accumulationInBuffer, formatOut, channelsOut, pRunningEffectBufferOut, effectFormatOut, effectChannelsOut, framesToAccumulate, ma_dither_mode_none);
2954 
2955                 /* We have the data in the final output format, so now we just accumulate or overwrite. */
2956                 if (isAccumulation) {
2957                     ma_unclipped_accumulate_pcm_frames(ma_offset_ptr(pRunningDst, totalFramesAccumulated * ma_get_accumulation_bytes_per_frame(formatOut, channelsOut)), accumulationInBuffer, framesToAccumulate, formatOut, channelsOut);
2958                 } else {
2959                     ma_clip_pcm_frames(ma_offset_ptr(pRunningDst, totalFramesAccumulated * ma_get_bytes_per_frame(formatOut, channelsOut)), accumulationInBuffer, framesToAccumulate, formatOut, channelsOut);
2960                 }
2961 
2962                 totalFramesAccumulated += framesToAccumulate;
2963                 pRunningEffectBufferOut = ma_offset_ptr(pRunningEffectBufferOut, framesToAccumulate * ma_get_bytes_per_frame(formatOut, channelsOut));
2964             }
2965         }
2966 
2967         totalFramesProcessedIn  += effectFrameCountIn;
2968         totalFramesProcessedOut += effectFrameCountOut;
2969 
2970         pRunningSrc = ma_offset_ptr(pRunningSrc, effectFrameCountIn * ma_get_accumulation_bytes_per_frame(formatIn, channelsIn));
2971         if (isAccumulation) {
2972             pRunningDst = ma_offset_ptr(pRunningDst, effectFrameCountOut * ma_get_accumulation_bytes_per_frame(formatIn, channelsIn));
2973         } else {
2974             pRunningDst = ma_offset_ptr(pRunningDst, effectFrameCountOut * ma_get_bytes_per_frame(formatOut, channelsOut));
2975         }
2976     }
2977 
2978     return MA_SUCCESS;
2979 }
2980 
2981 
ma_mix_pcm_frames_u8(ma_int16 * pDst,const ma_uint8 * pSrc,ma_uint32 channels,ma_uint64 frameCount,float volume)2982 static ma_result ma_mix_pcm_frames_u8(ma_int16* pDst, const ma_uint8* pSrc, ma_uint32 channels, ma_uint64 frameCount, float volume)
2983 {
2984     ma_uint64 iSample;
2985     ma_uint64 sampleCount;
2986 
2987     if (pDst == NULL || pSrc == NULL || channels == 0) {
2988         return MA_INVALID_ARGS;
2989     }
2990 
2991     if (volume == 0) {
2992         return MA_SUCCESS;  /* No changes if the volume is 0. */
2993     }
2994 
2995     sampleCount = frameCount * channels;
2996 
2997     if (volume == 1) {
2998         for (iSample = 0; iSample < sampleCount; iSample += 1) {
2999             pDst[iSample] += ma_pcm_sample_u8_to_s16_no_scale(pSrc[iSample]);
3000         }
3001     } else {
3002         ma_int16 volumeFixed = ma_float_to_fixed_16(volume);
3003         for (iSample = 0; iSample < sampleCount; iSample += 1) {
3004             pDst[iSample] += ma_apply_volume_unclipped_u8(ma_pcm_sample_u8_to_s16_no_scale(pSrc[iSample]), volumeFixed);
3005         }
3006     }
3007 
3008     return MA_SUCCESS;
3009 }
3010 
ma_mix_pcm_frames_s16(ma_int32 * pDst,const ma_int16 * pSrc,ma_uint32 channels,ma_uint64 frameCount,float volume)3011 static ma_result ma_mix_pcm_frames_s16(ma_int32* pDst, const ma_int16* pSrc, ma_uint32 channels, ma_uint64 frameCount, float volume)
3012 {
3013     ma_uint64 iSample;
3014     ma_uint64 sampleCount;
3015 
3016     if (pDst == NULL || pSrc == NULL || channels == 0) {
3017         return MA_INVALID_ARGS;
3018     }
3019 
3020     if (volume == 0) {
3021         return MA_SUCCESS;  /* No changes if the volume is 0. */
3022     }
3023 
3024     sampleCount = frameCount * channels;
3025 
3026     if (volume == 1) {
3027         for (iSample = 0; iSample < sampleCount; iSample += 1) {
3028             pDst[iSample] += pSrc[iSample];
3029         }
3030     } else {
3031         ma_int16 volumeFixed = ma_float_to_fixed_16(volume);
3032         for (iSample = 0; iSample < sampleCount; iSample += 1) {
3033             pDst[iSample] += ma_apply_volume_unclipped_s16(pSrc[iSample], volumeFixed);
3034         }
3035     }
3036 
3037     return MA_SUCCESS;
3038 }
3039 
ma_mix_pcm_frames_s24(ma_int64 * pDst,const ma_uint8 * pSrc,ma_uint32 channels,ma_uint64 frameCount,float volume)3040 static ma_result ma_mix_pcm_frames_s24(ma_int64* pDst, const ma_uint8* pSrc, ma_uint32 channels, ma_uint64 frameCount, float volume)
3041 {
3042     ma_uint64 iSample;
3043     ma_uint64 sampleCount;
3044 
3045     if (pDst == NULL || pSrc == NULL || channels == 0) {
3046         return MA_INVALID_ARGS;
3047     }
3048 
3049     if (volume == 0) {
3050         return MA_SUCCESS;  /* No changes if the volume is 0. */
3051     }
3052 
3053     sampleCount = frameCount * channels;
3054 
3055     if (volume == 1) {
3056         for (iSample = 0; iSample < sampleCount; iSample += 1) {
3057             pDst[iSample] += ma_pcm_sample_s24_to_s32_no_scale(&pSrc[iSample*3]);
3058         }
3059     } else {
3060         ma_int16 volumeFixed = ma_float_to_fixed_16(volume);
3061         for (iSample = 0; iSample < sampleCount; iSample += 1) {
3062             pDst[iSample] += ma_apply_volume_unclipped_s24(ma_pcm_sample_s24_to_s32_no_scale(&pSrc[iSample*3]), volumeFixed);
3063         }
3064     }
3065 
3066     return MA_SUCCESS;
3067 }
3068 
ma_mix_pcm_frames_s32(ma_int64 * pDst,const ma_int32 * pSrc,ma_uint32 channels,ma_uint64 frameCount,float volume)3069 static ma_result ma_mix_pcm_frames_s32(ma_int64* pDst, const ma_int32* pSrc, ma_uint32 channels, ma_uint64 frameCount, float volume)
3070 {
3071     ma_uint64 iSample;
3072     ma_uint64 sampleCount;
3073 
3074     if (pDst == NULL || pSrc == NULL || channels == 0) {
3075         return MA_INVALID_ARGS;
3076     }
3077 
3078     if (volume == 0) {
3079         return MA_SUCCESS;  /* No changes if the volume is 0. */
3080     }
3081 
3082 
3083     sampleCount = frameCount * channels;
3084 
3085     if (volume == 1) {
3086         for (iSample = 0; iSample < sampleCount; iSample += 1) {
3087             pDst[iSample] += pSrc[iSample];
3088         }
3089     } else {
3090         ma_int16 volumeFixed = ma_float_to_fixed_16(volume);
3091         for (iSample = 0; iSample < sampleCount; iSample += 1) {
3092             pDst[iSample] += ma_apply_volume_unclipped_s32(pSrc[iSample], volumeFixed);
3093         }
3094     }
3095 
3096     return MA_SUCCESS;
3097 }
3098 
ma_mix_pcm_frames_f32(float * pDst,const float * pSrc,ma_uint32 channels,ma_uint64 frameCount,float volume)3099 static ma_result ma_mix_pcm_frames_f32(float* pDst, const float* pSrc, ma_uint32 channels, ma_uint64 frameCount, float volume)
3100 {
3101     ma_uint64 iSample;
3102     ma_uint64 sampleCount;
3103 
3104     if (pDst == NULL || pSrc == NULL || channels == 0) {
3105         return MA_INVALID_ARGS;
3106     }
3107 
3108     if (volume == 0) {
3109         return MA_SUCCESS;  /* No changes if the volume is 0. */
3110     }
3111 
3112     sampleCount = frameCount * channels;
3113 
3114     if (volume == 1) {
3115         for (iSample = 0; iSample < sampleCount; iSample += 1) {
3116             pDst[iSample] += pSrc[iSample];
3117         }
3118     } else {
3119         for (iSample = 0; iSample < sampleCount; iSample += 1) {
3120             pDst[iSample] += ma_apply_volume_unclipped_f32(pSrc[iSample], volume);
3121         }
3122     }
3123 
3124     return MA_SUCCESS;
3125 }
3126 
ma_mix_pcm_frames(void * pDst,const void * pSrc,ma_uint64 frameCount,ma_format format,ma_uint32 channels,float volume)3127 static ma_result ma_mix_pcm_frames(void* pDst, const void* pSrc, ma_uint64 frameCount, ma_format format, ma_uint32 channels, float volume)
3128 {
3129     ma_result result;
3130 
3131     switch (format)
3132     {
3133         case ma_format_u8:  result = ma_mix_pcm_frames_u8( (ma_int16*)pDst, (const ma_uint8*)pSrc, channels, frameCount, volume); break;
3134         case ma_format_s16: result = ma_mix_pcm_frames_s16((ma_int32*)pDst, (const ma_int16*)pSrc, channels, frameCount, volume); break;
3135         case ma_format_s24: result = ma_mix_pcm_frames_s24((ma_int64*)pDst, (const ma_uint8*)pSrc, channels, frameCount, volume); break;
3136         case ma_format_s32: result = ma_mix_pcm_frames_s32((ma_int64*)pDst, (const ma_int32*)pSrc, channels, frameCount, volume); break;
3137         case ma_format_f32: result = ma_mix_pcm_frames_f32((   float*)pDst, (const    float*)pSrc, channels, frameCount, volume); break;
3138         default: return MA_INVALID_ARGS;    /* Unknown format. */
3139     }
3140 
3141     return result;
3142 }
3143 
ma_mix_pcm_frames_ex(void * pDst,ma_format formatOut,ma_uint32 channelsOut,const void * pSrc,ma_format formatIn,ma_uint32 channelsIn,ma_uint64 frameCount,float volume)3144 static ma_result ma_mix_pcm_frames_ex(void* pDst, ma_format formatOut, ma_uint32 channelsOut, const void* pSrc, ma_format formatIn, ma_uint32 channelsIn, ma_uint64 frameCount, float volume)
3145 {
3146     if (pDst == NULL || pSrc == NULL) {
3147         return MA_INVALID_ARGS;
3148     }
3149 
3150     if (formatOut == formatIn && channelsOut == channelsIn) {
3151         /* Fast path. */
3152         return ma_mix_pcm_frames(pDst, pSrc, frameCount, formatOut, channelsOut, volume);
3153     } else {
3154         /* Slow path. Data conversion required. */
3155         ma_uint8  buffer[MA_DATA_CONVERTER_STACK_BUFFER_SIZE];
3156         ma_uint32 bufferCapInFrames = sizeof(buffer) / ma_get_bytes_per_frame(formatOut, channelsOut);
3157         ma_uint64 totalFramesProcessed = 0;
3158         /* */ void* pRunningDst = pDst;
3159         const void* pRunningSrc = pSrc;
3160 
3161         while (totalFramesProcessed < frameCount) {
3162             ma_uint64 framesToProcess = frameCount - totalFramesProcessed;
3163             if (framesToProcess > bufferCapInFrames) {
3164                 framesToProcess = bufferCapInFrames;
3165             }
3166 
3167             /* Conversion. */
3168             ma_convert_pcm_frames_format_and_channels(buffer, formatOut, channelsOut, pRunningSrc, formatIn, channelsIn, framesToProcess, ma_dither_mode_none);
3169 
3170             /* Mixing. */
3171             ma_mix_pcm_frames(pRunningDst, buffer, framesToProcess, formatOut, channelsOut, volume);
3172 
3173             totalFramesProcessed += framesToProcess;
3174             pRunningDst = ma_offset_ptr(pRunningDst, framesToProcess * ma_get_accumulation_bytes_per_frame(formatOut, channelsOut));
3175             pRunningSrc = ma_offset_ptr(pRunningSrc, framesToProcess * ma_get_bytes_per_frame(formatIn, channelsIn));
3176         }
3177     }
3178 
3179     return MA_SUCCESS;
3180 }
3181 
3182 
ma_mix_accumulation_buffers_u8(ma_int16 * pDst,const ma_int16 * pSrc,ma_uint64 sampleCount,float volume)3183 static void ma_mix_accumulation_buffers_u8(ma_int16* pDst, const ma_int16* pSrc, ma_uint64 sampleCount, float volume)
3184 {
3185     ma_uint64 iSample;
3186     ma_int16  volumeFixed;
3187 
3188     MA_ASSERT(pDst != NULL);
3189     MA_ASSERT(pSrc != NULL);
3190 
3191     volumeFixed = ma_float_to_fixed_16(volume);
3192 
3193     for (iSample = 0; iSample < sampleCount; iSample += 1) {
3194         pDst[iSample] += ma_apply_volume_unclipped_u8(pSrc[iSample], volumeFixed);
3195     }
3196 }
3197 
ma_mix_accumulation_buffers_s16(ma_int32 * pDst,const ma_int32 * pSrc,ma_uint64 sampleCount,float volume)3198 static void ma_mix_accumulation_buffers_s16(ma_int32* pDst, const ma_int32* pSrc, ma_uint64 sampleCount, float volume)
3199 {
3200     ma_uint64 iSample;
3201     ma_int16  volumeFixed;
3202 
3203     MA_ASSERT(pDst != NULL);
3204     MA_ASSERT(pSrc != NULL);
3205 
3206     volumeFixed = ma_float_to_fixed_16(volume);
3207 
3208     for (iSample = 0; iSample < sampleCount; iSample += 1) {
3209         pDst[iSample] += ma_apply_volume_unclipped_s16(pSrc[iSample], volumeFixed);
3210     }
3211 }
3212 
ma_mix_accumulation_buffers_s24(ma_int64 * pDst,const ma_int64 * pSrc,ma_uint64 sampleCount,float volume)3213 static void ma_mix_accumulation_buffers_s24(ma_int64* pDst, const ma_int64* pSrc, ma_uint64 sampleCount, float volume)
3214 {
3215     ma_uint64 iSample;
3216     ma_int16  volumeFixed;
3217 
3218     MA_ASSERT(pDst != NULL);
3219     MA_ASSERT(pSrc != NULL);
3220 
3221     volumeFixed = ma_float_to_fixed_16(volume);
3222 
3223     for (iSample = 0; iSample < sampleCount; iSample += 1) {
3224         pDst[iSample] += ma_apply_volume_unclipped_s24(pSrc[iSample], volumeFixed);
3225     }
3226 }
3227 
ma_mix_accumulation_buffers_s32(ma_int64 * pDst,const ma_int64 * pSrc,ma_uint64 sampleCount,float volume)3228 static void ma_mix_accumulation_buffers_s32(ma_int64* pDst, const ma_int64* pSrc, ma_uint64 sampleCount, float volume)
3229 {
3230     ma_uint64 iSample;
3231     ma_int16  volumeFixed;
3232 
3233     MA_ASSERT(pDst != NULL);
3234     MA_ASSERT(pSrc != NULL);
3235 
3236     volumeFixed = ma_float_to_fixed_16(volume);
3237 
3238     for (iSample = 0; iSample < sampleCount; iSample += 1) {
3239         pDst[iSample] += ma_apply_volume_unclipped_s32(pSrc[iSample], volumeFixed);
3240     }
3241 }
3242 
ma_mix_accumulation_buffers_f32(float * pDst,const float * pSrc,ma_uint64 sampleCount,float volume)3243 static void ma_mix_accumulation_buffers_f32(float* pDst, const float* pSrc, ma_uint64 sampleCount, float volume)
3244 {
3245     ma_uint64 iSample;
3246 
3247     MA_ASSERT(pDst != NULL);
3248     MA_ASSERT(pSrc != NULL);
3249 
3250     for (iSample = 0; iSample < sampleCount; iSample += 1) {
3251         pDst[iSample] += ma_apply_volume_unclipped_f32(pSrc[iSample], volume);
3252     }
3253 }
3254 
ma_mix_accumulation_buffers(void * pDst,const void * pSrc,ma_uint64 frameCount,ma_format formatIn,ma_uint32 channelsIn,float volume)3255 static void ma_mix_accumulation_buffers(void* pDst, const void* pSrc, ma_uint64 frameCount, ma_format formatIn, ma_uint32 channelsIn, float volume)
3256 {
3257     ma_uint64 sampleCount;
3258 
3259     MA_ASSERT(pDst != NULL);
3260     MA_ASSERT(pSrc != NULL);
3261 
3262     sampleCount = frameCount * channelsIn;
3263 
3264     switch (formatIn)
3265     {
3266         case ma_format_u8:  ma_mix_accumulation_buffers_u8( (ma_int16*)pDst, (const ma_int16*)pSrc, sampleCount, volume); break;
3267         case ma_format_s16: ma_mix_accumulation_buffers_s16((ma_int32*)pDst, (const ma_int32*)pSrc, sampleCount, volume); break;
3268         case ma_format_s24: ma_mix_accumulation_buffers_s24((ma_int64*)pDst, (const ma_int64*)pSrc, sampleCount, volume); break;
3269         case ma_format_s32: ma_mix_accumulation_buffers_s32((ma_int64*)pDst, (const ma_int64*)pSrc, sampleCount, volume); break;
3270         case ma_format_f32: ma_mix_accumulation_buffers_f32((   float*)pDst, (const    float*)pSrc, sampleCount, volume); break;
3271         default: break;
3272     }
3273 }
3274 
ma_mix_accumulation_buffers_ex(void * pDst,ma_format formatOut,ma_uint32 channelsOut,const void * pSrc,ma_format formatIn,ma_uint32 channelsIn,ma_uint64 frameCount,float volume)3275 static void ma_mix_accumulation_buffers_ex(void* pDst, ma_format formatOut, ma_uint32 channelsOut, const void* pSrc, ma_format formatIn, ma_uint32 channelsIn, ma_uint64 frameCount, float volume)
3276 {
3277     if (formatOut == formatIn && channelsOut == channelsIn) {
3278         /* Fast path. No conversion required. */
3279         ma_mix_accumulation_buffers(pDst, pSrc, frameCount, formatIn, channelsIn, volume);
3280     } else {
3281         /* Slow path. Conversion required. The way we're going to do this is clip the input buffer, and then use existing mixing infrastructure to mix as if it were regular input. */
3282         ma_uint8  clippedSrcBuffer[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; /* formatIn, channelsIn */
3283         ma_uint32 clippedSrcBufferCapInFrames = sizeof(clippedSrcBuffer) / ma_get_bytes_per_frame(formatIn, channelsIn);
3284         ma_uint64 totalFramesProcessed = 0;
3285         /* */ void* pRunningDst = pDst;
3286         const void* pRunningSrc = pSrc;
3287 
3288         while (totalFramesProcessed < frameCount) {
3289             ma_uint64 framesToProcess = frameCount - totalFramesProcessed;
3290             if (framesToProcess > clippedSrcBufferCapInFrames) {
3291                 framesToProcess = clippedSrcBufferCapInFrames;
3292             }
3293 
3294             /* Volume and clip. */
3295             ma_volume_and_clip_pcm_frames(clippedSrcBuffer, pRunningSrc, framesToProcess, formatIn, channelsIn, volume);
3296 
3297             /* Mix. */
3298             ma_mix_pcm_frames_ex(pRunningDst, formatOut, channelsOut, clippedSrcBuffer, formatIn, channelsIn, framesToProcess, 1);
3299 
3300             totalFramesProcessed += framesToProcess;
3301             pRunningDst = ma_offset_ptr(pRunningDst, framesToProcess * ma_get_accumulation_bytes_per_frame(formatOut, channelsOut));
3302             pRunningSrc = ma_offset_ptr(pRunningSrc, framesToProcess * ma_get_accumulation_bytes_per_frame(formatIn,  channelsIn ));
3303         }
3304     }
3305 }
3306 
3307 
3308 
3309 
ma_mixer_config_init(ma_format format,ma_uint32 channels,ma_uint64 accumulationBufferSizeInFrames,void * pPreAllocatedAccumulationBuffer,const ma_allocation_callbacks * pAllocationCallbacks)3310 MA_API ma_mixer_config ma_mixer_config_init(ma_format format, ma_uint32 channels, ma_uint64 accumulationBufferSizeInFrames, void* pPreAllocatedAccumulationBuffer, const ma_allocation_callbacks* pAllocationCallbacks)
3311 {
3312     ma_mixer_config config;
3313 
3314     MA_ZERO_OBJECT(&config);
3315     config.format = format;
3316     config.channels = channels;
3317     config.accumulationBufferSizeInFrames = accumulationBufferSizeInFrames;
3318     config.pPreAllocatedAccumulationBuffer = pPreAllocatedAccumulationBuffer;
3319     config.volume = 1;
3320     ma_allocation_callbacks_init_copy(&config.allocationCallbacks, pAllocationCallbacks);
3321 
3322     return config;
3323 }
3324 
3325 
ma_mixer_init(ma_mixer_config * pConfig,ma_mixer * pMixer)3326 MA_API ma_result ma_mixer_init(ma_mixer_config* pConfig, ma_mixer* pMixer)
3327 {
3328     if (pMixer == NULL) {
3329         return MA_INVALID_ARGS;
3330     }
3331 
3332     MA_ZERO_OBJECT(pMixer);
3333 
3334     if (pConfig == NULL) {
3335         return MA_INVALID_ARGS;
3336     }
3337 
3338     if (pConfig->accumulationBufferSizeInFrames == 0) {
3339         return MA_INVALID_ARGS; /* Must have an accumulation buffer. */
3340     }
3341 
3342     pMixer->format                         = pConfig->format;
3343     pMixer->channels                       = pConfig->channels;
3344     pMixer->accumulationBufferSizeInFrames = pConfig->accumulationBufferSizeInFrames;
3345     pMixer->pAccumulationBuffer            = pConfig->pPreAllocatedAccumulationBuffer;
3346     ma_allocation_callbacks_init_copy(&pMixer->allocationCallbacks, &pConfig->allocationCallbacks);
3347     pMixer->volume                         = pConfig->volume;
3348 
3349     if (pMixer->pAccumulationBuffer == NULL) {
3350         ma_uint64 accumulationBufferSizeInBytes = pConfig->accumulationBufferSizeInFrames * ma_get_accumulation_bytes_per_frame(pMixer->format, pMixer->channels);
3351         if (accumulationBufferSizeInBytes > MA_SIZE_MAX) {
3352             return MA_OUT_OF_MEMORY;
3353         }
3354 
3355         pMixer->pAccumulationBuffer = ma__malloc_from_callbacks((size_t)accumulationBufferSizeInBytes, &pMixer->allocationCallbacks);   /* Safe cast. */
3356         if (pMixer->pAccumulationBuffer == NULL) {
3357             return MA_OUT_OF_MEMORY;
3358         }
3359 
3360         pMixer->ownsAccumulationBuffer = MA_TRUE;
3361     } else {
3362         pMixer->ownsAccumulationBuffer = MA_FALSE;
3363     }
3364 
3365     return MA_SUCCESS;
3366 }
3367 
ma_mixer_uninit(ma_mixer * pMixer)3368 MA_API void ma_mixer_uninit(ma_mixer* pMixer)
3369 {
3370     if (pMixer == NULL) {
3371         return;
3372     }
3373 
3374     if (pMixer->ownsAccumulationBuffer) {
3375         ma__free_from_callbacks(pMixer->pAccumulationBuffer, &pMixer->allocationCallbacks);
3376     }
3377 }
3378 
ma_mixer_begin(ma_mixer * pMixer,ma_mixer * pParentMixer,ma_uint64 * pFrameCountOut,ma_uint64 * pFrameCountIn)3379 MA_API ma_result ma_mixer_begin(ma_mixer* pMixer, ma_mixer* pParentMixer, ma_uint64* pFrameCountOut, ma_uint64* pFrameCountIn)
3380 {
3381     ma_uint64 frameCountOut;
3382     ma_uint64 frameCountIn;
3383 
3384     if (pMixer == NULL) {
3385         return MA_INVALID_ARGS;
3386     }
3387 
3388     if (pMixer->mixingState.isInsideBeginEnd == MA_TRUE) {
3389         return MA_INVALID_OPERATION;    /* Cannot call this while already inside a begin/end pair. */
3390     }
3391 
3392     /* If we're submixing we need to make the frame counts compatible with the parent mixer. */
3393     if (pParentMixer != NULL) {
3394         /* The output frame count must match the input frame count of the parent. If this cannot be accommodated we need to fail. */
3395         frameCountOut = pParentMixer->mixingState.frameCountIn;
3396         frameCountIn  = frameCountOut;
3397     } else {
3398         if (pFrameCountOut == NULL) {
3399             return MA_INVALID_ARGS; /* The desired output frame count is required for a root level mixer. */
3400         }
3401 
3402         frameCountOut = *pFrameCountOut;
3403     }
3404 
3405     if (pMixer->pEffect != NULL) {
3406         frameCountIn = ma_effect_get_required_input_frame_count(pMixer->pEffect, frameCountOut);
3407         if (frameCountIn > pMixer->accumulationBufferSizeInFrames) {
3408             /*
3409             The required number of input frames for the requested number of output frames is too much to fit in the accumulation buffer. We need
3410             to reduce the output frame count to accommodate.
3411             */
3412             ma_uint64 newFrameCountOut;
3413             newFrameCountOut = ma_effect_get_expected_output_frame_count(pMixer->pEffect, pMixer->accumulationBufferSizeInFrames);
3414             MA_ASSERT(newFrameCountOut <= frameCountOut);
3415 
3416             frameCountOut = newFrameCountOut;
3417             frameCountIn  = ma_effect_get_required_input_frame_count(pMixer->pEffect, frameCountOut);
3418         }
3419     } else {
3420         frameCountIn = frameCountOut;
3421 
3422         if (frameCountIn  > pMixer->accumulationBufferSizeInFrames) {
3423             frameCountIn  = pMixer->accumulationBufferSizeInFrames;
3424             frameCountOut = pMixer->accumulationBufferSizeInFrames;
3425         }
3426     }
3427 
3428     /* If the output frame count cannot match the parent's input frame count we need to fail. */
3429     if (pParentMixer != NULL && frameCountOut != pParentMixer->mixingState.frameCountIn) {
3430         return MA_INVALID_OPERATION;    /* Not compatible with the parent mixer. */
3431     }
3432 
3433     pMixer->mixingState.isInsideBeginEnd = MA_TRUE;
3434     pMixer->mixingState.frameCountOut    = frameCountOut;
3435     pMixer->mixingState.frameCountIn     = frameCountIn;
3436 
3437     ma_zero_memory_64(pMixer->pAccumulationBuffer, frameCountIn * ma_get_accumulation_bytes_per_frame(pMixer->format, pMixer->channels));
3438 
3439     if (pFrameCountOut != NULL) {
3440         *pFrameCountOut = frameCountOut;
3441     }
3442     if (pFrameCountIn != NULL) {
3443         *pFrameCountIn  = frameCountIn;
3444     }
3445 
3446     return MA_SUCCESS;
3447 }
3448 
ma_mixer_end(ma_mixer * pMixer,ma_mixer * pParentMixer,void * pFramesOut,ma_uint64 outputOffsetInFrames)3449 MA_API ma_result ma_mixer_end(ma_mixer* pMixer, ma_mixer* pParentMixer, void* pFramesOut, ma_uint64 outputOffsetInFrames)
3450 {
3451     if (pMixer == NULL) {
3452         return MA_INVALID_ARGS;
3453     }
3454 
3455     /* It's an error for both pParentMixer and pFramesOut to be NULL. */
3456     if (pParentMixer == NULL && pFramesOut == NULL) {
3457         return MA_INVALID_ARGS;
3458     }
3459 
3460     /* If both pParentMixer and pFramesOut are both non-NULL, it indicates an error on the callers side. Make sure they're aware of it. */
3461     if (pParentMixer != NULL && pFramesOut != NULL) {
3462         MA_ASSERT(MA_FALSE);
3463         return MA_INVALID_ARGS;
3464     }
3465 
3466     if (pMixer->mixingState.isInsideBeginEnd == MA_FALSE) {
3467         return MA_INVALID_OPERATION;    /* No matching begin. */
3468     }
3469 
3470     /* Completely different paths if we're outputting to a parent mixer rather than directly to an output buffer. */
3471     if (pParentMixer != NULL) {
3472         ma_format localFormatOut;
3473         ma_uint32 localChannelsOut;
3474         ma_format parentFormatIn;
3475         ma_uint32 parentChannelsIn;
3476         void* pDst;
3477 
3478         /*
3479         We need to accumulate the output of pMixer straight into the accumulation buffer of pParentMixer. If the output format of pMixer is different
3480         to the input format of pParentMixer it needs to be converted.
3481         */
3482         ma_mixer_get_output_data_format(pMixer, &localFormatOut, &localChannelsOut);
3483         ma_mixer_get_input_data_format(pParentMixer, &parentFormatIn, &parentChannelsIn);
3484 
3485         /* A reminder that the output frame count of pMixer must match the input frame count of pParentMixer. */
3486         MA_ASSERT(pMixer->mixingState.frameCountOut == pParentMixer->mixingState.frameCountIn);
3487 
3488         pDst = ma_offset_ptr(pParentMixer->pAccumulationBuffer, outputOffsetInFrames * ma_get_accumulation_bytes_per_frame(parentFormatIn, parentChannelsIn));
3489 
3490         if (pMixer->pEffect == NULL) {
3491             /* No effect. Input needs to come straight from the accumulation buffer. */
3492             ma_mix_accumulation_buffers_ex(pDst, parentFormatIn, parentChannelsIn, pMixer->pAccumulationBuffer, localFormatOut, localChannelsOut, pMixer->mixingState.frameCountOut, pMixer->volume);
3493         } else {
3494             /* With effect. Input needs to be pre-processed from the effect. */
3495             ma_volume_and_clip_and_effect_pcm_frames(pDst, parentFormatIn, parentChannelsIn, pParentMixer->mixingState.frameCountIn, pMixer->pAccumulationBuffer, pMixer->format, pMixer->channels, pMixer->mixingState.frameCountIn, pMixer->volume, pMixer->pEffect, /*isAccumulation*/ MA_TRUE);
3496         }
3497     } else {
3498         /* We're not submixing so we can overwite. */
3499         void* pDst;
3500 
3501         pDst = ma_offset_ptr(pFramesOut, outputOffsetInFrames * ma_get_bytes_per_frame(pMixer->format, pMixer->channels));
3502 
3503         if (pMixer->pEffect == NULL) {
3504             /* All we need to do is convert the accumulation buffer to the output format. */
3505             ma_volume_and_clip_pcm_frames(pDst, pMixer->pAccumulationBuffer, pMixer->mixingState.frameCountOut, pMixer->format, pMixer->channels, pMixer->volume);
3506         } else {
3507             /* We need to run our accumulation through the effect. */
3508             ma_volume_and_clip_and_effect_pcm_frames(pDst, pMixer->format, pMixer->channels, pMixer->mixingState.frameCountOut, pMixer->pAccumulationBuffer, pMixer->format, pMixer->channels, pMixer->mixingState.frameCountIn, pMixer->volume, pMixer->pEffect, /*isAccumulation*/ MA_FALSE);
3509         }
3510     }
3511 
3512     pMixer->mixingState.isInsideBeginEnd = MA_FALSE;
3513     pMixer->mixingState.frameCountOut    = 0;
3514     pMixer->mixingState.frameCountIn     = 0;
3515 
3516     return MA_SUCCESS;
3517 }
3518 
ma_mixer_mix_pcm_frames(ma_mixer * pMixer,const void * pFramesIn,ma_uint64 offsetInFrames,ma_uint64 frameCountIn,float volume,ma_format formatIn,ma_uint32 channelsIn)3519 MA_API ma_result ma_mixer_mix_pcm_frames(ma_mixer* pMixer, const void* pFramesIn, ma_uint64 offsetInFrames, ma_uint64 frameCountIn, float volume, ma_format formatIn, ma_uint32 channelsIn)
3520 {
3521     if (pMixer == NULL || pFramesIn == NULL) {
3522         return MA_INVALID_ARGS;
3523     }
3524 
3525     if (frameCountIn > pMixer->mixingState.frameCountIn) {
3526         return MA_INVALID_ARGS; /* Passing in too many input frames. */
3527     }
3528 
3529     ma_mix_pcm_frames(ma_offset_ptr(pMixer->pAccumulationBuffer, offsetInFrames * ma_get_accumulation_bytes_per_frame(pMixer->format, pMixer->channels)), pFramesIn, frameCountIn, formatIn, channelsIn, volume);
3530 
3531     return MA_SUCCESS;
3532 }
3533 
ma_mixer_mix_data_source_mmap(ma_mixer * pMixer,ma_data_source * pDataSource,ma_uint64 offsetInFrames,ma_uint64 frameCountIn,ma_uint64 * pFrameCountOut,float volume,ma_effect * pEffect,ma_format formatIn,ma_uint32 channelsIn,ma_bool32 loop)3534 static ma_result ma_mixer_mix_data_source_mmap(ma_mixer* pMixer, ma_data_source* pDataSource, ma_uint64 offsetInFrames, ma_uint64 frameCountIn, ma_uint64* pFrameCountOut, float volume, ma_effect* pEffect, ma_format formatIn, ma_uint32 channelsIn, ma_bool32 loop)
3535 {
3536     ma_result result = MA_SUCCESS;
3537     ma_uint64 totalFramesProcessed = 0;
3538     void* pRunningAccumulationBuffer = NULL;
3539     ma_bool32 preEffectConversionRequired = MA_FALSE;
3540     ma_format effectFormatIn = ma_format_unknown;
3541     ma_uint32 effectChannelsIn = 0;
3542     ma_format effectFormatOut = ma_format_unknown;
3543     ma_uint32 effectChannelsOut = 0;
3544 
3545     MA_ASSERT(pMixer      != NULL);
3546     MA_ASSERT(pDataSource != NULL);
3547 
3548     if (pFrameCountOut != NULL) {
3549         *pFrameCountOut = 0;
3550     }
3551 
3552     if ((offsetInFrames + frameCountIn) > pMixer->mixingState.frameCountIn) {
3553         return MA_INVALID_ARGS; /* Passing in too many input frames. */
3554     }
3555 
3556     /* Initially offset the accumulation buffer by the offset. */
3557     pRunningAccumulationBuffer = ma_offset_ptr(pMixer->pAccumulationBuffer, offsetInFrames * ma_get_accumulation_bytes_per_frame(pMixer->format, pMixer->channels));
3558 
3559     if (pEffect != NULL) {
3560         /* We need to know the effect's input and output data format before we'll be able to apply it properly. */
3561         result = ma_effect_get_input_data_format(pEffect, &effectFormatIn, &effectChannelsIn, NULL);
3562         if (result != MA_SUCCESS) {
3563             return result;
3564         }
3565 
3566         result = ma_effect_get_output_data_format(pEffect, &effectFormatOut, &effectChannelsOut, NULL);
3567         if (result != MA_SUCCESS) {
3568             return result;
3569         }
3570 
3571         preEffectConversionRequired = (formatIn != effectFormatIn || channelsIn != effectChannelsIn);
3572     }
3573 
3574     while (totalFramesProcessed < frameCountIn) {
3575         void* pMappedBuffer;
3576         ma_uint64 framesToProcess = frameCountIn - totalFramesProcessed;
3577 
3578         if (pEffect == NULL) {
3579             /* Fast path. Mix directly from the data source and don't bother applying an effect. */
3580             result = ma_data_source_map(pDataSource, &pMappedBuffer, &framesToProcess);
3581             if (result != MA_SUCCESS) {
3582                 break;  /* Failed to map. Abort. */
3583             }
3584 
3585             if (framesToProcess == 0) {
3586                 break;  /* Wasn't able to map any data. Abort. */
3587             }
3588 
3589             ma_mix_pcm_frames_ex(pRunningAccumulationBuffer, pMixer->format, pMixer->channels, pMappedBuffer, formatIn, channelsIn, framesToProcess, volume);
3590 
3591             result = ma_data_source_unmap(pDataSource, framesToProcess);
3592         } else {
3593             /* Slow path. Need to apply an effect. This requires the use of an intermediary buffer. */
3594             ma_uint8 effectInBuffer [MA_DATA_CONVERTER_STACK_BUFFER_SIZE];
3595             ma_uint8 effectOutBuffer[MA_DATA_CONVERTER_STACK_BUFFER_SIZE];
3596             ma_uint32 effectInBufferCap  = sizeof(effectInBuffer)  / ma_get_bytes_per_frame(effectFormatIn,  effectChannelsIn);
3597             ma_uint32 effectOutBufferCap = sizeof(effectOutBuffer) / ma_get_bytes_per_frame(effectFormatOut, effectChannelsOut);
3598             ma_uint64 framesMapped;
3599 
3600             if (framesToProcess > effectOutBufferCap) {
3601                 framesToProcess = effectOutBufferCap;
3602             }
3603 
3604             framesMapped = ma_effect_get_required_input_frame_count(pEffect, framesToProcess);
3605             if (framesMapped > effectInBufferCap) {
3606                 framesMapped = effectInBufferCap;
3607             }
3608 
3609             /* We need to map our input data first. The input data will be either fed directly into the effect, or will be converted first. */
3610             result = ma_data_source_map(pDataSource, &pMappedBuffer, &framesMapped);
3611             if (result != MA_SUCCESS) {
3612                 break;  /* Failed to map. Abort. */
3613             }
3614 
3615             /* We have the data from the data source so no we can apply the effect. */
3616             if (preEffectConversionRequired == MA_FALSE) {
3617                 /* Fast path. No format required before applying the effect. */
3618                 ma_effect_process_pcm_frames(pEffect, pMappedBuffer, &framesMapped, effectOutBuffer, &framesToProcess);
3619             } else {
3620                 /* Slow path. Need to convert the data before applying the effect. */
3621                 ma_convert_pcm_frames_format_and_channels(effectInBuffer, effectFormatIn, effectChannelsIn, pMappedBuffer, formatIn, channelsIn, framesMapped, ma_dither_mode_none);
3622                 ma_effect_process_pcm_frames(pEffect, effectInBuffer, &framesMapped, effectOutBuffer, &framesToProcess);
3623             }
3624 
3625             /* The effect has been applied so now we can mix it. */
3626             ma_mix_pcm_frames_ex(pRunningAccumulationBuffer, pMixer->format, pMixer->channels, effectOutBuffer, effectFormatOut, effectChannelsOut, framesToProcess, volume);
3627 
3628             /* We're finished with the input data. */
3629             result = ma_data_source_unmap(pDataSource, framesMapped);   /* Do this last because the result code is used below to determine whether or not we need to loop. */
3630         }
3631 
3632         totalFramesProcessed += framesToProcess;
3633         pRunningAccumulationBuffer = ma_offset_ptr(pRunningAccumulationBuffer, framesToProcess * ma_get_accumulation_bytes_per_frame(pMixer->format, pMixer->channels));
3634 
3635         if (result != MA_SUCCESS) {
3636             if (result == MA_AT_END) {
3637                 if (loop) {
3638                     ma_data_source_seek_to_pcm_frame(pDataSource, 0);
3639                     result = MA_SUCCESS;    /* Make sure we don't return MA_AT_END which will happen if we conicidentally hit the end of the data source at the same time as we finish outputting. */
3640                 } else {
3641                     break;  /* We've reached the end and we're not looping. */
3642                 }
3643             } else {
3644                 break;  /* An error occurred. */
3645             }
3646         }
3647     }
3648 
3649     if (pFrameCountOut != NULL) {
3650         *pFrameCountOut = totalFramesProcessed;
3651     }
3652 
3653     return result;
3654 }
3655 
ma_mixer_mix_data_source_read(ma_mixer * pMixer,ma_data_source * pDataSource,ma_uint64 offsetInFrames,ma_uint64 frameCountIn,ma_uint64 * pFrameCountOut,float volume,ma_effect * pEffect,ma_format formatIn,ma_uint32 channelsIn,ma_bool32 loop)3656 static ma_result ma_mixer_mix_data_source_read(ma_mixer* pMixer, ma_data_source* pDataSource, ma_uint64 offsetInFrames, ma_uint64 frameCountIn, ma_uint64* pFrameCountOut, float volume, ma_effect* pEffect, ma_format formatIn, ma_uint32 channelsIn, ma_bool32 loop)
3657 {
3658     ma_result result = MA_SUCCESS;
3659     ma_uint8  preMixBuffer[MA_DATA_CONVERTER_STACK_BUFFER_SIZE];
3660     ma_uint32 preMixBufferCap;
3661     ma_uint64 totalFramesProcessed = 0;
3662     void* pRunningAccumulationBuffer = pMixer->pAccumulationBuffer;
3663     ma_format effectFormatIn = ma_format_unknown;
3664     ma_uint32 effectChannelsIn = 0;
3665     ma_format preMixFormat = ma_format_unknown;
3666     ma_uint32 preMixChannels = 0;
3667     ma_bool32 preEffectConversionRequired = MA_FALSE;
3668 
3669     MA_ASSERT(pMixer      != NULL);
3670     MA_ASSERT(pDataSource != NULL);
3671 
3672     if (pFrameCountOut != NULL) {
3673         *pFrameCountOut = 0;
3674     }
3675 
3676     if ((offsetInFrames + frameCountIn) > pMixer->mixingState.frameCountIn) {
3677         return MA_INVALID_ARGS; /* Passing in too many input frames. */
3678     }
3679 
3680     if (pEffect == NULL) {
3681         preMixFormat   = formatIn;
3682         preMixChannels = channelsIn;
3683     } else {
3684         /* We need to know the effect's input and output data format before we'll be able to apply it properly. */
3685         result = ma_effect_get_input_data_format(pEffect, &effectFormatIn, &effectChannelsIn, NULL);
3686         if (result != MA_SUCCESS) {
3687             return result;
3688         }
3689 
3690         result = ma_effect_get_output_data_format(pEffect, &preMixFormat, &preMixChannels, NULL);
3691         if (result != MA_SUCCESS) {
3692             return result;
3693         }
3694 
3695         preEffectConversionRequired = (formatIn != effectFormatIn || channelsIn != effectChannelsIn);
3696     }
3697 
3698     preMixBufferCap = sizeof(preMixBuffer) / ma_get_bytes_per_frame(preMixFormat, preMixChannels);
3699 
3700     totalFramesProcessed = 0;
3701 
3702     /* Initially offset the accumulation buffer by the offset. */
3703     pRunningAccumulationBuffer = ma_offset_ptr(pMixer->pAccumulationBuffer, offsetInFrames * ma_get_accumulation_bytes_per_frame(pMixer->format, pMixer->channels));
3704 
3705     while (totalFramesProcessed < frameCountIn) {
3706         ma_uint64 framesRead;
3707         ma_uint64 framesToRead = frameCountIn - totalFramesProcessed;
3708         if (framesToRead > preMixBufferCap) {
3709             framesToRead = preMixBufferCap;
3710         }
3711 
3712         if (pEffect == NULL) {
3713             result = ma_data_source_read_pcm_frames(pDataSource, preMixBuffer, framesToRead, &framesRead, loop);
3714             ma_mix_pcm_frames_ex(pRunningAccumulationBuffer, pMixer->format, pMixer->channels, preMixBuffer, formatIn, channelsIn, framesRead, volume);
3715         } else {
3716             ma_uint8 callbackBuffer[MA_DATA_CONVERTER_STACK_BUFFER_SIZE];
3717             ma_uint8 effectInBuffer[MA_DATA_CONVERTER_STACK_BUFFER_SIZE];
3718             ma_uint32 callbackBufferCap = sizeof(callbackBuffer) / ma_get_bytes_per_frame(formatIn, channelsIn);
3719             ma_uint32 effectInBufferCap = sizeof(effectInBuffer) / ma_get_bytes_per_frame(effectFormatIn, effectChannelsIn);
3720             ma_uint64 effectFrameCountOut;
3721             ma_uint64 effectFrameCountIn;
3722             ma_uint64 framesReadFromCallback;
3723             ma_uint64 framesToReadFromCallback = ma_effect_get_required_input_frame_count(pEffect, framesToRead);
3724             if (framesToReadFromCallback > callbackBufferCap) {
3725                 framesToReadFromCallback = callbackBufferCap;
3726             }
3727             if (framesToReadFromCallback > effectInBufferCap) {
3728                 framesToReadFromCallback = effectInBufferCap;
3729             }
3730 
3731             /*
3732             We can now read some data from the callback. We should never read more input frame than will be consumed. If the format of the callback is the same as the effect's input
3733             format we can save ourselves a copy and run on a slightly faster path.
3734             */
3735             if (preEffectConversionRequired == MA_FALSE) {
3736                 /* Fast path. No need for conversion between the callback and the  */
3737                 result = ma_data_source_read_pcm_frames(pDataSource, effectInBuffer, framesToReadFromCallback, &framesReadFromCallback, loop);
3738             } else {
3739                 /* Slow path. Conversion between the callback and the effect required. */
3740                 result = ma_data_source_read_pcm_frames(pDataSource, callbackBuffer, framesToReadFromCallback, &framesReadFromCallback, loop);
3741                 ma_convert_pcm_frames_format_and_channels(effectInBuffer, effectFormatIn, effectChannelsIn, callbackBuffer, formatIn, channelsIn, framesReadFromCallback, ma_dither_mode_none);
3742             }
3743 
3744             /* We have our input data for the effect so now we just process as much as we can based on our input and output frame counts. */
3745             effectFrameCountIn  = framesReadFromCallback;
3746             effectFrameCountOut = framesToRead;
3747             ma_effect_process_pcm_frames(pEffect, effectInBuffer, &effectFrameCountIn, preMixBuffer, &effectFrameCountOut);
3748 
3749             /* At this point the effect should be applied and we can mix it. */
3750             framesRead = (ma_uint32)effectFrameCountOut;    /* Safe cast. */
3751             ma_mix_pcm_frames_ex(pRunningAccumulationBuffer, pMixer->format, pMixer->channels, preMixBuffer, preMixFormat, preMixChannels, framesRead, volume);
3752 
3753             /* An emergency failure case. Abort if we didn't consume any input nor any output frames. */
3754             if (framesRead == 0 && framesReadFromCallback == 0) {
3755                 break;
3756             }
3757         }
3758 
3759         totalFramesProcessed += framesRead;
3760         pRunningAccumulationBuffer = ma_offset_ptr(pRunningAccumulationBuffer, framesRead * ma_get_accumulation_bytes_per_frame(pMixer->format, pMixer->channels));
3761 
3762         /* If the data source is busy we need to end mixing now. */
3763         if (result == MA_BUSY || result == MA_AT_END) {
3764             break;
3765         }
3766     }
3767 
3768     if (pFrameCountOut != NULL) {
3769         *pFrameCountOut = totalFramesProcessed;
3770     }
3771 
3772     return result;
3773 }
3774 
ma_mixer_mix_data_source(ma_mixer * pMixer,ma_data_source * pDataSource,ma_uint64 offsetInFrames,ma_uint64 frameCountIn,ma_uint64 * pFrameCountOut,float volume,ma_effect * pEffect,ma_bool32 loop)3775 MA_API ma_result ma_mixer_mix_data_source(ma_mixer* pMixer, ma_data_source* pDataSource, ma_uint64 offsetInFrames, ma_uint64 frameCountIn, ma_uint64* pFrameCountOut, float volume, ma_effect* pEffect, ma_bool32 loop)
3776 {
3777     ma_result result;
3778     ma_format formatIn;
3779     ma_uint32 channelsIn;
3780     ma_bool32 supportsMMap = MA_FALSE;
3781     ma_data_source_callbacks* pDataSourceCallbacks = (ma_data_source_callbacks*)pDataSource;
3782 
3783     if (pMixer == NULL) {
3784         return MA_INVALID_ARGS;
3785     }
3786 
3787     result = ma_data_source_get_data_format(pDataSource, &formatIn, &channelsIn, NULL);
3788     if (result != MA_SUCCESS) {
3789         return result;
3790     }
3791 
3792     /* Use memory mapping if it's available. */
3793     if (pDataSourceCallbacks->onMap != NULL && pDataSourceCallbacks->onUnmap != NULL) {
3794         supportsMMap = MA_TRUE;
3795     }
3796 
3797     if (supportsMMap) {
3798         /* Fast path. This is memory mapping mode. */
3799         return ma_mixer_mix_data_source_mmap(pMixer, pDataSourceCallbacks, offsetInFrames, frameCountIn, pFrameCountOut, volume, pEffect, formatIn, channelsIn, loop);
3800     } else {
3801         /* Slow path. This is reading mode. */
3802         return ma_mixer_mix_data_source_read(pMixer, pDataSourceCallbacks, offsetInFrames, frameCountIn, pFrameCountOut, volume, pEffect, formatIn, channelsIn, loop);
3803     }
3804 }
3805 
3806 
3807 typedef struct
3808 {
3809     ma_data_source_callbacks ds;
3810     ma_rb* pRB;
3811     ma_format format;
3812     ma_uint32 channels;
3813     void* pMappedBuffer;
3814 } ma_rb_data_source;
3815 
ma_rb_data_source__on_map(ma_data_source * pDataSource,void ** ppFramesOut,ma_uint64 * pFrameCount)3816 static ma_result ma_rb_data_source__on_map(ma_data_source* pDataSource, void** ppFramesOut, ma_uint64* pFrameCount)
3817 {
3818     ma_rb_data_source* pRB = (ma_rb_data_source*)pDataSource;
3819     ma_result result;
3820     ma_uint32 bpf = ma_get_bytes_per_frame(pRB->format, pRB->channels);
3821     size_t sizeInBytes;
3822 
3823     sizeInBytes = (size_t)(*pFrameCount * bpf);
3824     result = ma_rb_acquire_read(pRB->pRB, &sizeInBytes, ppFramesOut);
3825     *pFrameCount = sizeInBytes / bpf;
3826 
3827     pRB->pMappedBuffer = *ppFramesOut;
3828 
3829     return result;
3830 }
3831 
ma_rb_data_source__on_unmap(ma_data_source * pDataSource,ma_uint64 frameCount)3832 static ma_result ma_rb_data_source__on_unmap(ma_data_source* pDataSource, ma_uint64 frameCount)
3833 {
3834     ma_rb_data_source* pRB = (ma_rb_data_source*)pDataSource;
3835     ma_result result;
3836     ma_uint32 bpf = ma_get_bytes_per_frame(pRB->format, pRB->channels);
3837     size_t sizeInBytes;
3838 
3839     sizeInBytes = (size_t)(frameCount * bpf);
3840     result = ma_rb_commit_read(pRB->pRB, sizeInBytes, pRB->pMappedBuffer);
3841 
3842     pRB->pMappedBuffer = NULL;
3843 
3844     return result;  /* We never actually return MA_AT_END here because a ring buffer doesn't have any notion of an end. */
3845 }
3846 
ma_rb_data_source__on_get_format(ma_data_source * pDataSource,ma_format * pFormat,ma_uint32 * pChannels,ma_uint32 * pSampleRate)3847 static ma_result ma_rb_data_source__on_get_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate)
3848 {
3849     ma_rb_data_source* pRB = (ma_rb_data_source*)pDataSource;
3850 
3851     *pFormat     = pRB->format;
3852     *pChannels   = pRB->channels;
3853     *pSampleRate = 0;   /* No sample rate. */
3854 
3855     return MA_SUCCESS;
3856 }
3857 
ma_rb_data_source_init(ma_rb * pRB,ma_format format,ma_uint32 channels,ma_rb_data_source * pDataSource)3858 static ma_result ma_rb_data_source_init(ma_rb* pRB, ma_format format, ma_uint32 channels, ma_rb_data_source* pDataSource)
3859 {
3860     if (pRB == NULL) {
3861         return MA_INVALID_ARGS;
3862     }
3863 
3864     pDataSource->ds.onRead          = NULL;
3865     pDataSource->ds.onSeek          = NULL;  /* We can't really seek in a ring buffer - there's no notion of a beginning and an end in a ring buffer. */
3866     pDataSource->ds.onMap           = ma_rb_data_source__on_map;
3867     pDataSource->ds.onUnmap         = ma_rb_data_source__on_unmap;
3868     pDataSource->ds.onGetDataFormat = ma_rb_data_source__on_get_format;
3869     pDataSource->pRB                = pRB;
3870     pDataSource->format             = format;
3871     pDataSource->channels           = channels;
3872 
3873     return MA_SUCCESS;
3874 }
3875 
ma_mixer_mix_rb(ma_mixer * pMixer,ma_rb * pRB,ma_uint64 offsetInFrames,ma_uint64 frameCountIn,ma_uint64 * pFrameCountOut,float volume,ma_effect * pEffect,ma_format formatIn,ma_uint32 channelsIn)3876 MA_API ma_result ma_mixer_mix_rb(ma_mixer* pMixer, ma_rb* pRB, ma_uint64 offsetInFrames, ma_uint64 frameCountIn, ma_uint64* pFrameCountOut, float volume, ma_effect* pEffect, ma_format formatIn, ma_uint32 channelsIn)
3877 {
3878     /* Ring buffer mixing can be implemented in terms of a memory mapped data source. */
3879     ma_rb_data_source ds;
3880     ma_rb_data_source_init(pRB, formatIn, channelsIn, &ds); /* Will never fail and does not require an uninit() implementation. */
3881 
3882     return ma_mixer_mix_data_source(pMixer, &ds, offsetInFrames, frameCountIn, pFrameCountOut, volume, pEffect, MA_TRUE);   /* Ring buffers always loop, but the loop parameter will never actually be used because ma_rb_data_source__on_unmap() will never return MA_AT_END. */
3883 }
3884 
ma_mixer_mix_pcm_rb(ma_mixer * pMixer,ma_pcm_rb * pRB,ma_uint64 offsetInFrames,ma_uint64 frameCountIn,ma_uint64 * pFrameCountOut,float volume,ma_effect * pEffect)3885 MA_API ma_result ma_mixer_mix_pcm_rb(ma_mixer* pMixer, ma_pcm_rb* pRB, ma_uint64 offsetInFrames, ma_uint64 frameCountIn, ma_uint64* pFrameCountOut, float volume, ma_effect* pEffect)
3886 {
3887     return ma_mixer_mix_rb(pMixer, &pRB->rb, offsetInFrames, frameCountIn, pFrameCountOut, volume, pEffect, pRB->format, pRB->channels);
3888 }
3889 
3890 
ma_mixer_set_volume(ma_mixer * pMixer,float volume)3891 MA_API ma_result ma_mixer_set_volume(ma_mixer* pMixer, float volume)
3892 {
3893     if (pMixer == NULL) {
3894         return MA_INVALID_ARGS;
3895     }
3896 
3897     if (volume < 0.0f || volume > 1.0f) {
3898         return MA_INVALID_ARGS;
3899     }
3900 
3901     pMixer->volume = volume;
3902 
3903     return MA_SUCCESS;
3904 }
3905 
ma_mixer_get_volume(ma_mixer * pMixer,float * pVolume)3906 MA_API ma_result ma_mixer_get_volume(ma_mixer* pMixer, float* pVolume)
3907 {
3908     if (pVolume == NULL) {
3909         return MA_INVALID_ARGS;
3910     }
3911 
3912     if (pMixer == NULL) {
3913         *pVolume = 0;
3914         return MA_INVALID_ARGS;
3915     }
3916 
3917     *pVolume = pMixer->volume;
3918 
3919     return MA_SUCCESS;
3920 }
3921 
ma_mixer_set_gain_db(ma_mixer * pMixer,float gainDB)3922 MA_API ma_result ma_mixer_set_gain_db(ma_mixer* pMixer, float gainDB)
3923 {
3924     if (gainDB > 0) {
3925         return MA_INVALID_ARGS;
3926     }
3927 
3928     return ma_mixer_set_volume(pMixer, ma_gain_db_to_factor(gainDB));
3929 }
3930 
ma_mixer_get_gain_db(ma_mixer * pMixer,float * pGainDB)3931 MA_API ma_result ma_mixer_get_gain_db(ma_mixer* pMixer, float* pGainDB)
3932 {
3933     float factor;
3934     ma_result result;
3935 
3936     if (pGainDB == NULL) {
3937         return MA_INVALID_ARGS;
3938     }
3939 
3940     result = ma_mixer_get_volume(pMixer, &factor);
3941     if (result != MA_SUCCESS) {
3942         *pGainDB = 0;
3943         return result;
3944     }
3945 
3946     *pGainDB = ma_factor_to_gain_db(factor);
3947 
3948     return MA_SUCCESS;
3949 }
3950 
3951 
ma_mixer_set_effect(ma_mixer * pMixer,ma_effect * pEffect)3952 MA_API ma_result ma_mixer_set_effect(ma_mixer* pMixer, ma_effect* pEffect)
3953 {
3954     if (pMixer == NULL) {
3955         return MA_INVALID_ARGS;
3956     }
3957 
3958     if (pMixer->pEffect == pEffect) {
3959         return MA_SUCCESS;  /* No-op. */
3960     }
3961 
3962     /* The effect cannot be changed if we're in the middle of a begin/end pair. */
3963     if (pMixer->mixingState.isInsideBeginEnd) {
3964         return MA_INVALID_OPERATION;
3965     }
3966 
3967     pMixer->pEffect = pEffect;
3968 
3969     return MA_SUCCESS;
3970 }
3971 
ma_mixer_get_effect(ma_mixer * pMixer,ma_effect ** ppEffect)3972 MA_API ma_result ma_mixer_get_effect(ma_mixer* pMixer, ma_effect** ppEffect)
3973 {
3974     if (ppEffect == NULL) {
3975         return MA_INVALID_ARGS;
3976     }
3977 
3978     *ppEffect = NULL;   /* Safety. */
3979 
3980     if (pMixer == NULL) {
3981         return MA_INVALID_ARGS;
3982     }
3983 
3984     *ppEffect = pMixer->pEffect;
3985 
3986     return MA_SUCCESS;
3987 }
3988 
ma_mixer_get_output_data_format(ma_mixer * pMixer,ma_format * pFormat,ma_uint32 * pChannels)3989 MA_API ma_result ma_mixer_get_output_data_format(ma_mixer* pMixer, ma_format* pFormat, ma_uint32* pChannels)
3990 {
3991     if (pMixer == NULL) {
3992         return MA_INVALID_ARGS;
3993     }
3994 
3995     /* If we have an effect, the output data format will be the effect's output data format. */
3996     if (pMixer->pEffect != NULL) {
3997         return ma_effect_get_output_data_format(pMixer->pEffect, pFormat, pChannels, NULL);
3998     } else {
3999         if (pFormat != NULL) {
4000             *pFormat = pMixer->format;
4001         }
4002 
4003         if (pChannels != NULL) {
4004             *pChannels = pMixer->channels;
4005         }
4006 
4007         return MA_SUCCESS;
4008     }
4009 }
4010 
ma_mixer_get_input_data_format(ma_mixer * pMixer,ma_format * pFormat,ma_uint32 * pChannels)4011 MA_API ma_result ma_mixer_get_input_data_format(ma_mixer* pMixer, ma_format* pFormat, ma_uint32* pChannels)
4012 {
4013     if (pMixer == NULL) {
4014         return MA_INVALID_ARGS;
4015     }
4016 
4017     if (pFormat != NULL) {
4018         *pFormat = pMixer->format;
4019     }
4020 
4021     if (pChannels != NULL) {
4022         *pChannels = pMixer->channels;
4023     }
4024 
4025     return MA_SUCCESS;
4026 }
4027 
4028 
4029 
4030 
4031 
ma_slot_allocator_init(ma_slot_allocator * pAllocator)4032 MA_API ma_result ma_slot_allocator_init(ma_slot_allocator* pAllocator)
4033 {
4034     if (pAllocator == NULL) {
4035         return MA_INVALID_ARGS;
4036     }
4037 
4038     MA_ZERO_OBJECT(pAllocator);
4039 
4040     return MA_SUCCESS;
4041 }
4042 
ma_slot_allocator_alloc(ma_slot_allocator * pAllocator,ma_uint64 * pSlot)4043 MA_API ma_result ma_slot_allocator_alloc(ma_slot_allocator* pAllocator, ma_uint64* pSlot)
4044 {
4045     ma_uint32 capacity;
4046     ma_uint32 iAttempt;
4047     const ma_uint32 maxAttempts = 2;    /* The number of iterations to perform until returning MA_OUT_OF_MEMORY if no slots can be found. */
4048 
4049     if (pAllocator == NULL || pSlot == NULL) {
4050         return MA_INVALID_ARGS;
4051     }
4052 
4053     capacity = ma_countof(pAllocator->groups) * 32;
4054 
4055     for (iAttempt = 0; iAttempt < maxAttempts; iAttempt += 1) {
4056         /* We need to acquire a suitable bitfield first. This is a bitfield that's got an available slot within it. */
4057         ma_uint32 iGroup;
4058         for (iGroup = 0; iGroup < ma_countof(pAllocator->groups); iGroup += 1) {
4059             /* CAS */
4060             for (;;) {
4061                 ma_uint32 newBitfield;
4062                 ma_uint32 oldBitfield;
4063                 ma_uint32 bitOffset;
4064 
4065                 oldBitfield = pAllocator->groups[iGroup].bitfield;
4066 
4067                 /* Fast check to see if anything is available. */
4068                 if (oldBitfield == 0xFFFFFFFF) {
4069                     break;  /* No available bits in this bitfield. */
4070                 }
4071 
4072                 bitOffset = ma_ffs_32(~oldBitfield);
4073                 MA_ASSERT(bitOffset < 32);
4074 
4075                 newBitfield = oldBitfield | (1 << bitOffset);
4076 
4077                 if (c89atomic_compare_and_swap_32(&pAllocator->groups[iGroup].bitfield, oldBitfield, newBitfield) == oldBitfield) {
4078                     ma_uint32 slotIndex;
4079 
4080                     /* Increment the counter as soon as possible to have other threads report out-of-memory sooner than later. */
4081                     c89atomic_fetch_add_32(&pAllocator->count, 1);
4082 
4083                     /* The slot index is required for constructing the output value. */
4084                     slotIndex = (iGroup << 5) + bitOffset;  /* iGroup << 5 = iGroup * 32 */
4085 
4086                     /* Increment the reference count before constructing the output value. */
4087                     pAllocator->slots[slotIndex] += 1;
4088 
4089                     /* Construct the output value. */
4090                     *pSlot = ((ma_uint64)pAllocator->slots[slotIndex] << 32 | slotIndex);
4091 
4092                     return MA_SUCCESS;
4093                 }
4094             }
4095         }
4096 
4097         /* We weren't able to find a slot. If it's because we've reached our capacity we need to return MA_OUT_OF_MEMORY. Otherwise we need to do another iteration and try again. */
4098         if (pAllocator->count < capacity) {
4099             ma_yield();
4100         } else {
4101             return MA_OUT_OF_MEMORY;
4102         }
4103     }
4104 
4105     /* We couldn't find a slot within the maximum number of attempts. */
4106     return MA_OUT_OF_MEMORY;
4107 }
4108 
ma_slot_allocator_free(ma_slot_allocator * pAllocator,ma_uint64 slot)4109 MA_API ma_result ma_slot_allocator_free(ma_slot_allocator* pAllocator, ma_uint64 slot)
4110 {
4111     ma_uint32 iGroup;
4112     ma_uint32 iBit;
4113 
4114     if (pAllocator == NULL) {
4115         return MA_INVALID_ARGS;
4116     }
4117 
4118     iGroup = (slot & 0xFFFFFFFF) >> 5;   /* slot / 32 */
4119     iBit   = (slot & 0xFFFFFFFF) & 31;   /* slot % 32 */
4120 
4121     if (iGroup >= ma_countof(pAllocator->groups)) {
4122         return MA_INVALID_ARGS;
4123     }
4124 
4125     MA_ASSERT(iBit < 32);   /* This must be true due to the logic we used to actually calculate it. */
4126 
4127     while (pAllocator->count > 0) {
4128         /* CAS */
4129         ma_uint32 newBitfield;
4130         ma_uint32 oldBitfield;
4131 
4132         oldBitfield = pAllocator->groups[iGroup].bitfield;
4133         newBitfield = oldBitfield & ~(1 << iBit);
4134 
4135         if (c89atomic_compare_and_swap_32(&pAllocator->groups[iGroup].bitfield, oldBitfield, newBitfield) == oldBitfield) {
4136             c89atomic_fetch_sub_32(&pAllocator->count, 1);
4137             return MA_SUCCESS;
4138         }
4139     }
4140 
4141     /* Getting here means there are no allocations available for freeing. */
4142     return MA_INVALID_OPERATION;
4143 }
4144 
4145 
4146 
ma_async_notification_signal(ma_async_notification * pNotification,int code)4147 MA_API ma_result ma_async_notification_signal(ma_async_notification* pNotification, int code)
4148 {
4149     ma_async_notification_callbacks* pNotificationCallbacks = (ma_async_notification_callbacks*)pNotification;
4150 
4151     if (pNotification == NULL) {
4152         return MA_INVALID_ARGS;
4153     }
4154 
4155     if (pNotificationCallbacks->onSignal == NULL) {
4156         return MA_NOT_IMPLEMENTED;
4157     }
4158 
4159     pNotificationCallbacks->onSignal(pNotification, code);
4160     return MA_INVALID_ARGS;
4161 }
4162 
4163 
ma_async_notification_event__on_signal(ma_async_notification * pNotification,int code)4164 static void ma_async_notification_event__on_signal(ma_async_notification* pNotification, int code)
4165 {
4166     if (code == MA_NOTIFICATION_COMPLETE || code == MA_NOTIFICATION_FAILED) {
4167         ma_async_notification_event_signal((ma_async_notification_event*)pNotification);
4168     }
4169 }
4170 
ma_async_notification_event_init(ma_async_notification_event * pNotificationEvent)4171 MA_API ma_result ma_async_notification_event_init(ma_async_notification_event* pNotificationEvent)
4172 {
4173     ma_result result;
4174 
4175     if (pNotificationEvent == NULL) {
4176         return MA_INVALID_ARGS;
4177     }
4178 
4179     pNotificationEvent->cb.onSignal = ma_async_notification_event__on_signal;
4180 
4181     result = ma_event_init(&pNotificationEvent->e);
4182     if (result != MA_SUCCESS) {
4183         return result;
4184     }
4185 
4186     return MA_SUCCESS;
4187 }
4188 
ma_async_notification_event_uninit(ma_async_notification_event * pNotificationEvent)4189 MA_API ma_result ma_async_notification_event_uninit(ma_async_notification_event* pNotificationEvent)
4190 {
4191     if (pNotificationEvent == NULL) {
4192         return MA_INVALID_ARGS;
4193     }
4194 
4195     ma_event_uninit(&pNotificationEvent->e);
4196     return MA_SUCCESS;
4197 }
4198 
ma_async_notification_event_wait(ma_async_notification_event * pNotificationEvent)4199 MA_API ma_result ma_async_notification_event_wait(ma_async_notification_event* pNotificationEvent)
4200 {
4201     if (pNotificationEvent == NULL) {
4202         return MA_INVALID_ARGS;
4203     }
4204 
4205     return ma_event_wait(&pNotificationEvent->e);
4206 }
4207 
ma_async_notification_event_signal(ma_async_notification_event * pNotificationEvent)4208 MA_API ma_result ma_async_notification_event_signal(ma_async_notification_event* pNotificationEvent)
4209 {
4210     if (pNotificationEvent == NULL) {
4211         return MA_INVALID_ARGS;
4212     }
4213 
4214     return ma_event_signal(&pNotificationEvent->e);
4215 }
4216 
4217 
4218 
4219 #define MA_JOB_ID_NONE      ~((ma_uint64)0)
4220 #define MA_JOB_SLOT_NONE    ~((ma_uint16)0)
4221 
ma_job_extract_refcount(ma_uint64 toc)4222 static MA_INLINE ma_uint32 ma_job_extract_refcount(ma_uint64 toc)
4223 {
4224     return (ma_uint32)(toc >> 32);
4225 }
4226 
ma_job_extract_slot(ma_uint64 toc)4227 static MA_INLINE ma_uint16 ma_job_extract_slot(ma_uint64 toc)
4228 {
4229     return (ma_uint16)(toc & 0x0000FFFF);
4230 }
4231 
ma_job_extract_code(ma_uint64 toc)4232 static MA_INLINE ma_uint16 ma_job_extract_code(ma_uint64 toc)
4233 {
4234     return (ma_uint16)((toc & 0xFFFF0000) >> 16);
4235 }
4236 
ma_job_toc_to_allocation(ma_uint64 toc)4237 static MA_INLINE ma_uint64 ma_job_toc_to_allocation(ma_uint64 toc)
4238 {
4239     return ((ma_uint64)ma_job_extract_refcount(toc) << 32) | (ma_uint64)ma_job_extract_slot(toc);
4240 }
4241 
4242 
ma_job_init(ma_uint16 code)4243 MA_API ma_job ma_job_init(ma_uint16 code)
4244 {
4245     ma_job job;
4246 
4247     MA_ZERO_OBJECT(&job);
4248     job.toc.code = code;
4249     job.toc.slot = MA_JOB_SLOT_NONE;    /* Temp value. Will be allocated when posted to a queue. */
4250     job.next     = MA_JOB_ID_NONE;
4251 
4252     return job;
4253 }
4254 
4255 
4256 /*
4257 Lock free queue implementation based on the paper by Michael and Scott: Nonblocking Algorithms and Preemption-Safe Locking on Multiprogrammed Shared Memory Multiprocessors
4258 */
ma_job_queue_init(ma_uint32 flags,ma_job_queue * pQueue)4259 MA_API ma_result ma_job_queue_init(ma_uint32 flags, ma_job_queue* pQueue)
4260 {
4261     if (pQueue == NULL) {
4262         return MA_INVALID_ARGS;
4263     }
4264 
4265     MA_ZERO_OBJECT(pQueue);
4266     pQueue->flags = flags;
4267 
4268     ma_slot_allocator_init(&pQueue->allocator); /* Will not fail. */
4269 
4270     /* We need a semaphore if we're running in synchronous mode. */
4271     if ((pQueue->flags & MA_JOB_QUEUE_FLAG_NON_BLOCKING) == 0) {
4272         ma_semaphore_init(0, &pQueue->sem);
4273     }
4274 
4275     /*
4276     Our queue needs to be initialized with a free standing node. This should always be slot 0. Required for the lock free algorithm. The first job in the queue is
4277     just a dummy item for giving us the first item in the list which is stored in the "next" member.
4278     */
4279     ma_slot_allocator_alloc(&pQueue->allocator, &pQueue->head);  /* Will never fail. */
4280     pQueue->jobs[ma_job_extract_slot(pQueue->head)].next = MA_JOB_ID_NONE;
4281     pQueue->tail = pQueue->head;
4282 
4283     return MA_SUCCESS;
4284 }
4285 
ma_job_queue_uninit(ma_job_queue * pQueue)4286 MA_API ma_result ma_job_queue_uninit(ma_job_queue* pQueue)
4287 {
4288     if (pQueue == NULL) {
4289         return MA_INVALID_ARGS;
4290     }
4291 
4292     /* All we need to do is uninitialize the semaphore. */
4293     if ((pQueue->flags & MA_JOB_QUEUE_FLAG_NON_BLOCKING) == 0) {
4294         ma_semaphore_uninit(&pQueue->sem);
4295     }
4296 
4297     return MA_SUCCESS;
4298 }
4299 
ma_job_queue_post(ma_job_queue * pQueue,const ma_job * pJob)4300 MA_API ma_result ma_job_queue_post(ma_job_queue* pQueue, const ma_job* pJob)
4301 {
4302     ma_result result;
4303     ma_uint64 slot;
4304     ma_uint64 tail;
4305     ma_uint64 next;
4306 
4307     if (pQueue == NULL || pJob == NULL) {
4308         return MA_INVALID_ARGS;
4309     }
4310 
4311     /* We need a new slot. */
4312     result = ma_slot_allocator_alloc(&pQueue->allocator, &slot);
4313     if (result != MA_SUCCESS) {
4314         return result;  /* Probably ran out of slots. If so, MA_OUT_OF_MEMORY will be returned. */
4315     }
4316 
4317     /* At this point we should have a slot to place the job. */
4318     MA_ASSERT(ma_job_extract_slot(slot) < MA_RESOURCE_MANAGER_JOB_QUEUE_CAPACITY);
4319 
4320     /* We need to put the job into memory before we do anything. */
4321     pQueue->jobs[ma_job_extract_slot(slot)]                = *pJob;
4322     pQueue->jobs[ma_job_extract_slot(slot)].toc.allocation = slot;           /* This will overwrite the job code. */
4323     pQueue->jobs[ma_job_extract_slot(slot)].toc.code       = pJob->toc.code; /* The job code needs to be applied again because the line above overwrote it. */
4324     pQueue->jobs[ma_job_extract_slot(slot)].next           = MA_JOB_ID_NONE; /* Reset for safety. */
4325 
4326     /* The job is stored in memory so now we need to add it to our linked list. We only ever add items to the end of the list. */
4327     for (;;) {
4328         tail = pQueue->tail;
4329         next = pQueue->jobs[ma_job_extract_slot(tail)].next;
4330 
4331         if (ma_job_toc_to_allocation(tail) == ma_job_toc_to_allocation(pQueue->tail)) {
4332             if (ma_job_extract_slot(next) == 0xFFFF) {
4333                 if (c89atomic_compare_and_swap_64(&pQueue->jobs[ma_job_extract_slot(tail)].next, next, slot) == next) {
4334                     break;
4335                 }
4336             } else {
4337                 c89atomic_compare_and_swap_64(&pQueue->tail, tail, next);
4338             }
4339         }
4340     }
4341     c89atomic_compare_and_swap_64(&pQueue->tail, tail, slot);
4342 
4343 
4344     /* Signal the semaphore as the last step if we're using synchronous mode. */
4345     if ((pQueue->flags & MA_JOB_QUEUE_FLAG_NON_BLOCKING) == 0) {
4346         ma_semaphore_release(&pQueue->sem);
4347     }
4348 
4349     return MA_SUCCESS;
4350 }
4351 
ma_job_queue_next(ma_job_queue * pQueue,ma_job * pJob)4352 MA_API ma_result ma_job_queue_next(ma_job_queue* pQueue, ma_job* pJob)
4353 {
4354     ma_uint64 head;
4355     ma_uint64 tail;
4356     ma_uint64 next;
4357 
4358     if (pQueue == NULL || pJob == NULL) {
4359         return MA_INVALID_ARGS;
4360     }
4361 
4362     /* If we're running in synchronous mode we'll need to wait on a semaphore. */
4363     if ((pQueue->flags & MA_JOB_QUEUE_FLAG_NON_BLOCKING) == 0) {
4364         ma_semaphore_wait(&pQueue->sem);
4365     }
4366 
4367     /* Now we need to remove the root item from the list. This must be done without locking. */
4368     for (;;) {
4369         head = pQueue->head;
4370         tail = pQueue->tail;
4371         next = pQueue->jobs[ma_job_extract_slot(head)].next;
4372 
4373         if (ma_job_toc_to_allocation(head) == ma_job_toc_to_allocation(pQueue->head)) {
4374             if (ma_job_toc_to_allocation(head) == ma_job_toc_to_allocation(tail)) {
4375                 if (ma_job_extract_slot(next) == 0xFFFF) {
4376                     return MA_NO_DATA_AVAILABLE;
4377                 }
4378                 c89atomic_compare_and_swap_64(&pQueue->tail, tail, next);
4379             } else {
4380                 *pJob = pQueue->jobs[ma_job_extract_slot(next)];
4381                 if (c89atomic_compare_and_swap_64(&pQueue->head, head, next) == head) {
4382                     break;
4383                 }
4384             }
4385         }
4386     }
4387 
4388     ma_slot_allocator_free(&pQueue->allocator, head);
4389 
4390     /*
4391     If it's a quit job make sure it's put back on the queue to ensure other threads have an opportunity to detect it and terminate naturally. We
4392     could instead just leave it on the queue, but that would involve fiddling with the lock-free code above and I want to keep that as simple as
4393     possible.
4394     */
4395     if (pJob->toc.code == MA_JOB_QUIT) {
4396         ma_job_queue_post(pQueue, pJob);
4397         return MA_CANCELLED;    /* Return a cancelled status just in case the thread is checking return codes and not properly checking for a quit job. */
4398     }
4399 
4400     return MA_SUCCESS;
4401 }
4402 
ma_job_queue_free(ma_job_queue * pQueue,ma_job * pJob)4403 MA_API ma_result ma_job_queue_free(ma_job_queue* pQueue, ma_job* pJob)
4404 {
4405     if (pQueue == NULL || pJob == NULL) {
4406         return MA_INVALID_ARGS;
4407     }
4408 
4409     return ma_slot_allocator_free(&pQueue->allocator, ma_job_toc_to_allocation(pJob->toc.allocation));
4410 }
4411 
4412 
4413 
4414 
4415 
4416 #ifndef MA_DEFAULT_HASH_SEED
4417 #define MA_DEFAULT_HASH_SEED    42
4418 #endif
4419 
4420 /* MurmurHash3. Based on code from https://github.com/PeterScott/murmur3/blob/master/murmur3.c (public domain). */
ma_rotl32(ma_uint32 x,ma_int8 r)4421 static MA_INLINE ma_uint32 ma_rotl32(ma_uint32 x, ma_int8 r)
4422 {
4423     return (x << r) | (x >> (32 - r));
4424 }
4425 
ma_hash_getblock(const ma_uint32 * blocks,int i)4426 static MA_INLINE ma_uint32 ma_hash_getblock(const ma_uint32* blocks, int i)
4427 {
4428     if (ma_is_little_endian()) {
4429         return blocks[i];
4430     } else {
4431         return ma_swap_endian_uint32(blocks[i]);
4432     }
4433 }
4434 
ma_hash_fmix32(ma_uint32 h)4435 static MA_INLINE ma_uint32 ma_hash_fmix32(ma_uint32 h)
4436 {
4437     h ^= h >> 16;
4438     h *= 0x85ebca6b;
4439     h ^= h >> 13;
4440     h *= 0xc2b2ae35;
4441     h ^= h >> 16;
4442 
4443     return h;
4444 }
4445 
ma_hash_32(const void * key,int len,ma_uint32 seed)4446 static ma_uint32 ma_hash_32(const void* key, int len, ma_uint32 seed)
4447 {
4448     const ma_uint8* data = (const ma_uint8*)key;
4449     const ma_uint32* blocks;
4450     const ma_uint8* tail;
4451     const int nblocks = len / 4;
4452     ma_uint32 h1 = seed;
4453     ma_uint32 c1 = 0xcc9e2d51;
4454     ma_uint32 c2 = 0x1b873593;
4455     ma_uint32 k1;
4456     int i;
4457 
4458     blocks = (const ma_uint32 *)(data + nblocks*4);
4459 
4460     for(i = -nblocks; i; i++) {
4461         k1 = ma_hash_getblock(blocks,i);
4462 
4463         k1 *= c1;
4464         k1 = ma_rotl32(k1, 15);
4465         k1 *= c2;
4466 
4467         h1 ^= k1;
4468         h1 = ma_rotl32(h1, 13);
4469         h1 = h1*5 + 0xe6546b64;
4470     }
4471 
4472 
4473     tail = (const ma_uint8*)(data + nblocks*4);
4474 
4475     k1 = 0;
4476     switch(len & 3) {
4477         case 3: k1 ^= tail[2] << 16;
4478         case 2: k1 ^= tail[1] << 8;
4479         case 1: k1 ^= tail[0];
4480                 k1 *= c1; k1 = ma_rotl32(k1, 15); k1 *= c2; h1 ^= k1;
4481     };
4482 
4483 
4484     h1 ^= len;
4485     h1  = ma_hash_fmix32(h1);
4486 
4487     return h1;
4488 }
4489 /* End MurmurHash3 */
4490 
ma_hash_string_32(const char * str)4491 static ma_uint32 ma_hash_string_32(const char* str)
4492 {
4493     return ma_hash_32(str, (int)strlen(str), MA_DEFAULT_HASH_SEED);
4494 }
4495 
4496 
4497 
4498 /*
4499 Basic BST Functions
4500 */
ma_resource_manager_data_buffer_node_search(ma_resource_manager * pResourceManager,ma_uint32 hashedName32,ma_resource_manager_data_buffer_node ** ppDataBufferNode)4501 static ma_result ma_resource_manager_data_buffer_node_search(ma_resource_manager* pResourceManager, ma_uint32 hashedName32, ma_resource_manager_data_buffer_node** ppDataBufferNode)
4502 {
4503     ma_resource_manager_data_buffer_node* pCurrentNode;
4504 
4505     MA_ASSERT(pResourceManager != NULL);
4506     MA_ASSERT(ppDataBufferNode != NULL);
4507 
4508     pCurrentNode = pResourceManager->pRootDataBufferNode;
4509     while (pCurrentNode != NULL) {
4510         if (hashedName32 == pCurrentNode->hashedName32) {
4511             break;  /* Found. */
4512         } else if (hashedName32 < pCurrentNode->hashedName32) {
4513             pCurrentNode = pCurrentNode->pChildLo;
4514         } else {
4515             pCurrentNode = pCurrentNode->pChildHi;
4516         }
4517     }
4518 
4519     *ppDataBufferNode = pCurrentNode;
4520 
4521     if (pCurrentNode == NULL) {
4522         return MA_DOES_NOT_EXIST;
4523     } else {
4524         return MA_SUCCESS;
4525     }
4526 }
4527 
ma_resource_manager_data_buffer_node_insert_point(ma_resource_manager * pResourceManager,ma_uint32 hashedName32,ma_resource_manager_data_buffer_node ** ppInsertPoint)4528 static ma_result ma_resource_manager_data_buffer_node_insert_point(ma_resource_manager* pResourceManager, ma_uint32 hashedName32, ma_resource_manager_data_buffer_node** ppInsertPoint)
4529 {
4530     ma_result result = MA_SUCCESS;
4531     ma_resource_manager_data_buffer_node* pCurrentNode;
4532 
4533     MA_ASSERT(pResourceManager != NULL);
4534     MA_ASSERT(ppInsertPoint    != NULL);
4535 
4536     *ppInsertPoint = NULL;
4537 
4538     if (pResourceManager->pRootDataBufferNode == NULL) {
4539         return MA_SUCCESS;  /* No items. */
4540     }
4541 
4542     /* We need to find the node that will become the parent of the new node. If a node is found that already has the same hashed name we need to return MA_ALREADY_EXISTS. */
4543     pCurrentNode = pResourceManager->pRootDataBufferNode;
4544     while (pCurrentNode != NULL) {
4545         if (hashedName32 == pCurrentNode->hashedName32) {
4546             result = MA_ALREADY_EXISTS;
4547             break;
4548         } else {
4549             if (hashedName32 < pCurrentNode->hashedName32) {
4550                 if (pCurrentNode->pChildLo == NULL) {
4551                     result = MA_SUCCESS;
4552                     break;
4553                 } else {
4554                     pCurrentNode = pCurrentNode->pChildLo;
4555                 }
4556             } else {
4557                 if (pCurrentNode->pChildHi == NULL) {
4558                     result = MA_SUCCESS;
4559                     break;
4560                 } else {
4561                     pCurrentNode = pCurrentNode->pChildHi;
4562                 }
4563             }
4564         }
4565     }
4566 
4567     *ppInsertPoint = pCurrentNode;
4568     return result;
4569 }
4570 
ma_resource_manager_data_buffer_node_insert_at(ma_resource_manager * pResourceManager,ma_resource_manager_data_buffer_node * pDataBufferNode,ma_resource_manager_data_buffer_node * pInsertPoint)4571 static ma_result ma_resource_manager_data_buffer_node_insert_at(ma_resource_manager* pResourceManager, ma_resource_manager_data_buffer_node* pDataBufferNode, ma_resource_manager_data_buffer_node* pInsertPoint)
4572 {
4573     MA_ASSERT(pResourceManager != NULL);
4574     MA_ASSERT(pDataBufferNode  != NULL);
4575 
4576     /* The key must have been set before calling this function. */
4577     MA_ASSERT(pDataBufferNode->hashedName32 != 0);
4578 
4579     if (pInsertPoint == NULL) {
4580         /* It's the first node. */
4581         pResourceManager->pRootDataBufferNode = pDataBufferNode;
4582     } else {
4583         /* It's not the first node. It needs to be inserted. */
4584         if (pDataBufferNode->hashedName32 < pInsertPoint->hashedName32) {
4585             MA_ASSERT(pInsertPoint->pChildLo == NULL);
4586             pInsertPoint->pChildLo = pDataBufferNode;
4587         } else {
4588             MA_ASSERT(pInsertPoint->pChildHi == NULL);
4589             pInsertPoint->pChildHi = pDataBufferNode;
4590         }
4591     }
4592 
4593     pDataBufferNode->pParent = pInsertPoint;
4594 
4595     return MA_SUCCESS;
4596 }
4597 
4598 #if 0   /* Unused for now. */
4599 static ma_result ma_resource_manager_data_buffer_node_insert(ma_resource_manager* pResourceManager, ma_resource_manager_data_buffer_node* pDataBufferNode)
4600 {
4601     ma_result result;
4602     ma_resource_manager_data_buffer_node* pInsertPoint;
4603 
4604     MA_ASSERT(pResourceManager != NULL);
4605     MA_ASSERT(pDataBufferNode  != NULL);
4606 
4607     result = ma_resource_manager_data_buffer_node_insert_point(pResourceManager, pDataBufferNode->hashedName32, &pInsertPoint);
4608     if (result != MA_SUCCESS) {
4609         return MA_INVALID_ARGS;
4610     }
4611 
4612     return ma_resource_manager_data_buffer_node_insert_at(pResourceManager, pDataBufferNode, pInsertPoint);
4613 }
4614 #endif
4615 
ma_resource_manager_data_buffer_node_find_min(ma_resource_manager_data_buffer_node * pDataBufferNode)4616 static MA_INLINE ma_resource_manager_data_buffer_node* ma_resource_manager_data_buffer_node_find_min(ma_resource_manager_data_buffer_node* pDataBufferNode)
4617 {
4618     ma_resource_manager_data_buffer_node* pCurrentNode;
4619 
4620     MA_ASSERT(pDataBufferNode != NULL);
4621 
4622     pCurrentNode = pDataBufferNode;
4623     while (pCurrentNode->pChildLo != NULL) {
4624         pCurrentNode = pCurrentNode->pChildLo;
4625     }
4626 
4627     return pCurrentNode;
4628 }
4629 
ma_resource_manager_data_buffer_node_find_max(ma_resource_manager_data_buffer_node * pDataBufferNode)4630 static MA_INLINE ma_resource_manager_data_buffer_node* ma_resource_manager_data_buffer_node_find_max(ma_resource_manager_data_buffer_node* pDataBufferNode)
4631 {
4632     ma_resource_manager_data_buffer_node* pCurrentNode;
4633 
4634     MA_ASSERT(pDataBufferNode != NULL);
4635 
4636     pCurrentNode = pDataBufferNode;
4637     while (pCurrentNode->pChildHi != NULL) {
4638         pCurrentNode = pCurrentNode->pChildHi;
4639     }
4640 
4641     return pCurrentNode;
4642 }
4643 
ma_resource_manager_data_buffer_node_find_inorder_successor(ma_resource_manager_data_buffer_node * pDataBufferNode)4644 static MA_INLINE ma_resource_manager_data_buffer_node* ma_resource_manager_data_buffer_node_find_inorder_successor(ma_resource_manager_data_buffer_node* pDataBufferNode)
4645 {
4646     MA_ASSERT(pDataBufferNode           != NULL);
4647     MA_ASSERT(pDataBufferNode->pChildHi != NULL);
4648 
4649     return ma_resource_manager_data_buffer_node_find_min(pDataBufferNode->pChildHi);
4650 }
4651 
ma_resource_manager_data_buffer_node_find_inorder_predecessor(ma_resource_manager_data_buffer_node * pDataBufferNode)4652 static MA_INLINE ma_resource_manager_data_buffer_node* ma_resource_manager_data_buffer_node_find_inorder_predecessor(ma_resource_manager_data_buffer_node* pDataBufferNode)
4653 {
4654     MA_ASSERT(pDataBufferNode           != NULL);
4655     MA_ASSERT(pDataBufferNode->pChildLo != NULL);
4656 
4657     return ma_resource_manager_data_buffer_node_find_max(pDataBufferNode->pChildLo);
4658 }
4659 
ma_resource_manager_data_buffer_node_remove(ma_resource_manager * pResourceManager,ma_resource_manager_data_buffer_node * pDataBufferNode)4660 static ma_result ma_resource_manager_data_buffer_node_remove(ma_resource_manager* pResourceManager, ma_resource_manager_data_buffer_node* pDataBufferNode)
4661 {
4662     MA_ASSERT(pResourceManager != NULL);
4663     MA_ASSERT(pDataBufferNode  != NULL);
4664 
4665     if (pDataBufferNode->pChildLo == NULL) {
4666         if (pDataBufferNode->pChildHi == NULL) {
4667             /* Simple case - deleting a buffer with no children. */
4668             if (pDataBufferNode->pParent == NULL) {
4669                 MA_ASSERT(pResourceManager->pRootDataBufferNode == pDataBufferNode);    /* There is only a single buffer in the tree which should be equal to the root node. */
4670                 pResourceManager->pRootDataBufferNode = NULL;
4671             } else {
4672                 if (pDataBufferNode->pParent->pChildLo == pDataBufferNode) {
4673                     pDataBufferNode->pParent->pChildLo = NULL;
4674                 } else {
4675                     pDataBufferNode->pParent->pChildHi = NULL;
4676                 }
4677             }
4678         } else {
4679             /* Node has one child - pChildHi != NULL. */
4680             pDataBufferNode->pChildHi->pParent = pDataBufferNode->pParent;
4681 
4682             if (pDataBufferNode->pParent == NULL) {
4683                 MA_ASSERT(pResourceManager->pRootDataBufferNode == pDataBufferNode);
4684                 pResourceManager->pRootDataBufferNode = pDataBufferNode->pChildHi;
4685             } else {
4686                 if (pDataBufferNode->pParent->pChildLo == pDataBufferNode) {
4687                     pDataBufferNode->pParent->pChildLo = pDataBufferNode->pChildHi;
4688                 } else {
4689                     pDataBufferNode->pParent->pChildHi = pDataBufferNode->pChildHi;
4690                 }
4691             }
4692         }
4693     } else {
4694         if (pDataBufferNode->pChildHi == NULL) {
4695             /* Node has one child - pChildLo != NULL. */
4696             pDataBufferNode->pChildLo->pParent = pDataBufferNode->pParent;
4697 
4698             if (pDataBufferNode->pParent == NULL) {
4699                 MA_ASSERT(pResourceManager->pRootDataBufferNode == pDataBufferNode);
4700                 pResourceManager->pRootDataBufferNode = pDataBufferNode->pChildLo;
4701             } else {
4702                 if (pDataBufferNode->pParent->pChildLo == pDataBufferNode) {
4703                     pDataBufferNode->pParent->pChildLo = pDataBufferNode->pChildLo;
4704                 } else {
4705                     pDataBufferNode->pParent->pChildHi = pDataBufferNode->pChildLo;
4706                 }
4707             }
4708         } else {
4709             /* Complex case - deleting a node with two children. */
4710             ma_resource_manager_data_buffer_node* pReplacementDataBufferNode;
4711 
4712             /* For now we are just going to use the in-order successor as the replacement, but we may want to try to keep this balanced by switching between the two. */
4713             pReplacementDataBufferNode = ma_resource_manager_data_buffer_node_find_inorder_successor(pDataBufferNode);
4714             MA_ASSERT(pReplacementDataBufferNode != NULL);
4715 
4716             /*
4717             Now that we have our replacement node we can make the change. The simple way to do this would be to just exchange the values, and then remove the replacement
4718             node, however we track specific nodes via pointers which means we can't just swap out the values. We need to instead just change the pointers around. The
4719             replacement node should have at most 1 child. Therefore, we can detach it in terms of our simpler cases above. What we're essentially doing is detaching the
4720             replacement node and reinserting it into the same position as the deleted node.
4721             */
4722             MA_ASSERT(pReplacementDataBufferNode->pParent  != NULL);  /* The replacement node should never be the root which means it should always have a parent. */
4723             MA_ASSERT(pReplacementDataBufferNode->pChildLo == NULL);  /* Because we used in-order successor. This would be pChildHi == NULL if we used in-order predecessor. */
4724 
4725             if (pReplacementDataBufferNode->pChildHi == NULL) {
4726                 if (pReplacementDataBufferNode->pParent->pChildLo == pReplacementDataBufferNode) {
4727                     pReplacementDataBufferNode->pParent->pChildLo = NULL;
4728                 } else {
4729                     pReplacementDataBufferNode->pParent->pChildHi = NULL;
4730                 }
4731             } else {
4732                 if (pReplacementDataBufferNode->pParent->pChildLo == pReplacementDataBufferNode) {
4733                     pReplacementDataBufferNode->pParent->pChildLo = pReplacementDataBufferNode->pChildHi;
4734                 } else {
4735                     pReplacementDataBufferNode->pParent->pChildHi = pReplacementDataBufferNode->pChildHi;
4736                 }
4737             }
4738 
4739 
4740             /* The replacement node has essentially been detached from the binary tree, so now we need to replace the old data buffer with it. The first thing to update is the parent */
4741             if (pDataBufferNode->pParent != NULL) {
4742                 if (pDataBufferNode->pParent->pChildLo == pDataBufferNode) {
4743                     pDataBufferNode->pParent->pChildLo = pReplacementDataBufferNode;
4744                 } else {
4745                     pDataBufferNode->pParent->pChildHi = pReplacementDataBufferNode;
4746                 }
4747             }
4748 
4749             /* Now need to update the replacement node's pointers. */
4750             pReplacementDataBufferNode->pParent  = pDataBufferNode->pParent;
4751             pReplacementDataBufferNode->pChildLo = pDataBufferNode->pChildLo;
4752             pReplacementDataBufferNode->pChildHi = pDataBufferNode->pChildHi;
4753 
4754             /* Now the children of the replacement node need to have their parent pointers updated. */
4755             if (pReplacementDataBufferNode->pChildLo != NULL) {
4756                 pReplacementDataBufferNode->pChildLo->pParent = pReplacementDataBufferNode;
4757             }
4758             if (pReplacementDataBufferNode->pChildHi != NULL) {
4759                 pReplacementDataBufferNode->pChildHi->pParent = pReplacementDataBufferNode;
4760             }
4761 
4762             /* Now the root node needs to be updated. */
4763             if (pResourceManager->pRootDataBufferNode == pDataBufferNode) {
4764                 pResourceManager->pRootDataBufferNode = pReplacementDataBufferNode;
4765             }
4766         }
4767     }
4768 
4769     return MA_SUCCESS;
4770 }
4771 
4772 #if 0   /* Unused for now. */
4773 static ma_result ma_resource_manager_data_buffer_node_remove_by_key(ma_resource_manager* pResourceManager, ma_uint32 hashedName32)
4774 {
4775     ma_result result;
4776     ma_resource_manager_data_buffer_node* pDataBufferNode;
4777 
4778     result = ma_resource_manager_data_buffer_search(pResourceManager, hashedName32, &pDataBufferNode);
4779     if (result != MA_SUCCESS) {
4780         return result;  /* Could not find the data buffer. */
4781     }
4782 
4783     return ma_resource_manager_data_buffer_remove(pResourceManager, pDataBufferNode);
4784 }
4785 #endif
4786 
ma_resource_manager_data_buffer_node_increment_ref(ma_resource_manager * pResourceManager,ma_resource_manager_data_buffer_node * pDataBufferNode,ma_uint32 * pNewRefCount)4787 static ma_result ma_resource_manager_data_buffer_node_increment_ref(ma_resource_manager* pResourceManager, ma_resource_manager_data_buffer_node* pDataBufferNode, ma_uint32* pNewRefCount)
4788 {
4789     ma_uint32 refCount;
4790 
4791     MA_ASSERT(pResourceManager != NULL);
4792     MA_ASSERT(pDataBufferNode  != NULL);
4793 
4794     (void)pResourceManager;
4795 
4796     refCount = c89atomic_fetch_add_32(&pDataBufferNode->refCount, 1) + 1;
4797 
4798     if (pNewRefCount != NULL) {
4799         *pNewRefCount = refCount;
4800     }
4801 
4802     return MA_SUCCESS;
4803 }
4804 
ma_resource_manager_data_buffer_node_decrement_ref(ma_resource_manager * pResourceManager,ma_resource_manager_data_buffer_node * pDataBufferNode,ma_uint32 * pNewRefCount)4805 static ma_result ma_resource_manager_data_buffer_node_decrement_ref(ma_resource_manager* pResourceManager, ma_resource_manager_data_buffer_node* pDataBufferNode, ma_uint32* pNewRefCount)
4806 {
4807     ma_uint32 refCount;
4808 
4809     MA_ASSERT(pResourceManager != NULL);
4810     MA_ASSERT(pDataBufferNode  != NULL);
4811 
4812     (void)pResourceManager;
4813 
4814     refCount = c89atomic_fetch_sub_32(&pDataBufferNode->refCount, 1) - 1;
4815 
4816     if (pNewRefCount != NULL) {
4817         *pNewRefCount = refCount;
4818     }
4819 
4820     return MA_SUCCESS;
4821 }
4822 
4823 
4824 
ma_resource_manager_data_buffer_node_free(ma_resource_manager * pResourceManager,ma_resource_manager_data_buffer_node * pDataBufferNode)4825 static void ma_resource_manager_data_buffer_node_free(ma_resource_manager* pResourceManager, ma_resource_manager_data_buffer_node* pDataBufferNode)
4826 {
4827     MA_ASSERT(pResourceManager != NULL);
4828     MA_ASSERT(pDataBufferNode  != NULL);
4829 
4830     if (pDataBufferNode->isDataOwnedByResourceManager) {
4831         if (pDataBufferNode->data.type == ma_resource_manager_data_buffer_encoding_encoded) {
4832             ma__free_from_callbacks((void*)pDataBufferNode->data.encoded.pData, &pResourceManager->config.allocationCallbacks/*, MA_ALLOCATION_TYPE_ENCODED_BUFFER*/);
4833             pDataBufferNode->data.encoded.pData       = NULL;
4834             pDataBufferNode->data.encoded.sizeInBytes = 0;
4835         } else {
4836             ma__free_from_callbacks((void*)pDataBufferNode->data.decoded.pData, &pResourceManager->config.allocationCallbacks/*, MA_ALLOCATION_TYPE_DECODED_BUFFER*/);
4837             pDataBufferNode->data.decoded.pData       = NULL;
4838             pDataBufferNode->data.decoded.frameCount  = 0;
4839         }
4840     }
4841 
4842     /* The data buffer itself needs to be freed. */
4843     ma__free_from_callbacks(pDataBufferNode, &pResourceManager->config.allocationCallbacks/*, MA_ALLOCATION_TYPE_RESOURCE_MANAGER_DATA_BUFFER*/);
4844 }
4845 
4846 
4847 
ma_resource_manager_job_thread(void * pUserData)4848 static ma_thread_result MA_THREADCALL ma_resource_manager_job_thread(void* pUserData)
4849 {
4850     ma_resource_manager* pResourceManager = (ma_resource_manager*)pUserData;
4851     MA_ASSERT(pResourceManager != NULL);
4852 
4853     for (;;) {
4854         ma_result result;
4855         ma_job job;
4856 
4857         result = ma_resource_manager_next_job(pResourceManager, &job);
4858         if (result != MA_SUCCESS) {
4859             break;
4860         }
4861 
4862         /* Terminate if we got a quit message. */
4863         if (job.toc.code == MA_JOB_QUIT) {
4864             break;
4865         }
4866 
4867         ma_resource_manager_process_job(pResourceManager, &job);
4868     }
4869 
4870     return (ma_thread_result)0;
4871 }
4872 
4873 
ma_resource_manager_config_init()4874 MA_API ma_resource_manager_config ma_resource_manager_config_init()
4875 {
4876     ma_resource_manager_config config;
4877 
4878     MA_ZERO_OBJECT(&config);
4879     config.decodedFormat     = ma_format_unknown;
4880     config.decodedChannels   = 0;
4881     config.decodedSampleRate = 0;
4882     config.jobThreadCount    = 1;   /* A single miniaudio-managed job thread by default. */
4883 
4884     return config;
4885 }
4886 
4887 
ma_resource_manager_init(const ma_resource_manager_config * pConfig,ma_resource_manager * pResourceManager)4888 MA_API ma_result ma_resource_manager_init(const ma_resource_manager_config* pConfig, ma_resource_manager* pResourceManager)
4889 {
4890     ma_result result;
4891     ma_uint32 jobQueueFlags;
4892     ma_uint32 iJobThread;
4893 
4894     if (pResourceManager == NULL) {
4895         return MA_INVALID_ARGS;
4896     }
4897 
4898     MA_ZERO_OBJECT(pResourceManager);
4899 
4900     if (pConfig == NULL) {
4901         return MA_INVALID_ARGS;
4902     }
4903 
4904     if (pConfig->jobThreadCount > ma_countof(pResourceManager->jobThreads)) {
4905         return MA_INVALID_ARGS; /* Requesting too many job threads. */
4906     }
4907 
4908     pResourceManager->config = *pConfig;
4909     ma_allocation_callbacks_init_copy(&pResourceManager->config.allocationCallbacks, &pConfig->allocationCallbacks);
4910 
4911     if (pResourceManager->config.pVFS == NULL) {
4912         result = ma_default_vfs_init(&pResourceManager->defaultVFS, &pResourceManager->config.allocationCallbacks);
4913         if (result != MA_SUCCESS) {
4914             return result;  /* Failed to initialize the default file system. */
4915         }
4916 
4917         pResourceManager->config.pVFS = &pResourceManager->defaultVFS;
4918     }
4919 
4920     /* Job queue. */
4921     jobQueueFlags = 0;
4922     if ((pConfig->flags & MA_RESOURCE_MANAGER_FLAG_NON_BLOCKING) != 0) {
4923         if (pConfig->jobThreadCount > 0) {
4924             return MA_INVALID_ARGS; /* Non-blocking mode is only valid for self-managed job threads. */
4925         }
4926 
4927         jobQueueFlags |= MA_JOB_QUEUE_FLAG_NON_BLOCKING;
4928     }
4929 
4930     result = ma_job_queue_init(jobQueueFlags, &pResourceManager->jobQueue);
4931     if (result != MA_SUCCESS) {
4932         ma_mutex_uninit(&pResourceManager->dataBufferLock);
4933         return result;
4934     }
4935 
4936 
4937     /* Data buffer lock. */
4938     result = ma_mutex_init(&pResourceManager->dataBufferLock);
4939     if (result != MA_SUCCESS) {
4940         return result;
4941     }
4942 
4943 
4944     /* Create the job threads last to ensure the threads has access to valid data. */
4945     for (iJobThread = 0; iJobThread < pConfig->jobThreadCount; iJobThread += 1) {
4946         result = ma_thread_create(&pResourceManager->jobThreads[iJobThread], ma_thread_priority_normal, 0, ma_resource_manager_job_thread, pResourceManager);
4947         if (result != MA_SUCCESS) {
4948             ma_mutex_uninit(&pResourceManager->dataBufferLock);
4949             ma_job_queue_uninit(&pResourceManager->jobQueue);
4950             return result;
4951         }
4952     }
4953 
4954     return MA_SUCCESS;
4955 }
4956 
4957 
ma_resource_manager_delete_all_data_buffer_nodes(ma_resource_manager * pResourceManager)4958 static void ma_resource_manager_delete_all_data_buffer_nodes(ma_resource_manager* pResourceManager)
4959 {
4960     MA_ASSERT(pResourceManager);
4961 
4962     /* If everything was done properly, there shouldn't be any active data buffers. */
4963     while (pResourceManager->pRootDataBufferNode != NULL) {
4964         ma_resource_manager_data_buffer_node* pDataBufferNode = pResourceManager->pRootDataBufferNode;
4965         ma_resource_manager_data_buffer_node_remove(pResourceManager, pDataBufferNode);
4966 
4967         /* The data buffer has been removed from the BST, so now we need to free it's data. */
4968         ma_resource_manager_data_buffer_node_free(pResourceManager, pDataBufferNode);
4969     }
4970 }
4971 
ma_resource_manager_uninit(ma_resource_manager * pResourceManager)4972 MA_API void ma_resource_manager_uninit(ma_resource_manager* pResourceManager)
4973 {
4974     ma_uint32 iJobThread;
4975 
4976     if (pResourceManager == NULL) {
4977         return;
4978     }
4979 
4980     /*
4981     Job threads need to be killed first. To do this we need to post a quit message to the message queue and then wait for the thread. The quit message will never be removed from the
4982     queue which means it will never not be returned after being encounted for the first time which means all threads will eventually receive it.
4983     */
4984     ma_resource_manager_post_job_quit(pResourceManager);
4985 
4986     /* Wait for every job to finish before continuing to ensure nothing is sill trying to access any of our objects below. */
4987     for (iJobThread = 0; iJobThread < pResourceManager->config.jobThreadCount; iJobThread += 1) {
4988         ma_thread_wait(&pResourceManager->jobThreads[iJobThread]);
4989     }
4990 
4991     /* At this point the thread should have returned and no other thread should be accessing our data. We can now delete all data buffers. */
4992     ma_resource_manager_delete_all_data_buffer_nodes(pResourceManager);
4993 
4994     /* The job queue is no longer needed. */
4995     ma_job_queue_uninit(&pResourceManager->jobQueue);
4996 
4997     /* We're no longer doing anything with data buffers so the lock can now be uninitialized. */
4998     ma_mutex_uninit(&pResourceManager->dataBufferLock);
4999 }
5000 
5001 
ma_resource_manager__init_decoder(ma_resource_manager * pResourceManager,const char * pFilePath,ma_decoder * pDecoder)5002 static ma_result ma_resource_manager__init_decoder(ma_resource_manager* pResourceManager, const char* pFilePath, ma_decoder* pDecoder)
5003 {
5004     ma_decoder_config config;
5005 
5006     MA_ASSERT(pResourceManager != NULL);
5007     MA_ASSERT(pFilePath        != NULL);
5008     MA_ASSERT(pDecoder         != NULL);
5009 
5010     config = ma_decoder_config_init(pResourceManager->config.decodedFormat, pResourceManager->config.decodedChannels, pResourceManager->config.decodedSampleRate);
5011     config.allocationCallbacks = pResourceManager->config.allocationCallbacks;
5012 
5013     return ma_decoder_init_vfs(pResourceManager->config.pVFS, pFilePath, &config, pDecoder);
5014 }
5015 
ma_resource_manager_data_buffer_init_connector(ma_resource_manager_data_buffer * pDataBuffer,ma_async_notification * pNotification)5016 static ma_result ma_resource_manager_data_buffer_init_connector(ma_resource_manager_data_buffer* pDataBuffer, ma_async_notification* pNotification)
5017 {
5018     ma_result result;
5019 
5020     MA_ASSERT(pDataBuffer != NULL);
5021 
5022     /* The underlying data buffer must be initialized before we'll be able to know how to initialize the backend. */
5023     result = ma_resource_manager_data_buffer_result(pDataBuffer);
5024     if (result != MA_SUCCESS && result != MA_BUSY) {
5025         return result;  /* The data buffer is in an erroneous state. */
5026     }
5027 
5028 
5029     /*
5030     We need to initialize either a ma_decoder or an ma_audio_buffer depending on whether or not the backing data is encoded or decoded. These act as the
5031     "instance" to the data and are used to form the connection between underlying data buffer and the data source. If the data buffer is decoded, we can use
5032     an ma_audio_buffer. This enables us to use memory mapping when mixing which saves us a bit of data movement overhead.
5033     */
5034     if (pDataBuffer->pNode->data.type == ma_resource_manager_data_buffer_encoding_decoded) {
5035         pDataBuffer->connectorType = ma_resource_manager_data_buffer_connector_buffer;
5036     } else {
5037         pDataBuffer->connectorType = ma_resource_manager_data_buffer_connector_decoder;
5038     }
5039 
5040     if (pDataBuffer->connectorType == ma_resource_manager_data_buffer_connector_buffer) {
5041         ma_audio_buffer_config config;
5042         config = ma_audio_buffer_config_init(pDataBuffer->pNode->data.decoded.format, pDataBuffer->pNode->data.decoded.channels, pDataBuffer->pNode->data.decoded.frameCount, pDataBuffer->pNode->data.encoded.pData, NULL);
5043         result = ma_audio_buffer_init(&config, &pDataBuffer->connector.buffer);
5044 
5045         pDataBuffer->lengthInPCMFrames = pDataBuffer->connector.buffer.sizeInFrames;
5046     } else {
5047         ma_decoder_config configOut;
5048         configOut = ma_decoder_config_init(pDataBuffer->pResourceManager->config.decodedFormat, pDataBuffer->pResourceManager->config.decodedChannels, pDataBuffer->pResourceManager->config.decodedSampleRate);
5049 
5050         if (pDataBuffer->pNode->data.type == ma_resource_manager_data_buffer_encoding_decoded) {
5051             ma_decoder_config configIn;
5052             ma_uint64 sizeInBytes;
5053 
5054             configIn  = ma_decoder_config_init(pDataBuffer->pNode->data.decoded.format, pDataBuffer->pNode->data.decoded.channels, pDataBuffer->pNode->data.decoded.sampleRate);
5055 
5056             sizeInBytes = pDataBuffer->pNode->data.decoded.frameCount * ma_get_bytes_per_frame(configIn.format, configIn.channels);
5057             if (sizeInBytes > MA_SIZE_MAX) {
5058                 result = MA_TOO_BIG;
5059             } else {
5060                 result = ma_decoder_init_memory_raw(pDataBuffer->pNode->data.decoded.pData, (size_t)sizeInBytes, &configIn, &configOut, &pDataBuffer->connector.decoder);  /* Safe cast thanks to the check above. */
5061             }
5062 
5063             /*
5064             We will know the length for decoded sounds. Don't use ma_decoder_get_length_in_pcm_frames() as it may return 0 for sounds where the length
5065             is not known until it has been fully decoded which we've just done at a higher level.
5066             */
5067             pDataBuffer->lengthInPCMFrames = pDataBuffer->pNode->data.decoded.frameCount;
5068         } else {
5069             configOut.allocationCallbacks = pDataBuffer->pResourceManager->config.allocationCallbacks;
5070             result = ma_decoder_init_memory(pDataBuffer->pNode->data.encoded.pData, pDataBuffer->pNode->data.encoded.sizeInBytes, &configOut, &pDataBuffer->connector.decoder);
5071 
5072             /* Our only option is to use ma_decoder_get_length_in_pcm_frames() when loading from an encoded data source. */
5073             pDataBuffer->lengthInPCMFrames = ma_decoder_get_length_in_pcm_frames(&pDataBuffer->connector.decoder);
5074         }
5075     }
5076 
5077     /*
5078     We can only do mapping if the data source's backend is an audio buffer. If it's not, clear out the callbacks thereby preventing the mixer from attempting
5079     memory map mode, only to fail.
5080     */
5081     if (pDataBuffer->connectorType != ma_resource_manager_data_buffer_connector_buffer) {
5082         pDataBuffer->ds.onMap   = NULL;
5083         pDataBuffer->ds.onUnmap = NULL;
5084     }
5085 
5086     /*
5087     Initialization of the connector is when we can fire the MA_NOTIFICATION_INIT notification. This will give the application access to
5088     the format/channels/rate of the data source.
5089     */
5090     if (result == MA_SUCCESS) {
5091         if (pNotification != NULL) {
5092             ma_async_notification_signal(pNotification, MA_NOTIFICATION_INIT);
5093         }
5094     }
5095 
5096     /* At this point the backend should be initialized. We do *not* want to set pDataSource->result here - that needs to be done at a higher level to ensure it's done as the last step. */
5097     return result;
5098 }
5099 
ma_resource_manager_data_buffer_uninit_connector(ma_resource_manager * pResourceManager,ma_resource_manager_data_buffer * pDataBuffer)5100 static ma_result ma_resource_manager_data_buffer_uninit_connector(ma_resource_manager* pResourceManager, ma_resource_manager_data_buffer* pDataBuffer)
5101 {
5102     MA_ASSERT(pResourceManager != NULL);
5103     MA_ASSERT(pDataBuffer      != NULL);
5104 
5105     if (pDataBuffer->connectorType == ma_resource_manager_data_buffer_connector_decoder) {
5106         ma_decoder_uninit(&pDataBuffer->connector.decoder);
5107     } else {
5108         ma_audio_buffer_uninit(&pDataBuffer->connector.buffer);
5109     }
5110 
5111     return MA_SUCCESS;
5112 }
5113 
ma_resource_manager_data_buffer_next_execution_order(ma_resource_manager_data_buffer * pDataBuffer)5114 static ma_uint32 ma_resource_manager_data_buffer_next_execution_order(ma_resource_manager_data_buffer* pDataBuffer)
5115 {
5116     MA_ASSERT(pDataBuffer != NULL);
5117     return c89atomic_fetch_add_32(&pDataBuffer->pNode->executionCounter, 1);
5118 }
5119 
ma_resource_manager_data_buffer_is_busy(ma_resource_manager_data_buffer * pDataBuffer,ma_uint64 requiredFrameCount)5120 static ma_bool32 ma_resource_manager_data_buffer_is_busy(ma_resource_manager_data_buffer* pDataBuffer, ma_uint64 requiredFrameCount)
5121 {
5122     /*
5123     Here is where we determine whether or not we need to return MA_BUSY from a data source callback. If we don't have enough data loaded to output all requiredFrameCount frames
5124     we will abort with MA_BUSY. We could also choose to do a partial read (only reading as many frames are available), but it's just easier to abort early and I don't think it
5125     really makes much practical difference. This only applies to decoded buffers.
5126     */
5127     if (pDataBuffer->pNode->data.type == ma_resource_manager_data_buffer_encoding_decoded) {
5128         ma_uint64 availableFrames;
5129 
5130         /* If the sound has been fully loaded then we'll never be busy. */
5131         if (pDataBuffer->pNode->data.decoded.decodedFrameCount == pDataBuffer->pNode->data.decoded.frameCount) {
5132             return MA_FALSE;    /* The sound is fully loaded. The buffer will never be busy. */
5133         }
5134 
5135         if (ma_resource_manager_data_buffer_get_available_frames(pDataBuffer, &availableFrames) == MA_SUCCESS) {
5136             return availableFrames < requiredFrameCount;
5137         }
5138     }
5139 
5140     return MA_FALSE;
5141 }
5142 
ma_resource_manager_data_buffer_get_connector(ma_resource_manager_data_buffer * pDataBuffer)5143 static ma_data_source* ma_resource_manager_data_buffer_get_connector(ma_resource_manager_data_buffer* pDataBuffer)
5144 {
5145     if (pDataBuffer->connectorType == ma_resource_manager_data_buffer_connector_buffer) {
5146         return &pDataBuffer->connector.buffer;
5147     } else {
5148         return &pDataBuffer->connector.decoder;
5149     }
5150 }
5151 
5152 
ma_resource_manager_data_buffer_cb__read_pcm_frames(ma_data_source * pDataSource,void * pFramesOut,ma_uint64 frameCount,ma_uint64 * pFramesRead)5153 static ma_result ma_resource_manager_data_buffer_cb__read_pcm_frames(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead)
5154 {
5155     return ma_resource_manager_data_buffer_read_pcm_frames((ma_resource_manager_data_buffer*)pDataSource, pFramesOut, frameCount, pFramesRead);
5156 }
5157 
ma_resource_manager_data_buffer_cb__seek_to_pcm_frame(ma_data_source * pDataSource,ma_uint64 frameIndex)5158 static ma_result ma_resource_manager_data_buffer_cb__seek_to_pcm_frame(ma_data_source* pDataSource, ma_uint64 frameIndex)
5159 {
5160     return ma_resource_manager_data_buffer_seek_to_pcm_frame((ma_resource_manager_data_buffer*)pDataSource, frameIndex);
5161 }
5162 
ma_resource_manager_data_buffer_cb__map(ma_data_source * pDataSource,void ** ppFramesOut,ma_uint64 * pFrameCount)5163 static ma_result ma_resource_manager_data_buffer_cb__map(ma_data_source* pDataSource, void** ppFramesOut, ma_uint64* pFrameCount)
5164 {
5165     return ma_resource_manager_data_buffer_map((ma_resource_manager_data_buffer*)pDataSource, ppFramesOut, pFrameCount);
5166 }
5167 
ma_resource_manager_data_buffer_cb__unmap(ma_data_source * pDataSource,ma_uint64 frameCount)5168 static ma_result ma_resource_manager_data_buffer_cb__unmap(ma_data_source* pDataSource, ma_uint64 frameCount)
5169 {
5170     return ma_resource_manager_data_buffer_unmap((ma_resource_manager_data_buffer*)pDataSource, frameCount);
5171 }
5172 
ma_resource_manager_data_buffer_cb__get_data_format(ma_data_source * pDataSource,ma_format * pFormat,ma_uint32 * pChannels,ma_uint32 * pSampleRate)5173 static ma_result ma_resource_manager_data_buffer_cb__get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate)
5174 {
5175     return ma_resource_manager_data_buffer_get_data_format((ma_resource_manager_data_buffer*)pDataSource, pFormat, pChannels, pSampleRate);
5176 }
5177 
ma_resource_manager_data_buffer_cb__get_cursor_in_pcm_frames(ma_data_source * pDataSource,ma_uint64 * pCursor)5178 static ma_result ma_resource_manager_data_buffer_cb__get_cursor_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pCursor)
5179 {
5180     return ma_resource_manager_data_buffer_get_cursor_in_pcm_frames((ma_resource_manager_data_buffer*)pDataSource, pCursor);
5181 }
5182 
ma_resource_manager_data_buffer_cb__get_length_in_pcm_frames(ma_data_source * pDataSource,ma_uint64 * pLength)5183 static ma_result ma_resource_manager_data_buffer_cb__get_length_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pLength)
5184 {
5185     return ma_resource_manager_data_buffer_get_length_in_pcm_frames((ma_resource_manager_data_buffer*)pDataSource, pLength);
5186 }
5187 
ma_resource_manager_data_buffer_init_nolock(ma_resource_manager * pResourceManager,const char * pFilePath,ma_uint32 hashedName32,ma_uint32 flags,ma_async_notification * pNotification,ma_resource_manager_data_buffer * pDataBuffer)5188 static ma_result ma_resource_manager_data_buffer_init_nolock(ma_resource_manager* pResourceManager, const char* pFilePath, ma_uint32 hashedName32, ma_uint32 flags, ma_async_notification* pNotification, ma_resource_manager_data_buffer* pDataBuffer)
5189 {
5190     ma_result result;
5191     ma_resource_manager_data_buffer_node* pInsertPoint;
5192     char* pFilePathCopy;    /* Allocated here, freed in the job thread. */
5193     ma_resource_manager_data_buffer_encoding dataBufferType;
5194     ma_bool32 async;
5195 
5196     MA_ASSERT(pResourceManager != NULL);
5197     MA_ASSERT(pFilePath        != NULL);
5198     MA_ASSERT(pDataBuffer      != NULL);
5199 
5200     MA_ZERO_OBJECT(pDataBuffer);
5201     pDataBuffer->ds.onRead          = ma_resource_manager_data_buffer_cb__read_pcm_frames;
5202     pDataBuffer->ds.onSeek          = ma_resource_manager_data_buffer_cb__seek_to_pcm_frame;
5203     pDataBuffer->ds.onMap           = ma_resource_manager_data_buffer_cb__map;
5204     pDataBuffer->ds.onUnmap         = ma_resource_manager_data_buffer_cb__unmap;
5205     pDataBuffer->ds.onGetDataFormat = ma_resource_manager_data_buffer_cb__get_data_format;
5206     pDataBuffer->ds.onGetCursor     = ma_resource_manager_data_buffer_cb__get_cursor_in_pcm_frames;
5207     pDataBuffer->ds.onGetLength     = ma_resource_manager_data_buffer_cb__get_length_in_pcm_frames;
5208 
5209     pDataBuffer->pResourceManager   = pResourceManager;
5210     pDataBuffer->flags              = flags;
5211 
5212     /* The backend type hasn't been determined yet - that happens when it's initialized properly by the job thread. */
5213     pDataBuffer->connectorType = ma_resource_manager_data_buffer_connector_unknown;
5214 
5215     /* The encoding of the data buffer is taken from the flags. */
5216     if ((flags & MA_DATA_SOURCE_FLAG_DECODE) != 0) {
5217         dataBufferType = ma_resource_manager_data_buffer_encoding_decoded;
5218     } else {
5219         dataBufferType = ma_resource_manager_data_buffer_encoding_encoded;
5220     }
5221 
5222     /* The data buffer needs to be loaded by the calling thread if we're in synchronous mode. */
5223     async = (flags & MA_DATA_SOURCE_FLAG_ASYNC) != 0;
5224 
5225     /*
5226     The first thing to do is find the insertion point. If it's already loaded it means we can just increment the reference counter and signal the event. Otherwise we
5227     need to do a full load.
5228     */
5229     result = ma_resource_manager_data_buffer_node_insert_point(pResourceManager, hashedName32, &pInsertPoint);
5230     if (result == MA_ALREADY_EXISTS) {
5231         /* Fast path. The data buffer already exists. We just need to increment the reference counter and signal the event, if any. */
5232         pDataBuffer->pNode = pInsertPoint;
5233 
5234         result = ma_resource_manager_data_buffer_node_increment_ref(pResourceManager, pDataBuffer->pNode, NULL);
5235         if (result != MA_SUCCESS) {
5236             return result;  /* Should never happen. Failed to increment the reference count. */
5237         }
5238 
5239         /* The existing node may be in the middle of loading. We need to wait for the node to finish loading before going any further. */
5240         /* TODO: This needs to be improved so that when loading asynchronously we post a message to the job queue instead of just waiting. */
5241         while (pDataBuffer->pNode->result == MA_BUSY) {
5242             ma_yield();
5243         }
5244 
5245         result = ma_resource_manager_data_buffer_init_connector(pDataBuffer, pNotification);
5246         if (result != MA_SUCCESS) {
5247             ma_resource_manager_data_buffer_node_free(pDataBuffer->pResourceManager, pDataBuffer->pNode);
5248             return result;
5249         }
5250 
5251         if (pNotification != NULL) {
5252             ma_async_notification_signal(pNotification, MA_NOTIFICATION_COMPLETE);
5253         }
5254     } else {
5255         /* Slow path. The data for this buffer has not yet been initialized. The first thing to do is allocate the new data buffer and insert it into the BST. */
5256         pDataBuffer->pNode = (ma_resource_manager_data_buffer_node*)ma__malloc_from_callbacks(sizeof(*pDataBuffer->pNode), &pResourceManager->config.allocationCallbacks/*, MA_ALLOCATION_TYPE_RESOURCE_MANAGER_DATA_BUFFER*/);
5257         if (pDataBuffer->pNode == NULL) {
5258             return MA_OUT_OF_MEMORY;
5259         }
5260 
5261         MA_ZERO_OBJECT(pDataBuffer->pNode);
5262         pDataBuffer->pNode->hashedName32 = hashedName32;
5263         pDataBuffer->pNode->refCount     = 1;        /* Always set to 1 by default (this is our first reference). */
5264         pDataBuffer->pNode->data.type    = dataBufferType;
5265         pDataBuffer->pNode->result       = MA_BUSY;  /* I think it's good practice to set the status to MA_BUSY by default. */
5266 
5267         result = ma_resource_manager_data_buffer_node_insert_at(pResourceManager, pDataBuffer->pNode, pInsertPoint);
5268         if (result != MA_SUCCESS) {
5269             return result;  /* Should never happen. Failed to insert the data buffer into the BST. */
5270         }
5271 
5272         /*
5273         The new data buffer has been inserted into the BST. We now need to load the data. If we are loading synchronously we need to load
5274         everything from the calling thread because we may be in a situation where there are no job threads running and therefore the data
5275         will never get loaded. If we are loading asynchronously, we can assume at least one job thread exists and we can do everything
5276         from there.
5277         */
5278         pDataBuffer->pNode->isDataOwnedByResourceManager = MA_TRUE;
5279         pDataBuffer->pNode->result = MA_BUSY;
5280 
5281         if (async) {
5282             /* Asynchronous. Post to the job thread. */
5283             ma_job job;
5284 
5285             /* We need a copy of the file path. We should probably make this more efficient, but for now we'll do a transient memory allocation. */
5286             pFilePathCopy = ma_copy_string(pFilePath, &pResourceManager->config.allocationCallbacks/*, MA_ALLOCATION_TYPE_TRANSIENT_STRING*/);
5287             if (pFilePathCopy == NULL) {
5288                 if (pNotification != NULL) {
5289                     ma_async_notification_signal(pNotification, MA_NOTIFICATION_COMPLETE);
5290                 }
5291 
5292                 ma_resource_manager_data_buffer_node_remove(pResourceManager, pDataBuffer->pNode);
5293                 ma__free_from_callbacks(pDataBuffer->pNode, &pResourceManager->config.allocationCallbacks/*, MA_ALLOCATION_TYPE_RESOURCE_MANAGER_DATA_BUFFER*/);
5294                 return MA_OUT_OF_MEMORY;
5295             }
5296 
5297             /* We now have everything we need to post the job to the job thread. */
5298             job = ma_job_init(MA_JOB_LOAD_DATA_BUFFER);
5299             job.order = ma_resource_manager_data_buffer_next_execution_order(pDataBuffer);
5300             job.loadDataBuffer.pDataBuffer   = pDataBuffer;
5301             job.loadDataBuffer.pFilePath     = pFilePathCopy;
5302             job.loadDataBuffer.pNotification = pNotification;
5303             result = ma_resource_manager_post_job(pResourceManager, &job);
5304             if (result != MA_SUCCESS) {
5305                 /* Failed to post the job to the queue. Probably ran out of space. */
5306                 if (pNotification != NULL) {
5307                     ma_async_notification_signal(pNotification, MA_NOTIFICATION_COMPLETE);
5308                 }
5309 
5310                 ma_resource_manager_data_buffer_node_remove(pResourceManager, pDataBuffer->pNode);
5311                 ma__free_from_callbacks(pDataBuffer->pNode, &pResourceManager->config.allocationCallbacks/*, MA_ALLOCATION_TYPE_RESOURCE_MANAGER_DATA_BUFFER*/);
5312                 ma__free_from_callbacks(pFilePathCopy,      &pResourceManager->config.allocationCallbacks/*, MA_ALLOCATION_TYPE_TRANSIENT_STRING*/);
5313                 return result;
5314             }
5315         } else {
5316             /* Synchronous. Do everything here. */
5317             if (pDataBuffer->pNode->data.type == ma_resource_manager_data_buffer_encoding_encoded) {
5318                 /* No decoding. Just store the file contents in memory. */
5319                 void* pData;
5320                 size_t sizeInBytes;
5321                 result = ma_vfs_open_and_read_file_ex(pResourceManager->config.pVFS, pFilePath, &pData, &sizeInBytes, &pResourceManager->config.allocationCallbacks, MA_ALLOCATION_TYPE_ENCODED_BUFFER);
5322                 if (result == MA_SUCCESS) {
5323                     pDataBuffer->pNode->data.encoded.pData       = pData;
5324                     pDataBuffer->pNode->data.encoded.sizeInBytes = sizeInBytes;
5325                 }
5326             } else  {
5327                 /* Decoding. */
5328                 ma_decoder decoder;
5329 
5330                 result = ma_resource_manager__init_decoder(pResourceManager, pFilePath, &decoder);
5331                 if (result == MA_SUCCESS) {
5332                     ma_uint64 totalFrameCount;
5333                     ma_uint64 dataSizeInBytes;
5334                     void* pData = NULL;
5335 
5336                     pDataBuffer->pNode->data.decoded.format     = decoder.outputFormat;
5337                     pDataBuffer->pNode->data.decoded.channels   = decoder.outputChannels;
5338                     pDataBuffer->pNode->data.decoded.sampleRate = decoder.outputSampleRate;
5339 
5340                     totalFrameCount = ma_decoder_get_length_in_pcm_frames(&decoder);
5341                     if (totalFrameCount > 0) {
5342                         /* It's a known length. We can use an optimized allocation strategy in this case. */
5343                         dataSizeInBytes = totalFrameCount * ma_get_bytes_per_frame(decoder.outputFormat, decoder.outputChannels);
5344                         if (dataSizeInBytes <= MA_SIZE_MAX) {
5345                             pData = ma__malloc_from_callbacks((size_t)dataSizeInBytes, &pResourceManager->config.allocationCallbacks/*, MA_ALLOCATION_TYPE_DECODED_BUFFER*/);
5346                             if (pData != NULL) {
5347                                 totalFrameCount = ma_decoder_read_pcm_frames(&decoder, pData, totalFrameCount);
5348                             } else {
5349                                 result = MA_OUT_OF_MEMORY;
5350                             }
5351                         } else {
5352                             result = MA_TOO_BIG;
5353                         }
5354                     } else {
5355                         /* It's an unknown length. We need to dynamically expand the buffer as we decode. To start with we allocate space for one page. We'll then double it as we need more space. */
5356                         ma_uint64 bytesPerFrame;
5357                         ma_uint64 pageSizeInFrames;
5358                         ma_uint64 dataSizeInFrames;
5359 
5360                         bytesPerFrame    = ma_get_bytes_per_frame(decoder.outputFormat, decoder.outputChannels);
5361                         pageSizeInFrames = MA_RESOURCE_MANAGER_PAGE_SIZE_IN_MILLISECONDS * (decoder.outputSampleRate/1000);
5362                         dataSizeInFrames = 0;
5363 
5364                         /* Keep loading, page-by-page. */
5365                         for (;;) {
5366                             ma_uint64 framesRead;
5367 
5368                             /* Expand the buffer if need be. */
5369                             if (totalFrameCount + pageSizeInFrames > dataSizeInFrames) {
5370                                 ma_uint64 oldDataSizeInFrames;
5371                                 ma_uint64 oldDataSizeInBytes;
5372                                 ma_uint64 newDataSizeInFrames;
5373                                 ma_uint64 newDataSizeInBytes;
5374                                 void* pNewData;
5375 
5376                                 oldDataSizeInFrames = (dataSizeInFrames);
5377                                 newDataSizeInFrames = (dataSizeInFrames == 0) ? pageSizeInFrames : dataSizeInFrames * 2;
5378 
5379                                 oldDataSizeInBytes = bytesPerFrame * oldDataSizeInFrames;
5380                                 newDataSizeInBytes = bytesPerFrame * newDataSizeInFrames;
5381 
5382                                 if (newDataSizeInBytes > MA_SIZE_MAX) {
5383                                     result = MA_TOO_BIG;
5384                                     break;
5385                                 }
5386 
5387                                 pNewData = ma__realloc_from_callbacks(pData, (size_t)newDataSizeInBytes, (size_t)oldDataSizeInBytes, &pResourceManager->config.allocationCallbacks/*, MA_ALLOCATION_TYPE_DECODED_BUFFER*/);
5388                                 if (pNewData == NULL) {
5389                                     ma__free_from_callbacks(pData, &pResourceManager->config.allocationCallbacks/*, MA_ALLOCATION_TYPE_DECODED_BUFFER*/);
5390                                     result = MA_OUT_OF_MEMORY;
5391                                     break;
5392                                 }
5393 
5394                                 pData            = pNewData;
5395                                 dataSizeInFrames = newDataSizeInFrames;
5396                             }
5397 
5398                             framesRead = ma_decoder_read_pcm_frames(&decoder, ma_offset_ptr(pData, bytesPerFrame * totalFrameCount), pageSizeInFrames);
5399                             totalFrameCount += framesRead;
5400 
5401                             if (framesRead < pageSizeInFrames) {
5402                                 /* We've reached the end. As we were loading we were doubling the size of the buffer each time we needed more memory. Let's try reducing this by doing a final realloc(). */
5403                                 size_t newDataSizeInBytes = (size_t)(totalFrameCount  * bytesPerFrame);
5404                                 size_t oldDataSizeInBytes = (size_t)(dataSizeInFrames * bytesPerFrame);
5405                                 void* pNewData = ma__realloc_from_callbacks(pData, newDataSizeInBytes, oldDataSizeInBytes, &pResourceManager->config.allocationCallbacks);
5406                                 if (pNewData != NULL) {
5407                                     pData = pNewData;
5408                                 }
5409 
5410                                 /* We're done, so get out of the loop. */
5411                                 break;
5412                             }
5413                         }
5414                     }
5415 
5416                     if (result == MA_SUCCESS) {
5417                         pDataBuffer->pNode->data.decoded.pData             = pData;
5418                         pDataBuffer->pNode->data.decoded.frameCount        = totalFrameCount;
5419                         pDataBuffer->pNode->data.decoded.decodedFrameCount = totalFrameCount;  /* We've decoded everything. */
5420                     } else {
5421                         pDataBuffer->pNode->data.decoded.pData             = NULL;
5422                         pDataBuffer->pNode->data.decoded.frameCount        = 0;
5423                         pDataBuffer->pNode->data.decoded.decodedFrameCount = 0;
5424                     }
5425 
5426                     ma_decoder_uninit(&decoder);
5427                 }
5428             }
5429 
5430             /* When loading synchronously we need to initialize the connector straight away. */
5431             if (result == MA_SUCCESS) {
5432                 result = ma_resource_manager_data_buffer_init_connector(pDataBuffer, pNotification);
5433             }
5434 
5435             pDataBuffer->pNode->result = result;
5436         }
5437 
5438         /* If we failed to initialize make sure we fire the event and free memory. */
5439         if (result != MA_SUCCESS) {
5440             if (pNotification != NULL) {
5441                 ma_async_notification_signal(pNotification, MA_NOTIFICATION_COMPLETE);
5442             }
5443 
5444             ma_resource_manager_data_buffer_node_remove(pResourceManager, pDataBuffer->pNode);
5445             ma__free_from_callbacks(pDataBuffer->pNode, &pResourceManager->config.allocationCallbacks/*, MA_ALLOCATION_TYPE_RESOURCE_MANAGER_DATA_BUFFER*/);
5446             return result;
5447         }
5448 
5449         /* We'll need to fire the event if we have one in synchronous mode. */
5450         if (async == MA_FALSE) {
5451             if (pNotification != NULL) {
5452                 ma_async_notification_signal(pNotification, MA_NOTIFICATION_COMPLETE);
5453             }
5454         }
5455     }
5456 
5457     return MA_SUCCESS;
5458 }
5459 
ma_resource_manager_data_buffer_init(ma_resource_manager * pResourceManager,const char * pFilePath,ma_uint32 flags,ma_async_notification * pNotification,ma_resource_manager_data_buffer * pDataBuffer)5460 MA_API ma_result ma_resource_manager_data_buffer_init(ma_resource_manager* pResourceManager, const char* pFilePath, ma_uint32 flags, ma_async_notification* pNotification, ma_resource_manager_data_buffer* pDataBuffer)
5461 {
5462     ma_result result;
5463     ma_uint32 hashedName32;
5464 
5465     if (pDataBuffer == NULL) {
5466         return MA_INVALID_ARGS;
5467     }
5468 
5469     if (pResourceManager == NULL || pFilePath == NULL) {
5470         return MA_INVALID_ARGS;
5471     }
5472 
5473     /* Do as much set up before entering into the critical section to reduce our lock time as much as possible. */
5474     hashedName32 = ma_hash_string_32(pFilePath);
5475 
5476     /* At this point we can now enter the critical section. */
5477     ma_mutex_lock(&pResourceManager->dataBufferLock);
5478     {
5479         result = ma_resource_manager_data_buffer_init_nolock(pResourceManager, pFilePath, hashedName32, flags, pNotification, pDataBuffer);
5480     }
5481     ma_mutex_unlock(&pResourceManager->dataBufferLock);
5482 
5483     return result;
5484 }
5485 
ma_resource_manager_data_buffer_uninit_internal(ma_resource_manager_data_buffer * pDataBuffer)5486 static ma_result ma_resource_manager_data_buffer_uninit_internal(ma_resource_manager_data_buffer* pDataBuffer)
5487 {
5488     MA_ASSERT(pDataBuffer != NULL);
5489 
5490     /* The connector should be uninitialized first. */
5491     ma_resource_manager_data_buffer_uninit_connector(pDataBuffer->pResourceManager, pDataBuffer);
5492     pDataBuffer->connectorType = ma_resource_manager_data_buffer_connector_unknown;
5493 
5494     /* Free the node last. */
5495     ma_resource_manager_data_buffer_node_free(pDataBuffer->pResourceManager, pDataBuffer->pNode);
5496 
5497     return MA_SUCCESS;
5498 }
5499 
ma_resource_manager_data_buffer_uninit_nolock(ma_resource_manager_data_buffer * pDataBuffer)5500 static ma_result ma_resource_manager_data_buffer_uninit_nolock(ma_resource_manager_data_buffer* pDataBuffer)
5501 {
5502     ma_uint32 result;
5503     ma_uint32 refCount;
5504 
5505     MA_ASSERT(pDataBuffer != NULL);
5506 
5507     result = ma_resource_manager_data_buffer_node_decrement_ref(pDataBuffer->pResourceManager, pDataBuffer->pNode, &refCount);
5508     if (result != MA_SUCCESS) {
5509         return result;
5510     }
5511 
5512     /* If the reference count has hit zero it means we need to delete the data buffer and it's backing data (so long as it's owned by the resource manager). */
5513     if (refCount == 0) {
5514         ma_bool32 asyncUninit = MA_TRUE;
5515 
5516         result = ma_resource_manager_data_buffer_node_remove(pDataBuffer->pResourceManager, pDataBuffer->pNode);
5517         if (result != MA_SUCCESS) {
5518             return result;  /* An error occurred when trying to remove the data buffer. This should never happen. */
5519         }
5520 
5521         if (pDataBuffer->pNode->result == MA_SUCCESS) {
5522             asyncUninit = MA_FALSE;
5523         }
5524 
5525         /*
5526         The data buffer has been removed from the BST so now we need to delete the underyling data. This needs to be done in a separate thread. We don't
5527         want to delete anything if the data is owned by the application. Also, just to be safe, we set the result to MA_UNAVAILABLE.
5528         */
5529         c89atomic_exchange_32(&pDataBuffer->pNode->result, MA_UNAVAILABLE);
5530 
5531         if (asyncUninit == MA_FALSE) {
5532             /* The data buffer can be deleted synchronously. */
5533             return ma_resource_manager_data_buffer_uninit_internal(pDataBuffer);
5534         } else {
5535             /*
5536             The data buffer needs to be deleted asynchronously because it's still loading. With the status set to MA_UNAVAILABLE, no more pages will
5537             be loaded and the uninitialization should happen fairly quickly. Since the caller owns the data buffer, we need to wait for this event
5538             to get processed before returning.
5539             */
5540             ma_async_notification_event waitEvent;
5541             ma_job job;
5542 
5543             result = ma_async_notification_event_init(&waitEvent);
5544             if (result != MA_SUCCESS) {
5545                 return result;  /* Failed to create the wait event. This should rarely if ever happen. */
5546             }
5547 
5548             job = ma_job_init(MA_JOB_FREE_DATA_BUFFER);
5549             job.order = ma_resource_manager_data_buffer_next_execution_order(pDataBuffer);
5550             job.freeDataBuffer.pDataBuffer   = pDataBuffer;
5551             job.freeDataBuffer.pNotification = &waitEvent;
5552 
5553             result = ma_resource_manager_post_job(pDataBuffer->pResourceManager, &job);
5554             if (result != MA_SUCCESS) {
5555                 ma_async_notification_event_uninit(&waitEvent);
5556                 return result;
5557             }
5558 
5559             ma_async_notification_event_wait(&waitEvent);
5560             ma_async_notification_event_uninit(&waitEvent);
5561         }
5562     }
5563 
5564     return MA_SUCCESS;
5565 }
5566 
ma_resource_manager_data_buffer_uninit(ma_resource_manager_data_buffer * pDataBuffer)5567 MA_API ma_result ma_resource_manager_data_buffer_uninit(ma_resource_manager_data_buffer* pDataBuffer)
5568 {
5569     ma_result result;
5570 
5571     if (pDataBuffer == NULL) {
5572         return MA_INVALID_ARGS;
5573     }
5574 
5575     ma_mutex_lock(&pDataBuffer->pResourceManager->dataBufferLock);
5576     {
5577         result = ma_resource_manager_data_buffer_uninit_nolock(pDataBuffer);
5578     }
5579     ma_mutex_unlock(&pDataBuffer->pResourceManager->dataBufferLock);
5580 
5581     return result;
5582 }
5583 
ma_resource_manager_data_buffer_read_pcm_frames(ma_resource_manager_data_buffer * pDataBuffer,void * pFramesOut,ma_uint64 frameCount,ma_uint64 * pFramesRead)5584 MA_API ma_result ma_resource_manager_data_buffer_read_pcm_frames(ma_resource_manager_data_buffer* pDataBuffer, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead)
5585 {
5586     ma_result result;
5587     ma_uint64 framesRead;
5588     ma_bool32 skipBusyCheck = MA_FALSE;
5589 
5590     /*
5591     We cannot be using the data buffer after it's been uninitialized. If you trigger this assert it means you're trying to read from the data buffer after
5592     it's been uninitialized or is in the process of uninitializing.
5593     */
5594     MA_ASSERT(pDataBuffer->pNode->result != MA_UNAVAILABLE);
5595 
5596     /* If we haven't yet got a connector we need to abort. */
5597     if (pDataBuffer->connectorType == ma_resource_manager_data_buffer_connector_unknown) {
5598         return MA_BUSY; /* Still loading. */
5599     }
5600 
5601     if (pDataBuffer->seekToCursorOnNextRead) {
5602         pDataBuffer->seekToCursorOnNextRead = MA_FALSE;
5603 
5604         result = ma_data_source_seek_to_pcm_frame(ma_resource_manager_data_buffer_get_connector(pDataBuffer), pDataBuffer->cursorInPCMFrames);
5605         if (result != MA_SUCCESS) {
5606             return result;
5607         }
5608     }
5609 
5610     if (skipBusyCheck == MA_FALSE) {
5611         if (ma_resource_manager_data_buffer_is_busy(pDataBuffer, frameCount)) {
5612             return MA_BUSY;
5613         }
5614     }
5615 
5616     result = ma_data_source_read_pcm_frames(ma_resource_manager_data_buffer_get_connector(pDataBuffer), pFramesOut, frameCount, &framesRead, pDataBuffer->isLooping);
5617     pDataBuffer->cursorInPCMFrames += framesRead;
5618 
5619     if (pFramesRead != NULL) {
5620         *pFramesRead = framesRead;
5621     }
5622 
5623     return result;
5624 }
5625 
ma_resource_manager_data_buffer_seek_to_pcm_frame(ma_resource_manager_data_buffer * pDataBuffer,ma_uint64 frameIndex)5626 MA_API ma_result ma_resource_manager_data_buffer_seek_to_pcm_frame(ma_resource_manager_data_buffer* pDataBuffer, ma_uint64 frameIndex)
5627 {
5628     ma_result result;
5629 
5630     /* We cannot be using the data source after it's been uninitialized. */
5631     MA_ASSERT(pDataBuffer->pNode->result != MA_UNAVAILABLE);
5632 
5633     /* If we haven't yet got a connector we need to abort. */
5634     if (pDataBuffer->connectorType == ma_resource_manager_data_buffer_connector_unknown) {
5635         pDataBuffer->cursorInPCMFrames = frameIndex;
5636         pDataBuffer->seekToCursorOnNextRead = MA_TRUE;
5637         return MA_BUSY; /* Still loading. */
5638     }
5639 
5640     result = ma_data_source_seek_to_pcm_frame(ma_resource_manager_data_buffer_get_connector(pDataBuffer), frameIndex);
5641     if (result != MA_SUCCESS) {
5642         return result;
5643     }
5644 
5645     pDataBuffer->cursorInPCMFrames = frameIndex;
5646     pDataBuffer->seekToCursorOnNextRead = MA_FALSE;
5647 
5648     return MA_SUCCESS;
5649 }
5650 
ma_resource_manager_data_buffer_map(ma_resource_manager_data_buffer * pDataBuffer,void ** ppFramesOut,ma_uint64 * pFrameCount)5651 MA_API ma_result ma_resource_manager_data_buffer_map(ma_resource_manager_data_buffer* pDataBuffer, void** ppFramesOut, ma_uint64* pFrameCount)
5652 {
5653     ma_result result;
5654     ma_bool32 skipBusyCheck = MA_FALSE;
5655 
5656     /* We cannot be using the data source after it's been uninitialized. */
5657     MA_ASSERT(pDataBuffer->pNode->result != MA_UNAVAILABLE);
5658 
5659     /* If we haven't yet got a connector we need to abort. */
5660     if (pDataBuffer->connectorType == ma_resource_manager_data_buffer_connector_unknown) {
5661         return MA_BUSY; /* Still loading. */
5662     }
5663 
5664     if (pDataBuffer->seekToCursorOnNextRead) {
5665         pDataBuffer->seekToCursorOnNextRead = MA_FALSE;
5666 
5667         result = ma_data_source_seek_to_pcm_frame(ma_resource_manager_data_buffer_get_connector(pDataBuffer), pDataBuffer->cursorInPCMFrames);
5668         if (result != MA_SUCCESS) {
5669             return result;
5670         }
5671     }
5672 
5673     if (skipBusyCheck == MA_FALSE) {
5674         if (ma_resource_manager_data_buffer_is_busy(pDataBuffer, *pFrameCount)) {
5675             return MA_BUSY;
5676         }
5677     }
5678 
5679     /* The frame cursor is incremented in unmap(). */
5680     return ma_data_source_map(ma_resource_manager_data_buffer_get_connector(pDataBuffer), ppFramesOut, pFrameCount);
5681 }
5682 
ma_resource_manager_data_buffer_unmap(ma_resource_manager_data_buffer * pDataBuffer,ma_uint64 frameCount)5683 MA_API ma_result ma_resource_manager_data_buffer_unmap(ma_resource_manager_data_buffer* pDataBuffer, ma_uint64 frameCount)
5684 {
5685     ma_result result;
5686 
5687     /* We cannot be using the data source after it's been uninitialized. */
5688     MA_ASSERT(pDataBuffer->pNode->result != MA_UNAVAILABLE);
5689 
5690     result = ma_data_source_unmap(ma_resource_manager_data_buffer_get_connector(pDataBuffer), frameCount);
5691     if (result == MA_SUCCESS) {
5692         pDataBuffer->cursorInPCMFrames += frameCount;
5693     }
5694 
5695     return result;
5696 }
5697 
ma_resource_manager_data_buffer_get_data_format(ma_resource_manager_data_buffer * pDataBuffer,ma_format * pFormat,ma_uint32 * pChannels,ma_uint32 * pSampleRate)5698 MA_API ma_result ma_resource_manager_data_buffer_get_data_format(ma_resource_manager_data_buffer* pDataBuffer, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate)
5699 {
5700     /* We cannot be using the data source after it's been uninitialized. */
5701     MA_ASSERT(pDataBuffer->pNode->result != MA_UNAVAILABLE);
5702 
5703     /* If we haven't yet got a connector we need to abort. */
5704     if (pDataBuffer->connectorType == ma_resource_manager_data_buffer_connector_unknown) {
5705         return MA_BUSY; /* Still loading. */
5706     }
5707 
5708     if (pDataBuffer->connectorType == ma_resource_manager_data_buffer_connector_buffer) {
5709         MA_ASSERT(pDataBuffer->pNode->data.type == ma_resource_manager_data_buffer_encoding_decoded);
5710 
5711         *pFormat     = pDataBuffer->pNode->data.decoded.format;
5712         *pChannels   = pDataBuffer->pNode->data.decoded.channels;
5713         *pSampleRate = pDataBuffer->pNode->data.decoded.sampleRate;
5714 
5715         return MA_SUCCESS;
5716     } else {
5717         return ma_data_source_get_data_format(&pDataBuffer->connector.decoder, pFormat, pChannels, pSampleRate);
5718     }
5719 }
5720 
ma_resource_manager_data_buffer_get_cursor_in_pcm_frames(ma_resource_manager_data_buffer * pDataBuffer,ma_uint64 * pCursor)5721 MA_API ma_result ma_resource_manager_data_buffer_get_cursor_in_pcm_frames(ma_resource_manager_data_buffer* pDataBuffer, ma_uint64* pCursor)
5722 {
5723     /* We cannot be using the data source after it's been uninitialized. */
5724     MA_ASSERT(pDataBuffer->pNode->result != MA_UNAVAILABLE);
5725 
5726     if (pDataBuffer == NULL || pCursor == NULL) {
5727         return MA_INVALID_ARGS;
5728     }
5729 
5730     *pCursor = pDataBuffer->cursorInPCMFrames;
5731 
5732     return MA_SUCCESS;
5733 }
5734 
ma_resource_manager_data_buffer_get_length_in_pcm_frames(ma_resource_manager_data_buffer * pDataBuffer,ma_uint64 * pLength)5735 MA_API ma_result ma_resource_manager_data_buffer_get_length_in_pcm_frames(ma_resource_manager_data_buffer* pDataBuffer, ma_uint64* pLength)
5736 {
5737     /* We cannot be using the data source after it's been uninitialized. */
5738     MA_ASSERT(pDataBuffer->pNode->result != MA_UNAVAILABLE);
5739 
5740     if (pDataBuffer == NULL || pLength == NULL) {
5741         return MA_INVALID_ARGS;
5742     }
5743 
5744     if (pDataBuffer->connectorType == ma_resource_manager_data_buffer_connector_unknown) {
5745         return MA_BUSY; /* Still loading. */
5746     }
5747 
5748     *pLength = pDataBuffer->lengthInPCMFrames;
5749     if (*pLength == 0) {
5750         return MA_NOT_IMPLEMENTED;
5751     }
5752 
5753     return MA_SUCCESS;
5754 }
5755 
ma_resource_manager_data_buffer_result(const ma_resource_manager_data_buffer * pDataBuffer)5756 MA_API ma_result ma_resource_manager_data_buffer_result(const ma_resource_manager_data_buffer* pDataBuffer)
5757 {
5758     if (pDataBuffer == NULL) {
5759         return MA_INVALID_ARGS;
5760     }
5761 
5762     return pDataBuffer->pNode->result;
5763 }
5764 
ma_resource_manager_data_buffer_set_looping(ma_resource_manager_data_buffer * pDataBuffer,ma_bool32 isLooping)5765 MA_API ma_result ma_resource_manager_data_buffer_set_looping(ma_resource_manager_data_buffer* pDataBuffer, ma_bool32 isLooping)
5766 {
5767     if (pDataBuffer == NULL) {
5768         return MA_INVALID_ARGS;
5769     }
5770 
5771     c89atomic_exchange_32(&pDataBuffer->isLooping, isLooping);
5772 
5773     return MA_SUCCESS;
5774 }
5775 
ma_resource_manager_data_buffer_get_looping(const ma_resource_manager_data_buffer * pDataBuffer,ma_bool32 * pIsLooping)5776 MA_API ma_result ma_resource_manager_data_buffer_get_looping(const ma_resource_manager_data_buffer* pDataBuffer, ma_bool32* pIsLooping)
5777 {
5778     if (pDataBuffer == NULL || pIsLooping == NULL) {
5779         return MA_INVALID_ARGS;
5780     }
5781 
5782     *pIsLooping = pDataBuffer->isLooping;
5783 
5784     return MA_SUCCESS;
5785 }
5786 
ma_resource_manager_data_buffer_get_available_frames(ma_resource_manager_data_buffer * pDataBuffer,ma_uint64 * pAvailableFrames)5787 MA_API ma_result ma_resource_manager_data_buffer_get_available_frames(ma_resource_manager_data_buffer* pDataBuffer, ma_uint64* pAvailableFrames)
5788 {
5789     if (pAvailableFrames == NULL) {
5790         return MA_INVALID_ARGS;
5791     }
5792 
5793     *pAvailableFrames = 0;
5794 
5795     if (pDataBuffer == NULL) {
5796         return MA_INVALID_ARGS;
5797     }
5798 
5799     if (pDataBuffer->connectorType == ma_resource_manager_data_buffer_connector_unknown) {
5800         if (ma_resource_manager_data_buffer_result(pDataBuffer) == MA_BUSY) {
5801             return MA_BUSY;
5802         } else {
5803             return MA_INVALID_OPERATION;    /* No connector. */
5804         }
5805     }
5806 
5807     if (pDataBuffer->connectorType == ma_resource_manager_data_buffer_connector_buffer) {
5808         /* Retrieve the available frames based on how many frames we've currently decoded, and *not* the total capacity of the audio buffer. */
5809         if (pDataBuffer->pNode->data.decoded.decodedFrameCount > pDataBuffer->cursorInPCMFrames) {
5810             *pAvailableFrames = pDataBuffer->pNode->data.decoded.decodedFrameCount - pDataBuffer->cursorInPCMFrames;
5811         } else {
5812             *pAvailableFrames = 0;
5813         }
5814 
5815         return MA_SUCCESS;
5816     } else {
5817         return ma_decoder_get_available_frames(&pDataBuffer->connector.decoder, pAvailableFrames);
5818     }
5819 }
5820 
5821 
ma_resource_manager_register_data_nolock(ma_resource_manager * pResourceManager,ma_uint32 hashedName32,ma_resource_manager_data_buffer_encoding type,ma_resource_manager_memory_buffer * pExistingData,ma_resource_manager_data_buffer * pDataBuffer)5822 static ma_result ma_resource_manager_register_data_nolock(ma_resource_manager* pResourceManager, ma_uint32 hashedName32, ma_resource_manager_data_buffer_encoding type, ma_resource_manager_memory_buffer* pExistingData, ma_resource_manager_data_buffer* pDataBuffer)
5823 {
5824     ma_result result;
5825     ma_resource_manager_data_buffer_node* pInsertPoint;
5826 
5827     result = ma_resource_manager_data_buffer_node_insert_point(pResourceManager, hashedName32, &pInsertPoint);
5828     if (result == MA_ALREADY_EXISTS) {
5829         /* Fast path. The data buffer already exists. We just need to increment the reference counter and signal the event, if any. */
5830         pDataBuffer->pNode = pInsertPoint;
5831 
5832         result = ma_resource_manager_data_buffer_node_increment_ref(pResourceManager, pDataBuffer->pNode, NULL);
5833         if (result != MA_SUCCESS) {
5834             return result;  /* Should never happen. Failed to increment the reference count. */
5835         }
5836     } else {
5837         /* Slow path. The data for this buffer has not yet been initialized. The first thing to do is allocate the new data buffer and insert it into the BST. */
5838         pDataBuffer->pNode = (ma_resource_manager_data_buffer_node*)ma__malloc_from_callbacks(sizeof(*pDataBuffer->pNode), &pResourceManager->config.allocationCallbacks/*, MA_ALLOCATION_TYPE_RESOURCE_MANAGER_DATA_BUFFER*/);
5839         if (pDataBuffer->pNode == NULL) {
5840             return MA_OUT_OF_MEMORY;
5841         }
5842 
5843         MA_ZERO_OBJECT(pDataBuffer->pNode);
5844         pDataBuffer->pNode->hashedName32 = hashedName32;
5845         pDataBuffer->pNode->refCount     = 1;        /* Always set to 1 by default (this is our first reference). */
5846         pDataBuffer->pNode->data.type    = type;
5847         pDataBuffer->pNode->result       = MA_SUCCESS;
5848 
5849         result = ma_resource_manager_data_buffer_node_insert_at(pResourceManager, pDataBuffer->pNode, pInsertPoint);
5850         if (result != MA_SUCCESS) {
5851             return result;  /* Should never happen. Failed to insert the data buffer into the BST. */
5852         }
5853 
5854         pDataBuffer->pNode->isDataOwnedByResourceManager = MA_FALSE;
5855         pDataBuffer->pNode->data = *pExistingData;
5856     }
5857 
5858     return MA_SUCCESS;
5859 }
5860 
ma_resource_manager_register_data(ma_resource_manager * pResourceManager,const char * pName,ma_resource_manager_data_buffer_encoding type,ma_resource_manager_memory_buffer * pExistingData,ma_resource_manager_data_buffer * pDataBuffer)5861 static ma_result ma_resource_manager_register_data(ma_resource_manager* pResourceManager, const char* pName, ma_resource_manager_data_buffer_encoding type, ma_resource_manager_memory_buffer* pExistingData, ma_resource_manager_data_buffer* pDataBuffer)
5862 {
5863     ma_result result = MA_SUCCESS;
5864     ma_uint32 hashedName32;
5865 
5866     if (pResourceManager == NULL || pName == NULL) {
5867         return MA_INVALID_ARGS;
5868     }
5869 
5870     hashedName32 = ma_hash_string_32(pName);
5871 
5872     ma_mutex_lock(&pResourceManager->dataBufferLock);
5873     {
5874         result = ma_resource_manager_register_data_nolock(pResourceManager, hashedName32, type, pExistingData, pDataBuffer);
5875     }
5876     ma_mutex_lock(&pResourceManager->dataBufferLock);
5877 
5878     return result;
5879 }
5880 
ma_resource_manager_register_decoded_data(ma_resource_manager * pResourceManager,const char * pName,const void * pData,ma_uint64 frameCount,ma_format format,ma_uint32 channels,ma_uint32 sampleRate)5881 MA_API ma_result ma_resource_manager_register_decoded_data(ma_resource_manager* pResourceManager, const char* pName, const void* pData, ma_uint64 frameCount, ma_format format, ma_uint32 channels, ma_uint32 sampleRate)
5882 {
5883     ma_resource_manager_memory_buffer data;
5884     data.type               = ma_resource_manager_data_buffer_encoding_decoded;
5885     data.decoded.pData      = pData;
5886     data.decoded.frameCount = frameCount;
5887     data.decoded.format     = format;
5888     data.decoded.channels   = channels;
5889     data.decoded.sampleRate = sampleRate;
5890 
5891     return ma_resource_manager_register_data(pResourceManager, pName, data.type, &data, NULL);
5892 }
5893 
ma_resource_manager_register_encoded_data(ma_resource_manager * pResourceManager,const char * pName,const void * pData,size_t sizeInBytes)5894 MA_API ma_result ma_resource_manager_register_encoded_data(ma_resource_manager* pResourceManager, const char* pName, const void* pData, size_t sizeInBytes)
5895 {
5896     ma_resource_manager_memory_buffer data;
5897     data.type                = ma_resource_manager_data_buffer_encoding_encoded;
5898     data.encoded.pData       = pData;
5899     data.encoded.sizeInBytes = sizeInBytes;
5900 
5901     return ma_resource_manager_register_data(pResourceManager, pName, data.type, &data, NULL);
5902 }
5903 
5904 
ma_resource_manager_unregister_data_nolock(ma_resource_manager * pResourceManager,ma_uint32 hashedName32)5905 static ma_result ma_resource_manager_unregister_data_nolock(ma_resource_manager* pResourceManager, ma_uint32 hashedName32)
5906 {
5907     ma_result result;
5908     ma_resource_manager_data_buffer_node* pDataBufferNode;
5909     ma_uint32 refCount;
5910 
5911     result = ma_resource_manager_data_buffer_node_search(pResourceManager, hashedName32, &pDataBufferNode);
5912     if (result != MA_SUCCESS) {
5913         return result;  /* Couldn't find the node. */
5914     }
5915 
5916     result = ma_resource_manager_data_buffer_node_decrement_ref(pResourceManager, pDataBufferNode, &refCount);
5917     if (result != MA_SUCCESS) {
5918         return result;
5919     }
5920 
5921     /* If the reference count has hit zero it means we can remove it from the BST. */
5922     if (refCount == 0) {
5923         result = ma_resource_manager_data_buffer_node_remove(pResourceManager, pDataBufferNode);
5924         if (result != MA_SUCCESS) {
5925             return result;  /* An error occurred when trying to remove the data buffer. This should never happen. */
5926         }
5927     }
5928 
5929     /* Finally we need to free the node. */
5930     ma_resource_manager_data_buffer_node_free(pResourceManager, pDataBufferNode);
5931 
5932     return MA_SUCCESS;
5933 }
5934 
ma_resource_manager_unregister_data(ma_resource_manager * pResourceManager,const char * pName)5935 MA_API ma_result ma_resource_manager_unregister_data(ma_resource_manager* pResourceManager, const char* pName)
5936 {
5937     ma_result result;
5938     ma_uint32 hashedName32;
5939 
5940     if (pResourceManager == NULL || pName == NULL) {
5941         return MA_INVALID_ARGS;
5942     }
5943 
5944     hashedName32 = ma_hash_string_32(pName);
5945 
5946     /*
5947     It's assumed that the data specified by pName was registered with a prior call to ma_resource_manager_register_encoded/decoded_data(). To unregister it, all
5948     we need to do is delete the data buffer by it's name.
5949     */
5950     ma_mutex_lock(&pResourceManager->dataBufferLock);
5951     {
5952         result = ma_resource_manager_unregister_data_nolock(pResourceManager, hashedName32);
5953     }
5954     ma_mutex_unlock(&pResourceManager->dataBufferLock);
5955 
5956     return result;
5957 }
5958 
5959 
ma_resource_manager_data_stream_next_execution_order(ma_resource_manager_data_stream * pDataStream)5960 static ma_uint32 ma_resource_manager_data_stream_next_execution_order(ma_resource_manager_data_stream* pDataStream)
5961 {
5962     MA_ASSERT(pDataStream != NULL);
5963     return c89atomic_fetch_add_32(&pDataStream->executionCounter, 1);
5964 }
5965 
5966 
ma_resource_manager_data_stream_cb__read_pcm_frames(ma_data_source * pDataSource,void * pFramesOut,ma_uint64 frameCount,ma_uint64 * pFramesRead)5967 static ma_result ma_resource_manager_data_stream_cb__read_pcm_frames(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead)
5968 {
5969     return ma_resource_manager_data_stream_read_pcm_frames((ma_resource_manager_data_stream*)pDataSource, pFramesOut, frameCount, pFramesRead);
5970 }
5971 
ma_resource_manager_data_stream_cb__seek_to_pcm_frame(ma_data_source * pDataSource,ma_uint64 frameIndex)5972 static ma_result ma_resource_manager_data_stream_cb__seek_to_pcm_frame(ma_data_source* pDataSource, ma_uint64 frameIndex)
5973 {
5974     return ma_resource_manager_data_stream_seek_to_pcm_frame((ma_resource_manager_data_stream*)pDataSource, frameIndex);
5975 }
5976 
ma_resource_manager_data_stream_cb__map(ma_data_source * pDataSource,void ** ppFramesOut,ma_uint64 * pFrameCount)5977 static ma_result ma_resource_manager_data_stream_cb__map(ma_data_source* pDataSource, void** ppFramesOut, ma_uint64* pFrameCount)
5978 {
5979     return ma_resource_manager_data_stream_map((ma_resource_manager_data_stream*)pDataSource, ppFramesOut, pFrameCount);
5980 }
5981 
ma_resource_manager_data_stream_cb__unmap(ma_data_source * pDataSource,ma_uint64 frameCount)5982 static ma_result ma_resource_manager_data_stream_cb__unmap(ma_data_source* pDataSource, ma_uint64 frameCount)
5983 {
5984     return ma_resource_manager_data_stream_unmap((ma_resource_manager_data_stream*)pDataSource, frameCount);
5985 }
5986 
ma_resource_manager_data_stream_cb__get_data_format(ma_data_source * pDataSource,ma_format * pFormat,ma_uint32 * pChannels,ma_uint32 * pSampleRate)5987 static ma_result ma_resource_manager_data_stream_cb__get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate)
5988 {
5989     return ma_resource_manager_data_stream_get_data_format((ma_resource_manager_data_stream*)pDataSource, pFormat, pChannels, pSampleRate);
5990 }
5991 
ma_resource_manager_data_stream_cb__get_cursor_in_pcm_frames(ma_data_source * pDataSource,ma_uint64 * pCursor)5992 static ma_result ma_resource_manager_data_stream_cb__get_cursor_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pCursor)
5993 {
5994     return ma_resource_manager_data_stream_get_cursor_in_pcm_frames((ma_resource_manager_data_stream*)pDataSource, pCursor);
5995 }
5996 
ma_resource_manager_data_stream_cb__get_length_in_pcm_frames(ma_data_source * pDataSource,ma_uint64 * pLength)5997 static ma_result ma_resource_manager_data_stream_cb__get_length_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pLength)
5998 {
5999     return ma_resource_manager_data_stream_get_length_in_pcm_frames((ma_resource_manager_data_stream*)pDataSource, pLength);
6000 }
6001 
ma_resource_manager_data_stream_init(ma_resource_manager * pResourceManager,const char * pFilePath,ma_uint32 flags,ma_async_notification * pNotification,ma_resource_manager_data_stream * pDataStream)6002 MA_API ma_result ma_resource_manager_data_stream_init(ma_resource_manager* pResourceManager, const char* pFilePath, ma_uint32 flags, ma_async_notification* pNotification, ma_resource_manager_data_stream* pDataStream)
6003 {
6004     ma_result result;
6005     char* pFilePathCopy;
6006     ma_job job;
6007 
6008     if (pDataStream == NULL) {
6009         if (pNotification != NULL) {
6010             ma_async_notification_signal(pNotification, MA_NOTIFICATION_COMPLETE);
6011         }
6012 
6013         return MA_INVALID_ARGS;
6014     }
6015 
6016     MA_ZERO_OBJECT(pDataStream);
6017     pDataStream->ds.onRead          = ma_resource_manager_data_stream_cb__read_pcm_frames;
6018     pDataStream->ds.onSeek          = ma_resource_manager_data_stream_cb__seek_to_pcm_frame;
6019     pDataStream->ds.onMap           = ma_resource_manager_data_stream_cb__map;
6020     pDataStream->ds.onUnmap         = ma_resource_manager_data_stream_cb__unmap;
6021     pDataStream->ds.onGetDataFormat = ma_resource_manager_data_stream_cb__get_data_format;
6022     pDataStream->ds.onGetCursor     = ma_resource_manager_data_stream_cb__get_cursor_in_pcm_frames;
6023     pDataStream->ds.onGetLength     = ma_resource_manager_data_stream_cb__get_length_in_pcm_frames;
6024 
6025     pDataStream->pResourceManager   = pResourceManager;
6026     pDataStream->flags              = flags;
6027     pDataStream->result             = MA_BUSY;
6028 
6029     if (pResourceManager == NULL || pFilePath == NULL) {
6030         if (pNotification != NULL) {
6031             ma_async_notification_signal(pNotification, MA_NOTIFICATION_COMPLETE);
6032         }
6033 
6034         return MA_INVALID_ARGS;
6035     }
6036 
6037     /* We want all access to the VFS and the internal decoder to happen on the job thread just to keep things easier to manage for the VFS.  */
6038 
6039     /* We need a copy of the file path. We should probably make this more efficient, but for now we'll do a transient memory allocation. */
6040     pFilePathCopy = ma_copy_string(pFilePath, &pResourceManager->config.allocationCallbacks/*, MA_ALLOCATION_TYPE_TRANSIENT_STRING*/);
6041     if (pFilePathCopy == NULL) {
6042         if (pNotification != NULL) {
6043             ma_async_notification_signal(pNotification, MA_NOTIFICATION_FAILED);
6044         }
6045 
6046         return MA_OUT_OF_MEMORY;
6047     }
6048 
6049     /* We now have everything we need to post the job. This is the last thing we need to do from here. The rest will be done by the job thread. */
6050     job = ma_job_init(MA_JOB_LOAD_DATA_STREAM);
6051     job.order = ma_resource_manager_data_stream_next_execution_order(pDataStream);
6052     job.loadDataStream.pDataStream   = pDataStream;
6053     job.loadDataStream.pFilePath     = pFilePathCopy;
6054     job.loadDataStream.pNotification = pNotification;
6055     result = ma_resource_manager_post_job(pResourceManager, &job);
6056     if (result != MA_SUCCESS) {
6057         if (pNotification != NULL) {
6058             ma_async_notification_signal(pNotification, MA_NOTIFICATION_FAILED);
6059         }
6060 
6061         ma__free_from_callbacks(pFilePathCopy, &pResourceManager->config.allocationCallbacks/*, MA_ALLOCATION_TYPE_TRANSIENT_STRING*/);
6062         return result;
6063     }
6064 
6065     return MA_SUCCESS;
6066 }
6067 
ma_resource_manager_data_stream_uninit(ma_resource_manager_data_stream * pDataStream)6068 MA_API ma_result ma_resource_manager_data_stream_uninit(ma_resource_manager_data_stream* pDataStream)
6069 {
6070     ma_async_notification_event freeEvent;
6071     ma_job job;
6072 
6073     if (pDataStream == NULL) {
6074         return MA_INVALID_ARGS;
6075     }
6076 
6077     /* The first thing to do is set the result to unavailable. This will prevent future page decoding. */
6078     c89atomic_exchange_32(&pDataStream->result, MA_UNAVAILABLE);
6079 
6080     /*
6081     We need to post a job to ensure we're not in the middle or decoding or anything. Because the object is owned by the caller, we'll need
6082     to wait for it to complete before returning which means we need an event.
6083     */
6084     ma_async_notification_event_init(&freeEvent);
6085 
6086     job = ma_job_init(MA_JOB_FREE_DATA_STREAM);
6087     job.order = ma_resource_manager_data_stream_next_execution_order(pDataStream);
6088     job.freeDataStream.pDataStream   = pDataStream;
6089     job.freeDataStream.pNotification = &freeEvent;
6090     ma_resource_manager_post_job(pDataStream->pResourceManager, &job);
6091 
6092     /* We need to wait for the job to finish processing before we return. */
6093     ma_async_notification_event_wait(&freeEvent);
6094     ma_async_notification_event_uninit(&freeEvent);
6095 
6096     return MA_SUCCESS;
6097 }
6098 
6099 
ma_resource_manager_data_stream_get_page_size_in_frames(ma_resource_manager_data_stream * pDataStream)6100 static ma_uint32 ma_resource_manager_data_stream_get_page_size_in_frames(ma_resource_manager_data_stream* pDataStream)
6101 {
6102     MA_ASSERT(pDataStream != NULL);
6103     MA_ASSERT(pDataStream->isDecoderInitialized == MA_TRUE);
6104 
6105     return MA_RESOURCE_MANAGER_PAGE_SIZE_IN_MILLISECONDS * (pDataStream->decoder.outputSampleRate/1000);
6106 }
6107 
ma_resource_manager_data_stream_get_page_data_pointer(ma_resource_manager_data_stream * pDataStream,ma_uint32 pageIndex,ma_uint32 relativeCursor)6108 static void* ma_resource_manager_data_stream_get_page_data_pointer(ma_resource_manager_data_stream* pDataStream, ma_uint32 pageIndex, ma_uint32 relativeCursor)
6109 {
6110     MA_ASSERT(pDataStream != NULL);
6111     MA_ASSERT(pDataStream->isDecoderInitialized == MA_TRUE);
6112     MA_ASSERT(pageIndex == 0 || pageIndex == 1);
6113 
6114     return ma_offset_ptr(pDataStream->pPageData, ((ma_resource_manager_data_stream_get_page_size_in_frames(pDataStream) * pageIndex) + relativeCursor) * ma_get_bytes_per_frame(pDataStream->decoder.outputFormat, pDataStream->decoder.outputChannels));
6115 }
6116 
ma_resource_manager_data_stream_fill_page(ma_resource_manager_data_stream * pDataStream,ma_uint32 pageIndex)6117 static void ma_resource_manager_data_stream_fill_page(ma_resource_manager_data_stream* pDataStream, ma_uint32 pageIndex)
6118 {
6119     ma_uint64 pageSizeInFrames;
6120     ma_uint64 totalFramesReadForThisPage = 0;
6121     void* pPageData = ma_resource_manager_data_stream_get_page_data_pointer(pDataStream, pageIndex, 0);
6122 
6123     pageSizeInFrames = ma_resource_manager_data_stream_get_page_size_in_frames(pDataStream);
6124 
6125     if (pDataStream->isLooping) {
6126         while (totalFramesReadForThisPage < pageSizeInFrames) {
6127             ma_uint64 framesRemaining;
6128             ma_uint64 framesRead;
6129 
6130             framesRemaining = pageSizeInFrames - totalFramesReadForThisPage;
6131             framesRead = ma_decoder_read_pcm_frames(&pDataStream->decoder, ma_offset_pcm_frames_ptr(pPageData, totalFramesReadForThisPage, pDataStream->decoder.outputFormat, pDataStream->decoder.outputChannels), framesRemaining);
6132             totalFramesReadForThisPage += framesRead;
6133 
6134             /* Loop back to the start if we reached the end. We'll also have a known length at this point as well. */
6135             if (framesRead < framesRemaining) {
6136                 if (pDataStream->totalLengthInPCMFrames == 0) {
6137                     ma_decoder_get_cursor_in_pcm_frames(&pDataStream->decoder, &pDataStream->totalLengthInPCMFrames);
6138                 }
6139 
6140                 ma_decoder_seek_to_pcm_frame(&pDataStream->decoder, 0);
6141             }
6142         }
6143     } else {
6144         totalFramesReadForThisPage = ma_decoder_read_pcm_frames(&pDataStream->decoder, pPageData, pageSizeInFrames);
6145     }
6146 
6147     if (totalFramesReadForThisPage < pageSizeInFrames) {
6148         c89atomic_exchange_32(&pDataStream->isDecoderAtEnd, MA_TRUE);
6149     }
6150 
6151     c89atomic_exchange_32(&pDataStream->pageFrameCount[pageIndex], (ma_uint32)totalFramesReadForThisPage);
6152     c89atomic_exchange_32(&pDataStream->isPageValid[pageIndex], MA_TRUE);
6153 }
6154 
ma_resource_manager_data_stream_fill_pages(ma_resource_manager_data_stream * pDataStream)6155 static void ma_resource_manager_data_stream_fill_pages(ma_resource_manager_data_stream* pDataStream)
6156 {
6157     ma_uint32 iPage;
6158 
6159     MA_ASSERT(pDataStream != NULL);
6160 
6161     /* For each page... */
6162     for (iPage = 0; iPage < 2; iPage += 1) {
6163         ma_resource_manager_data_stream_fill_page(pDataStream, iPage);
6164 
6165         /* If we reached the end make sure we get out of the loop to prevent us from trying to load the second page. */
6166         if (pDataStream->isDecoderAtEnd) {
6167             break;
6168         }
6169     }
6170 }
6171 
ma_resource_manager_data_stream_read_pcm_frames(ma_resource_manager_data_stream * pDataStream,void * pFramesOut,ma_uint64 frameCount,ma_uint64 * pFramesRead)6172 MA_API ma_result ma_resource_manager_data_stream_read_pcm_frames(ma_resource_manager_data_stream* pDataStream, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead)
6173 {
6174     ma_result result = MA_SUCCESS;
6175     ma_uint64 totalFramesProcessed;
6176     ma_format format;
6177     ma_uint32 channels;
6178 
6179     /* We cannot be using the data source after it's been uninitialized. */
6180     MA_ASSERT(pDataStream->result != MA_UNAVAILABLE);
6181 
6182     if (pDataStream == NULL) {
6183         return MA_INVALID_ARGS;
6184     }
6185 
6186     if (pDataStream->result != MA_SUCCESS) {
6187         return MA_INVALID_OPERATION;
6188     }
6189 
6190     /* Don't attempt to read while we're in the middle of seeking. Tell the caller that we're busy. */
6191     if (pDataStream->seekCounter > 0) {
6192         return MA_BUSY;
6193     }
6194 
6195     ma_resource_manager_data_stream_get_data_format(pDataStream, &format, &channels, NULL);
6196 
6197     /* Reading is implemented in terms of map/unmap. We need to run this in a loop because mapping is clamped against page boundaries. */
6198     totalFramesProcessed = 0;
6199     while (totalFramesProcessed < frameCount) {
6200         void* pMappedFrames;
6201         ma_uint64 mappedFrameCount;
6202 
6203         mappedFrameCount = frameCount - totalFramesProcessed;
6204         result = ma_resource_manager_data_stream_map(pDataStream, &pMappedFrames, &mappedFrameCount);
6205         if (result != MA_SUCCESS) {
6206             break;
6207         }
6208 
6209         /* Copy the mapped data to the output buffer if we have one. It's allowed for pFramesOut to be NULL in which case a relative forward seek is performed. */
6210         if (pFramesOut != NULL) {
6211             ma_copy_pcm_frames(ma_offset_pcm_frames_ptr(pFramesOut, totalFramesProcessed, format, channels), pMappedFrames, mappedFrameCount, format, channels);
6212         }
6213 
6214         totalFramesProcessed += mappedFrameCount;
6215 
6216         result = ma_resource_manager_data_stream_unmap(pDataStream, mappedFrameCount);
6217         if (result != MA_SUCCESS) {
6218             break;  /* This is really bad - will only get an error here if we failed to post a job to the queue for loading the next page. */
6219         }
6220     }
6221 
6222     if (pFramesRead != NULL) {
6223         *pFramesRead = totalFramesProcessed;
6224     }
6225 
6226     return result;
6227 }
6228 
ma_resource_manager_data_stream_map(ma_resource_manager_data_stream * pDataStream,void ** ppFramesOut,ma_uint64 * pFrameCount)6229 MA_API ma_result ma_resource_manager_data_stream_map(ma_resource_manager_data_stream* pDataStream, void** ppFramesOut, ma_uint64* pFrameCount)
6230 {
6231     ma_uint64 framesAvailable;
6232     ma_uint64 frameCount = 0;
6233 
6234     /* We cannot be using the data source after it's been uninitialized. */
6235     MA_ASSERT(pDataStream->result != MA_UNAVAILABLE);
6236 
6237     if (pFrameCount != NULL) {
6238         frameCount = *pFrameCount;
6239         *pFrameCount = 0;
6240     }
6241     if (ppFramesOut != NULL) {
6242         *ppFramesOut = NULL;
6243     }
6244 
6245     if (pDataStream == NULL || ppFramesOut == NULL || pFrameCount == NULL) {
6246         return MA_INVALID_ARGS;
6247     }
6248 
6249     if (pDataStream->result != MA_SUCCESS) {
6250         return MA_INVALID_OPERATION;
6251     }
6252 
6253     /* Don't attempt to read while we're in the middle of seeking. Tell the caller that we're busy. */
6254     if (pDataStream->seekCounter > 0) {
6255         return MA_BUSY;
6256     }
6257 
6258     /* If the page we're on is invalid it means we've caught up to the job thread. */
6259     if (pDataStream->isPageValid[pDataStream->currentPageIndex] == MA_FALSE) {
6260         framesAvailable = 0;
6261     } else {
6262         /*
6263         The page we're on is valid so we must have some frames available. We need to make sure that we don't overflow into the next page, even if it's valid. The reason is
6264         that the unmap process will only post an update for one page at a time. Keeping mapping tied to page boundaries makes this simpler.
6265         */
6266         MA_ASSERT(pDataStream->pageFrameCount[pDataStream->currentPageIndex] >= pDataStream->relativeCursor);
6267         framesAvailable = pDataStream->pageFrameCount[pDataStream->currentPageIndex] - pDataStream->relativeCursor;
6268     }
6269 
6270     /* If there's no frames available and the result is set to MA_AT_END we need to return MA_AT_END. */
6271     if (framesAvailable == 0) {
6272         if (pDataStream->isDecoderAtEnd) {
6273             return MA_AT_END;
6274         } else {
6275             return MA_BUSY; /* There are no frames available, but we're not marked as EOF so we might have caught up to the job thread. Need to return MA_BUSY and wait for more data. */
6276         }
6277     }
6278 
6279     MA_ASSERT(framesAvailable > 0);
6280 
6281     if (frameCount > framesAvailable) {
6282         frameCount = framesAvailable;
6283     }
6284 
6285     *ppFramesOut = ma_resource_manager_data_stream_get_page_data_pointer(pDataStream, pDataStream->currentPageIndex, pDataStream->relativeCursor);
6286     *pFrameCount = frameCount;
6287 
6288     return MA_SUCCESS;
6289 }
6290 
ma_resource_manager_data_stream_unmap(ma_resource_manager_data_stream * pDataStream,ma_uint64 frameCount)6291 MA_API ma_result ma_resource_manager_data_stream_unmap(ma_resource_manager_data_stream* pDataStream, ma_uint64 frameCount)
6292 {
6293     ma_uint32 newRelativeCursor;
6294     ma_uint32 pageSizeInFrames;
6295     ma_job job;
6296 
6297     /* We cannot be using the data source after it's been uninitialized. */
6298     MA_ASSERT(pDataStream->result != MA_UNAVAILABLE);
6299 
6300     if (pDataStream == NULL) {
6301         return MA_INVALID_ARGS;
6302     }
6303 
6304     if (pDataStream->result != MA_SUCCESS) {
6305         return MA_INVALID_OPERATION;
6306     }
6307 
6308     /* The frame count should always fit inside a 32-bit integer. */
6309     if (frameCount > 0xFFFFFFFF) {
6310         return MA_INVALID_ARGS;
6311     }
6312 
6313     pageSizeInFrames = ma_resource_manager_data_stream_get_page_size_in_frames(pDataStream);
6314 
6315     /* The absolute cursor needs to be updated. We want to make sure to loop if possible. */
6316     pDataStream->absoluteCursor += frameCount;
6317     if (pDataStream->absoluteCursor > pDataStream->totalLengthInPCMFrames && pDataStream->totalLengthInPCMFrames > 0) {
6318         pDataStream->absoluteCursor = pDataStream->absoluteCursor % pDataStream->totalLengthInPCMFrames;
6319     }
6320 
6321     /* Here is where we need to check if we need to load a new page, and if so, post a job to load it. */
6322     newRelativeCursor = pDataStream->relativeCursor + (ma_uint32)frameCount;
6323 
6324     /* If the new cursor has flowed over to the next page we need to mark the old one as invalid and post an event for it. */
6325     if (newRelativeCursor >= pageSizeInFrames) {
6326         newRelativeCursor -= pageSizeInFrames;
6327 
6328         /* Here is where we post the job start decoding. */
6329         job = ma_job_init(MA_JOB_PAGE_DATA_STREAM);
6330         job.order = ma_resource_manager_data_stream_next_execution_order(pDataStream);
6331         job.pageDataStream.pDataStream = pDataStream;
6332         job.pageDataStream.pageIndex   = pDataStream->currentPageIndex;
6333 
6334         /* The page needs to be marked as invalid so that the public API doesn't try reading from it. */
6335         c89atomic_exchange_32(&pDataStream->isPageValid[pDataStream->currentPageIndex], MA_FALSE);
6336 
6337         /* Before posting the job we need to make sure we set some state. */
6338         pDataStream->relativeCursor   = newRelativeCursor;
6339         pDataStream->currentPageIndex = (pDataStream->currentPageIndex + 1) & 0x01;
6340         return ma_resource_manager_post_job(pDataStream->pResourceManager, &job);
6341     } else {
6342         /* We haven't moved into a new page so we can just move the cursor forward. */
6343         pDataStream->relativeCursor = newRelativeCursor;
6344         return MA_SUCCESS;
6345     }
6346 }
6347 
ma_resource_manager_data_stream_seek_to_pcm_frame(ma_resource_manager_data_stream * pDataStream,ma_uint64 frameIndex)6348 MA_API ma_result ma_resource_manager_data_stream_seek_to_pcm_frame(ma_resource_manager_data_stream* pDataStream, ma_uint64 frameIndex)
6349 {
6350     ma_job job;
6351 
6352     /* We cannot be using the data source after it's been uninitialized. */
6353     MA_ASSERT(pDataStream->result != MA_UNAVAILABLE);
6354 
6355     if (pDataStream == NULL) {
6356         return MA_INVALID_ARGS;
6357     }
6358 
6359     if (pDataStream->result != MA_SUCCESS && pDataStream->result != MA_BUSY) {
6360         return MA_INVALID_OPERATION;
6361     }
6362 
6363     /* Increment the seek counter first to indicate to read_paged_pcm_frames() and map_paged_pcm_frames() that we are in the middle of a seek and MA_BUSY should be returned. */
6364     c89atomic_fetch_add_32(&pDataStream->seekCounter, 1);
6365 
6366     /*
6367     We need to clear our currently loaded pages so that the stream starts playback from the new seek point as soon as possible. These are for the purpose of the public
6368     API and will be ignored by the seek job. The seek job will operate on the assumption that both pages have been marked as invalid and the cursor is at the start of
6369     the first page.
6370     */
6371     pDataStream->relativeCursor   = 0;
6372     pDataStream->currentPageIndex = 0;
6373     c89atomic_exchange_32(&pDataStream->isPageValid[0], MA_FALSE);
6374     c89atomic_exchange_32(&pDataStream->isPageValid[1], MA_FALSE);
6375 
6376     /*
6377     The public API is not allowed to touch the internal decoder so we need to use a job to perform the seek. When seeking, the job thread will assume both pages
6378     are invalid and any content contained within them will be discarded and replaced with newly decoded data.
6379     */
6380     job = ma_job_init(MA_JOB_SEEK_DATA_STREAM);
6381     job.order = ma_resource_manager_data_stream_next_execution_order(pDataStream);
6382     job.seekDataStream.pDataStream = pDataStream;
6383     job.seekDataStream.frameIndex  = frameIndex;
6384     return ma_resource_manager_post_job(pDataStream->pResourceManager, &job);
6385 }
6386 
ma_resource_manager_data_stream_get_data_format(ma_resource_manager_data_stream * pDataStream,ma_format * pFormat,ma_uint32 * pChannels,ma_uint32 * pSampleRate)6387 MA_API ma_result ma_resource_manager_data_stream_get_data_format(ma_resource_manager_data_stream* pDataStream, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate)
6388 {
6389     /* We cannot be using the data source after it's been uninitialized. */
6390     MA_ASSERT(pDataStream->result != MA_UNAVAILABLE);
6391 
6392     if (pDataStream == NULL) {
6393         return MA_INVALID_ARGS;
6394     }
6395 
6396     if (pDataStream->result != MA_SUCCESS) {
6397         return MA_INVALID_OPERATION;
6398     }
6399 
6400     /*
6401     We're being a little bit naughty here and accessing the internal decoder from the public API. The output data format is constant, and we've defined this function
6402     such that the application is responsible for ensuring it's not called while uninitializing so it should be safe.
6403     */
6404     return ma_data_source_get_data_format(&pDataStream->decoder, pFormat, pChannels, pSampleRate);
6405 }
6406 
ma_resource_manager_data_stream_get_cursor_in_pcm_frames(ma_resource_manager_data_stream * pDataStream,ma_uint64 * pCursor)6407 MA_API ma_result ma_resource_manager_data_stream_get_cursor_in_pcm_frames(ma_resource_manager_data_stream* pDataStream, ma_uint64* pCursor)
6408 {
6409     /* We cannot be using the data source after it's been uninitialized. */
6410     MA_ASSERT(pDataStream->result != MA_UNAVAILABLE);
6411 
6412     if (pDataStream == NULL || pCursor == NULL) {
6413         return MA_INVALID_ARGS;
6414     }
6415 
6416     if (pDataStream->result != MA_SUCCESS) {
6417         return MA_INVALID_OPERATION;
6418     }
6419 
6420     *pCursor = pDataStream->absoluteCursor;
6421 
6422     return MA_SUCCESS;
6423 }
6424 
ma_resource_manager_data_stream_get_length_in_pcm_frames(ma_resource_manager_data_stream * pDataStream,ma_uint64 * pLength)6425 MA_API ma_result ma_resource_manager_data_stream_get_length_in_pcm_frames(ma_resource_manager_data_stream* pDataStream, ma_uint64* pLength)
6426 {
6427     /* We cannot be using the data source after it's been uninitialized. */
6428     MA_ASSERT(pDataStream->result != MA_UNAVAILABLE);
6429 
6430     if (pDataStream == NULL) {
6431         return MA_INVALID_ARGS;
6432     }
6433 
6434     if (pDataStream->result != MA_SUCCESS) {
6435         return pDataStream->result;
6436     }
6437 
6438     /*
6439     We most definitely do not want to be calling ma_decoder_get_length_in_pcm_frames() directly. Instead we want to use a cached value that we
6440     calculated when we initialized it on the job thread.
6441     */
6442     *pLength = pDataStream->totalLengthInPCMFrames;
6443     if (*pLength == 0) {
6444         return MA_NOT_IMPLEMENTED;  /* Some decoders may not have a known length. */
6445     }
6446 
6447     return MA_SUCCESS;
6448 }
6449 
ma_resource_manager_data_stream_result(const ma_resource_manager_data_stream * pDataStream)6450 MA_API ma_result ma_resource_manager_data_stream_result(const ma_resource_manager_data_stream* pDataStream)
6451 {
6452     if (pDataStream == NULL) {
6453         return MA_INVALID_ARGS;
6454     }
6455 
6456     return pDataStream->result;
6457 }
6458 
ma_resource_manager_data_stream_set_looping(ma_resource_manager_data_stream * pDataStream,ma_bool32 isLooping)6459 MA_API ma_result ma_resource_manager_data_stream_set_looping(ma_resource_manager_data_stream* pDataStream, ma_bool32 isLooping)
6460 {
6461     if (pDataStream == NULL) {
6462         return MA_INVALID_ARGS;
6463     }
6464 
6465     c89atomic_exchange_32(&pDataStream->isLooping, isLooping);
6466 
6467     return MA_SUCCESS;
6468 }
6469 
ma_resource_manager_data_stream_get_looping(const ma_resource_manager_data_stream * pDataStream,ma_bool32 * pIsLooping)6470 MA_API ma_result ma_resource_manager_data_stream_get_looping(const ma_resource_manager_data_stream* pDataStream, ma_bool32* pIsLooping)
6471 {
6472     if (pDataStream == NULL || pIsLooping == NULL) {
6473         return MA_INVALID_ARGS;
6474     }
6475 
6476     *pIsLooping = pDataStream->isLooping;
6477 
6478     return MA_SUCCESS;
6479 }
6480 
ma_resource_manager_data_stream_get_available_frames(ma_resource_manager_data_stream * pDataStream,ma_uint64 * pAvailableFrames)6481 MA_API ma_result ma_resource_manager_data_stream_get_available_frames(ma_resource_manager_data_stream* pDataStream, ma_uint64* pAvailableFrames)
6482 {
6483     volatile ma_uint32 pageIndex0;
6484     volatile ma_uint32 pageIndex1;
6485     volatile ma_uint32 relativeCursor;
6486     ma_uint64 availableFrames;
6487 
6488     if (pAvailableFrames == NULL) {
6489         return MA_INVALID_ARGS;
6490     }
6491 
6492     *pAvailableFrames = 0;
6493 
6494     if (pDataStream == NULL) {
6495         return MA_INVALID_ARGS;
6496     }
6497 
6498     pageIndex0     =  pDataStream->currentPageIndex;
6499     pageIndex1     = (pDataStream->currentPageIndex + 1) & 0x01;
6500     relativeCursor =  pDataStream->relativeCursor;
6501 
6502     availableFrames = 0;
6503     if (pDataStream->isPageValid[pageIndex0]) {
6504         availableFrames += pDataStream->pageFrameCount[pageIndex0] - relativeCursor;
6505         if (pDataStream->isPageValid[pageIndex1]) {
6506             availableFrames += pDataStream->pageFrameCount[pageIndex1];
6507         }
6508     }
6509 
6510     *pAvailableFrames = availableFrames;
6511     return MA_SUCCESS;
6512 }
6513 
6514 
6515 
ma_resource_manager_data_source_init(ma_resource_manager * pResourceManager,const char * pName,ma_uint32 flags,ma_async_notification * pNotification,ma_resource_manager_data_source * pDataSource)6516 MA_API ma_result ma_resource_manager_data_source_init(ma_resource_manager* pResourceManager, const char* pName, ma_uint32 flags, ma_async_notification* pNotification, ma_resource_manager_data_source* pDataSource)
6517 {
6518     if (pDataSource == NULL) {
6519         return MA_INVALID_ARGS;
6520     }
6521 
6522     MA_ZERO_OBJECT(pDataSource);
6523 
6524     if (pResourceManager == NULL || pName == NULL) {
6525         return MA_INVALID_ARGS;
6526     }
6527 
6528     pDataSource->flags = flags;
6529 
6530     /* The data source itself is just a data stream or a data buffer. */
6531     if ((flags & MA_DATA_SOURCE_FLAG_STREAM) != 0) {
6532         return ma_resource_manager_data_stream_init(pResourceManager, pName, flags, pNotification, &pDataSource->stream);
6533     } else {
6534         return ma_resource_manager_data_buffer_init(pResourceManager, pName, flags, pNotification, &pDataSource->buffer);
6535     }
6536 }
6537 
ma_resource_manager_data_source_uninit(ma_resource_manager_data_source * pDataSource)6538 MA_API ma_result ma_resource_manager_data_source_uninit(ma_resource_manager_data_source* pDataSource)
6539 {
6540     if (pDataSource == NULL) {
6541         return MA_INVALID_ARGS;
6542     }
6543 
6544     /* All we need to is uninitialize the underlying data buffer or data stream. */
6545     if ((pDataSource->flags & MA_DATA_SOURCE_FLAG_STREAM) != 0) {
6546         return ma_resource_manager_data_stream_uninit(&pDataSource->stream);
6547     } else {
6548         return ma_resource_manager_data_buffer_uninit(&pDataSource->buffer);
6549     }
6550 }
6551 
ma_resource_manager_data_source_read_pcm_frames(ma_resource_manager_data_source * pDataSource,void * pFramesOut,ma_uint64 frameCount,ma_uint64 * pFramesRead)6552 MA_API ma_result ma_resource_manager_data_source_read_pcm_frames(ma_resource_manager_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead)
6553 {
6554     if (pDataSource == NULL) {
6555         return MA_INVALID_ARGS;
6556     }
6557 
6558     if ((pDataSource->flags & MA_DATA_SOURCE_FLAG_STREAM) != 0) {
6559         return ma_resource_manager_data_stream_read_pcm_frames(&pDataSource->stream, pFramesOut, frameCount, pFramesRead);
6560     } else {
6561         return ma_resource_manager_data_buffer_read_pcm_frames(&pDataSource->buffer, pFramesOut, frameCount, pFramesRead);
6562     }
6563 }
6564 
ma_resource_manager_data_source_seek_to_pcm_frame(ma_resource_manager_data_source * pDataSource,ma_uint64 frameIndex)6565 MA_API ma_result ma_resource_manager_data_source_seek_to_pcm_frame(ma_resource_manager_data_source* pDataSource, ma_uint64 frameIndex)
6566 {
6567     if (pDataSource == NULL) {
6568         return MA_INVALID_ARGS;
6569     }
6570 
6571     if ((pDataSource->flags & MA_DATA_SOURCE_FLAG_STREAM) != 0) {
6572         return ma_resource_manager_data_stream_seek_to_pcm_frame(&pDataSource->stream, frameIndex);
6573     } else {
6574         return ma_resource_manager_data_buffer_seek_to_pcm_frame(&pDataSource->buffer, frameIndex);
6575     }
6576 }
6577 
ma_resource_manager_data_source_map(ma_resource_manager_data_source * pDataSource,void ** ppFramesOut,ma_uint64 * pFrameCount)6578 MA_API ma_result ma_resource_manager_data_source_map(ma_resource_manager_data_source* pDataSource, void** ppFramesOut, ma_uint64* pFrameCount)
6579 {
6580     if (pDataSource == NULL) {
6581         return MA_INVALID_ARGS;
6582     }
6583 
6584     if ((pDataSource->flags & MA_DATA_SOURCE_FLAG_STREAM) != 0) {
6585         return ma_resource_manager_data_stream_map(&pDataSource->stream, ppFramesOut, pFrameCount);
6586     } else {
6587         return ma_resource_manager_data_buffer_map(&pDataSource->buffer, ppFramesOut, pFrameCount);
6588     }
6589 }
6590 
ma_resource_manager_data_source_unmap(ma_resource_manager_data_source * pDataSource,ma_uint64 frameCount)6591 MA_API ma_result ma_resource_manager_data_source_unmap(ma_resource_manager_data_source* pDataSource, ma_uint64 frameCount)
6592 {
6593     if (pDataSource == NULL) {
6594         return MA_INVALID_ARGS;
6595     }
6596 
6597     if ((pDataSource->flags & MA_DATA_SOURCE_FLAG_STREAM) != 0) {
6598         return ma_resource_manager_data_stream_unmap(&pDataSource->stream, frameCount);
6599     } else {
6600         return ma_resource_manager_data_buffer_unmap(&pDataSource->buffer, frameCount);
6601     }
6602 }
6603 
ma_resource_manager_data_source_get_data_format(ma_resource_manager_data_source * pDataSource,ma_format * pFormat,ma_uint32 * pChannels,ma_uint32 * pSampleRate)6604 MA_API ma_result ma_resource_manager_data_source_get_data_format(ma_resource_manager_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate)
6605 {
6606     if (pDataSource == NULL) {
6607         return MA_INVALID_ARGS;
6608     }
6609 
6610     if ((pDataSource->flags & MA_DATA_SOURCE_FLAG_STREAM) != 0) {
6611         return ma_resource_manager_data_stream_get_data_format(&pDataSource->stream, pFormat, pChannels, pSampleRate);
6612     } else {
6613         return ma_resource_manager_data_buffer_get_data_format(&pDataSource->buffer, pFormat, pChannels, pSampleRate);
6614     }
6615 }
6616 
ma_resource_manager_data_source_get_cursor_in_pcm_frames(ma_resource_manager_data_source * pDataSource,ma_uint64 * pCursor)6617 MA_API ma_result ma_resource_manager_data_source_get_cursor_in_pcm_frames(ma_resource_manager_data_source* pDataSource, ma_uint64* pCursor)
6618 {
6619     if (pDataSource == NULL) {
6620         return MA_INVALID_ARGS;
6621     }
6622 
6623     if ((pDataSource->flags & MA_DATA_SOURCE_FLAG_STREAM) != 0) {
6624         return ma_resource_manager_data_stream_get_cursor_in_pcm_frames(&pDataSource->stream, pCursor);
6625     } else {
6626         return ma_resource_manager_data_buffer_get_cursor_in_pcm_frames(&pDataSource->buffer, pCursor);
6627     }
6628 }
6629 
ma_resource_manager_data_source_get_length_in_pcm_frames(ma_resource_manager_data_source * pDataSource,ma_uint64 * pLength)6630 MA_API ma_result ma_resource_manager_data_source_get_length_in_pcm_frames(ma_resource_manager_data_source* pDataSource, ma_uint64* pLength)
6631 {
6632     if (pDataSource == NULL) {
6633         return MA_INVALID_ARGS;
6634     }
6635 
6636     if ((pDataSource->flags & MA_DATA_SOURCE_FLAG_STREAM) != 0) {
6637         return ma_resource_manager_data_stream_get_length_in_pcm_frames(&pDataSource->stream, pLength);
6638     } else {
6639         return ma_resource_manager_data_buffer_get_length_in_pcm_frames(&pDataSource->buffer, pLength);
6640     }
6641 }
6642 
ma_resource_manager_data_source_result(const ma_resource_manager_data_source * pDataSource)6643 MA_API ma_result ma_resource_manager_data_source_result(const ma_resource_manager_data_source* pDataSource)
6644 {
6645     if (pDataSource == NULL) {
6646         return MA_INVALID_ARGS;
6647     }
6648 
6649     if ((pDataSource->flags & MA_DATA_SOURCE_FLAG_STREAM) != 0) {
6650         return ma_resource_manager_data_stream_result(&pDataSource->stream);
6651     } else {
6652         return ma_resource_manager_data_buffer_result(&pDataSource->buffer);
6653     }
6654 }
6655 
ma_resource_manager_data_source_set_looping(ma_resource_manager_data_source * pDataSource,ma_bool32 isLooping)6656 MA_API ma_result ma_resource_manager_data_source_set_looping(ma_resource_manager_data_source* pDataSource, ma_bool32 isLooping)
6657 {
6658     if (pDataSource == NULL) {
6659         return MA_INVALID_ARGS;
6660     }
6661 
6662     if ((pDataSource->flags & MA_DATA_SOURCE_FLAG_STREAM) != 0) {
6663         return ma_resource_manager_data_stream_set_looping(&pDataSource->stream, isLooping);
6664     } else {
6665         return ma_resource_manager_data_buffer_set_looping(&pDataSource->buffer, isLooping);
6666     }
6667 }
6668 
ma_resource_manager_data_source_get_looping(const ma_resource_manager_data_source * pDataSource,ma_bool32 * pIsLooping)6669 MA_API ma_result ma_resource_manager_data_source_get_looping(const ma_resource_manager_data_source* pDataSource, ma_bool32* pIsLooping)
6670 {
6671     if (pDataSource == NULL || pIsLooping == NULL) {
6672         return MA_INVALID_ARGS;
6673     }
6674 
6675     if ((pDataSource->flags & MA_DATA_SOURCE_FLAG_STREAM) != 0) {
6676         return ma_resource_manager_data_stream_get_looping(&pDataSource->stream, pIsLooping);
6677     } else {
6678         return ma_resource_manager_data_buffer_get_looping(&pDataSource->buffer, pIsLooping);
6679     }
6680 }
6681 
ma_resource_manager_data_source_get_available_frames(ma_resource_manager_data_source * pDataSource,ma_uint64 * pAvailableFrames)6682 MA_API ma_result ma_resource_manager_data_source_get_available_frames(ma_resource_manager_data_source* pDataSource, ma_uint64* pAvailableFrames)
6683 {
6684     if (pAvailableFrames == NULL) {
6685         return MA_INVALID_ARGS;
6686     }
6687 
6688     *pAvailableFrames = 0;
6689 
6690     if (pDataSource == NULL) {
6691         return MA_INVALID_ARGS;
6692     }
6693 
6694     if ((pDataSource->flags & MA_DATA_SOURCE_FLAG_STREAM) != 0) {
6695         return ma_resource_manager_data_stream_get_available_frames(&pDataSource->stream, pAvailableFrames);
6696     } else {
6697         return ma_resource_manager_data_buffer_get_available_frames(&pDataSource->buffer, pAvailableFrames);
6698     }
6699 }
6700 
6701 
ma_resource_manager_post_job(ma_resource_manager * pResourceManager,const ma_job * pJob)6702 MA_API ma_result ma_resource_manager_post_job(ma_resource_manager* pResourceManager, const ma_job* pJob)
6703 {
6704     if (pResourceManager == NULL) {
6705         return MA_INVALID_ARGS;
6706     }
6707 
6708     return ma_job_queue_post(&pResourceManager->jobQueue, pJob);
6709 }
6710 
ma_resource_manager_post_job_quit(ma_resource_manager * pResourceManager)6711 MA_API ma_result ma_resource_manager_post_job_quit(ma_resource_manager* pResourceManager)
6712 {
6713     ma_job job = ma_job_init(MA_JOB_QUIT);
6714     return ma_resource_manager_post_job(pResourceManager, &job);
6715 }
6716 
ma_resource_manager_next_job(ma_resource_manager * pResourceManager,ma_job * pJob)6717 MA_API ma_result ma_resource_manager_next_job(ma_resource_manager* pResourceManager, ma_job* pJob)
6718 {
6719     if (pResourceManager == NULL) {
6720         return MA_INVALID_ARGS;
6721     }
6722 
6723     return ma_job_queue_next(&pResourceManager->jobQueue, pJob);
6724 }
6725 
6726 
ma_resource_manager_process_job__load_data_buffer(ma_resource_manager * pResourceManager,ma_job * pJob)6727 static ma_result ma_resource_manager_process_job__load_data_buffer(ma_resource_manager* pResourceManager, ma_job* pJob)
6728 {
6729     ma_result result = MA_SUCCESS;
6730     ma_resource_manager_data_buffer* pDataBuffer;
6731     ma_decoder* pDecoder = NULL;       /* Malloc'd here, and then free'd on the last page decode. */
6732     ma_uint64 totalFrameCount = 0;
6733     void* pData = NULL;
6734     ma_uint64 dataSizeInBytes = 0;
6735     ma_uint64 framesRead = 0;   /* <-- Keeps track of how many frames we read for the first page. */
6736 
6737     MA_ASSERT(pResourceManager != NULL);
6738     MA_ASSERT(pJob             != NULL);
6739     MA_ASSERT(pJob->loadDataBuffer.pFilePath          != NULL);
6740     MA_ASSERT(pJob->loadDataBuffer.pDataBuffer        != NULL);
6741     MA_ASSERT(pJob->freeDataBuffer.pDataBuffer->pNode != NULL);
6742     MA_ASSERT(pJob->loadDataBuffer.pDataBuffer->pNode->isDataOwnedByResourceManager == MA_TRUE);  /* The data should always be owned by the resource manager. */
6743 
6744     pDataBuffer = pJob->loadDataBuffer.pDataBuffer;
6745 
6746     /* First thing we need to do is check whether or not the data buffer is getting deleted. If so we just abort. */
6747     if (pDataBuffer->pNode->result != MA_BUSY) {
6748         result = MA_INVALID_OPERATION;    /* The data buffer may be getting deleted before it's even been loaded. */
6749         goto done;
6750     }
6751 
6752     /* The data buffer is not getting deleted, but we may be getting executed out of order. If so, we need to push the job back onto the queue and return. */
6753     if (pJob->order != pDataBuffer->pNode->executionPointer) {
6754         return ma_resource_manager_post_job(pResourceManager, pJob);    /* Attempting to execute out of order. Probably interleaved with a MA_JOB_FREE_DATA_BUFFER job. */
6755     }
6756 
6757     if (pDataBuffer->pNode->data.type == ma_resource_manager_data_buffer_encoding_encoded) {
6758         /* No decoding. Just store the file contents in memory. */
6759         size_t sizeInBytes;
6760 
6761         result = ma_vfs_open_and_read_file_ex(pResourceManager->config.pVFS, pJob->loadDataBuffer.pFilePath, &pData, &sizeInBytes, &pResourceManager->config.allocationCallbacks, MA_ALLOCATION_TYPE_ENCODED_BUFFER);
6762         if (result == MA_SUCCESS) {
6763             pDataBuffer->pNode->data.encoded.pData       = pData;
6764             pDataBuffer->pNode->data.encoded.sizeInBytes = sizeInBytes;
6765         }
6766 
6767         result = ma_resource_manager_data_buffer_init_connector(pDataBuffer, pJob->loadDataBuffer.pNotification);
6768     } else  {
6769         /* Decoding. */
6770         ma_uint64 dataSizeInFrames;
6771         ma_uint64 pageSizeInFrames;
6772 
6773         /*
6774         With the file initialized we now need to initialize the decoder. We need to pass this decoder around on the job queue so we'll need to
6775         allocate memory for this dynamically.
6776         */
6777         pDecoder = (ma_decoder*)ma__malloc_from_callbacks(sizeof(*pDecoder), &pResourceManager->config.allocationCallbacks/*, MA_ALLOCATION_TYPE_DECODER*/);
6778         if (pDecoder == NULL) {
6779             result = MA_OUT_OF_MEMORY;
6780             goto done;
6781         }
6782 
6783         result = ma_resource_manager__init_decoder(pResourceManager, pJob->loadDataBuffer.pFilePath, pDecoder);
6784 
6785         /* Make sure we never set the result code to MA_BUSY or else we'll get everything confused. */
6786         if (result == MA_BUSY) {
6787             result = MA_ERROR;
6788         }
6789 
6790         if (result != MA_SUCCESS) {
6791             ma__free_from_callbacks(pDecoder, &pResourceManager->config.allocationCallbacks/*, MA_ALLOCATION_TYPE_DECODER*/);
6792             goto done;
6793         }
6794 
6795         /*
6796         Getting here means we have the decoder. We can now get prepared to start decoding. The first thing we need is a buffer, but to determine the
6797         size we need to get the length of the sound in PCM frames. If the length cannot be determined we need to mark it as such and not set the data
6798         pointer in the data buffer until the very end.
6799 
6800         If after decoding the first page we complete decoding we need to fire the event and ensure the status is set to MA_SUCCESS.
6801         */
6802         pDataBuffer->pNode->data.decoded.format     = pDecoder->outputFormat;
6803         pDataBuffer->pNode->data.decoded.channels   = pDecoder->outputChannels;
6804         pDataBuffer->pNode->data.decoded.sampleRate = pDecoder->outputSampleRate;
6805 
6806         pageSizeInFrames = MA_RESOURCE_MANAGER_PAGE_SIZE_IN_MILLISECONDS * (pDecoder->outputSampleRate/1000);
6807 
6808         totalFrameCount = ma_decoder_get_length_in_pcm_frames(pDecoder);
6809         if (totalFrameCount > 0) {
6810             /* It's a known length. We can allocate the buffer now. */
6811             dataSizeInFrames = totalFrameCount;
6812         } else {
6813             /* It's an unknown length. We need to dynamically expand the buffer as we decode. To start with we allocate space for one page. We'll then double it as we need more space. */
6814             dataSizeInFrames = pageSizeInFrames;
6815         }
6816 
6817         dataSizeInBytes = dataSizeInFrames * ma_get_bytes_per_frame(pDecoder->outputFormat, pDecoder->outputChannels);
6818         if (dataSizeInBytes > MA_SIZE_MAX) {
6819             ma__free_from_callbacks(pDecoder, &pResourceManager->config.allocationCallbacks/*, MA_ALLOCATION_TYPE_DECODER*/);
6820             result = MA_TOO_BIG;
6821             goto done;
6822         }
6823 
6824         pData = ma__malloc_from_callbacks((size_t)dataSizeInBytes, &pResourceManager->config.allocationCallbacks/*, MA_ALLOCATION_TYPE_DECODED_BUFFER*/);
6825         if (pData == NULL) {
6826             ma__free_from_callbacks(pDecoder, &pResourceManager->config.allocationCallbacks/*, MA_ALLOCATION_TYPE_DECODER*/);
6827             result = MA_OUT_OF_MEMORY;
6828             goto done;
6829         }
6830 
6831         /* The buffer needs to be initialized to silence in case the caller reads from it. */
6832         ma_silence_pcm_frames(pData, dataSizeInFrames, pDecoder->outputFormat, pDecoder->outputChannels);
6833 
6834 
6835         /* We should have enough room in our buffer for at least a whole page, or the entire file (if it's less than a page). We can now decode that first page. */
6836         framesRead = ma_decoder_read_pcm_frames(pDecoder, pData, pageSizeInFrames);
6837         if (framesRead < pageSizeInFrames) {
6838             /* We've read the entire sound. This is the simple case. We just need to set the result to MA_SUCCESS. */
6839             pDataBuffer->pNode->data.decoded.pData      = pData;
6840             pDataBuffer->pNode->data.decoded.frameCount = framesRead;
6841 
6842             /*
6843             decodedFrameCount is what other threads will use to determine whether or not data is available. We must ensure pData and frameCount
6844             is set *before* setting the number of available frames. This way, the other thread need only check if decodedFrameCount > 0, in
6845             which case it can assume pData and frameCount are valid.
6846             */
6847             c89atomic_thread_fence(c89atomic_memory_order_acquire);
6848             pDataBuffer->pNode->data.decoded.decodedFrameCount = framesRead;
6849 
6850             ma_decoder_uninit(pDecoder);
6851             ma__free_from_callbacks(pDecoder, &pResourceManager->config.allocationCallbacks/*, MA_ALLOCATION_TYPE_DECODER*/);
6852 
6853             result = ma_resource_manager_data_buffer_init_connector(pDataBuffer, pJob->loadDataBuffer.pNotification);
6854             goto done;
6855         } else {
6856             /* We've still got more to decode. We just set the result to MA_BUSY which will tell the next section below to post a paging event. */
6857             result = MA_BUSY;
6858         }
6859 
6860         /* If we successfully initialized and the sound is of a known length we can start initialize the connector. */
6861         if (result == MA_SUCCESS || result == MA_BUSY) {
6862             if (pDataBuffer->pNode->data.decoded.decodedFrameCount > 0) {
6863                 result = ma_resource_manager_data_buffer_init_connector(pDataBuffer, pJob->loadDataBuffer.pNotification);
6864             }
6865         }
6866     }
6867 
6868 done:
6869     ma__free_from_callbacks(pJob->loadDataBuffer.pFilePath, &pResourceManager->config.allocationCallbacks/*, MA_ALLOCATION_TYPE_TRANSIENT_STRING*/);
6870 
6871     /*
6872     We need to set the result to at the very end to ensure no other threads try reading the data before we've fully initialized the object. Other threads
6873     are going to be inspecting this variable to determine whether or not they're ready to read data. We can only change the result if it's set to MA_BUSY
6874     because otherwise we may be changing away from an error code which would be bad. An example is if the application creates a data buffer, but then
6875     immediately deletes it before we've got to this point. In this case, pDataBuffer->result will be MA_UNAVAILABLE, and setting it to MA_SUCCESS or any
6876     other error code would cause the buffer to look like it's in a state that it's not.
6877     */
6878     c89atomic_compare_and_swap_32(&pDataBuffer->pNode->result, MA_BUSY, result);
6879 
6880     /*
6881     If our result is MA_BUSY we need to post a job to start loading. It's important that we do this after setting the result to the buffer so that the
6882     decoding process happens at the right time. If we don't, there's a window where the MA_JOB_PAGE_DATA_BUFFER event will see a status of something
6883     other than MA_BUSY and then abort the decoding process with an error.
6884     */
6885     if (result == MA_BUSY && pDecoder != NULL) {
6886         /* We've still got more to decode. We need to post a job to continue decoding. */
6887         ma_job pageDataBufferJob;
6888 
6889         MA_ASSERT(pDecoder != NULL);
6890         MA_ASSERT(pData    != NULL);
6891 
6892         pageDataBufferJob = ma_job_init(MA_JOB_PAGE_DATA_BUFFER);
6893         pageDataBufferJob.order                                 = ma_resource_manager_data_buffer_next_execution_order(pDataBuffer);
6894         pageDataBufferJob.pageDataBuffer.pDataBuffer            = pDataBuffer;
6895         pageDataBufferJob.pageDataBuffer.pDecoder               = pDecoder;
6896         pageDataBufferJob.pageDataBuffer.pCompletedNotification = pJob->loadDataBuffer.pNotification;
6897         pageDataBufferJob.pageDataBuffer.pData                  = pData;
6898         pageDataBufferJob.pageDataBuffer.dataSizeInBytes        = (size_t)dataSizeInBytes;   /* Safe cast. Was checked for > MA_SIZE_MAX earlier. */
6899         pageDataBufferJob.pageDataBuffer.decodedFrameCount      = framesRead;
6900 
6901         if (totalFrameCount > 0) {
6902             pageDataBufferJob.pageDataBuffer.isUnknownLength = MA_FALSE;
6903 
6904             pDataBuffer->pNode->data.decoded.pData      = pData;
6905             pDataBuffer->pNode->data.decoded.frameCount = totalFrameCount;
6906 
6907             /*
6908             decodedFrameCount is what other threads will use to determine whether or not data is available. We must ensure pData and frameCount
6909             is set *before* setting the number of available frames. This way, the other thread need only check if decodedFrameCount > 0, in
6910             which case it can assume pData and frameCount are valid.
6911             */
6912             c89atomic_thread_fence(c89atomic_memory_order_acquire);
6913             pDataBuffer->pNode->data.decoded.decodedFrameCount = framesRead;
6914 
6915             /* The sound is of a known length so we can go ahead and initialize the connector now. */
6916             result = ma_resource_manager_data_buffer_init_connector(pDataBuffer, pJob->loadDataBuffer.pNotification);
6917         } else {
6918             pageDataBufferJob.pageDataBuffer.isUnknownLength = MA_TRUE;
6919 
6920             /*
6921             These members are all set after the last page has been decoded. The reason for this is that the application should not be attempting to
6922             read any data until the sound is fully decoded because we're going to be dynamically expanding pData and we'll be introducing complications
6923             by letting the application get access to it.
6924             */
6925             pDataBuffer->pNode->data.decoded.pData             = NULL;
6926             pDataBuffer->pNode->data.decoded.frameCount        = 0;
6927             pDataBuffer->pNode->data.decoded.decodedFrameCount = 0;
6928         }
6929 
6930         /* The job has been set up so it can now be posted. */
6931         result = ma_resource_manager_post_job(pResourceManager, &pageDataBufferJob);
6932 
6933         /* The result needs to be set to MA_BUSY to ensure the status of the data buffer is set properly in the next section. */
6934         if (result == MA_SUCCESS) {
6935             result = MA_BUSY;
6936         }
6937 
6938         /* We want to make sure we don't signal the event here. It needs to be delayed until the last page. */
6939         pJob->loadDataBuffer.pNotification = NULL;
6940 
6941         /* Make sure the buffer's status is updated appropriately, but make sure we never move away from a MA_BUSY state to ensure we don't overwrite any error codes. */
6942         c89atomic_compare_and_swap_32(&pDataBuffer->pNode->result, MA_BUSY, result);
6943     }
6944 
6945     /* Only signal the other threads after the result has been set just for cleanliness sake. */
6946     if (pJob->loadDataBuffer.pNotification != NULL) {
6947         ma_async_notification_signal(pJob->loadDataBuffer.pNotification, MA_NOTIFICATION_COMPLETE);
6948     }
6949 
6950     c89atomic_fetch_add_32(&pDataBuffer->pNode->executionPointer, 1);
6951     return result;
6952 }
6953 
ma_resource_manager_process_job__free_data_buffer(ma_resource_manager * pResourceManager,ma_job * pJob)6954 static ma_result ma_resource_manager_process_job__free_data_buffer(ma_resource_manager* pResourceManager, ma_job* pJob)
6955 {
6956     MA_ASSERT(pResourceManager != NULL);
6957     MA_ASSERT(pJob             != NULL);
6958     MA_ASSERT(pJob->freeDataBuffer.pDataBuffer        != NULL);
6959     MA_ASSERT(pJob->freeDataBuffer.pDataBuffer->pNode != NULL);
6960     MA_ASSERT(pJob->freeDataBuffer.pDataBuffer->pNode->result == MA_UNAVAILABLE);
6961 
6962     if (pJob->order != pJob->freeDataBuffer.pDataBuffer->pNode->executionPointer) {
6963         return ma_resource_manager_post_job(pResourceManager, pJob);    /* Out of order. */
6964     }
6965 
6966     ma_resource_manager_data_buffer_uninit_internal(pJob->freeDataBuffer.pDataBuffer);
6967 
6968     /* The event needs to be signalled last. */
6969     if (pJob->freeDataBuffer.pNotification != NULL) {
6970         ma_async_notification_signal(pJob->freeDataBuffer.pNotification, MA_NOTIFICATION_COMPLETE);
6971     }
6972 
6973     /*c89atomic_fetch_add_32(&pJob->freeDataBuffer.pDataBuffer->pNode->executionPointer, 1);*/
6974     return MA_SUCCESS;
6975 }
6976 
ma_resource_manager_process_job__page_data_buffer(ma_resource_manager * pResourceManager,ma_job * pJob)6977 static ma_result ma_resource_manager_process_job__page_data_buffer(ma_resource_manager* pResourceManager, ma_job* pJob)
6978 {
6979     ma_result result = MA_SUCCESS;
6980     ma_uint64 pageSizeInFrames;
6981     ma_uint64 framesRead;
6982     void* pRunningData;
6983     ma_job jobCopy;
6984     ma_resource_manager_data_buffer* pDataBuffer;
6985 
6986     MA_ASSERT(pResourceManager != NULL);
6987     MA_ASSERT(pJob             != NULL);
6988 
6989     pDataBuffer = pJob->pageDataBuffer.pDataBuffer;
6990 
6991     /* Don't do any more decoding if the data buffer has started the uninitialization process. */
6992     if (pDataBuffer->pNode->result != MA_BUSY) {
6993         return MA_INVALID_OPERATION;
6994     }
6995 
6996     if (pJob->order != pDataBuffer->pNode->executionPointer) {
6997         return ma_resource_manager_post_job(pResourceManager, pJob);    /* Out of order. */
6998     }
6999 
7000     /* We're going to base everything off the original job. */
7001     jobCopy = *pJob;
7002 
7003     /* We need to know the size of a page in frames to know how many frames to decode. */
7004     pageSizeInFrames = MA_RESOURCE_MANAGER_PAGE_SIZE_IN_MILLISECONDS * (jobCopy.pageDataBuffer.pDecoder->outputSampleRate/1000);
7005 
7006     /* If the total length is unknown we may need to expand the size of the buffer. */
7007     if (jobCopy.pageDataBuffer.isUnknownLength == MA_TRUE) {
7008         ma_uint64 requiredSize = (jobCopy.pageDataBuffer.decodedFrameCount + pageSizeInFrames) * ma_get_bytes_per_frame(jobCopy.pageDataBuffer.pDecoder->outputFormat, jobCopy.pageDataBuffer.pDecoder->outputChannels);
7009         if (requiredSize <= MA_SIZE_MAX) {
7010             if (requiredSize > jobCopy.pageDataBuffer.dataSizeInBytes) {
7011                 size_t newSize = (size_t)ma_max(requiredSize, jobCopy.pageDataBuffer.dataSizeInBytes * 2);
7012                 void *pNewData = ma__realloc_from_callbacks(jobCopy.pageDataBuffer.pData, newSize, jobCopy.pageDataBuffer.dataSizeInBytes, &pResourceManager->config.allocationCallbacks/*, MA_ALLOCATION_TYPE_DECODED_BUFFER*/);
7013                 if (pNewData != NULL) {
7014                     jobCopy.pageDataBuffer.pData           = pNewData;
7015                     jobCopy.pageDataBuffer.dataSizeInBytes = newSize;
7016                 } else {
7017                     result = MA_OUT_OF_MEMORY;
7018                 }
7019             }
7020         } else {
7021             result = MA_TOO_BIG;
7022         }
7023     }
7024 
7025     /* We should have the memory set up so now we can decode the next page. */
7026     if (result == MA_SUCCESS) {
7027         pRunningData = ma_offset_ptr(jobCopy.pageDataBuffer.pData, jobCopy.pageDataBuffer.decodedFrameCount * ma_get_bytes_per_frame(jobCopy.pageDataBuffer.pDecoder->outputFormat, jobCopy.pageDataBuffer.pDecoder->outputChannels));
7028 
7029         framesRead = ma_decoder_read_pcm_frames(jobCopy.pageDataBuffer.pDecoder, pRunningData, pageSizeInFrames);
7030         if (framesRead < pageSizeInFrames) {
7031             result = MA_AT_END;
7032         }
7033 
7034         /* If the total length is known we can increment out decoded frame count. Otherwise it needs to be left at 0 until the last page is decoded. */
7035         if (jobCopy.pageDataBuffer.isUnknownLength == MA_FALSE) {
7036             pDataBuffer->pNode->data.decoded.decodedFrameCount += framesRead;
7037         }
7038 
7039         /*
7040         If there's more to decode, post a job to keep decoding. Note that we always increment the decoded frame count in the copy of the job because it'll be
7041         referenced below and we'll need to know the new frame count.
7042         */
7043         jobCopy.pageDataBuffer.decodedFrameCount += framesRead;
7044 
7045         if (result != MA_AT_END) {
7046             jobCopy.order = ma_resource_manager_data_buffer_next_execution_order(pDataBuffer);   /* We need a fresh execution order. */
7047             result = ma_resource_manager_post_job(pResourceManager, &jobCopy);
7048         }
7049     }
7050 
7051     /*
7052     The result will be MA_SUCCESS if another page is in the queue for decoding. Otherwise it will be set to MA_AT_END if the end has been reached or
7053     any other result code if some other error occurred. If we are not decoding another page we need to free the decoder and close the file.
7054     */
7055     if (result != MA_SUCCESS) {
7056         ma_decoder_uninit(jobCopy.pageDataBuffer.pDecoder);
7057         ma__free_from_callbacks(jobCopy.pageDataBuffer.pDecoder, &pResourceManager->config.allocationCallbacks/*, MA_ALLOCATION_TYPE_DECODER*/);
7058 
7059         /* When the length is unknown we were doubling the size of the buffer each time we needed more data. Let's try reducing this by doing a final realloc(). */
7060         if (jobCopy.pageDataBuffer.isUnknownLength) {
7061             ma_uint64 newSizeInBytes = jobCopy.pageDataBuffer.decodedFrameCount * ma_get_bytes_per_frame(pDataBuffer->pNode->data.decoded.format, pDataBuffer->pNode->data.decoded.channels);
7062             void* pNewData = ma__realloc_from_callbacks(jobCopy.pageDataBuffer.pData, (size_t)newSizeInBytes, jobCopy.pageDataBuffer.dataSizeInBytes, &pResourceManager->config.allocationCallbacks);
7063             if (pNewData != NULL) {
7064                 jobCopy.pageDataBuffer.pData = pNewData;
7065                 jobCopy.pageDataBuffer.dataSizeInBytes = (size_t)newSizeInBytes;    /* <-- Don't really need to set this, but I think it's good practice. */
7066             }
7067         }
7068 
7069         /*
7070         We can now set the frame counts appropriately. We want to set the frame count regardless of whether or not it had a known length just in case we have
7071         a weird situation where the frame count an opening time was different to the final count we got after reading.
7072         */
7073         pDataBuffer->pNode->data.decoded.pData      = jobCopy.pageDataBuffer.pData;
7074         pDataBuffer->pNode->data.decoded.frameCount = jobCopy.pageDataBuffer.decodedFrameCount;
7075 
7076         /*
7077         decodedFrameCount is what other threads will use to determine whether or not data is available. We must ensure pData and frameCount
7078         is set *before* setting the number of available frames. This way, the other thread need only check if decodedFrameCount > 0, in
7079         which case it can assume pData and frameCount are valid.
7080         */
7081         c89atomic_thread_fence(c89atomic_memory_order_seq_cst);
7082         pDataBuffer->pNode->data.decoded.decodedFrameCount = jobCopy.pageDataBuffer.decodedFrameCount;
7083 
7084 
7085         /* If we reached the end we need to treat it as successful. */
7086         if (result == MA_AT_END) {
7087             result  = MA_SUCCESS;
7088         }
7089 
7090         /* If it was an unknown length, we can finally initialize the connector. For sounds of a known length, the connector was initialized when the first page was decoded in MA_JOB_LOAD_DATA_BUFFER. */
7091         if (jobCopy.pageDataBuffer.isUnknownLength) {
7092             result = ma_resource_manager_data_buffer_init_connector(pDataBuffer, pJob->pageDataBuffer.pCompletedNotification);
7093         }
7094 
7095         /* We need to set the status of the page so other things can know about it. We can only change the status away from MA_BUSY. If it's anything else it cannot be changed. */
7096         c89atomic_compare_and_swap_32(&pDataBuffer->pNode->result, MA_BUSY, result);
7097 
7098         /* We need to signal an event to indicate that we're done. */
7099         if (jobCopy.pageDataBuffer.pCompletedNotification != NULL) {
7100             ma_async_notification_signal(jobCopy.pageDataBuffer.pCompletedNotification, MA_NOTIFICATION_COMPLETE);
7101         }
7102     }
7103 
7104     c89atomic_fetch_add_32(&pDataBuffer->pNode->executionPointer, 1);
7105     return result;
7106 }
7107 
7108 
ma_resource_manager_process_job__load_data_stream(ma_resource_manager * pResourceManager,ma_job * pJob)7109 static ma_result ma_resource_manager_process_job__load_data_stream(ma_resource_manager* pResourceManager, ma_job* pJob)
7110 {
7111     ma_result result = MA_SUCCESS;
7112     ma_decoder_config decoderConfig;
7113     ma_uint32 pageBufferSizeInBytes;
7114     ma_resource_manager_data_stream* pDataStream;
7115 
7116     MA_ASSERT(pResourceManager != NULL);
7117     MA_ASSERT(pJob             != NULL);
7118 
7119     pDataStream = pJob->loadDataStream.pDataStream;
7120 
7121     if (pDataStream->result != MA_BUSY) {
7122         result = MA_INVALID_OPERATION;  /* Most likely the data stream is being uninitialized. */
7123         goto done;
7124     }
7125 
7126     if (pJob->order != pDataStream->executionPointer) {
7127         return ma_resource_manager_post_job(pResourceManager, pJob);    /* Out of order. */
7128     }
7129 
7130     /* We need to initialize the decoder first so we can determine the size of the pages. */
7131     decoderConfig = ma_decoder_config_init(pResourceManager->config.decodedFormat, pResourceManager->config.decodedChannels, pResourceManager->config.decodedSampleRate);
7132     decoderConfig.allocationCallbacks = pResourceManager->config.allocationCallbacks;
7133 
7134     result = ma_decoder_init_vfs(pResourceManager->config.pVFS, pJob->loadDataStream.pFilePath, &decoderConfig, &pDataStream->decoder);
7135     if (result != MA_SUCCESS) {
7136         goto done;
7137     }
7138 
7139     /* Retrieve the total length of the file before marking the decoder are loaded. */
7140     pDataStream->totalLengthInPCMFrames = ma_decoder_get_length_in_pcm_frames(&pDataStream->decoder);
7141 
7142     /*
7143     Only mark the decoder as initialized when the length of the decoder has been retrieved because that can possibly require a scan over the whole file
7144     and we don't want to have another thread trying to access the decoder while it's scanning.
7145     */
7146     pDataStream->isDecoderInitialized = MA_TRUE;
7147 
7148     /* We have the decoder so we can now initialize our page buffer. */
7149     pageBufferSizeInBytes = ma_resource_manager_data_stream_get_page_size_in_frames(pDataStream) * 2 * ma_get_bytes_per_frame(pDataStream->decoder.outputFormat, pDataStream->decoder.outputChannels);
7150 
7151     pDataStream->pPageData = ma__malloc_from_callbacks(pageBufferSizeInBytes, &pResourceManager->config.allocationCallbacks/*, MA_ALLOCATION_TYPE_DECODED_BUFFER*/);
7152     if (pDataStream->pPageData == NULL) {
7153         ma_decoder_uninit(&pDataStream->decoder);
7154         result = MA_OUT_OF_MEMORY;
7155         goto done;
7156     }
7157 
7158     /* We have our decoder and our page buffer, so now we need to fill our pages. */
7159     ma_resource_manager_data_stream_fill_pages(pDataStream);
7160 
7161     /* And now we're done. We want to make sure the result is MA_SUCCESS. */
7162     result = MA_SUCCESS;
7163 
7164 done:
7165     ma__free_from_callbacks(pJob->loadDataStream.pFilePath, &pResourceManager->config.allocationCallbacks/*, MA_ALLOCATION_TYPE_TRANSIENT_STRING*/);
7166 
7167     /* We can only change the status away from MA_BUSY. If it's set to anything else it means an error has occurred somewhere or the uninitialization process has started (most likely). */
7168     c89atomic_compare_and_swap_32(&pDataStream->result, MA_BUSY, result);
7169 
7170     /* Only signal the other threads after the result has been set just for cleanliness sake. */
7171     if (pJob->loadDataStream.pNotification != NULL) {
7172         ma_async_notification_signal(pJob->loadDataStream.pNotification, MA_NOTIFICATION_INIT);
7173         ma_async_notification_signal(pJob->loadDataStream.pNotification, MA_NOTIFICATION_COMPLETE);
7174     }
7175 
7176     c89atomic_fetch_add_32(&pDataStream->executionPointer, 1);
7177     return result;
7178 }
7179 
ma_resource_manager_process_job__free_data_stream(ma_resource_manager * pResourceManager,ma_job * pJob)7180 static ma_result ma_resource_manager_process_job__free_data_stream(ma_resource_manager* pResourceManager, ma_job* pJob)
7181 {
7182     ma_resource_manager_data_stream* pDataStream;
7183 
7184     MA_ASSERT(pResourceManager != NULL);
7185     MA_ASSERT(pJob             != NULL);
7186 
7187     pDataStream = pJob->freeDataStream.pDataStream;
7188     MA_ASSERT(pDataStream != NULL);
7189 
7190     /* If our status is not MA_UNAVAILABLE we have a bug somewhere. */
7191     MA_ASSERT(pDataStream->result == MA_UNAVAILABLE);
7192 
7193     if (pJob->order != pDataStream->executionPointer) {
7194         return ma_resource_manager_post_job(pResourceManager, pJob);    /* Out of order. */
7195     }
7196 
7197     if (pDataStream->isDecoderInitialized) {
7198         ma_decoder_uninit(&pDataStream->decoder);
7199     }
7200 
7201     if (pDataStream->pPageData != NULL) {
7202         ma__free_from_callbacks(pDataStream->pPageData, &pResourceManager->config.allocationCallbacks/*, MA_ALLOCATION_TYPE_DECODED_BUFFER*/);
7203         pDataStream->pPageData = NULL;  /* Just in case... */
7204     }
7205 
7206     /* The event needs to be signalled last. */
7207     if (pJob->freeDataStream.pNotification != NULL) {
7208         ma_async_notification_signal(pJob->freeDataStream.pNotification, MA_NOTIFICATION_COMPLETE);
7209     }
7210 
7211     /*c89atomic_fetch_add_32(&pDataStream->executionPointer, 1);*/
7212     return MA_SUCCESS;
7213 }
7214 
ma_resource_manager_process_job__page_data_stream(ma_resource_manager * pResourceManager,ma_job * pJob)7215 static ma_result ma_resource_manager_process_job__page_data_stream(ma_resource_manager* pResourceManager, ma_job* pJob)
7216 {
7217     ma_result result = MA_SUCCESS;
7218     ma_resource_manager_data_stream* pDataStream;
7219 
7220     MA_ASSERT(pResourceManager != NULL);
7221     MA_ASSERT(pJob             != NULL);
7222 
7223     pDataStream = pJob->pageDataStream.pDataStream;
7224     MA_ASSERT(pDataStream != NULL);
7225 
7226     /* For streams, the status should be MA_SUCCESS. */
7227     if (pDataStream->result != MA_SUCCESS) {
7228         result = MA_INVALID_OPERATION;
7229         goto done;
7230     }
7231 
7232     if (pJob->order != pDataStream->executionPointer) {
7233         return ma_resource_manager_post_job(pResourceManager, pJob);    /* Out of order. */
7234     }
7235 
7236     ma_resource_manager_data_stream_fill_page(pDataStream, pJob->pageDataStream.pageIndex);
7237 
7238 done:
7239     c89atomic_fetch_add_32(&pDataStream->executionPointer, 1);
7240     return result;
7241 }
7242 
ma_resource_manager_process_job__seek_data_stream(ma_resource_manager * pResourceManager,ma_job * pJob)7243 static ma_result ma_resource_manager_process_job__seek_data_stream(ma_resource_manager* pResourceManager, ma_job* pJob)
7244 {
7245     ma_result result = MA_SUCCESS;
7246     ma_resource_manager_data_stream* pDataStream;
7247 
7248     MA_ASSERT(pResourceManager != NULL);
7249     MA_ASSERT(pJob             != NULL);
7250 
7251     pDataStream = pJob->seekDataStream.pDataStream;
7252     MA_ASSERT(pDataStream != NULL);
7253 
7254     /* For streams the status should be MA_SUCCESS for this to do anything. */
7255     if (pDataStream->result != MA_SUCCESS || pDataStream->isDecoderInitialized == MA_FALSE) {
7256         result = MA_INVALID_OPERATION;
7257         goto done;
7258     }
7259 
7260     if (pJob->order != pDataStream->executionPointer) {
7261         return ma_resource_manager_post_job(pResourceManager, pJob);    /* Out of order. */
7262     }
7263 
7264     /*
7265     With seeking we just assume both pages are invalid and the relative frame cursor at at position 0. This is basically exactly the same as loading, except
7266     instead of initializing the decoder, we seek to a frame.
7267     */
7268     ma_decoder_seek_to_pcm_frame(&pDataStream->decoder, pJob->seekDataStream.frameIndex);
7269 
7270     /* After seeking we'll need to reload the pages. */
7271     ma_resource_manager_data_stream_fill_pages(pDataStream);
7272 
7273     /* We need to let the public API know that we're done seeking. */
7274     c89atomic_fetch_sub_32(&pDataStream->seekCounter, 1);
7275 
7276 done:
7277     c89atomic_fetch_add_32(&pDataStream->executionPointer, 1);
7278     return result;
7279 }
7280 
ma_resource_manager_process_job(ma_resource_manager * pResourceManager,ma_job * pJob)7281 MA_API ma_result ma_resource_manager_process_job(ma_resource_manager* pResourceManager, ma_job* pJob)
7282 {
7283     if (pResourceManager == NULL || pJob == NULL) {
7284         return MA_INVALID_ARGS;
7285     }
7286 
7287     switch (pJob->toc.code)
7288     {
7289         /* Data Buffer */
7290         case MA_JOB_LOAD_DATA_BUFFER: return ma_resource_manager_process_job__load_data_buffer(pResourceManager, pJob);
7291         case MA_JOB_FREE_DATA_BUFFER: return ma_resource_manager_process_job__free_data_buffer(pResourceManager, pJob);
7292         case MA_JOB_PAGE_DATA_BUFFER: return ma_resource_manager_process_job__page_data_buffer(pResourceManager, pJob);
7293 
7294         /* Data Stream */
7295         case MA_JOB_LOAD_DATA_STREAM: return ma_resource_manager_process_job__load_data_stream(pResourceManager, pJob);
7296         case MA_JOB_FREE_DATA_STREAM: return ma_resource_manager_process_job__free_data_stream(pResourceManager, pJob);
7297         case MA_JOB_PAGE_DATA_STREAM: return ma_resource_manager_process_job__page_data_stream(pResourceManager, pJob);
7298         case MA_JOB_SEEK_DATA_STREAM: return ma_resource_manager_process_job__seek_data_stream(pResourceManager, pJob);
7299 
7300         default: break;
7301     }
7302 
7303     /* Getting here means we don't know what the job code is and cannot do anything with it. */
7304     return MA_INVALID_OPERATION;
7305 }
7306 
ma_resource_manager_process_next_job(ma_resource_manager * pResourceManager)7307 MA_API ma_result ma_resource_manager_process_next_job(ma_resource_manager* pResourceManager)
7308 {
7309     ma_result result;
7310     ma_job job;
7311 
7312     if (pResourceManager == NULL) {
7313         return MA_INVALID_ARGS;
7314     }
7315 
7316     /* This will return MA_CANCELLED if the next job is a quit job. */
7317     result = ma_resource_manager_next_job(pResourceManager, &job);
7318     if (result != MA_SUCCESS) {
7319         return result;
7320     }
7321 
7322     return ma_resource_manager_process_job(pResourceManager, &job);
7323 }
7324 
7325 
7326 
7327 
7328 
ma_panner_config_init(ma_format format,ma_uint32 channels)7329 MA_API ma_panner_config ma_panner_config_init(ma_format format, ma_uint32 channels)
7330 {
7331     ma_panner_config config;
7332 
7333     MA_ZERO_OBJECT(&config);
7334     config.format   = format;
7335     config.channels = channels;
7336     config.mode     = ma_pan_mode_balance;  /* Set to balancing mode by default because it's consistent with other audio engines and most likely what the caller is expecting. */
7337     config.pan      = 0;
7338 
7339     return config;
7340 }
7341 
7342 
ma_panner_effect__on_process_pcm_frames(ma_effect * pEffect,const void * pFramesIn,ma_uint64 * pFrameCountIn,void * pFramesOut,ma_uint64 * pFrameCountOut)7343 static ma_result ma_panner_effect__on_process_pcm_frames(ma_effect* pEffect, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut)
7344 {
7345     ma_panner* pPanner = (ma_panner*)pEffect;
7346     ma_result result;
7347     ma_uint64 frameCount;
7348 
7349     /* The panner has a 1:1 relationship between input and output frame counts. */
7350     frameCount = ma_min(*pFrameCountIn, *pFrameCountOut);
7351 
7352     result = ma_panner_process_pcm_frames(pPanner, pFramesOut, pFramesIn, ma_min(*pFrameCountIn, *pFrameCountOut));
7353 
7354     *pFrameCountIn  = frameCount;
7355     *pFrameCountOut = frameCount;
7356 
7357     return result;
7358 }
7359 
ma_panner_effect__on_get_data_format(ma_effect * pEffect,ma_format * pFormat,ma_uint32 * pChannels,ma_uint32 * pSampleRate)7360 static ma_result ma_panner_effect__on_get_data_format(ma_effect* pEffect, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate)
7361 {
7362     ma_panner* pPanner = (ma_panner*)pEffect;
7363 
7364     *pFormat     = pPanner->format;
7365     *pChannels   = pPanner->channels;
7366     *pSampleRate = 0;   /* There's no notion of sample rate with this effect. */
7367 
7368     return MA_SUCCESS;
7369 }
7370 
ma_panner_init(const ma_panner_config * pConfig,ma_panner * pPanner)7371 MA_API ma_result ma_panner_init(const ma_panner_config* pConfig, ma_panner* pPanner)
7372 {
7373     if (pPanner == NULL) {
7374         return MA_INVALID_ARGS;
7375     }
7376 
7377     MA_ZERO_OBJECT(pPanner);
7378 
7379     if (pConfig == NULL) {
7380         return MA_INVALID_ARGS;
7381     }
7382 
7383     pPanner->effect.onProcessPCMFrames            = ma_panner_effect__on_process_pcm_frames;
7384     pPanner->effect.onGetRequiredInputFrameCount  = NULL;
7385     pPanner->effect.onGetExpectedOutputFrameCount = NULL;
7386     pPanner->effect.onGetInputDataFormat          = ma_panner_effect__on_get_data_format;   /* Same format for both input and output. */
7387     pPanner->effect.onGetOutputDataFormat         = ma_panner_effect__on_get_data_format;
7388 
7389     pPanner->format   = pConfig->format;
7390     pPanner->channels = pConfig->channels;
7391     pPanner->mode     = pConfig->mode;
7392     pPanner->pan      = pConfig->pan;
7393 
7394     return MA_SUCCESS;
7395 }
7396 
7397 
7398 
ma_stereo_balance_pcm_frames_f32(float * pFramesOut,const float * pFramesIn,ma_uint64 frameCount,float pan)7399 static void ma_stereo_balance_pcm_frames_f32(float* pFramesOut, const float* pFramesIn, ma_uint64 frameCount, float pan)
7400 {
7401     ma_uint64 iFrame;
7402 
7403     if (pan > 0) {
7404         float factor = 1.0f - pan;
7405         if (pFramesOut == pFramesIn) {
7406             for (iFrame = 0; iFrame < frameCount; iFrame += 1) {
7407                 pFramesOut[iFrame*2 + 0] = pFramesIn[iFrame*2 + 0] * factor;
7408             }
7409         } else {
7410             for (iFrame = 0; iFrame < frameCount; iFrame += 1) {
7411                 pFramesOut[iFrame*2 + 0] = pFramesIn[iFrame*2 + 0] * factor;
7412                 pFramesOut[iFrame*2 + 1] = pFramesIn[iFrame*2 + 1];
7413             }
7414         }
7415     } else {
7416         float factor = 1.0f + pan;
7417         if (pFramesOut == pFramesIn) {
7418             for (iFrame = 0; iFrame < frameCount; iFrame += 1) {
7419                 pFramesOut[iFrame*2 + 1] = pFramesIn[iFrame*2 + 1] * factor;
7420             }
7421         } else {
7422             for (iFrame = 0; iFrame < frameCount; iFrame += 1) {
7423                 pFramesOut[iFrame*2 + 0] = pFramesIn[iFrame*2 + 0];
7424                 pFramesOut[iFrame*2 + 1] = pFramesIn[iFrame*2 + 1] * factor;
7425             }
7426         }
7427     }
7428 }
7429 
ma_stereo_balance_pcm_frames(void * pFramesOut,const void * pFramesIn,ma_uint64 frameCount,ma_format format,float pan)7430 static void ma_stereo_balance_pcm_frames(void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount, ma_format format, float pan)
7431 {
7432     if (pan == 0) {
7433         /* Fast path. No panning required. */
7434         if (pFramesOut == pFramesIn) {
7435             /* No-op */
7436         } else {
7437             ma_copy_pcm_frames(pFramesOut, pFramesIn, frameCount, format, 2);
7438         }
7439     }
7440 
7441     switch (format) {
7442         case ma_format_f32: ma_stereo_balance_pcm_frames_f32((float*)pFramesOut, (float*)pFramesIn, frameCount, pan); break;
7443 
7444         /* Unknown format. Just copy. */
7445         default:
7446         {
7447             ma_copy_pcm_frames(pFramesOut, pFramesIn, frameCount, format, 2);
7448         } break;
7449     }
7450 }
7451 
7452 
ma_stereo_pan_pcm_frames_f32(float * pFramesOut,const float * pFramesIn,ma_uint64 frameCount,float pan)7453 static void ma_stereo_pan_pcm_frames_f32(float* pFramesOut, const float* pFramesIn, ma_uint64 frameCount, float pan)
7454 {
7455     ma_uint64 iFrame;
7456 
7457     if (pan > 0) {
7458         float factorL0 = 1.0f - pan;
7459         float factorL1 = 0.0f + pan;
7460 
7461         for (iFrame = 0; iFrame < frameCount; iFrame += 1) {
7462             float sample0 = (pFramesIn[iFrame*2 + 0] * factorL0);
7463             float sample1 = (pFramesIn[iFrame*2 + 0] * factorL1) + pFramesIn[iFrame*2 + 1];
7464 
7465             pFramesOut[iFrame*2 + 0] = sample0;
7466             pFramesOut[iFrame*2 + 1] = sample1;
7467         }
7468     } else {
7469         float factorR0 = 0.0f - pan;
7470         float factorR1 = 1.0f + pan;
7471 
7472         for (iFrame = 0; iFrame < frameCount; iFrame += 1) {
7473             float sample0 = pFramesIn[iFrame*2 + 0] + (pFramesIn[iFrame*2 + 1] * factorR0);
7474             float sample1 =                           (pFramesIn[iFrame*2 + 1] * factorR1);
7475 
7476             pFramesOut[iFrame*2 + 0] = sample0;
7477             pFramesOut[iFrame*2 + 1] = sample1;
7478         }
7479     }
7480 }
7481 
ma_stereo_pan_pcm_frames(void * pFramesOut,const void * pFramesIn,ma_uint64 frameCount,ma_format format,float pan)7482 static void ma_stereo_pan_pcm_frames(void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount, ma_format format, float pan)
7483 {
7484     if (pan == 0) {
7485         /* Fast path. No panning required. */
7486         if (pFramesOut == pFramesIn) {
7487             /* No-op */
7488         } else {
7489             ma_copy_pcm_frames(pFramesOut, pFramesIn, frameCount, format, 2);
7490         }
7491     }
7492 
7493     switch (format) {
7494         case ma_format_f32: ma_stereo_pan_pcm_frames_f32((float*)pFramesOut, (float*)pFramesIn, frameCount, pan); break;
7495 
7496         /* Unknown format. Just copy. */
7497         default:
7498         {
7499             ma_copy_pcm_frames(pFramesOut, pFramesIn, frameCount, format, 2);
7500         } break;
7501     }
7502 }
7503 
ma_panner_process_pcm_frames(ma_panner * pPanner,void * pFramesOut,const void * pFramesIn,ma_uint64 frameCount)7504 MA_API ma_result ma_panner_process_pcm_frames(ma_panner* pPanner, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount)
7505 {
7506     if (pPanner == NULL || pFramesOut == NULL || pFramesIn == NULL) {
7507         return MA_INVALID_ARGS;
7508     }
7509 
7510     if (pPanner->channels == 2) {
7511         /* Stereo case. For now assume channel 0 is left and channel right is 1, but should probably add support for a channel map. */
7512         if (pPanner->mode == ma_pan_mode_balance) {
7513             ma_stereo_balance_pcm_frames(pFramesOut, pFramesIn, frameCount, pPanner->format, pPanner->pan);
7514         } else {
7515             ma_stereo_pan_pcm_frames(pFramesOut, pFramesIn, frameCount, pPanner->format, pPanner->pan);
7516         }
7517     } else {
7518         if (pPanner->channels == 1) {
7519             /* Panning has no effect on mono streams. */
7520             ma_copy_pcm_frames(pFramesOut, pFramesIn, frameCount, pPanner->format, pPanner->channels);
7521         } else {
7522             /* For now we're not going to support non-stereo set ups. Not sure how I want to handle this case just yet. */
7523             ma_copy_pcm_frames(pFramesOut, pFramesIn, frameCount, pPanner->format, pPanner->channels);
7524         }
7525     }
7526 
7527     return MA_SUCCESS;
7528 }
7529 
ma_panner_set_mode(ma_panner * pPanner,ma_pan_mode mode)7530 MA_API ma_result ma_panner_set_mode(ma_panner* pPanner, ma_pan_mode mode)
7531 {
7532     if (pPanner == NULL) {
7533         return MA_INVALID_ARGS;
7534     }
7535 
7536     pPanner->mode = mode;
7537 
7538     return MA_SUCCESS;
7539 }
7540 
ma_panner_set_pan(ma_panner * pPanner,float pan)7541 MA_API ma_result ma_panner_set_pan(ma_panner* pPanner, float pan)
7542 {
7543     if (pPanner == NULL) {
7544         return MA_INVALID_ARGS;
7545     }
7546 
7547     pPanner->pan = ma_clamp(pan, -1.0f, 1.0f);
7548 
7549     return MA_SUCCESS;
7550 }
7551 
7552 
7553 
7554 
ma_spatializer_config_init(ma_engine * pEngine,ma_format format,ma_uint32 channels)7555 MA_API ma_spatializer_config ma_spatializer_config_init(ma_engine* pEngine, ma_format format, ma_uint32 channels)
7556 {
7557     ma_spatializer_config config;
7558 
7559     MA_ZERO_OBJECT(&config);
7560 
7561     config.pEngine  = pEngine;
7562     config.format   = format;
7563     config.channels = channels;
7564     config.position = ma_vec3f(0, 0, 0);
7565     config.rotation = ma_quatf(0, 0, 0, 1);
7566 
7567     return config;
7568 }
7569 
7570 
ma_spatializer_effect__on_process_pcm_frames(ma_effect * pEffect,const void * pFramesIn,ma_uint64 * pFrameCountIn,void * pFramesOut,ma_uint64 * pFrameCountOut)7571 static ma_result ma_spatializer_effect__on_process_pcm_frames(ma_effect* pEffect, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut)
7572 {
7573     ma_spatializer* pSpatializer = (ma_spatializer*)pEffect;
7574     ma_result result;
7575     ma_uint64 frameCount;
7576 
7577     /* The spatializer has a 1:1 relationship between input and output frame counts. */
7578     frameCount = ma_min(*pFrameCountIn, *pFrameCountOut);
7579 
7580     result = ma_spatializer_process_pcm_frames(pSpatializer, pFramesOut, pFramesIn, frameCount);
7581 
7582     *pFrameCountIn  = frameCount;
7583     *pFrameCountOut = frameCount;
7584 
7585     return result;
7586 }
7587 
ma_spatializer_effect__on_get_data_format(ma_effect * pEffect,ma_format * pFormat,ma_uint32 * pChannels,ma_uint32 * pSampleRate)7588 static ma_result ma_spatializer_effect__on_get_data_format(ma_effect* pEffect, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate)
7589 {
7590     ma_spatializer* pSpatializer = (ma_spatializer*)pEffect;
7591 
7592     *pFormat     = pSpatializer->format;
7593     *pChannels   = pSpatializer->channels;
7594     *pSampleRate = 0;   /* There's no notion of sample rate with this effect. */
7595 
7596     return MA_SUCCESS;
7597 }
7598 
ma_spatializer_init(const ma_spatializer_config * pConfig,ma_spatializer * pSpatializer)7599 MA_API ma_result ma_spatializer_init(const ma_spatializer_config* pConfig, ma_spatializer* pSpatializer)
7600 {
7601     if (pSpatializer == NULL) {
7602         return MA_INVALID_ARGS;
7603     }
7604 
7605     MA_ZERO_OBJECT(pSpatializer);
7606 
7607     if (pConfig == NULL) {
7608         return MA_INVALID_ARGS;
7609     }
7610 
7611     pSpatializer->effect.onProcessPCMFrames            = ma_spatializer_effect__on_process_pcm_frames;
7612     pSpatializer->effect.onGetRequiredInputFrameCount  = NULL;
7613     pSpatializer->effect.onGetExpectedOutputFrameCount = NULL;
7614     pSpatializer->effect.onGetInputDataFormat          = ma_spatializer_effect__on_get_data_format;  /* Same format for both input and output. */
7615     pSpatializer->effect.onGetOutputDataFormat         = ma_spatializer_effect__on_get_data_format;
7616 
7617     pSpatializer->pEngine  = pConfig->pEngine;
7618     pSpatializer->format   = pConfig->format;
7619     pSpatializer->channels = pConfig->channels;
7620     pSpatializer->position = pConfig->position;
7621     pSpatializer->rotation = pConfig->rotation;
7622 
7623     return MA_SUCCESS;
7624 }
7625 
ma_spatializer_process_pcm_frames(ma_spatializer * pSpatializer,void * pFramesOut,const void * pFramesIn,ma_uint64 frameCount)7626 MA_API ma_result ma_spatializer_process_pcm_frames(ma_spatializer* pSpatializer, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount)
7627 {
7628     if (pSpatializer || pFramesOut == NULL || pFramesIn) {
7629         return MA_INVALID_ARGS;
7630     }
7631 
7632     /* TODO: Implement me. Just copying for now. */
7633     ma_copy_pcm_frames(pFramesOut, pFramesIn, frameCount, pSpatializer->format, pSpatializer->channels);
7634 
7635     return MA_SUCCESS;
7636 }
7637 
ma_spatializer_set_position(ma_spatializer * pSpatializer,ma_vec3 position)7638 MA_API ma_result ma_spatializer_set_position(ma_spatializer* pSpatializer, ma_vec3 position)
7639 {
7640     if (pSpatializer == NULL) {
7641         return MA_INVALID_ARGS;
7642     }
7643 
7644     pSpatializer->position = position;
7645 
7646     return MA_SUCCESS;
7647 }
7648 
ma_spatializer_set_rotation(ma_spatializer * pSpatializer,ma_quat rotation)7649 MA_API ma_result ma_spatializer_set_rotation(ma_spatializer* pSpatializer, ma_quat rotation)
7650 {
7651     if (pSpatializer == NULL) {
7652         return MA_INVALID_ARGS;
7653     }
7654 
7655     pSpatializer->rotation = rotation;
7656 
7657     return MA_SUCCESS;
7658 }
7659 
7660 
7661 
7662 
ma_dual_fader_config_init(ma_format format,ma_uint32 channels,ma_uint32 sampleRate)7663 MA_API ma_dual_fader_config ma_dual_fader_config_init(ma_format format, ma_uint32 channels, ma_uint32 sampleRate)
7664 {
7665     ma_dual_fader_config config;
7666 
7667     MA_ZERO_OBJECT(&config);
7668     config.format                   = format;
7669     config.channels                 = channels;
7670     config.sampleRate               = sampleRate;
7671     config.state[0].volumeBeg       = 1;
7672     config.state[0].volumeEnd       = 1;
7673     config.state[0].timeInFramesBeg = 0;
7674     config.state[0].timeInFramesEnd = 0;
7675     config.state[0].autoReset       = MA_TRUE;
7676     config.state[1].volumeBeg       = 1;
7677     config.state[1].volumeEnd       = 1;
7678     config.state[1].timeInFramesBeg = 0;
7679     config.state[1].timeInFramesEnd = 0;
7680     config.state[1].autoReset       = MA_TRUE;
7681 
7682     return config;
7683 }
7684 
7685 
ma_dual_fader_effect__on_process_pcm_frames(ma_effect * pEffect,const void * pFramesIn,ma_uint64 * pFrameCountIn,void * pFramesOut,ma_uint64 * pFrameCountOut)7686 static ma_result ma_dual_fader_effect__on_process_pcm_frames(ma_effect* pEffect, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut)
7687 {
7688     ma_dual_fader* pFader = (ma_dual_fader*)pEffect;
7689     ma_result result;
7690     ma_uint64 frameCount;
7691 
7692     /* The fader has a 1:1 relationship between input and output frame counts. */
7693     frameCount = ma_min(*pFrameCountIn, *pFrameCountOut);
7694 
7695     result = ma_dual_fader_process_pcm_frames(pFader, pFramesOut, pFramesIn, frameCount);
7696 
7697     *pFrameCountIn  = frameCount;
7698     *pFrameCountOut = frameCount;
7699 
7700     return result;
7701 }
7702 
ma_dual_fader_effect__on_get_data_format(ma_effect * pEffect,ma_format * pFormat,ma_uint32 * pChannels,ma_uint32 * pSampleRate)7703 static ma_result ma_dual_fader_effect__on_get_data_format(ma_effect* pEffect, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate)
7704 {
7705     return ma_dual_fader_get_data_format((ma_dual_fader*)pEffect, pFormat, pChannels, pSampleRate);
7706 }
7707 
ma_dual_fader_init(const ma_dual_fader_config * pConfig,ma_dual_fader * pFader)7708 MA_API ma_result ma_dual_fader_init(const ma_dual_fader_config* pConfig, ma_dual_fader* pFader)
7709 {
7710     if (pFader == NULL) {
7711         return MA_INVALID_ARGS;
7712     }
7713 
7714     MA_ZERO_OBJECT(pFader);
7715 
7716     if (pConfig == NULL) {
7717         return MA_INVALID_ARGS;
7718     }
7719 
7720     pFader->effect.onProcessPCMFrames            = ma_dual_fader_effect__on_process_pcm_frames;
7721     pFader->effect.onGetRequiredInputFrameCount  = NULL;
7722     pFader->effect.onGetExpectedOutputFrameCount = NULL;
7723     pFader->effect.onGetInputDataFormat          = ma_dual_fader_effect__on_get_data_format;
7724     pFader->effect.onGetOutputDataFormat         = ma_dual_fader_effect__on_get_data_format;
7725 
7726     pFader->config          = *pConfig;
7727     pFader->timeInFramesCur = 0;
7728 
7729     /* If the start time comes after the end time, just swap the fade parameters. */
7730     if (pFader->config.state[0].timeInFramesBeg > pFader->config.state[0].timeInFramesEnd) {
7731         ma_uint64 timeTemp;
7732         float volumeTemp;
7733 
7734         timeTemp = pFader->config.state[0].timeInFramesBeg;
7735         pFader->config.state[0].timeInFramesBeg = pFader->config.state[0].timeInFramesEnd;
7736         pFader->config.state[0].timeInFramesEnd = timeTemp;
7737 
7738         volumeTemp = pFader->config.state[0].volumeBeg;
7739         pFader->config.state[0].volumeBeg = pFader->config.state[0].volumeEnd;
7740         pFader->config.state[0].volumeEnd = volumeTemp;
7741     }
7742 
7743     if (pFader->config.state[1].timeInFramesBeg > pFader->config.state[1].timeInFramesEnd) {
7744         ma_uint64 timeTemp;
7745         float volumeTemp;
7746 
7747         timeTemp = pFader->config.state[0].timeInFramesBeg;
7748         pFader->config.state[1].timeInFramesBeg = pFader->config.state[1].timeInFramesEnd;
7749         pFader->config.state[1].timeInFramesEnd = timeTemp;
7750 
7751         volumeTemp = pFader->config.state[0].volumeBeg;
7752         pFader->config.state[1].volumeBeg = pFader->config.state[1].volumeEnd;
7753         pFader->config.state[1].volumeEnd = volumeTemp;
7754     }
7755 
7756     return MA_SUCCESS;
7757 }
7758 
7759 
ma_dual_fader_process_pcm_frames_by_index(ma_dual_fader * pFader,void * pFramesOut,const void * pFramesIn,ma_uint64 frameCount,ma_uint32 index)7760 MA_API ma_result ma_dual_fader_process_pcm_frames_by_index(ma_dual_fader* pFader, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount, ma_uint32 index)
7761 {
7762     ma_uint64 iFrame;
7763     ma_uint32 iChannel;
7764 
7765     MA_ASSERT(pFader != NULL);
7766 
7767     /* Optimized path when the current time has passed end of the fading period. */
7768     if (pFader->timeInFramesCur >= pFader->config.state[index].timeInFramesEnd) {
7769         if (pFramesOut == pFramesIn) {
7770             /* No-op. */
7771         } else {
7772             ma_copy_and_apply_volume_factor_pcm_frames(pFramesOut, pFramesIn, frameCount, pFader->config.format, pFader->config.channels, pFader->config.state[index].volumeEnd);
7773         }
7774     } else {
7775         ma_uint64 lo =  pFader->config.state[index].timeInFramesBeg;
7776         ma_uint64 hi =  pFader->config.state[index].timeInFramesEnd;
7777         ma_uint64 dt = (pFader->config.state[index].timeInFramesEnd - pFader->config.state[index].timeInFramesBeg);
7778 
7779         /* Only supporting f32 for the moment while we figure this out. */
7780         if (pFader->config.format == ma_format_f32) {
7781             const float* pFramesInF32  = (const float*)pFramesIn;
7782             /* */ float* pFramesOutF32 = (      float*)pFramesOut;
7783             float volumeCur = 1;
7784 
7785             for (iFrame = 0; iFrame < frameCount; iFrame += 1) {
7786                 /* The volume to apply is just a mix between the begin and end volume depending on the current time. */
7787                 ma_uint64 x = pFader->timeInFramesCur + iFrame;
7788                 float a;
7789 
7790                 if (dt == 0) {
7791                     if (x < lo) {
7792                         a = 0;
7793                     } else {
7794                         a = 1;
7795                     }
7796                 } else {
7797                     a = (ma_clamp(x, lo, hi) - lo) / (float)dt;
7798                 }
7799 
7800                 volumeCur = ma_mix_f32_fast(pFader->config.state[index].volumeBeg, pFader->config.state[index].volumeEnd, a);
7801 
7802                 for (iChannel = 0; iChannel < pFader->config.channels; iChannel += 1) {
7803                     pFramesOutF32[iFrame*pFader->config.channels + iChannel] = pFramesInF32[iFrame*pFader->config.channels + iChannel] * volumeCur;
7804                 }
7805             }
7806         } else {
7807             return MA_NOT_IMPLEMENTED;
7808         }
7809     }
7810 
7811     if (pFader->config.state[index].autoReset && ma_dual_fader_is_time_past_fade(pFader, index)) {
7812         ma_dual_fader_reset_fade(pFader, index);
7813     }
7814 
7815     return MA_SUCCESS;
7816 }
7817 
ma_dual_fader_process_pcm_frames(ma_dual_fader * pFader,void * pFramesOut,const void * pFramesIn,ma_uint64 frameCount)7818 MA_API ma_result ma_dual_fader_process_pcm_frames(ma_dual_fader* pFader, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount)
7819 {
7820     if (pFader == NULL) {
7821         return MA_INVALID_ARGS;
7822     }
7823 
7824     /* The input and output buffers are allowed to both be NULL in which case we just want to advance time forward. */
7825     if (pFramesOut != NULL || pFramesIn != NULL) {
7826         /* For now all we're doing is processing one sub-fade after the other. The second one operates on the output buffer in-place. */
7827         ma_dual_fader_process_pcm_frames_by_index(pFader, pFramesOut, pFramesIn,  frameCount, 0);
7828         ma_dual_fader_process_pcm_frames_by_index(pFader, pFramesOut, pFramesOut, frameCount, 1);   /* <-- Intentionally using the output buffer for both input and output because the first one will have written to the output. */
7829     }
7830 
7831     /* Move time forward. */
7832     pFader->timeInFramesCur += frameCount;
7833 
7834     return MA_SUCCESS;
7835 }
7836 
ma_dual_fader_get_data_format(const ma_dual_fader * pFader,ma_format * pFormat,ma_uint32 * pChannels,ma_uint32 * pSampleRate)7837 MA_API ma_result ma_dual_fader_get_data_format(const ma_dual_fader* pFader, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate)
7838 {
7839     if (pFader == NULL) {
7840         return MA_INVALID_ARGS;
7841     }
7842 
7843     if (pFormat != NULL) {
7844         *pFormat = pFader->config.format;
7845     }
7846 
7847     if (pChannels != NULL) {
7848         *pChannels = pFader->config.channels;
7849     }
7850 
7851     if (pSampleRate != NULL) {
7852         *pSampleRate = pFader->config.sampleRate;
7853     }
7854 
7855     return MA_SUCCESS;
7856 }
7857 
ma_dual_fader_set_fade(ma_dual_fader * pFader,ma_uint32 index,float volumeBeg,float volumeEnd,ma_uint64 timeInFramesBeg,ma_uint64 timeInFramesEnd)7858 MA_API ma_result ma_dual_fader_set_fade(ma_dual_fader* pFader, ma_uint32 index, float volumeBeg, float volumeEnd, ma_uint64 timeInFramesBeg, ma_uint64 timeInFramesEnd)
7859 {
7860     if (pFader == NULL) {
7861         return MA_INVALID_ARGS;
7862     }
7863 
7864     pFader->config.state[index].volumeBeg       = volumeBeg;
7865     pFader->config.state[index].volumeEnd       = volumeEnd;
7866     pFader->config.state[index].timeInFramesBeg = timeInFramesBeg;
7867     pFader->config.state[index].timeInFramesEnd = timeInFramesEnd;
7868 
7869     return MA_SUCCESS;
7870 }
7871 
ma_dual_fader_set_time(ma_dual_fader * pFader,ma_uint64 currentTimeInFrames)7872 MA_API ma_result ma_dual_fader_set_time(ma_dual_fader* pFader, ma_uint64 currentTimeInFrames)
7873 {
7874     if (pFader == NULL) {
7875         return MA_INVALID_ARGS;
7876     }
7877 
7878     if (pFader == NULL) {
7879         return MA_INVALID_ARGS;
7880     }
7881 
7882     pFader->timeInFramesCur = currentTimeInFrames;
7883 
7884     return MA_SUCCESS;
7885 }
7886 
ma_dual_fader_get_time(const ma_dual_fader * pFader,ma_uint64 * pCurrentTimeInFrames)7887 MA_API ma_result ma_dual_fader_get_time(const ma_dual_fader* pFader, ma_uint64* pCurrentTimeInFrames)
7888 {
7889     if (pCurrentTimeInFrames == NULL) {
7890         return MA_INVALID_ARGS;
7891     }
7892 
7893     *pCurrentTimeInFrames = 0;
7894 
7895     if (pFader == NULL) {
7896         return MA_INVALID_ARGS;
7897     }
7898 
7899     *pCurrentTimeInFrames = pFader->timeInFramesCur;
7900 
7901     return MA_SUCCESS;
7902 }
7903 
ma_dual_fader_is_time_past_fade(const ma_dual_fader * pFader,ma_uint32 index)7904 MA_API ma_bool32 ma_dual_fader_is_time_past_fade(const ma_dual_fader* pFader, ma_uint32 index)
7905 {
7906     if (pFader == NULL) {
7907         return MA_FALSE;
7908     }
7909 
7910     return pFader->timeInFramesCur >= pFader->config.state[index].timeInFramesEnd;
7911 }
7912 
ma_dual_fader_is_time_past_both_fades(const ma_dual_fader * pFader)7913 MA_API ma_bool32 ma_dual_fader_is_time_past_both_fades(const ma_dual_fader* pFader)
7914 {
7915     return ma_dual_fader_is_time_past_fade(pFader, 0) && ma_dual_fader_is_time_past_fade(pFader, 1);
7916 }
7917 
ma_dual_fader_is_in_fade(const ma_dual_fader * pFader,ma_uint32 index)7918 MA_API ma_bool32 ma_dual_fader_is_in_fade(const ma_dual_fader* pFader, ma_uint32 index)
7919 {
7920     if (pFader == NULL) {
7921         return MA_FALSE;
7922     }
7923 
7924     /* We're never fading if there's no time between the begin and the end. */
7925     if (pFader->config.state[index].volumeBeg == pFader->config.state[index].volumeEnd && pFader->config.state[index].timeInFramesBeg == pFader->config.state[index].timeInFramesEnd) {
7926         return MA_FALSE;
7927     }
7928 
7929     /* Getting here means a fade is happening. */
7930     if (index == 0) {
7931         return pFader->timeInFramesCur <= pFader->config.state[index].timeInFramesEnd;
7932     } else {
7933         return pFader->timeInFramesCur >= pFader->config.state[index].timeInFramesBeg;
7934     }
7935 }
7936 
ma_dual_fader_reset_fade(ma_dual_fader * pFader,ma_uint32 index)7937 MA_API ma_result ma_dual_fader_reset_fade(ma_dual_fader* pFader, ma_uint32 index)
7938 {
7939     if (pFader == NULL) {
7940         return MA_INVALID_ARGS;
7941     }
7942 
7943     /* Just reset back to defaults. */
7944     pFader->config.state[index].volumeBeg = 1;
7945     pFader->config.state[index].volumeEnd = 1;
7946     pFader->config.state[index].timeInFramesBeg = 0;
7947     pFader->config.state[index].timeInFramesEnd = 0;
7948 
7949     return MA_SUCCESS;
7950 }
7951 
ma_dual_fader_set_auto_reset(ma_dual_fader * pFader,ma_uint32 index,ma_bool32 autoReset)7952 MA_API ma_result ma_dual_fader_set_auto_reset(ma_dual_fader* pFader, ma_uint32 index, ma_bool32 autoReset)
7953 {
7954     if (pFader == NULL) {
7955         return MA_INVALID_ARGS;
7956     }
7957 
7958     pFader->config.state[index].autoReset = autoReset;
7959 
7960     return MA_SUCCESS;
7961 }
7962 
7963 
7964 
7965 
7966 /**************************************************************************************************************************************************************
7967 
7968 Engine
7969 
7970 **************************************************************************************************************************************************************/
7971 #define MA_SEEK_TARGET_NONE (~(ma_uint64)0)
7972 
ma_engine_effect__update_resampler_for_pitching(ma_engine_effect * pEngineEffect)7973 static void ma_engine_effect__update_resampler_for_pitching(ma_engine_effect* pEngineEffect)
7974 {
7975     MA_ASSERT(pEngineEffect != NULL);
7976 
7977     if (pEngineEffect->oldPitch != pEngineEffect->pitch) {
7978         pEngineEffect->oldPitch  = pEngineEffect->pitch;
7979         ma_data_converter_set_rate_ratio(&pEngineEffect->converter, pEngineEffect->pitch);
7980     }
7981 }
7982 
ma_engine_effect__on_process_pcm_frames__no_pre_effect_no_pitch(ma_engine_effect * pEngineEffect,const void * pFramesIn,ma_uint64 * pFrameCountIn,void * pFramesOut,ma_uint64 * pFrameCountOut)7983 static ma_result ma_engine_effect__on_process_pcm_frames__no_pre_effect_no_pitch(ma_engine_effect* pEngineEffect, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut)
7984 {
7985     ma_uint64 frameCount;
7986     ma_effect* pSubEffect[32];  /* The list of effects to be executed. Increase the size of this buffer if the number of sub-effects is exceeded. */
7987     ma_uint32 subEffectCount = 0;
7988 
7989     /*
7990     This will be called if either there is no pre-effect nor pitch shift, or the pre-effect and pitch shift have already been processed. In this case it's allowed for
7991     pFramesIn to be equal to pFramesOut as from here on we support in-place processing. Also, the input and output frame counts should always be equal.
7992     */
7993     frameCount = ma_min(*pFrameCountIn, *pFrameCountOut);
7994 
7995     /*
7996     This is a little inefficient, but it simplifies maintenance of this function a lot as we add new sub-effects. We are going to build a list of effects
7997     and then just run a loop to execute them. Some sub-effects must always be executed for state-updating reasons, but others can be skipped entirely.
7998     */
7999 
8000     /* Panning. This is a no-op when the engine has only 1 channel or the pan is 0. */
8001     if (pEngineEffect->pEngine->channels == 1 || pEngineEffect->panner.pan == 0) {
8002         /* Fast path. No panning. */
8003     } else {
8004         /* Slow path. Panning required. */
8005         pSubEffect[subEffectCount++] = &pEngineEffect->panner;
8006     }
8007 
8008     /* Spatialization. */
8009     if (pEngineEffect->isSpatial == MA_FALSE) {
8010         /* Fast path. No spatialization. */
8011     } else {
8012         /* Slow path. Spatialization required. */
8013         pSubEffect[subEffectCount++] = &pEngineEffect->spatializer;
8014     }
8015 
8016     /* Fader. Always required because timing information must always be updated. */
8017     pSubEffect[subEffectCount++] = &pEngineEffect->fader;
8018 
8019 
8020     /* We've built our list of effects, now we just need to execute them. */
8021     if (subEffectCount == 0) {
8022         /* Fast path. No sub-effects. */
8023         if (pFramesIn == pFramesOut) {
8024             /* Fast path. No-op. */
8025         } else {
8026             /* Slow path. Copy. */
8027             ma_copy_pcm_frames(pFramesOut, pFramesIn, frameCount, pEngineEffect->pEngine->format, pEngineEffect->pEngine->channels);
8028         }
8029     } else {
8030         /* Slow path. We have sub-effects to execute. The first effect reads from pFramesIn and then outputs to pFramesOut. The remaining read and write to pFramesOut in-place. */
8031         ma_uint32 iSubEffect = 0;
8032         for (iSubEffect = 0; iSubEffect < subEffectCount; iSubEffect += 1) {
8033             ma_uint64 frameCountIn  = frameCount;
8034             ma_uint64 frameCountOut = frameCount;
8035 
8036             ma_effect_process_pcm_frames(pSubEffect[iSubEffect], pFramesIn, &frameCountIn, pFramesOut, &frameCountOut);
8037 
8038             /* The first effect will have written to the output buffer which means we can now operate on the output buffer in-place. */
8039             if (iSubEffect == 0) {
8040                 pFramesIn = pFramesOut;
8041             }
8042         }
8043     }
8044 
8045 
8046     /* We're done. */
8047     *pFrameCountIn  = frameCount;
8048     *pFrameCountOut = frameCount;
8049 
8050     return MA_SUCCESS;
8051 }
8052 
ma_engine_effect__on_process_pcm_frames__no_pre_effect(ma_engine_effect * pEngineEffect,const void * pFramesIn,ma_uint64 * pFrameCountIn,void * pFramesOut,ma_uint64 * pFrameCountOut)8053 static ma_result ma_engine_effect__on_process_pcm_frames__no_pre_effect(ma_engine_effect* pEngineEffect, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut)
8054 {
8055     ma_bool32 isPitchingRequired = MA_FALSE;
8056 
8057     if (pEngineEffect->converter.hasResampler && pEngineEffect->pitch != 1) {
8058         isPitchingRequired = MA_TRUE;
8059     }
8060 
8061     /*
8062     This will be called if either there is no pre-effect or the pre-effect has already been processed. We can safely assume the input and output data in the engine's format so no
8063     data conversion should be necessary here.
8064     */
8065 
8066     /* Fast path for when no pitching is required. */
8067     if (isPitchingRequired == MA_FALSE) {
8068         /* Fast path. No pitch shifting. */
8069         return ma_engine_effect__on_process_pcm_frames__no_pre_effect_no_pitch(pEngineEffect, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut);
8070     } else {
8071         /* Slow path. Pitch shifting required. We need to run everything through our data converter first. */
8072 
8073         /*
8074         We can output straight into the output buffer. The remaining effects support in-place processing so when we process those we'll just pass in the output buffer
8075         as the input buffer as well and the effect will operate on the buffer in-place.
8076         */
8077         ma_result result;
8078         ma_uint64 frameCountIn;
8079         ma_uint64 frameCountOut;
8080 
8081         result = ma_data_converter_process_pcm_frames(&pEngineEffect->converter, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut);
8082         if (result != MA_SUCCESS) {
8083             return result;
8084         }
8085 
8086         /* Here is where we want to apply the remaining effects. These can be processed in-place which means we want to set the input and output buffers to be the same. */
8087         frameCountIn  = *pFrameCountOut;  /* Not a mistake. Intentionally set to *pFrameCountOut. */
8088         frameCountOut = *pFrameCountOut;
8089         return ma_engine_effect__on_process_pcm_frames__no_pre_effect_no_pitch(pEngineEffect, pFramesOut, &frameCountIn, pFramesOut, &frameCountOut);  /* Intentionally setting the input buffer to pFramesOut for in-place processing. */
8090     }
8091 }
8092 
ma_engine_effect__on_process_pcm_frames__general(ma_engine_effect * pEngineEffect,const void * pFramesIn,ma_uint64 * pFrameCountIn,void * pFramesOut,ma_uint64 * pFrameCountOut)8093 static ma_result ma_engine_effect__on_process_pcm_frames__general(ma_engine_effect* pEngineEffect, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut)
8094 {
8095     ma_result result;
8096     ma_uint64 frameCountIn  = *pFrameCountIn;
8097     ma_uint64 frameCountOut = *pFrameCountOut;
8098     ma_uint64 totalFramesProcessedIn  = 0;
8099     ma_uint64 totalFramesProcessedOut = 0;
8100     ma_format effectFormat;
8101     ma_uint32 effectChannels;
8102 
8103     MA_ASSERT(pEngineEffect  != NULL);
8104     MA_ASSERT(pEngineEffect->pPreEffect != NULL);
8105     MA_ASSERT(pFramesIn      != NULL);
8106     MA_ASSERT(pFrameCountIn  != NULL);
8107     MA_ASSERT(pFramesOut     != NULL);
8108     MA_ASSERT(pFrameCountOut != NULL);
8109 
8110     /* The effect's input and output format will be the engine's format. If the pre-effect is of a different format it will need to be converted appropriately. */
8111     effectFormat   = pEngineEffect->pEngine->format;
8112     effectChannels = pEngineEffect->pEngine->channels;
8113 
8114     /*
8115     Getting here means we have a pre-effect. This must alway be run first. We do this in chunks into an intermediary buffer and then call ma_engine_effect__on_process_pcm_frames__no_pre_effect()
8116     against the intermediary buffer. The output of ma_engine_effect__on_process_pcm_frames__no_pre_effect() will be the final output buffer.
8117     */
8118     while (totalFramesProcessedIn < frameCountIn && totalFramesProcessedOut < frameCountOut) {
8119         ma_uint8  preEffectOutBuffer[MA_DATA_CONVERTER_STACK_BUFFER_SIZE];  /* effectFormat / effectChannels */
8120         ma_uint32 preEffectOutBufferCap = sizeof(preEffectOutBuffer) / ma_get_bytes_per_frame(effectFormat, effectChannels);
8121         const void* pRunningFramesIn  = ma_offset_ptr(pFramesIn,  totalFramesProcessedIn  * ma_get_bytes_per_frame(effectFormat, effectChannels));
8122         /* */ void* pRunningFramesOut = ma_offset_ptr(pFramesOut, totalFramesProcessedOut * ma_get_bytes_per_frame(effectFormat, effectChannels));
8123         ma_uint64 frameCountInThisIteration;
8124         ma_uint64 frameCountOutThisIteration;
8125 
8126         frameCountOutThisIteration = frameCountOut - totalFramesProcessedOut;
8127         if (frameCountOutThisIteration > preEffectOutBufferCap) {
8128             frameCountOutThisIteration = preEffectOutBufferCap;
8129         }
8130 
8131         /* We need to ensure we don't read too many input frames that we won't be able to process them all in the next step. */
8132         frameCountInThisIteration = ma_data_converter_get_required_input_frame_count(&pEngineEffect->converter, frameCountOutThisIteration);
8133         if (frameCountInThisIteration > (frameCountIn - totalFramesProcessedIn)) {
8134             frameCountInThisIteration = (frameCountIn - totalFramesProcessedIn);
8135         }
8136 
8137         result = ma_effect_process_pcm_frames_ex(pEngineEffect->pPreEffect, pRunningFramesIn, &frameCountInThisIteration, preEffectOutBuffer, &frameCountOutThisIteration, effectFormat, effectChannels, effectFormat, effectChannels);
8138         if (result != MA_SUCCESS) {
8139             break;
8140         }
8141 
8142         totalFramesProcessedIn += frameCountInThisIteration;
8143 
8144         /* At this point we have run the pre-effect and we can now run it through the main engine effect. */
8145         frameCountOutThisIteration = frameCountOut - totalFramesProcessedOut;   /* Process as many frames as will fit in the output buffer. */
8146         result = ma_engine_effect__on_process_pcm_frames__no_pre_effect(pEngineEffect, preEffectOutBuffer, &frameCountInThisIteration, pRunningFramesOut, &frameCountOutThisIteration);
8147         if (result != MA_SUCCESS) {
8148             break;
8149         }
8150 
8151         totalFramesProcessedOut += frameCountOutThisIteration;
8152     }
8153 
8154 
8155     *pFrameCountIn  = totalFramesProcessedIn;
8156     *pFrameCountOut = totalFramesProcessedOut;
8157 
8158     return MA_SUCCESS;
8159 }
8160 
ma_engine_effect__on_process_pcm_frames(ma_effect * pEffect,const void * pFramesIn,ma_uint64 * pFrameCountIn,void * pFramesOut,ma_uint64 * pFrameCountOut)8161 static ma_result ma_engine_effect__on_process_pcm_frames(ma_effect* pEffect, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut)
8162 {
8163     ma_engine_effect* pEngineEffect = (ma_engine_effect*)pEffect;
8164     ma_result result;
8165 
8166     MA_ASSERT(pEffect != NULL);
8167 
8168     /* Make sure we update the resampler to take any pitch changes into account. Not doing this will result in incorrect frame counts being returned. */
8169     ma_engine_effect__update_resampler_for_pitching(pEngineEffect);
8170 
8171     /* Optimized path for when there is no pre-effect. */
8172     if (pEngineEffect->pPreEffect == NULL) {
8173         result = ma_engine_effect__on_process_pcm_frames__no_pre_effect(pEngineEffect, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut);
8174     } else {
8175         result = ma_engine_effect__on_process_pcm_frames__general(pEngineEffect, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut);
8176     }
8177 
8178     pEngineEffect->timeInFrames += *pFrameCountIn;
8179 
8180     return result;
8181 }
8182 
ma_engine_effect__on_get_required_input_frame_count(ma_effect * pEffect,ma_uint64 outputFrameCount)8183 static ma_uint64 ma_engine_effect__on_get_required_input_frame_count(ma_effect* pEffect, ma_uint64 outputFrameCount)
8184 {
8185     ma_engine_effect* pEngineEffect = (ma_engine_effect*)pEffect;
8186     ma_uint64 inputFrameCount;
8187 
8188     MA_ASSERT(pEffect != NULL);
8189 
8190     /* Make sure we update the resampler to take any pitch changes into account. Not doing this will result in incorrect frame counts being returned. */
8191     ma_engine_effect__update_resampler_for_pitching(pEngineEffect);
8192 
8193     inputFrameCount = ma_data_converter_get_required_input_frame_count(&pEngineEffect->converter, outputFrameCount);
8194 
8195     if (pEngineEffect->pPreEffect != NULL) {
8196         ma_uint64 preEffectInputFrameCount = ma_effect_get_required_input_frame_count(pEngineEffect->pPreEffect, outputFrameCount);
8197         if (inputFrameCount < preEffectInputFrameCount) {
8198             inputFrameCount = preEffectInputFrameCount;
8199         }
8200     }
8201 
8202     return inputFrameCount;
8203 }
8204 
ma_engine_effect__on_get_expected_output_frame_count(ma_effect * pEffect,ma_uint64 inputFrameCount)8205 static ma_uint64 ma_engine_effect__on_get_expected_output_frame_count(ma_effect* pEffect, ma_uint64 inputFrameCount)
8206 {
8207     ma_engine_effect* pEngineEffect = (ma_engine_effect*)pEffect;
8208     ma_uint64 outputFrameCount;
8209 
8210     MA_ASSERT(pEffect != NULL);
8211 
8212     /* Make sure we update the resampler to take any pitch changes into account. Not doing this will result in incorrect frame counts being returned. */
8213     ma_engine_effect__update_resampler_for_pitching(pEngineEffect);
8214 
8215     outputFrameCount = ma_data_converter_get_expected_output_frame_count(&pEngineEffect->converter, inputFrameCount);
8216 
8217     if (pEngineEffect->pPreEffect != NULL) {
8218         ma_uint64 preEffectOutputFrameCount = ma_effect_get_expected_output_frame_count(pEngineEffect->pPreEffect, inputFrameCount);
8219         if (outputFrameCount > preEffectOutputFrameCount) {
8220             outputFrameCount = preEffectOutputFrameCount;
8221         }
8222     }
8223 
8224     return outputFrameCount;
8225 }
8226 
ma_engine_effect__on_get_input_data_format(ma_effect * pEffect,ma_format * pFormat,ma_uint32 * pChannels,ma_uint32 * pSampleRate)8227 static ma_result ma_engine_effect__on_get_input_data_format(ma_effect* pEffect, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate)
8228 {
8229     ma_engine_effect* pEngineEffect = (ma_engine_effect*)pEffect;
8230 
8231     MA_ASSERT(pEffect != NULL);
8232 
8233     if (pEngineEffect->pPreEffect != NULL) {
8234         return ma_effect_get_input_data_format(pEngineEffect->pPreEffect, pFormat, pChannels, pSampleRate);
8235     } else {
8236         *pFormat     = pEngineEffect->converter.config.formatIn;
8237         *pChannels   = pEngineEffect->converter.config.channelsIn;
8238         *pSampleRate = pEngineEffect->converter.config.sampleRateIn;
8239 
8240         return MA_SUCCESS;
8241     }
8242 }
8243 
ma_engine_effect__on_get_output_data_format(ma_effect * pEffect,ma_format * pFormat,ma_uint32 * pChannels,ma_uint32 * pSampleRate)8244 static ma_result ma_engine_effect__on_get_output_data_format(ma_effect* pEffect, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate)
8245 {
8246     ma_engine_effect* pEngineEffect = (ma_engine_effect*)pEffect;
8247 
8248     MA_ASSERT(pEffect != NULL);
8249 
8250     *pFormat     = pEngineEffect->converter.config.formatOut;
8251     *pChannels   = pEngineEffect->converter.config.channelsOut;
8252     *pSampleRate = pEngineEffect->converter.config.sampleRateOut;
8253 
8254     return MA_SUCCESS;
8255 }
8256 
ma_engine_effect_init(ma_engine * pEngine,ma_engine_effect * pEffect)8257 static ma_result ma_engine_effect_init(ma_engine* pEngine, ma_engine_effect* pEffect)
8258 {
8259     ma_result result;
8260     ma_panner_config pannerConfig;
8261     ma_spatializer_config spatializerConfig;
8262     ma_dual_fader_config faderConfig;
8263     ma_data_converter_config converterConfig;
8264 
8265     MA_ASSERT(pEngine != NULL);
8266     MA_ASSERT(pEffect != NULL);
8267 
8268     MA_ZERO_OBJECT(pEffect);
8269 
8270     pEffect->baseEffect.onProcessPCMFrames            = ma_engine_effect__on_process_pcm_frames;
8271     pEffect->baseEffect.onGetRequiredInputFrameCount  = ma_engine_effect__on_get_required_input_frame_count;
8272     pEffect->baseEffect.onGetExpectedOutputFrameCount = ma_engine_effect__on_get_expected_output_frame_count;
8273     pEffect->baseEffect.onGetInputDataFormat          = ma_engine_effect__on_get_input_data_format;
8274     pEffect->baseEffect.onGetOutputDataFormat         = ma_engine_effect__on_get_output_data_format;
8275 
8276     pEffect->pEngine    = pEngine;
8277     pEffect->pPreEffect = NULL;
8278     pEffect->pitch      = 1;
8279     pEffect->oldPitch   = 1;
8280 
8281     pannerConfig = ma_panner_config_init(pEngine->format, pEngine->channels);
8282     result = ma_panner_init(&pannerConfig, &pEffect->panner);
8283     if (result != MA_SUCCESS) {
8284         return result;  /* Failed to create the panner. */
8285     }
8286 
8287     spatializerConfig = ma_spatializer_config_init(pEngine, pEngine->format, pEngine->channels);
8288     result = ma_spatializer_init(&spatializerConfig, &pEffect->spatializer);
8289     if (result != MA_SUCCESS) {
8290         return result;  /* Failed to create the spatializer. */
8291     }
8292 
8293     faderConfig = ma_dual_fader_config_init(pEngine->format, pEngine->channels, pEngine->sampleRate);
8294     result = ma_dual_fader_init(&faderConfig, &pEffect->fader);
8295     if (result != MA_SUCCESS) {
8296         return result;  /* Failed to create the fader. */
8297     }
8298 
8299 
8300     /* Our effect processor requires f32 for now, but I may implement an s16 optimized pipeline. */
8301 
8302 
8303     converterConfig = ma_data_converter_config_init(pEngine->format, pEngine->format, pEngine->channels, pEngine->channels, pEngine->sampleRate, pEngine->sampleRate);
8304 
8305     /*
8306     TODO: A few things to figure out with the resampler:
8307         - In order to support dynamic pitch shifting we need to set allowDynamicSampleRate which means the resampler will always be initialized and will always
8308           have samples run through it. An optimization would be to have a flag that disables pitch shifting. Can alternatively just skip running samples through
8309           the data converter when pitch=1, but this may result in glitching when moving away from pitch=1 due to the internal buffer not being update while the
8310           pitch=1 case was in place.
8311         - We may want to have customization over resampling properties.
8312     */
8313     converterConfig.resampling.allowDynamicSampleRate = MA_TRUE;    /* This makes sure a resampler is always initialized. TODO: Need a flag that specifies that no pitch shifting is required for this sound so we can avoid the cost of the resampler. Even when the pitch is 1, samples still run through the resampler. */
8314     converterConfig.resampling.algorithm              = ma_resample_algorithm_linear;
8315     converterConfig.resampling.linear.lpfOrder        = 0;
8316 
8317     result = ma_data_converter_init(&converterConfig, &pEffect->converter);
8318     if (result != MA_SUCCESS) {
8319         return result;
8320     }
8321 
8322     return MA_SUCCESS;
8323 }
8324 
ma_engine_effect_uninit(ma_engine * pEngine,ma_engine_effect * pEffect)8325 static void ma_engine_effect_uninit(ma_engine* pEngine, ma_engine_effect* pEffect)
8326 {
8327     MA_ASSERT(pEngine != NULL);
8328     MA_ASSERT(pEffect != NULL);
8329 
8330     (void)pEngine;
8331     ma_data_converter_uninit(&pEffect->converter);
8332 }
8333 
ma_engine_effect_reinit(ma_engine * pEngine,ma_engine_effect * pEffect)8334 static ma_result ma_engine_effect_reinit(ma_engine* pEngine, ma_engine_effect* pEffect)
8335 {
8336     /* This function assumes the data converter was previously initialized and needs to be uninitialized. */
8337     MA_ASSERT(pEngine != NULL);
8338     MA_ASSERT(pEffect != NULL);
8339 
8340     ma_engine_effect_uninit(pEngine, pEffect);
8341 
8342     return ma_engine_effect_init(pEngine, pEffect);
8343 }
8344 
ma_engine_effect_is_passthrough(ma_engine_effect * pEffect)8345 static ma_bool32 ma_engine_effect_is_passthrough(ma_engine_effect* pEffect)
8346 {
8347     MA_ASSERT(pEffect != NULL);
8348 
8349     /* A pre-effect will require processing. */
8350     if (pEffect->pPreEffect != NULL) {
8351         return MA_FALSE;
8352     }
8353 
8354     /* If pitch shifting we'll need to do processing through the resampler. */
8355     if (pEffect->pitch != 1) {
8356         return MA_FALSE;
8357     }
8358 
8359     /* If we're fading we need to make sure we do processing. */
8360     if (ma_dual_fader_is_time_past_both_fades(&pEffect->fader) == MA_FALSE) {
8361         return MA_FALSE;
8362     }
8363 
8364     return MA_TRUE;
8365 }
8366 
ma_engine_effect_set_time(ma_engine_effect * pEffect,ma_uint64 timeInFrames)8367 static ma_result ma_engine_effect_set_time(ma_engine_effect* pEffect, ma_uint64 timeInFrames)
8368 {
8369     MA_ASSERT(pEffect != NULL);
8370 
8371     pEffect->timeInFrames = timeInFrames;
8372     ma_dual_fader_set_time(&pEffect->fader, timeInFrames);
8373 
8374     return MA_SUCCESS;
8375 }
8376 
8377 
8378 static MA_INLINE ma_result ma_sound_stop_internal(ma_sound* pSound);
8379 static MA_INLINE ma_result ma_sound_group_stop_internal(ma_sound_group* pGroup);
8380 
ma_engine_config_init_default(void)8381 MA_API ma_engine_config ma_engine_config_init_default(void)
8382 {
8383     ma_engine_config config;
8384     MA_ZERO_OBJECT(&config);
8385 
8386     config.format = ma_format_f32;
8387 
8388     return config;
8389 }
8390 
8391 
ma_sound_mix_wait(ma_sound * pSound)8392 static void ma_sound_mix_wait(ma_sound* pSound)
8393 {
8394     /* This function is only safe when the sound is not flagged as playing. */
8395     MA_ASSERT(pSound->isPlaying == MA_FALSE);
8396 
8397     /* Just do a basic spin wait. */
8398     while (pSound->isMixing) {
8399         ma_yield();
8400     }
8401 }
8402 
ma_engine_mix_sound_internal(ma_engine * pEngine,ma_sound_group * pGroup,ma_sound * pSound,ma_uint64 frameCount)8403 static void ma_engine_mix_sound_internal(ma_engine* pEngine, ma_sound_group* pGroup, ma_sound* pSound, ma_uint64 frameCount)
8404 {
8405     ma_result result = MA_SUCCESS;
8406     ma_uint64 framesProcessed;
8407 
8408     /* Don't do anything if we're not playing. */
8409     if (pSound->isPlaying == MA_FALSE) {
8410         return;
8411     }
8412 
8413     /* If we're marked at the end we need to stop the sound and do nothing. */
8414     if (pSound->atEnd) {
8415         ma_sound_stop_internal(pSound);
8416         return;
8417     }
8418 
8419     /* If we're seeking, do so now before reading. */
8420     if (pSound->seekTarget != MA_SEEK_TARGET_NONE) {
8421         pSound->seekTarget  = MA_SEEK_TARGET_NONE;
8422         ma_data_source_seek_to_pcm_frame(pSound->pDataSource, pSound->seekTarget);
8423 
8424         /* Any time-dependant effects need to have their times updated. */
8425         ma_engine_effect_set_time(&pSound->effect, pSound->seekTarget);
8426     }
8427 
8428     /* If the sound is being delayed we don't want to mix anything, nor do we want to advance time forward from the perspective of the data source. */
8429     if ((pSound->runningTimeInEngineFrames + frameCount) > pSound->startDelayInEngineFrames) {
8430         /* We're not delayed so we can mix or seek. In order to get frame-exact playback timing we need to start mixing from an offset. */
8431         ma_uint64 currentTimeInFrames;
8432         ma_uint64 offsetInFrames;
8433 
8434         offsetInFrames = 0;
8435         if (pSound->startDelayInEngineFrames > pSound->runningTimeInEngineFrames) {
8436             offsetInFrames = pSound->startDelayInEngineFrames - pSound->runningTimeInEngineFrames;
8437         }
8438 
8439         MA_ASSERT(offsetInFrames < frameCount);
8440 
8441         /*
8442         An obvious optimization is to skip mixing if the sound is not audible. The problem with this, however, is that the effect may need to update some
8443         internal state such as timing information for things like fades, delays, echos, etc. We're going to always mix the sound if it's active and trust
8444         the mixer to optimize the volume = 0 case, and let the effect do it's own internal optimizations in non-audible cases.
8445         */
8446         result = ma_mixer_mix_data_source(&pGroup->mixer, pSound->pDataSource, offsetInFrames, (frameCount - offsetInFrames), &framesProcessed, pSound->volume, &pSound->effect, pSound->isLooping);
8447 
8448         /* If we reached the end of the sound we'll want to mark it as at the end and stop it. This should never be returned for looping sounds. */
8449         if (result == MA_AT_END) {
8450             c89atomic_exchange_32(&pSound->atEnd, MA_TRUE); /* This will be set to false in ma_sound_start(). */
8451         }
8452 
8453         /*
8454         For the benefit of the main effect we need to ensure the local time is updated explicitly. This is required for allowing time-based effects to
8455         support loop transitions properly.
8456         */
8457         result = ma_sound_get_cursor_in_pcm_frames(pSound, &currentTimeInFrames);
8458         if (result == MA_SUCCESS) {
8459             ma_engine_effect_set_time(&pSound->effect, currentTimeInFrames);
8460         }
8461 
8462         pSound->runningTimeInEngineFrames += offsetInFrames + framesProcessed;
8463     } else {
8464         /* The sound hasn't started yet. Just keep advancing time forward, but leave the data source alone. */
8465         pSound->runningTimeInEngineFrames += frameCount;
8466     }
8467 
8468     /* If we're stopping after a delay we need to check if the delay has expired and if so, stop for real. */
8469     if (pSound->stopDelayInEngineFramesRemaining > 0) {
8470         if (pSound->stopDelayInEngineFramesRemaining >= frameCount) {
8471             pSound->stopDelayInEngineFramesRemaining -= frameCount;
8472         } else {
8473             pSound->stopDelayInEngineFramesRemaining = 0;
8474         }
8475 
8476         /* Stop the sound if the delay has been reached. */
8477         if (pSound->stopDelayInEngineFramesRemaining == 0) {
8478             ma_sound_stop_internal(pSound);
8479         }
8480     }
8481 }
8482 
ma_engine_mix_sound(ma_engine * pEngine,ma_sound_group * pGroup,ma_sound * pSound,ma_uint64 frameCount)8483 static void ma_engine_mix_sound(ma_engine* pEngine, ma_sound_group* pGroup, ma_sound* pSound, ma_uint64 frameCount)
8484 {
8485     MA_ASSERT(pEngine != NULL);
8486     MA_ASSERT(pGroup  != NULL);
8487     MA_ASSERT(pSound  != NULL);
8488 
8489     c89atomic_exchange_32(&pSound->isMixing, MA_TRUE);  /* This must be done before checking the isPlaying state. */
8490     {
8491         ma_engine_mix_sound_internal(pEngine, pGroup, pSound, frameCount);
8492     }
8493     c89atomic_exchange_32(&pSound->isMixing, MA_FALSE);
8494 }
8495 
ma_engine_mix_sound_group(ma_engine * pEngine,ma_sound_group * pGroup,void * pFramesOut,ma_uint64 frameCount)8496 static void ma_engine_mix_sound_group(ma_engine* pEngine, ma_sound_group* pGroup, void* pFramesOut, ma_uint64 frameCount)
8497 {
8498     ma_result result;
8499     ma_mixer* pParentMixer = NULL;
8500     ma_uint64 frameCountOut;
8501     ma_uint64 frameCountIn;
8502     ma_uint64 totalFramesProcessed;
8503     ma_sound_group* pNextChildGroup;
8504     ma_sound* pNextSound;
8505 
8506     MA_ASSERT(pEngine    != NULL);
8507     MA_ASSERT(pGroup     != NULL);
8508     MA_ASSERT(frameCount != 0);
8509 
8510     /* Don't do anything if we're not playing. */
8511     if (pGroup->isPlaying == MA_FALSE) {
8512         return;
8513     }
8514 
8515     if (pGroup->pParent != NULL) {
8516         pParentMixer = &pGroup->pParent->mixer;
8517     }
8518 
8519     frameCountOut = frameCount;
8520     frameCountIn  = frameCount;
8521 
8522     /* If the group is being delayed we don't want to mix anything. */
8523     if ((pGroup->runningTimeInEngineFrames + frameCount) > pGroup->startDelayInEngineFrames) {
8524         /* We're not delayed so we can mix or seek. In order to get frame-exact playback timing we need to start mixing from an offset. */
8525         ma_uint64 offsetInFrames = 0;
8526         if (pGroup->startDelayInEngineFrames > pGroup->runningTimeInEngineFrames) {
8527             offsetInFrames = pGroup->startDelayInEngineFrames - pGroup->runningTimeInEngineFrames;
8528         }
8529 
8530         MA_ASSERT(offsetInFrames < frameCount);
8531 
8532         /* We need to loop here to ensure we fill every frame. This won't necessarily be able to be done in one iteration due to resampling within the effect. */
8533         totalFramesProcessed = 0;
8534         while (totalFramesProcessed < (frameCount - offsetInFrames)) {
8535             frameCountOut = frameCount - offsetInFrames - totalFramesProcessed;
8536             frameCountIn  = frameCount - offsetInFrames - totalFramesProcessed;
8537 
8538             /* Before can mix the group we need to mix it's children. */
8539             result = ma_mixer_begin(&pGroup->mixer, pParentMixer, &frameCountOut, &frameCountIn);
8540             if (result != MA_SUCCESS) {
8541                 break;
8542             }
8543 
8544             /* Child groups need to be mixed based on the parent's input frame count. */
8545             for (pNextChildGroup = pGroup->pFirstChild; pNextChildGroup != NULL; pNextChildGroup = pNextChildGroup->pNextSibling) {
8546                 ma_engine_mix_sound_group(pEngine, pNextChildGroup, NULL, frameCountIn);
8547             }
8548 
8549             /* Sounds in the group can now be mixed. This is where the real mixing work is done. */
8550             for (pNextSound = pGroup->pFirstSoundInGroup; pNextSound != NULL; pNextSound = pNextSound->pNextSoundInGroup) {
8551                 ma_engine_mix_sound(pEngine, pGroup, pNextSound, frameCountIn);
8552             }
8553 
8554             /* Now mix into the parent. */
8555             result = ma_mixer_end(&pGroup->mixer, pParentMixer, pFramesOut, offsetInFrames + totalFramesProcessed);
8556             if (result != MA_SUCCESS) {
8557                 break;
8558             }
8559 
8560             totalFramesProcessed += frameCountOut;
8561         }
8562 
8563         pGroup->runningTimeInEngineFrames += offsetInFrames + totalFramesProcessed;
8564     } else {
8565         /* The group hasn't started yet. Just keep advancing time forward, but leave the data source alone. */
8566         pGroup->runningTimeInEngineFrames += frameCount;
8567     }
8568 
8569     /* If we're stopping after a delay we need to check if the delay has expired and if so, stop for real. */
8570     if (pGroup->stopDelayInEngineFramesRemaining > 0) {
8571         if (pGroup->stopDelayInEngineFramesRemaining >= frameCount) {
8572             pGroup->stopDelayInEngineFramesRemaining -= frameCount;
8573         } else {
8574             pGroup->stopDelayInEngineFramesRemaining = 0;
8575         }
8576 
8577         /* Stop the sound if the delay has been reached. */
8578         if (pGroup->stopDelayInEngineFramesRemaining == 0) {
8579             ma_sound_group_stop_internal(pGroup);
8580         }
8581     }
8582 }
8583 
ma_engine_listener__data_callback_fixed(ma_engine * pEngine,void * pFramesOut,ma_uint32 frameCount)8584 static void ma_engine_listener__data_callback_fixed(ma_engine* pEngine, void* pFramesOut, ma_uint32 frameCount)
8585 {
8586     MA_ASSERT(pEngine != NULL);
8587     MA_ASSERT(pEngine->periodSizeInFrames == frameCount);   /* This must always be true. */
8588 
8589     /* Recursively mix the sound groups. */
8590     ma_engine_mix_sound_group(pEngine, &pEngine->masterSoundGroup, pFramesOut, frameCount);
8591 }
8592 
ma_engine_data_callback_internal(ma_device * pDevice,void * pFramesOut,const void * pFramesIn,ma_uint32 frameCount)8593 static void ma_engine_data_callback_internal(ma_device* pDevice, void* pFramesOut, const void* pFramesIn, ma_uint32 frameCount)
8594 {
8595     ma_engine_data_callback((ma_engine*)pDevice->pUserData, pFramesOut, pFramesIn, frameCount);
8596 }
8597 
ma_engine_init(const ma_engine_config * pConfig,ma_engine * pEngine)8598 MA_API ma_result ma_engine_init(const ma_engine_config* pConfig, ma_engine* pEngine)
8599 {
8600     ma_result result;
8601     ma_engine_config engineConfig;
8602     ma_context_config contextConfig;
8603 
8604     /* The config is allowed to be NULL in which case we use defaults for everything. */
8605     if (pConfig != NULL) {
8606         engineConfig = *pConfig;
8607     } else {
8608         engineConfig = ma_engine_config_init_default();
8609     }
8610 
8611     /*
8612     For now we only support f32 but may add support for other formats later. To do this we need to add support for all formats to ma_panner and ma_spatializer (and any other future effects).
8613     */
8614     if (engineConfig.format != ma_format_f32) {
8615         return MA_INVALID_ARGS; /* Format not supported. */
8616     }
8617 
8618     pEngine->pResourceManager         = engineConfig.pResourceManager;
8619     pEngine->pDevice                  = engineConfig.pDevice;
8620     pEngine->format                   = engineConfig.format;
8621     pEngine->channels                 = engineConfig.channels;
8622     pEngine->sampleRate               = engineConfig.sampleRate;
8623     pEngine->periodSizeInFrames       = engineConfig.periodSizeInFrames;
8624     pEngine->periodSizeInMilliseconds = engineConfig.periodSizeInMilliseconds;
8625     ma_allocation_callbacks_init_copy(&pEngine->allocationCallbacks, &engineConfig.allocationCallbacks);
8626 
8627 
8628     /* We need a context before we'll be able to create the default listener. */
8629     contextConfig = ma_context_config_init();
8630     contextConfig.allocationCallbacks = pEngine->allocationCallbacks;
8631 
8632     /* If we don't have a device, we need one. */
8633     if (pEngine->pDevice == NULL) {
8634         ma_device_config deviceConfig;
8635 
8636         pEngine->pDevice = (ma_device*)ma__malloc_from_callbacks(sizeof(*pEngine->pDevice), &pEngine->allocationCallbacks/*, MA_ALLOCATION_TYPE_CONTEXT*/);
8637         if (pEngine->pDevice == NULL) {
8638             return MA_OUT_OF_MEMORY;
8639         }
8640 
8641         deviceConfig = ma_device_config_init(ma_device_type_playback);
8642         deviceConfig.playback.pDeviceID       = engineConfig.pPlaybackDeviceID;
8643         deviceConfig.playback.format          = pEngine->format;
8644         deviceConfig.playback.channels        = pEngine->channels;
8645         deviceConfig.sampleRate               = pEngine->sampleRate;
8646         deviceConfig.dataCallback             = ma_engine_data_callback_internal;
8647         deviceConfig.pUserData                = pEngine;
8648         deviceConfig.periodSizeInFrames       = pEngine->periodSizeInFrames;
8649         deviceConfig.periodSizeInMilliseconds = pEngine->periodSizeInMilliseconds;
8650         deviceConfig.noPreZeroedOutputBuffer  = MA_TRUE;    /* We'll always be outputting to every frame in the callback so there's no need for a pre-silenced buffer. */
8651         deviceConfig.noClip                   = MA_TRUE;    /* The mixing engine will do clipping itself. */
8652 
8653         result = ma_device_init(engineConfig.pContext, &deviceConfig, pEngine->pDevice);
8654         if (result != MA_SUCCESS) {
8655             ma__free_from_callbacks(pEngine->pDevice, &pEngine->allocationCallbacks/*, MA_ALLOCATION_TYPE_CONTEXT*/);
8656             pEngine->pDevice = NULL;
8657             return result;
8658         }
8659 
8660         pEngine->ownsDevice = MA_TRUE;
8661     }
8662 
8663     /* With the device initialized we need an intermediary buffer for handling fixed sized updates. Currently using a ring buffer for this, but can probably use something a bit more optimal. */
8664     result = ma_pcm_rb_init(pEngine->pDevice->playback.format, pEngine->pDevice->playback.channels, pEngine->pDevice->playback.internalPeriodSizeInFrames, NULL, &pEngine->allocationCallbacks, &pEngine->fixedRB);
8665     if (result != MA_SUCCESS) {
8666         goto on_error_1;
8667     }
8668 
8669     /* Now that have the default listener we can ensure we have the format, channels and sample rate set to proper values to ensure future listeners are configured consistently. */
8670     pEngine->format                   = pEngine->pDevice->playback.format;
8671     pEngine->channels                 = pEngine->pDevice->playback.channels;
8672     pEngine->sampleRate               = pEngine->pDevice->sampleRate;
8673     pEngine->periodSizeInFrames       = pEngine->pDevice->playback.internalPeriodSizeInFrames;
8674     pEngine->periodSizeInMilliseconds = (pEngine->periodSizeInFrames * pEngine->sampleRate) / 1000;
8675 
8676 
8677     /* We need a default sound group. This must be done after setting the format, channels and sample rate to their proper values. */
8678     result = ma_sound_group_init(pEngine, NULL, &pEngine->masterSoundGroup);
8679     if (result != MA_SUCCESS) {
8680         goto on_error_2;  /* Failed to initialize master sound group. */
8681     }
8682 
8683 
8684     /* We need a resource manager. */
8685 #ifndef MA_NO_RESOURCE_MANAGER
8686     if (pEngine->pResourceManager == NULL) {
8687         ma_resource_manager_config resourceManagerConfig;
8688 
8689         pEngine->pResourceManager = (ma_resource_manager*)ma__malloc_from_callbacks(sizeof(*pEngine->pResourceManager), &pEngine->allocationCallbacks);
8690         if (pEngine->pResourceManager == NULL) {
8691             result = MA_OUT_OF_MEMORY;
8692             goto on_error_3;
8693         }
8694 
8695         resourceManagerConfig = ma_resource_manager_config_init();
8696         resourceManagerConfig.decodedFormat     = pEngine->format;
8697         resourceManagerConfig.decodedChannels   = 0;  /* Leave the decoded channel count as 0 so we can get good spatialization. */
8698         resourceManagerConfig.decodedSampleRate = pEngine->sampleRate;
8699         ma_allocation_callbacks_init_copy(&resourceManagerConfig.allocationCallbacks, &pEngine->allocationCallbacks);
8700         resourceManagerConfig.pVFS              = engineConfig.pResourceManagerVFS;
8701 
8702         result = ma_resource_manager_init(&resourceManagerConfig, pEngine->pResourceManager);
8703         if (result != MA_SUCCESS) {
8704             goto on_error_4;
8705         }
8706 
8707         pEngine->ownsResourceManager = MA_TRUE;
8708     }
8709 #endif
8710 
8711     /* Start the engine if required. This should always be the last step. */
8712     if (engineConfig.noAutoStart == MA_FALSE) {
8713         result = ma_engine_start(pEngine);
8714         if (result != MA_SUCCESS) {
8715             ma_engine_uninit(pEngine);
8716             return result;  /* Failed to start the engine. */
8717         }
8718     }
8719 
8720     return MA_SUCCESS;
8721 
8722 #ifndef MA_NO_RESOURCE_MANAGER
8723 on_error_4:
8724     if (pEngine->ownsResourceManager) {
8725         ma__free_from_callbacks(pEngine->pResourceManager, &pEngine->allocationCallbacks);
8726     }
8727 on_error_3:
8728     ma_sound_group_uninit(&pEngine->masterSoundGroup);
8729 #endif  /* MA_NO_RESOURCE_MANAGER */
8730 on_error_2:
8731     ma_pcm_rb_uninit(&pEngine->fixedRB);
8732 on_error_1:
8733     if (pEngine->ownsDevice) {
8734         ma_device_uninit(pEngine->pDevice);
8735         ma__free_from_callbacks(pEngine->pDevice, &pEngine->allocationCallbacks/*, MA_ALLOCATION_TYPE_CONTEXT*/);
8736     }
8737 
8738     return result;
8739 }
8740 
ma_engine_uninit(ma_engine * pEngine)8741 MA_API void ma_engine_uninit(ma_engine* pEngine)
8742 {
8743     if (pEngine == NULL) {
8744         return;
8745     }
8746 
8747     ma_sound_group_uninit(&pEngine->masterSoundGroup);
8748 
8749     if (pEngine->ownsDevice) {
8750         ma_device_uninit(pEngine->pDevice);
8751         ma__free_from_callbacks(pEngine->pDevice, &pEngine->allocationCallbacks/*, MA_ALLOCATION_TYPE_CONTEXT*/);
8752     }
8753 
8754     /* Uninitialize the resource manager last to ensure we don't have a thread still trying to access it. */
8755 #ifndef MA_NO_RESOURCE_MANAGER
8756     if (pEngine->ownsResourceManager) {
8757         ma_resource_manager_uninit(pEngine->pResourceManager);
8758         ma__free_from_callbacks(pEngine->pResourceManager, &pEngine->allocationCallbacks);
8759     }
8760 #endif
8761 }
8762 
ma_engine_data_callback(ma_engine * pEngine,void * pFramesOut,const void * pFramesIn,ma_uint32 frameCount)8763 MA_API void ma_engine_data_callback(ma_engine* pEngine, void* pFramesOut, const void* pFramesIn, ma_uint32 frameCount)
8764 {
8765     ma_uint32 pcmFramesAvailableInRB;
8766     ma_uint32 pcmFramesProcessed = 0;
8767     ma_uint8* pRunningOutput = (ma_uint8*)pFramesOut;
8768 
8769     if (pEngine == NULL) {
8770         return;
8771     }
8772 
8773     /* We need to do updates in fixed sizes based on the engine's period size in frames. */
8774 
8775     /*
8776     The first thing to do is check if there's enough data available in the ring buffer. If so we can read from it. Otherwise we need to keep filling
8777     the ring buffer until there's enough, making sure we only fill the ring buffer in chunks of pEngine->periodSizeInFrames.
8778     */
8779     while (pcmFramesProcessed < frameCount) {    /* Keep going until we've filled the output buffer. */
8780         ma_uint32 framesRemaining = frameCount - pcmFramesProcessed;
8781 
8782         pcmFramesAvailableInRB = ma_pcm_rb_available_read(&pEngine->fixedRB);
8783         if (pcmFramesAvailableInRB > 0) {
8784             ma_uint32 framesToRead = (framesRemaining < pcmFramesAvailableInRB) ? framesRemaining : pcmFramesAvailableInRB;
8785             void* pReadBuffer;
8786 
8787             ma_pcm_rb_acquire_read(&pEngine->fixedRB, &framesToRead, &pReadBuffer);
8788             {
8789                 memcpy(pRunningOutput, pReadBuffer, framesToRead * ma_get_bytes_per_frame(pEngine->pDevice->playback.format, pEngine->pDevice->playback.channels));
8790             }
8791             ma_pcm_rb_commit_read(&pEngine->fixedRB, framesToRead, pReadBuffer);
8792 
8793             pRunningOutput += framesToRead * ma_get_bytes_per_frame(pEngine->pDevice->playback.format, pEngine->pDevice->playback.channels);
8794             pcmFramesProcessed += framesToRead;
8795         } else {
8796             /*
8797             There's nothing in the buffer. Fill it with more data from the callback. We reset the buffer first so that the read and write pointers
8798             are reset back to the start so we can fill the ring buffer in chunks of pEngine->periodSizeInFrames which is what we initialized it
8799             with. Note that this is not how you would want to do it in a multi-threaded environment. In this case you would want to seek the write
8800             pointer forward via the producer thread and the read pointer forward via the consumer thread (this thread).
8801             */
8802             ma_uint32 framesToWrite = pEngine->periodSizeInFrames;
8803             void* pWriteBuffer;
8804 
8805             ma_pcm_rb_reset(&pEngine->fixedRB);
8806             ma_pcm_rb_acquire_write(&pEngine->fixedRB, &framesToWrite, &pWriteBuffer);
8807             {
8808                 MA_ASSERT(framesToWrite == pEngine->periodSizeInFrames);   /* <-- This should always work in this example because we just reset the ring buffer. */
8809                 ma_engine_listener__data_callback_fixed(pEngine, pWriteBuffer, framesToWrite);
8810             }
8811             ma_pcm_rb_commit_write(&pEngine->fixedRB, framesToWrite, pWriteBuffer);
8812         }
8813     }
8814 
8815     (void)pFramesIn;   /* Not doing anything with input right now. */
8816 }
8817 
8818 
ma_engine_start(ma_engine * pEngine)8819 MA_API ma_result ma_engine_start(ma_engine* pEngine)
8820 {
8821     ma_result result;
8822 
8823     if (pEngine == NULL) {
8824         return MA_INVALID_ARGS;
8825     }
8826 
8827     result = ma_device_start(pEngine->pDevice);
8828     if (result != MA_SUCCESS) {
8829         return result;
8830     }
8831 
8832     return MA_SUCCESS;
8833 }
8834 
ma_engine_stop(ma_engine * pEngine)8835 MA_API ma_result ma_engine_stop(ma_engine* pEngine)
8836 {
8837     ma_result result;
8838 
8839     if (pEngine == NULL) {
8840         return MA_INVALID_ARGS;
8841     }
8842 
8843     result = ma_device_stop(pEngine->pDevice);
8844     if (result != MA_SUCCESS) {
8845         return result;
8846     }
8847 
8848     return MA_SUCCESS;
8849 }
8850 
ma_engine_set_volume(ma_engine * pEngine,float volume)8851 MA_API ma_result ma_engine_set_volume(ma_engine* pEngine, float volume)
8852 {
8853     if (pEngine == NULL) {
8854         return MA_INVALID_ARGS;
8855     }
8856 
8857     return ma_device_set_master_volume(pEngine->pDevice, volume);
8858 }
8859 
ma_engine_set_gain_db(ma_engine * pEngine,float gainDB)8860 MA_API ma_result ma_engine_set_gain_db(ma_engine* pEngine, float gainDB)
8861 {
8862     if (pEngine == NULL) {
8863         return MA_INVALID_ARGS;
8864     }
8865 
8866     return ma_device_set_master_gain_db(pEngine->pDevice, gainDB);
8867 }
8868 
8869 
ma_engine_get_master_sound_group(ma_engine * pEngine)8870 MA_API ma_sound_group* ma_engine_get_master_sound_group(ma_engine* pEngine)
8871 {
8872     if (pEngine == NULL) {
8873         return NULL;
8874     }
8875 
8876     return &pEngine->masterSoundGroup;
8877 }
8878 
8879 
ma_engine_listener_set_position(ma_engine * pEngine,ma_vec3 position)8880 MA_API ma_result ma_engine_listener_set_position(ma_engine* pEngine, ma_vec3 position)
8881 {
8882     if (pEngine == NULL) {
8883         return MA_INVALID_ARGS;
8884     }
8885 
8886     pEngine->listener.position = position;
8887 
8888     return MA_SUCCESS;
8889 }
8890 
ma_engine_listener_set_rotation(ma_engine * pEngine,ma_quat rotation)8891 MA_API ma_result ma_engine_listener_set_rotation(ma_engine* pEngine, ma_quat rotation)
8892 {
8893     if (pEngine == NULL) {
8894         return MA_INVALID_ARGS;
8895     }
8896 
8897     pEngine->listener.rotation = rotation;
8898 
8899     return MA_SUCCESS;
8900 }
8901 
8902 
ma_engine_play_sound(ma_engine * pEngine,const char * pFilePath,ma_sound_group * pGroup)8903 MA_API ma_result ma_engine_play_sound(ma_engine* pEngine, const char* pFilePath, ma_sound_group* pGroup)
8904 {
8905     ma_result result;
8906     ma_sound* pSound = NULL;
8907     ma_sound* pNextSound = NULL;
8908     ma_uint32 dataSourceFlags = 0;
8909 
8910     if (pEngine == NULL || pFilePath == NULL) {
8911         return MA_INVALID_ARGS;
8912     }
8913 
8914     if (pGroup == NULL) {
8915         pGroup = &pEngine->masterSoundGroup;
8916     }
8917 
8918     dataSourceFlags |= MA_DATA_SOURCE_FLAG_ASYNC;
8919 
8920     /*
8921     Fire and forget sounds are never actually removed from the group. In practice there should never be a huge number of sounds playing at the same time so we
8922     should be able to get away with recycling sounds. What we need, however, is a way to switch out the old data source with a new one.
8923 
8924     The first thing to do is find an available sound. We will only be doing a forward iteration here so we should be able to do this part without locking. A
8925     sound will be available for recycling if it's marked as internal and is at the end.
8926     */
8927     for (pNextSound = pGroup->pFirstSoundInGroup; pNextSound != NULL; pNextSound = pNextSound->pNextSoundInGroup) {
8928         if (pNextSound->_isInternal) {
8929             /*
8930             We need to check that atEnd flag to determine if this sound is available. The problem is that another thread might be wanting to acquire this
8931             sound at the same time. We want to avoid as much locking as possible, so we'll do this as a compare and swap.
8932             */
8933             if (c89atomic_compare_and_swap_32(&pNextSound->atEnd, MA_TRUE, MA_FALSE) == MA_TRUE) {
8934                 /* We got it. */
8935                 pSound = pNextSound;
8936                 break;
8937             } else {
8938                 /* The sound is not available for recycling. Move on to the next one. */
8939             }
8940         }
8941     }
8942 
8943     if (pSound != NULL) {
8944         /*
8945         An existing sound is being recycled. There's no need to allocate memory or re-insert into the group (it's already there). All we need to do is replace
8946         the data source. The at-end flag has already been unset, and it will marked as playing at the end of this function.
8947         */
8948 
8949         /* The at-end flag should have been set to false when we acquired the sound for recycling. */
8950         MA_ASSERT(pSound->atEnd == MA_FALSE);
8951 
8952         /* We're just going to reuse the same data source as before so we need to make sure we uninitialize the old one first. */
8953         if (pSound->pDataSource != NULL) {  /* <-- Safety. Should never happen. */
8954             MA_ASSERT(pSound->ownsDataSource == MA_TRUE);
8955             ma_resource_manager_data_source_uninit(&pSound->resourceManagerDataSource);
8956         }
8957 
8958         /* The old data source has been uninitialized so now we need to initialize the new one. */
8959         result = ma_resource_manager_data_source_init(pEngine->pResourceManager, pFilePath, dataSourceFlags, NULL, &pSound->resourceManagerDataSource);
8960         if (result != MA_SUCCESS) {
8961             /* We failed to load the resource. We need to return an error. We must also put this sound back up for recycling by setting the at-end flag to true. */
8962             c89atomic_exchange_32(&pSound->atEnd, MA_TRUE); /* <-- Put the sound back up for recycling. */
8963             return result;
8964         }
8965 
8966         /* Set the data source again. It should always be set to the correct value but just set it again for completeness and consistency with the main init API. */
8967         pSound->pDataSource = &pSound->resourceManagerDataSource;
8968 
8969         /* We need to reset the effect. */
8970         result = ma_engine_effect_reinit(pEngine, &pSound->effect);
8971         if (result != MA_SUCCESS) {
8972             /* We failed to reinitialize the effect. The sound is currently in a bad state and we need to delete it and return an error. Should never happen. */
8973             ma_sound_uninit(pSound);
8974             return result;
8975         }
8976     } else {
8977         /* There's no available sounds for recycling. We need to allocate a sound. This can be done using a stack allocator. */
8978         pSound = (ma_sound*)ma__malloc_from_callbacks(sizeof(*pSound), &pEngine->allocationCallbacks/*, MA_ALLOCATION_TYPE_SOUND*/); /* TODO: This can certainly be optimized. */
8979         if (pSound == NULL) {
8980             return MA_OUT_OF_MEMORY;
8981         }
8982 
8983         result = ma_sound_init_from_file(pEngine, pFilePath, dataSourceFlags, NULL, pGroup, pSound);
8984         if (result != MA_SUCCESS) {
8985             ma__free_from_callbacks(pEngine, &pEngine->allocationCallbacks);
8986             return result;
8987         }
8988 
8989         /* The sound needs to be marked as internal for our own internal memory management reasons. This is how we know whether or not the sound is available for recycling. */
8990         pSound->_isInternal = MA_TRUE;  /* This is the only place _isInternal will be modified. We therefore don't need to worry about synchronizing access to this variable. */
8991     }
8992 
8993     /* Finally we can start playing the sound. */
8994     result = ma_sound_start(pSound);
8995     if (result != MA_SUCCESS) {
8996         /* Failed to start the sound. We need to uninitialize it and return an error. */
8997         ma_sound_uninit(pSound);
8998         return result;
8999     }
9000 
9001     return MA_SUCCESS;
9002 }
9003 
9004 
9005 
ma_sound_detach(ma_sound * pSound)9006 static ma_result ma_sound_detach(ma_sound* pSound)
9007 {
9008     ma_sound_group* pGroup;
9009 
9010     MA_ASSERT(pSound != NULL);
9011 
9012     pGroup = pSound->pGroup;
9013     MA_ASSERT(pGroup != NULL);
9014 
9015     /*
9016     The sound should never be in a playing state when this is called. It *can*, however, but in the middle of mixing in the mixing thread. It needs to finish
9017     mixing before being uninitialized completely, but that is done at a higher level to this function.
9018     */
9019     MA_ASSERT(pSound->isPlaying == MA_FALSE);
9020 
9021     /*
9022     We want the creation and deletion of sounds to be supported across multiple threads. An application may have any thread that want's to call
9023     ma_engine_play_sound(), for example. The application would expect this to just work. The problem, however, is that the mixing thread will be iterating over
9024     the list at the same time. We need to be careful with how we remove a sound from the list because we'll essentially be taking the sound out from under the
9025     mixing thread and the mixing thread must continue to work. Normally you would wrap the iteration in a lock as well, however an added complication is that
9026     the mixing thread cannot be locked as it's running on the audio thread, and locking in the audio thread is a no-no).
9027 
9028     To start with, ma_sound_detach() (this function) and ma_sound_attach() need to be wrapped in a lock. This lock will *not* be used by the
9029     mixing thread. We therefore need to craft this in a very particular way so as to ensure the mixing thread does not lose track of it's iteration state. What
9030     we don't want to do is clear the pNextSoundInGroup variable to NULL. This need to be maintained to ensure the mixing thread can continue iteration even
9031     after the sound has been removed from the group. This is acceptable because sounds are fixed to their group for the entire life, and this function will
9032     only ever be called when the sound is being uninitialized which therefore means it'll never be iterated again.
9033     */
9034     ma_mutex_lock(&pGroup->lock);
9035     {
9036         if (pSound->pPrevSoundInGroup == NULL) {
9037             /* The sound is the head of the list. All we need to do is change the pPrevSoundInGroup member of the next sound to NULL and make it the new head. */
9038 
9039             /* Make a new head. */
9040             c89atomic_exchange_ptr(&pGroup->pFirstSoundInGroup, pSound->pNextSoundInGroup);
9041         } else {
9042             /*
9043             The sound is not the head. We need to remove the sound from the group by simply changing the pNextSoundInGroup member of the previous sound. This is
9044             the important part. This is the part that allows the mixing thread to continue iteration without locking.
9045             */
9046             c89atomic_exchange_ptr(&pSound->pPrevSoundInGroup->pNextSoundInGroup, pSound->pNextSoundInGroup);
9047         }
9048 
9049         /* This doesn't really need to be done atomically because we've wrapped this in a lock and it's not used by the mixing thread. */
9050         if (pSound->pNextSoundInGroup != NULL) {
9051             c89atomic_exchange_ptr(&pSound->pNextSoundInGroup->pPrevSoundInGroup, pSound->pPrevSoundInGroup);
9052         }
9053     }
9054     ma_mutex_unlock(&pGroup->lock);
9055 
9056     return MA_SUCCESS;
9057 }
9058 
ma_sound_attach(ma_sound * pSound,ma_sound_group * pGroup)9059 static ma_result ma_sound_attach(ma_sound* pSound, ma_sound_group* pGroup)
9060 {
9061     MA_ASSERT(pSound != NULL);
9062     MA_ASSERT(pGroup != NULL);
9063     MA_ASSERT(pSound->pGroup == NULL);
9064 
9065     /* This should only ever be called when the sound is first initialized which means we should never be in a playing state. */
9066     MA_ASSERT(pSound->isPlaying == MA_FALSE);
9067 
9068     /* We can set the group at the start. */
9069     pSound->pGroup = pGroup;
9070 
9071     /*
9072     The sound will become the new head of the list. If we were only adding we could do this lock-free, but unfortunately we need to support fast, constant
9073     time removal of sounds from the list. This means we need to update two pointers, not just one, which means we can't use a standard compare-and-swap.
9074 
9075     One of our requirements is that the mixer thread must be able to iterate over the list *without* locking. We don't really need to do anything special
9076     here to support this, but we will want to use an atomic assignment.
9077     */
9078     ma_mutex_lock(&pGroup->lock);
9079     {
9080         ma_sound* pNewFirstSoundInGroup = pSound;
9081         ma_sound* pOldFirstSoundInGroup = pGroup->pFirstSoundInGroup;
9082 
9083         pNewFirstSoundInGroup->pNextSoundInGroup = pOldFirstSoundInGroup;
9084         if (pOldFirstSoundInGroup != NULL) {
9085             pOldFirstSoundInGroup->pPrevSoundInGroup = pNewFirstSoundInGroup;
9086         }
9087 
9088         c89atomic_exchange_ptr(&pGroup->pFirstSoundInGroup, pNewFirstSoundInGroup);
9089     }
9090     ma_mutex_unlock(&pGroup->lock);
9091 
9092     return MA_SUCCESS;
9093 }
9094 
9095 
9096 
ma_sound_preinit(ma_engine * pEngine,ma_uint32 flags,ma_sound_group * pGroup,ma_sound * pSound)9097 static ma_result ma_sound_preinit(ma_engine* pEngine, ma_uint32 flags, ma_sound_group* pGroup, ma_sound* pSound)
9098 {
9099     ma_result result;
9100 
9101     (void)flags;
9102 
9103     if (pSound == NULL) {
9104         return MA_INVALID_ARGS;
9105     }
9106 
9107     MA_ZERO_OBJECT(pSound);
9108 
9109     if (pEngine == NULL) {
9110         return MA_INVALID_ARGS;
9111     }
9112 
9113     pSound->pEngine = pEngine;
9114 
9115     /* An effect is used for handling panning, pitching, etc. */
9116     result = ma_engine_effect_init(pEngine, &pSound->effect);
9117     if (result != MA_SUCCESS) {
9118         return result;
9119     }
9120 
9121     pSound->pDataSource = NULL; /* This will be set a higher level outside of this function. */
9122     pSound->volume      = 1;
9123     pSound->seekTarget  = MA_SEEK_TARGET_NONE;
9124 
9125     if (pGroup == NULL) {
9126         pGroup = &pEngine->masterSoundGroup;
9127     }
9128 
9129     /*
9130     By default the sound needs to be added to the master group. It's safe to add to the master group before the sound has been fully initialized because
9131     the playing flag is set to false which means the group won't be attempting to do anything with it. Also, the sound won't prematurely be recycled
9132     because the atEnd flag is also set to false which is the indicator that the sound object is not available for recycling.
9133     */
9134     result = ma_sound_attach(pSound, pGroup);
9135     if (result != MA_SUCCESS) {
9136         ma_engine_effect_uninit(pEngine, &pSound->effect);
9137         return result;  /* Should never happen. Failed to attach the sound to the group. */
9138     }
9139 
9140     return MA_SUCCESS;
9141 }
9142 
9143 #ifndef MA_NO_RESOURCE_MANAGER
ma_sound_init_from_file(ma_engine * pEngine,const char * pFilePath,ma_uint32 flags,ma_async_notification * pNotification,ma_sound_group * pGroup,ma_sound * pSound)9144 MA_API ma_result ma_sound_init_from_file(ma_engine* pEngine, const char* pFilePath, ma_uint32 flags, ma_async_notification* pNotification, ma_sound_group* pGroup, ma_sound* pSound)
9145 {
9146     ma_result result;
9147 
9148     result = ma_sound_preinit(pEngine, flags, pGroup, pSound);
9149     if (result != MA_SUCCESS) {
9150         return result;
9151     }
9152 
9153     /*
9154     The preinitialization process has succeeded so now we need to load the data source from the resource manager. This needs to be the very last part of the
9155     process because we want to ensure the notification is only fired when the sound is fully initialized and usable. This is important because the caller may
9156     want to do things like query the length of the sound, set fade points, etc.
9157     */
9158     pSound->pDataSource    = &pSound->resourceManagerDataSource;   /* <-- Make sure the pointer to our data source is set before calling into the resource manager. */
9159     pSound->ownsDataSource = MA_TRUE;
9160 
9161     result = ma_resource_manager_data_source_init(pEngine->pResourceManager, pFilePath, flags, pNotification, &pSound->resourceManagerDataSource);
9162     if (result != MA_SUCCESS) {
9163         pSound->pDataSource = NULL;
9164         pSound->ownsDataSource = MA_FALSE;
9165         ma_sound_uninit(pSound);
9166         MA_ZERO_OBJECT(pSound);
9167         return result;
9168     }
9169 
9170     return MA_SUCCESS;
9171 }
9172 #endif
9173 
ma_sound_init_from_data_source(ma_engine * pEngine,ma_data_source * pDataSource,ma_uint32 flags,ma_sound_group * pGroup,ma_sound * pSound)9174 MA_API ma_result ma_sound_init_from_data_source(ma_engine* pEngine, ma_data_source* pDataSource, ma_uint32 flags, ma_sound_group* pGroup, ma_sound* pSound)
9175 {
9176     ma_result result;
9177 
9178     result = ma_sound_preinit(pEngine, flags, pGroup, pSound);
9179     if (result != MA_SUCCESS) {
9180         return result;
9181     }
9182 
9183     pSound->pDataSource = pDataSource;
9184     pSound->ownsDataSource = MA_FALSE;
9185 
9186     return MA_SUCCESS;
9187 }
9188 
ma_sound_uninit(ma_sound * pSound)9189 MA_API void ma_sound_uninit(ma_sound* pSound)
9190 {
9191     ma_result result;
9192 
9193     if (pSound == NULL) {
9194         return;
9195     }
9196 
9197     /* Make sure the sound is stopped as soon as possible to reduce the chance that it gets locked by the mixer. We also need to stop it before detaching from the group. */
9198     ma_sound_set_stop_delay(pSound, 0);   /* <-- Ensures the sound stops immediately. */
9199     result = ma_sound_stop(pSound);
9200     if (result != MA_SUCCESS) {
9201         return;
9202     }
9203 
9204     /* The sound needs to removed from the group to ensure it doesn't get iterated again and cause things to break again. This is thread-safe. */
9205     result = ma_sound_detach(pSound);
9206     if (result != MA_SUCCESS) {
9207         return;
9208     }
9209 
9210     /*
9211     The sound is detached from the group, but it may still be in the middle of mixing which means our data source is locked. We need to wait for
9212     this to finish before deleting from the resource manager.
9213 
9214     We could define this so that we don't wait if the sound does not own the underlying data source, but this might end up being dangerous because
9215     the application may think it's safe to destroy the data source when it actually isn't. It just feels untidy doing it like that.
9216     */
9217     ma_sound_mix_wait(pSound);
9218 
9219 
9220     /* Once the sound is detached from the group we can guarantee that it won't be referenced by the mixer thread which means it's safe for us to destroy the data source. */
9221 #ifndef MA_NO_RESOURCE_MANAGER
9222     if (pSound->ownsDataSource) {
9223         ma_resource_manager_data_source_uninit(&pSound->resourceManagerDataSource);
9224         pSound->pDataSource = NULL;
9225     }
9226 #else
9227     MA_ASSERT(pSound->ownsDataSource == MA_FALSE);
9228 #endif
9229 }
9230 
ma_sound_start(ma_sound * pSound)9231 MA_API ma_result ma_sound_start(ma_sound* pSound)
9232 {
9233     if (pSound == NULL) {
9234         return MA_INVALID_ARGS;
9235     }
9236 
9237     /* If the sound is already playing, do nothing. */
9238     if (pSound->isPlaying) {
9239         return MA_SUCCESS;
9240     }
9241 
9242     /* If the sound is at the end it means we want to start from the start again. */
9243     if (pSound->atEnd) {
9244         ma_result result = ma_data_source_seek_to_pcm_frame(pSound->pDataSource, 0);
9245         if (result != MA_SUCCESS) {
9246             return result;  /* Failed to seek back to the start. */
9247         }
9248 
9249         /* Make sure we clear the end indicator. */
9250         pSound->atEnd = MA_FALSE;
9251     }
9252 
9253     /* Once everything is set up we can tell the mixer thread about it. */
9254     c89atomic_exchange_32(&pSound->isPlaying, MA_TRUE);
9255 
9256     return MA_SUCCESS;
9257 }
9258 
ma_sound_stop_internal(ma_sound * pSound)9259 static MA_INLINE ma_result ma_sound_stop_internal(ma_sound* pSound)
9260 {
9261     MA_ASSERT(pSound != NULL);
9262 
9263     c89atomic_exchange_32(&pSound->isPlaying, MA_FALSE);
9264 
9265     return MA_SUCCESS;
9266 }
9267 
ma_sound_stop(ma_sound * pSound)9268 MA_API ma_result ma_sound_stop(ma_sound* pSound)
9269 {
9270     if (pSound == NULL) {
9271         return MA_INVALID_ARGS;
9272     }
9273 
9274     pSound->stopDelayInEngineFramesRemaining = pSound->stopDelayInEngineFrames;
9275 
9276     /* Stop immediately if we don't have a delay. */
9277     if (pSound->stopDelayInEngineFrames == 0) {
9278         ma_sound_stop_internal(pSound);
9279     }
9280 
9281     return MA_SUCCESS;
9282 }
9283 
ma_sound_set_volume(ma_sound * pSound,float volume)9284 MA_API ma_result ma_sound_set_volume(ma_sound* pSound, float volume)
9285 {
9286     if (pSound == NULL) {
9287         return MA_INVALID_ARGS;
9288     }
9289 
9290     pSound->volume = volume;
9291 
9292     return MA_SUCCESS;
9293 }
9294 
ma_sound_set_gain_db(ma_sound * pSound,float gainDB)9295 MA_API ma_result ma_sound_set_gain_db(ma_sound* pSound, float gainDB)
9296 {
9297     if (pSound == NULL) {
9298         return MA_INVALID_ARGS;
9299     }
9300 
9301     return ma_sound_set_volume(pSound, ma_gain_db_to_factor(gainDB));
9302 }
9303 
ma_sound_set_effect(ma_sound * pSound,ma_effect * pEffect)9304 MA_API ma_result ma_sound_set_effect(ma_sound* pSound, ma_effect* pEffect)
9305 {
9306     if (pSound == NULL) {
9307         return MA_INVALID_ARGS;
9308     }
9309 
9310     pSound->effect.pPreEffect = pEffect;
9311 
9312     return MA_SUCCESS;
9313 }
9314 
ma_sound_set_pitch(ma_sound * pSound,float pitch)9315 MA_API ma_result ma_sound_set_pitch(ma_sound* pSound, float pitch)
9316 {
9317     if (pSound == NULL) {
9318         return MA_INVALID_ARGS;
9319     }
9320 
9321     pSound->effect.pitch = pitch;
9322 
9323     return MA_SUCCESS;
9324 }
9325 
ma_sound_set_pan(ma_sound * pSound,float pan)9326 MA_API ma_result ma_sound_set_pan(ma_sound* pSound, float pan)
9327 {
9328     if (pSound == NULL) {
9329         return MA_INVALID_ARGS;
9330     }
9331 
9332     return ma_panner_set_pan(&pSound->effect.panner, pan);
9333 }
9334 
ma_sound_set_position(ma_sound * pSound,ma_vec3 position)9335 MA_API ma_result ma_sound_set_position(ma_sound* pSound, ma_vec3 position)
9336 {
9337     if (pSound == NULL) {
9338         return MA_INVALID_ARGS;
9339     }
9340 
9341     return ma_spatializer_set_position(&pSound->effect.spatializer, position);
9342 }
9343 
ma_sound_set_rotation(ma_sound * pSound,ma_quat rotation)9344 MA_API ma_result ma_sound_set_rotation(ma_sound* pSound, ma_quat rotation)
9345 {
9346     if (pSound == NULL) {
9347         return MA_INVALID_ARGS;
9348     }
9349 
9350     return ma_spatializer_set_rotation(&pSound->effect.spatializer, rotation);
9351 }
9352 
ma_sound_set_looping(ma_sound * pSound,ma_bool32 isLooping)9353 MA_API ma_result ma_sound_set_looping(ma_sound* pSound, ma_bool32 isLooping)
9354 {
9355     if (pSound == NULL) {
9356         return MA_INVALID_ARGS;
9357     }
9358 
9359     c89atomic_exchange_32(&pSound->isLooping, isLooping);
9360 
9361     /*
9362     This is a little bit of a hack, but basically we need to set the looping flag at the data source level if we are running a data source managed by
9363     the resource manager, and that is backed by a data stream. The reason for this is that the data stream itself needs to be aware of the looping
9364     requirements so that it can do seamless loop transitions. The better solution for this is to add ma_data_source_set_looping() and just call this
9365     generically.
9366     */
9367 #ifndef MA_NO_RESOURCE_MANAGER
9368     if (pSound->pDataSource == &pSound->resourceManagerDataSource) {
9369         ma_resource_manager_data_source_set_looping(&pSound->resourceManagerDataSource, isLooping);
9370     }
9371 #endif
9372 
9373     return MA_SUCCESS;
9374 }
9375 
ma_sound_set_fade_point_in_frames(ma_sound * pSound,ma_uint32 fadePointIndex,float volumeBeg,float volumeEnd,ma_uint64 timeInFramesBeg,ma_uint64 timeInFramesEnd)9376 MA_API ma_result ma_sound_set_fade_point_in_frames(ma_sound* pSound, ma_uint32 fadePointIndex, float volumeBeg, float volumeEnd, ma_uint64 timeInFramesBeg, ma_uint64 timeInFramesEnd)
9377 {
9378     if (pSound == NULL) {
9379         return MA_INVALID_ARGS;
9380     }
9381 
9382     return ma_dual_fader_set_fade(&pSound->effect.fader, fadePointIndex, volumeBeg, volumeEnd, timeInFramesBeg, timeInFramesEnd);
9383 }
9384 
ma_sound_set_fade_point_in_milliseconds(ma_sound * pSound,ma_uint32 fadePointIndex,float volumeBeg,float volumeEnd,ma_uint64 timeInMillisecondsBeg,ma_uint64 timeInMillisecondsEnd)9385 MA_API ma_result ma_sound_set_fade_point_in_milliseconds(ma_sound* pSound, ma_uint32 fadePointIndex, float volumeBeg, float volumeEnd, ma_uint64 timeInMillisecondsBeg, ma_uint64 timeInMillisecondsEnd)
9386 {
9387     ma_uint64 timeInFramesBeg;
9388     ma_uint64 timeInFramesEnd;
9389 
9390     if (pSound == NULL) {
9391         return MA_INVALID_ARGS;
9392     }
9393 
9394     timeInFramesBeg = (timeInMillisecondsBeg * pSound->effect.fader.config.sampleRate) / 1000;
9395     timeInFramesEnd = (timeInMillisecondsEnd * pSound->effect.fader.config.sampleRate) / 1000;
9396 
9397     return ma_sound_set_fade_point_in_frames(pSound, fadePointIndex, volumeBeg, volumeEnd, timeInFramesBeg, timeInFramesEnd);
9398 }
9399 
ma_sound_set_fade_point_auto_reset(ma_sound * pSound,ma_uint32 fadePointIndex,ma_bool32 autoReset)9400 MA_API ma_result ma_sound_set_fade_point_auto_reset(ma_sound* pSound, ma_uint32 fadePointIndex, ma_bool32 autoReset)
9401 {
9402     if (pSound == NULL) {
9403         return MA_INVALID_ARGS;
9404     }
9405 
9406     return ma_dual_fader_set_auto_reset(&pSound->effect.fader, fadePointIndex, autoReset);
9407 }
9408 
ma_sound_set_start_delay(ma_sound * pSound,ma_uint64 delayInMilliseconds)9409 MA_API ma_result ma_sound_set_start_delay(ma_sound* pSound, ma_uint64 delayInMilliseconds)
9410 {
9411     if (pSound == NULL) {
9412         return MA_INVALID_ARGS;
9413     }
9414 
9415     MA_ASSERT(pSound->pEngine != NULL);
9416 
9417     /*
9418     It's important that the delay be timed based on the engine's sample rate and not the rate of the sound. The reason is that no processing will be happening
9419     by the sound before playback has actually begun and we won't have accurate frame counters due to resampling.
9420     */
9421     pSound->startDelayInEngineFrames = (pSound->pEngine->sampleRate * delayInMilliseconds) / 1000;
9422 
9423     return MA_SUCCESS;
9424 }
9425 
ma_sound_set_stop_delay(ma_sound * pSound,ma_uint64 delayInMilliseconds)9426 MA_API ma_result ma_sound_set_stop_delay(ma_sound* pSound, ma_uint64 delayInMilliseconds)
9427 {
9428     if (pSound == NULL) {
9429         return MA_INVALID_ARGS;
9430     }
9431 
9432     MA_ASSERT(pSound->pEngine != NULL);
9433 
9434     pSound->stopDelayInEngineFrames = (pSound->pEngine->sampleRate * delayInMilliseconds) / 1000;
9435 
9436     return MA_SUCCESS;
9437 }
9438 
ma_sound_is_playing(const ma_sound * pSound)9439 MA_API ma_bool32 ma_sound_is_playing(const ma_sound* pSound)
9440 {
9441     if (pSound == NULL) {
9442         return MA_FALSE;
9443     }
9444 
9445     return pSound->isPlaying;
9446 }
9447 
ma_sound_at_end(const ma_sound * pSound)9448 MA_API ma_bool32 ma_sound_at_end(const ma_sound* pSound)
9449 {
9450     if (pSound == NULL) {
9451         return MA_FALSE;
9452     }
9453 
9454     return pSound->atEnd;
9455 }
9456 
ma_sound_get_time_in_frames(const ma_sound * pSound,ma_uint64 * pTimeInFrames)9457 MA_API ma_result ma_sound_get_time_in_frames(const ma_sound* pSound, ma_uint64* pTimeInFrames)
9458 {
9459     if (pTimeInFrames == NULL) {
9460         return MA_INVALID_ARGS;
9461     }
9462 
9463     *pTimeInFrames = 0;
9464 
9465     if (pSound == NULL) {
9466         return MA_INVALID_ARGS;
9467     }
9468 
9469     *pTimeInFrames = pSound->effect.timeInFrames;
9470 
9471     return MA_SUCCESS;
9472 }
9473 
ma_sound_seek_to_pcm_frame(ma_sound * pSound,ma_uint64 frameIndex)9474 MA_API ma_result ma_sound_seek_to_pcm_frame(ma_sound* pSound, ma_uint64 frameIndex)
9475 {
9476     if (pSound == NULL) {
9477         return MA_INVALID_ARGS;
9478     }
9479 
9480     /*
9481     Resource manager data sources are thread safe which means we can just seek immediately. However, we cannot guarantee that other data sources are
9482     thread safe as well so in that case we'll need to get the mixing thread to seek for us to ensure we don't try seeking at the same time as reading.
9483     */
9484 #ifndef MA_NO_RESOURCE_MANAGER
9485     if (pSound->pDataSource == &pSound->resourceManagerDataSource) {
9486         ma_result result = ma_resource_manager_data_source_seek_to_pcm_frame(&pSound->resourceManagerDataSource, frameIndex);
9487         if (result != MA_SUCCESS) {
9488             return result;
9489         }
9490 
9491         /* Time dependant effects need to have their timers updated. */
9492         return ma_engine_effect_set_time(&pSound->effect, frameIndex);
9493     }
9494 #endif
9495 
9496     /* Getting here means the data source is not a resource manager data source so we'll need to get the mixing thread to do the seeking for us. */
9497     pSound->seekTarget = frameIndex;
9498 
9499     return MA_SUCCESS;
9500 }
9501 
ma_sound_get_data_format(ma_sound * pSound,ma_format * pFormat,ma_uint32 * pChannels,ma_uint32 * pSampleRate)9502 MA_API ma_result ma_sound_get_data_format(ma_sound* pSound, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate)
9503 {
9504     if (pSound == NULL) {
9505         return MA_INVALID_ARGS;
9506     }
9507 
9508     return ma_data_source_get_data_format(pSound->pDataSource, pFormat, pChannels, pSampleRate);
9509 }
9510 
ma_sound_get_cursor_in_pcm_frames(ma_sound * pSound,ma_uint64 * pCursor)9511 MA_API ma_result ma_sound_get_cursor_in_pcm_frames(ma_sound* pSound, ma_uint64* pCursor)
9512 {
9513     if (pSound == NULL) {
9514         return MA_INVALID_ARGS;
9515     }
9516 
9517     return ma_data_source_get_cursor_in_pcm_frames(pSound->pDataSource, pCursor);
9518 }
9519 
ma_sound_get_length_in_pcm_frames(ma_sound * pSound,ma_uint64 * pLength)9520 MA_API ma_result ma_sound_get_length_in_pcm_frames(ma_sound* pSound, ma_uint64* pLength)
9521 {
9522     if (pSound == NULL) {
9523         return MA_INVALID_ARGS;
9524     }
9525 
9526     return ma_data_source_get_length_in_pcm_frames(pSound->pDataSource, pLength);
9527 }
9528 
9529 
9530 
ma_sound_group_attach(ma_sound_group * pGroup,ma_sound_group * pParentGroup)9531 static ma_result ma_sound_group_attach(ma_sound_group* pGroup, ma_sound_group* pParentGroup)
9532 {
9533     ma_sound_group* pNewFirstChild;
9534     ma_sound_group* pOldFirstChild;
9535 
9536     if (pGroup == NULL) {
9537         return MA_INVALID_ARGS;
9538     }
9539 
9540     MA_ASSERT(pGroup->pEngine != NULL);
9541 
9542     /* Don't do anything for the master sound group. This should never be attached to anything. */
9543     if (pGroup == &pGroup->pEngine->masterSoundGroup) {
9544         return MA_SUCCESS;
9545     }
9546 
9547     /* Must have a parent. */
9548     if (pParentGroup == NULL) {
9549         return MA_SUCCESS;
9550     }
9551 
9552     pNewFirstChild = pGroup;
9553     pOldFirstChild = pParentGroup->pFirstChild;
9554 
9555     /* It's an error for the group to already be assigned to a group. */
9556     MA_ASSERT(pGroup->pParent == NULL);
9557     pGroup->pParent = pParentGroup;
9558 
9559     /* Like sounds, we just make it so the new group becomes the new head. */
9560     pNewFirstChild->pNextSibling = pOldFirstChild;
9561     if (pOldFirstChild != NULL) {
9562         pOldFirstChild->pPrevSibling = pNewFirstChild;
9563     }
9564 
9565     pGroup->pFirstChild = pNewFirstChild;
9566 
9567     return MA_SUCCESS;
9568 }
9569 
ma_sound_group_detach(ma_sound_group * pGroup)9570 static ma_result ma_sound_group_detach(ma_sound_group* pGroup)
9571 {
9572     if (pGroup == NULL) {
9573         return MA_INVALID_ARGS;
9574     }
9575 
9576     MA_ASSERT(pGroup->pEngine != NULL);
9577 
9578     /* Don't do anything for the master sound group. This should never be detached from anything. */
9579     if (pGroup == &pGroup->pEngine->masterSoundGroup) {
9580         return MA_SUCCESS;
9581     }
9582 
9583     if (pGroup->pPrevSibling == NULL) {
9584         /* It's the first child in the parent group. */
9585         MA_ASSERT(pGroup->pParent != NULL);
9586         MA_ASSERT(pGroup->pParent->pFirstChild == pGroup);
9587 
9588         c89atomic_exchange_ptr(&pGroup->pParent->pFirstChild, pGroup->pNextSibling);
9589     } else {
9590         /* It's not the first child in the parent group. */
9591         c89atomic_exchange_ptr(&pGroup->pPrevSibling->pNextSibling, pGroup->pNextSibling);
9592     }
9593 
9594     /* The previous sibling needs to be changed for the old next sibling. */
9595     if (pGroup->pNextSibling != NULL) {
9596         pGroup->pNextSibling->pPrevSibling = pGroup->pPrevSibling;
9597     }
9598 
9599     return MA_SUCCESS;
9600 }
9601 
ma_sound_group_init(ma_engine * pEngine,ma_sound_group * pParentGroup,ma_sound_group * pGroup)9602 MA_API ma_result ma_sound_group_init(ma_engine* pEngine, ma_sound_group* pParentGroup, ma_sound_group* pGroup)
9603 {
9604     ma_result result;
9605     ma_mixer_config mixerConfig;
9606 
9607     if (pGroup == NULL) {
9608         return MA_INVALID_ARGS;
9609     }
9610 
9611     MA_ZERO_OBJECT(pGroup);
9612 
9613     if (pEngine == NULL) {
9614         return MA_INVALID_ARGS;
9615     }
9616 
9617     pGroup->pEngine = pEngine;
9618 
9619     /* Use the master group if the parent group is NULL, so long as it's not the master group itself. */
9620     if (pParentGroup == NULL && pGroup != &pEngine->masterSoundGroup) {
9621         pParentGroup = &pEngine->masterSoundGroup;
9622     }
9623 
9624 
9625     /* TODO: Look at the possibility of allowing groups to use a different format to the primary data format. Makes mixing and group management much more complicated. */
9626 
9627     /* For handling panning, etc. we'll need an engine effect. */
9628     result = ma_engine_effect_init(pEngine, &pGroup->effect);
9629     if (result != MA_SUCCESS) {
9630         return result;  /* Failed to initialize the engine effect. */
9631     }
9632 
9633     /* The sound group needs a mixer. This is what's used to mix each of the sounds contained within the group, and sub-groups. */
9634     mixerConfig = ma_mixer_config_init(pEngine->format, pEngine->channels, pEngine->periodSizeInFrames, NULL, &pEngine->allocationCallbacks);
9635     result = ma_mixer_init(&mixerConfig, &pGroup->mixer);
9636     if (result != MA_SUCCESS) {
9637         ma_engine_effect_uninit(pEngine, &pGroup->effect);
9638         return result;
9639     }
9640 
9641     /* The mixer's effect is always set to the main engine effect. */
9642     ma_mixer_set_effect(&pGroup->mixer, &pGroup->effect);
9643 
9644 
9645     /* Attach the sound group to it's parent if it has one (this will only happen if it's the master group). */
9646     if (pParentGroup != NULL) {
9647         result = ma_sound_group_attach(pGroup, pParentGroup);
9648         if (result != MA_SUCCESS) {
9649             ma_mixer_uninit(&pGroup->mixer);
9650             ma_engine_effect_uninit(pEngine, &pGroup->effect);
9651             return result;
9652         }
9653     } else {
9654         MA_ASSERT(pGroup == &pEngine->masterSoundGroup);    /* The master group is the only one allowed to not have a parent group. */
9655     }
9656 
9657     /*
9658     We need to initialize the lock that'll be used to synchronize adding and removing of sounds to the group. This lock is _not_ used by the mixing thread. The mixing
9659     thread is written in a way where a lock should not be required.
9660     */
9661     result = ma_mutex_init(&pGroup->lock);
9662     if (result != MA_SUCCESS) {
9663         ma_sound_group_detach(pGroup);
9664         ma_mixer_uninit(&pGroup->mixer);
9665         ma_engine_effect_uninit(pEngine, &pGroup->effect);
9666         return result;
9667     }
9668 
9669     /* The group needs to be started by default, but needs to be done after attaching to the internal list. */
9670     pGroup->isPlaying = MA_TRUE;
9671 
9672     return MA_SUCCESS;
9673 }
9674 
ma_sound_group_uninit_all_internal_sounds(ma_sound_group * pGroup)9675 static void ma_sound_group_uninit_all_internal_sounds(ma_sound_group* pGroup)
9676 {
9677     ma_sound* pCurrentSound;
9678 
9679     /* We need to be careful here that we keep our iteration valid. */
9680     pCurrentSound = pGroup->pFirstSoundInGroup;
9681     while (pCurrentSound != NULL) {
9682         ma_sound* pSoundToDelete = pCurrentSound;
9683         pCurrentSound = pCurrentSound->pNextSoundInGroup;
9684 
9685         if (pSoundToDelete->_isInternal) {
9686             ma_sound_uninit(pSoundToDelete);
9687         }
9688     }
9689 }
9690 
ma_sound_group_uninit(ma_sound_group * pGroup)9691 MA_API void ma_sound_group_uninit(ma_sound_group* pGroup)
9692 {
9693     ma_result result;
9694 
9695     ma_sound_group_set_stop_delay(pGroup, 0); /* <-- Make sure we disable fading out so the sound group is stopped immediately. */
9696     result = ma_sound_group_stop(pGroup);
9697     if (result != MA_SUCCESS) {
9698         MA_ASSERT(MA_FALSE);    /* Should never happen. Trigger an assert for debugging, but don't stop uninitializing in production to ensure we free memory down below. */
9699     }
9700 
9701     /* Any in-place sounds need to be uninitialized. */
9702     ma_sound_group_uninit_all_internal_sounds(pGroup);
9703 
9704     result = ma_sound_group_detach(pGroup);
9705     if (result != MA_SUCCESS) {
9706         MA_ASSERT(MA_FALSE);    /* As above, should never happen, but just in case trigger an assert in debug mode, but continue processing. */
9707     }
9708 
9709     ma_mixer_uninit(&pGroup->mixer);
9710     ma_mutex_uninit(&pGroup->lock);
9711 
9712     ma_engine_effect_uninit(pGroup->pEngine, &pGroup->effect);
9713 }
9714 
ma_sound_group_start(ma_sound_group * pGroup)9715 MA_API ma_result ma_sound_group_start(ma_sound_group* pGroup)
9716 {
9717     if (pGroup == NULL) {
9718         return MA_INVALID_ARGS;
9719     }
9720 
9721     c89atomic_exchange_32(&pGroup->isPlaying, MA_TRUE);
9722 
9723     return MA_SUCCESS;
9724 }
9725 
ma_sound_group_stop_internal(ma_sound_group * pGroup)9726 static MA_INLINE ma_result ma_sound_group_stop_internal(ma_sound_group* pGroup)
9727 {
9728     MA_ASSERT(pGroup != NULL);
9729 
9730     c89atomic_exchange_32(&pGroup->isPlaying, MA_FALSE);
9731 
9732     return MA_SUCCESS;
9733 }
9734 
ma_sound_group_stop(ma_sound_group * pGroup)9735 MA_API ma_result ma_sound_group_stop(ma_sound_group* pGroup)
9736 {
9737     if (pGroup == NULL) {
9738         return MA_INVALID_ARGS;
9739     }
9740 
9741     pGroup->stopDelayInEngineFramesRemaining = pGroup->stopDelayInEngineFrames;
9742 
9743     /* Stop immediately if we're not delaying. */
9744     if (pGroup->stopDelayInEngineFrames == 0) {
9745         ma_sound_group_stop_internal(pGroup);
9746     }
9747 
9748     return MA_SUCCESS;
9749 }
9750 
ma_sound_group_set_volume(ma_sound_group * pGroup,float volume)9751 MA_API ma_result ma_sound_group_set_volume(ma_sound_group* pGroup, float volume)
9752 {
9753     if (pGroup == NULL) {
9754         return MA_INVALID_ARGS;
9755     }
9756 
9757     /* The volume is set via the mixer. */
9758     ma_mixer_set_volume(&pGroup->mixer, volume);
9759 
9760     return MA_SUCCESS;
9761 }
9762 
ma_sound_group_set_gain_db(ma_sound_group * pGroup,float gainDB)9763 MA_API ma_result ma_sound_group_set_gain_db(ma_sound_group* pGroup, float gainDB)
9764 {
9765     return ma_sound_group_set_volume(pGroup, ma_gain_db_to_factor(gainDB));
9766 }
9767 
ma_sound_group_set_effect(ma_sound_group * pGroup,ma_effect * pEffect)9768 MA_API ma_result ma_sound_group_set_effect(ma_sound_group* pGroup, ma_effect* pEffect)
9769 {
9770     if (pGroup == NULL) {
9771         return MA_INVALID_ARGS;
9772     }
9773 
9774     pGroup->effect.pPreEffect = pEffect;
9775 
9776     return MA_SUCCESS;
9777 }
9778 
ma_sound_group_set_pan(ma_sound_group * pGroup,float pan)9779 MA_API ma_result ma_sound_group_set_pan(ma_sound_group* pGroup, float pan)
9780 {
9781     if (pGroup == NULL) {
9782         return MA_INVALID_ARGS;
9783     }
9784 
9785     return ma_panner_set_pan(&pGroup->effect.panner, pan);
9786 }
9787 
ma_sound_group_set_pitch(ma_sound_group * pGroup,float pitch)9788 MA_API ma_result ma_sound_group_set_pitch(ma_sound_group* pGroup, float pitch)
9789 {
9790     if (pGroup == NULL) {
9791         return MA_INVALID_ARGS;
9792     }
9793 
9794     pGroup->effect.pitch = pitch;
9795 
9796     return MA_SUCCESS;
9797 }
9798 
ma_sound_group_set_fade_point_in_frames(ma_sound_group * pGroup,ma_uint32 fadePointIndex,float volumeBeg,float volumeEnd,ma_uint64 timeInFramesBeg,ma_uint64 timeInFramesEnd)9799 MA_API ma_result ma_sound_group_set_fade_point_in_frames(ma_sound_group* pGroup, ma_uint32 fadePointIndex, float volumeBeg, float volumeEnd, ma_uint64 timeInFramesBeg, ma_uint64 timeInFramesEnd)
9800 {
9801     if (pGroup == NULL) {
9802         return MA_INVALID_ARGS;
9803     }
9804 
9805     return ma_dual_fader_set_fade(&pGroup->effect.fader, fadePointIndex, volumeBeg, volumeEnd, timeInFramesBeg, timeInFramesEnd);
9806 }
9807 
ma_sound_group_set_fade_point_in_milliseconds(ma_sound_group * pGroup,ma_uint32 fadePointIndex,float volumeBeg,float volumeEnd,ma_uint64 timeInMillisecondsBeg,ma_uint64 timeInMillisecondsEnd)9808 MA_API ma_result ma_sound_group_set_fade_point_in_milliseconds(ma_sound_group* pGroup, ma_uint32 fadePointIndex, float volumeBeg, float volumeEnd, ma_uint64 timeInMillisecondsBeg, ma_uint64 timeInMillisecondsEnd)
9809 {
9810     ma_uint64 timeInFramesBeg;
9811     ma_uint64 timeInFramesEnd;
9812 
9813     if (pGroup == NULL) {
9814         return MA_INVALID_ARGS;
9815     }
9816 
9817     timeInFramesBeg = (timeInMillisecondsBeg * pGroup->effect.fader.config.sampleRate) / 1000;
9818     timeInFramesEnd = (timeInMillisecondsEnd * pGroup->effect.fader.config.sampleRate) / 1000;
9819 
9820     return ma_sound_group_set_fade_point_in_frames(pGroup, fadePointIndex, volumeBeg, volumeEnd, timeInFramesBeg, timeInFramesEnd);
9821 }
9822 
ma_sound_group_set_fade_point_auto_reset(ma_sound_group * pGroup,ma_uint32 fadePointIndex,ma_bool32 autoReset)9823 MA_API ma_result ma_sound_group_set_fade_point_auto_reset(ma_sound_group* pGroup, ma_uint32 fadePointIndex, ma_bool32 autoReset)
9824 {
9825     if (pGroup == NULL) {
9826         return MA_INVALID_ARGS;
9827     }
9828 
9829     return ma_dual_fader_set_auto_reset(&pGroup->effect.fader, fadePointIndex, autoReset);
9830 }
9831 
ma_sound_group_set_start_delay(ma_sound_group * pGroup,ma_uint64 delayInMilliseconds)9832 MA_API ma_result ma_sound_group_set_start_delay(ma_sound_group* pGroup, ma_uint64 delayInMilliseconds)
9833 {
9834     if (pGroup == NULL) {
9835         return MA_INVALID_ARGS;
9836     }
9837 
9838     MA_ASSERT(pGroup->pEngine != NULL);
9839 
9840     pGroup->startDelayInEngineFrames = (pGroup->pEngine->sampleRate * delayInMilliseconds) / 1000;
9841 
9842     return MA_SUCCESS;
9843 }
9844 
ma_sound_group_set_stop_delay(ma_sound_group * pGroup,ma_uint64 delayInMilliseconds)9845 MA_API ma_result ma_sound_group_set_stop_delay(ma_sound_group* pGroup, ma_uint64 delayInMilliseconds)
9846 {
9847     if (pGroup == NULL) {
9848         return MA_INVALID_ARGS;
9849     }
9850 
9851     MA_ASSERT(pGroup->pEngine != NULL);
9852 
9853     pGroup->stopDelayInEngineFrames = (pGroup->pEngine->sampleRate * delayInMilliseconds) / 1000;
9854 
9855     return MA_SUCCESS;
9856 }
9857 
ma_sound_group_is_playing(const ma_sound_group * pGroup)9858 MA_API ma_bool32 ma_sound_group_is_playing(const ma_sound_group* pGroup)
9859 {
9860     if (pGroup == NULL) {
9861         return MA_FALSE;
9862     }
9863 
9864     return pGroup->isPlaying;
9865 }
9866 
ma_sound_group_get_time_in_frames(const ma_sound_group * pGroup,ma_uint64 * pTimeInFrames)9867 MA_API ma_result ma_sound_group_get_time_in_frames(const ma_sound_group* pGroup, ma_uint64* pTimeInFrames)
9868 {
9869     if (pTimeInFrames == NULL) {
9870         return MA_INVALID_ARGS;
9871     }
9872 
9873     *pTimeInFrames = 0;
9874 
9875     if (pGroup == NULL) {
9876         return MA_INVALID_ARGS;
9877     }
9878 
9879     *pTimeInFrames = pGroup->effect.timeInFrames;
9880 
9881     return MA_SUCCESS;
9882 }
9883 
9884 
9885 #endif
9886