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, ¤tTimeInFrames);
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