1 /* !!! THIS FILE WILL BE MERGED INTO miniaudio.h WHEN COMPLETE !!! */
2 
3 /*
4 EXPERIMENTAL
5 ============
6 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
7 basically straight off the top of my head - many of these are probably outright wrong or just generally bad ideas.
8 
9 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.
10 
11 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`
12 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
13 list of data sources. The `ma_resource_manager` is responsible for the actual loading, caching and unloading of those data sources. This decoupling is
14 something that I'm really liking right now and will likely stay in place for the final version.
15 
16 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
17 start by default. You can use `ma_engine_play_sound()` to "fire and forget" sounds.
18 
19 Sounds can be allocated to groups called `ma_sound_group`. This is how you can support submixing and is one way you could achieve the kinds of groupings you see
20 in games for things like SFX, Music and Voices. Unlike sounds, groups are started by default. When you stop a group, all sounds within that group will be
21 stopped atomically. When the group is started again, all sounds attached to the group will also be started, so long as the sound is also marked as started.
22 
23 The creation and deletion of sounds and groups should be thread safe.
24 
25 The engine runs on top of a node graph, and sounds and groups are just nodes within that graph. The output of a sound can be attached to the input of any node
26 on the graph. To apply an effect to a sound or group, attach it's output to the input of an effect node. See the Routing Infrastructure section below for
27 details on this.
28 
29 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! :)
30 */
31 #ifndef miniaudio_engine_h
32 #define miniaudio_engine_h
33 
34 #ifdef __cplusplus
35 extern "C" {
36 #endif
37 
38 /*
39 Memory Allocation Types
40 =======================
41 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
42 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
43 useful to be told exactly what it being allocated so you can optimize your allocations appropriately.
44 */
45 #define MA_ALLOCATION_TYPE_GENERAL                              0x00000001  /* A general memory allocation. */
46 #define MA_ALLOCATION_TYPE_CONTEXT                              0x00000002  /* A ma_context allocation. */
47 #define MA_ALLOCATION_TYPE_DEVICE                               0x00000003  /* A ma_device allocation. */
48 #define MA_ALLOCATION_TYPE_DECODER                              0x00000004  /* A ma_decoder allocation. */
49 #define MA_ALLOCATION_TYPE_AUDIO_BUFFER                         0x00000005  /* A ma_audio_buffer allocation. */
50 #define MA_ALLOCATION_TYPE_ENCODED_BUFFER                       0x00000006  /* Allocation for encoded audio data containing the raw file data of a sound file. */
51 #define MA_ALLOCATION_TYPE_DECODED_BUFFER                       0x00000007  /* Allocation for decoded audio data from a sound file. */
52 #define MA_ALLOCATION_TYPE_RESOURCE_MANAGER_DATA_BUFFER_NODE    0x00000010  /* A ma_resource_manager_data_buffer_node object. */
53 #define MA_ALLOCATION_TYPE_RESOURCE_MANAGER_DATA_BUFFER         0x00000011  /* A ma_resource_manager_data_buffer_node object. */
54 #define MA_ALLOCATION_TYPE_RESOURCE_MANAGER_DATA_STREAM         0x00000012  /* A ma_resource_manager_data_stream object. */
55 #define MA_ALLOCATION_TYPE_RESOURCE_MANAGER_DATA_SOURCE         0x00000013  /* A ma_resource_manager_data_source object. */
56 
57 
58 /*
59 Paged Audio Buffer
60 ==================
61 A paged audio buffer is made up of a linked list of pages. It's expandable, but not shrinkable. It
62 can be used for cases where audio data is streamed in asynchronously while allowing data to be read
63 at the same time.
64 
65 This is lock-free, but not 100% thread safe. You can append a page and read from the buffer across
66 simultaneously across different threads, however only one thread at a time can append, and only one
67 thread at a time can read and seek.
68 */
69 typedef struct ma_paged_audio_buffer_page ma_paged_audio_buffer_page;
70 struct ma_paged_audio_buffer_page
71 {
72     MA_ATOMIC ma_paged_audio_buffer_page* pNext;
73     ma_uint64 sizeInFrames;
74     ma_uint8 pAudioData[1];
75 };
76 
77 typedef struct
78 {
79     ma_format format;
80     ma_uint32 channels;
81     ma_paged_audio_buffer_page head;                /* Dummy head for the lock-free algorithm. Always has a size of 0. */
82     MA_ATOMIC ma_paged_audio_buffer_page* pTail;    /* Never null. Initially set to &head. */
83 } ma_paged_audio_buffer_data;
84 
85 MA_API ma_result ma_paged_audio_buffer_data_init(ma_format format, ma_uint32 channels, ma_paged_audio_buffer_data* pData);
86 MA_API void ma_paged_audio_buffer_data_uninit(ma_paged_audio_buffer_data* pData, const ma_allocation_callbacks* pAllocationCallbacks);
87 MA_API ma_paged_audio_buffer_page* ma_paged_audio_buffer_data_get_head(ma_paged_audio_buffer_data* pData);
88 MA_API ma_paged_audio_buffer_page* ma_paged_audio_buffer_data_get_tail(ma_paged_audio_buffer_data* pData);
89 MA_API ma_result ma_paged_audio_buffer_data_get_length_in_pcm_frames(ma_paged_audio_buffer_data* pData, ma_uint64* pLength);
90 MA_API ma_result ma_paged_audio_buffer_data_allocate_page(ma_paged_audio_buffer_data* pData, ma_uint64 pageSizeInFrames, const void* pInitialData, const ma_allocation_callbacks* pAllocationCallbacks, ma_paged_audio_buffer_page** ppPage);
91 MA_API ma_result ma_paged_audio_buffer_data_free_page(ma_paged_audio_buffer_data* pData, ma_paged_audio_buffer_page* pPage, const ma_allocation_callbacks* pAllocationCallbacks);
92 MA_API ma_result ma_paged_audio_buffer_data_append_page(ma_paged_audio_buffer_data* pData, ma_paged_audio_buffer_page* pPage);
93 MA_API ma_result ma_paged_audio_buffer_data_allocate_and_append_page(ma_paged_audio_buffer_data* pData, ma_uint32 pageSizeInFrames, const void* pInitialData, const ma_allocation_callbacks* pAllocationCallbacks);
94 
95 
96 typedef struct
97 {
98     ma_paged_audio_buffer_data* pData;  /* Must not be null. */
99 } ma_paged_audio_buffer_config;
100 
101 MA_API ma_paged_audio_buffer_config ma_paged_audio_buffer_config_init(ma_paged_audio_buffer_data* pData);
102 
103 
104 typedef struct
105 {
106     ma_data_source_base ds;
107     ma_paged_audio_buffer_data* pData;              /* Audio data is read from here. Cannot be null. */
108     ma_paged_audio_buffer_page* pCurrent;
109     ma_uint64 relativeCursor;                       /* Relative to the current page. */
110     ma_uint64 absoluteCursor;
111 } ma_paged_audio_buffer;
112 
113 MA_API ma_result ma_paged_audio_buffer_init(const ma_paged_audio_buffer_config* pConfig, ma_paged_audio_buffer* pPagedAudioBuffer);
114 MA_API void ma_paged_audio_buffer_uninit(ma_paged_audio_buffer* pPagedAudioBuffer);
115 MA_API ma_result ma_paged_audio_buffer_read_pcm_frames(ma_paged_audio_buffer* pPagedAudioBuffer, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead);   /* Returns MA_AT_END if no more pages available. */
116 MA_API ma_result ma_paged_audio_buffer_seek_to_pcm_frame(ma_paged_audio_buffer* pPagedAudioBuffer, ma_uint64 frameIndex);
117 MA_API ma_result ma_paged_audio_buffer_get_cursor_in_pcm_frames(ma_paged_audio_buffer* pPagedAudioBuffer, ma_uint64* pCursor);
118 MA_API ma_result ma_paged_audio_buffer_get_length_in_pcm_frames(ma_paged_audio_buffer* pPagedAudioBuffer, ma_uint64* pLength);
119 
120 
121 /*
122 Resource Management
123 ===================
124 Many programs will want to manage sound resources for things such as reference counting and streaming. This is supported by miniaudio via the
125 `ma_resource_manager` API.
126 
127 The resource manager is mainly responsible for the following:
128 
129     1) Loading of sound files into memory with reference counting.
130     2) Streaming of sound data
131 
132 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
133 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
134 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
135 loaded asynchronously.
136 
137 The example below is how you can initialize a resource manager using it's default configuration:
138 
139     ```c
140     ma_resource_manager_config config;
141     ma_resource_manager resourceManager;
142 
143     config = ma_resource_manager_config_init();
144     result = ma_resource_manager_init(&config, &resourceManager);
145     if (result != MA_SUCCESS) {
146         ma_device_uninit(&device);
147         printf("Failed to initialize the resource manager.");
148         return -1;
149     }
150     ```
151 
152 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
153 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
154 this, you configure the decoded format, channels and sample rate like the code below:
155 
156     ```c
157     config = ma_resource_manager_config_init();
158     config.decodedFormat     = device.playback.format;
159     config.decodedChannels   = device.playback.channels;
160     config.decodedSampleRate = device.sampleRate;
161     ```
162 
163 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
164 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
165 which may be prohibitive in high-performance and large scale scenarios like games.
166 
167 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
168 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:
169 
170     ```c
171     config = ma_resource_manager_config_init();
172     config.jobThreadCount = MY_JOB_THREAD_COUNT;
173     ```
174 
175 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
176 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
177 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
178 `ma_resource_manager_process_job()`:
179 
180     ```c
181     config = ma_resource_manager_config_init();
182     config.jobThreadCount = 0;                            // Don't manage any job threads internally.
183     config.flags = MA_RESOURCE_MANAGER_FLAG_NON_BLOCKING; // Optional. Makes `ma_resource_manager_next_job()` non-blocking.
184 
185     // ... Initialize your custom job threads ...
186 
187     void my_custom_job_thread(...)
188     {
189         for (;;) {
190             ma_job job;
191             ma_result result = ma_resource_manager_next_job(pMyResourceManager, &job);
192             if (result != MA_SUCCESS) {
193                 if (result == MA_NOT_DATA_AVAILABLE) {
194                     // No jobs are available. Keep going. Will only get this if the resource manager was initialized
195                     // with MA_RESOURCE_MANAGER_FLAG_NON_BLOCKING.
196                     continue;
197                 } else if (result == MA_CANCELLED) {
198                     // MA_JOB_QUIT was posted. Exit.
199                     break;
200                 } else {
201                     // Some other error occurred.
202                     break;
203                 }
204             }
205 
206             ma_resource_manager_process_job(pMyResourceManager, &job);
207         }
208     }
209     ```
210 
211 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
212 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
213 with the MA_RESOURCE_MANAGER_FLAG_NON_BLOCKING configuration flag.
214 
215 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
216 resource manager's config:
217 
218     ```c
219     // Initialize your custom VFS object. See documentation for VFS for information on how to do this.
220     my_custom_vfs vfs = my_custom_vfs_init();
221 
222     config = ma_resource_manager_config_init();
223     config.pVFS = &vfs;
224     ```
225 
226 If you do not specify a custom VFS, the resource manager will use the operating system's normal file operations. This is default.
227 
228 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
229 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
230 caller is responsible for the allocation and freeing of the data source. Below is an example for initializing a data source:
231 
232     ```c
233     ma_resource_manager_data_source dataSource;
234     ma_result result = ma_resource_manager_data_source_init(pResourceManager, pFilePath, flags, &dataSource);
235     if (result != MA_SUCCESS) {
236         // Error.
237     }
238 
239     // ...
240 
241     // A ma_resource_manager_data_source object is compatible with the `ma_data_source` API. To read data, just call
242     // the `ma_data_source_read_pcm_frames()` like you would with any normal data source.
243     result = ma_data_source_read_pcm_frames(&dataSource, pDecodedData, frameCount, &framesRead);
244     if (result != MA_SUCCESS) {
245         // Failed to read PCM frames.
246     }
247 
248     // ...
249 
250     ma_resource_manager_data_source_uninit(pResourceManager, &dataSource);
251     ```
252 
253 The `flags` parameter specifies how you want to perform loading of the sound file. It can be a combination of the following flags:
254 
255     ```
256     MA_DATA_SOURCE_STREAM
257     MA_DATA_SOURCE_DECODE
258     MA_DATA_SOURCE_ASYNC
259     ```
260 
261 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
262 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
263 `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
264 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
265 `MA_DATA_SOURCE_ASYNC` flag. This will result in `ma_resource_manager_data_source_init()` returning quickly, but no data will be returned by
266 `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
267 returned by `ma_data_source_read_pcm_frames()`.
268 
269 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
270 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
271 queue and then subsequently processed in a job thread.
272 
273 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.
274 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.
275 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
276 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
277 will be returned if the sound failed to load.
278 
279 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()`
280 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
281 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
282 file path. Use `ma_resource_manager_register_decoded_data()`, `ma_resource_manager_register_encoded_data()` and `ma_resource_manager_unregister_data()` to do
283 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
284 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
285 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
286 `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
287 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
288 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
289 a self-managed data pointer. When `MA_DATA_SOURCE_STREAM` is specified, it will try loading the file data through the VFS.
290 
291 
292 Resource Manager Implementation Details
293 ---------------------------------------
294 Resources are managed in two main ways:
295 
296     1) By storing the entire sound inside an in-memory buffer (referred to as a data buffer - `ma_resource_manager_data_buffer_node`)
297     2) By streaming audio data on the fly (referred to as a data stream - `ma_resource_manager_data_stream`)
298 
299 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
300 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
301 `ma_resource_manager_data_buffer_node` object.
302 
303 Another major feature of the resource manager is the ability to asynchronously decode audio files. This relieves the audio thread of time-consuming decoding
304 which can negatively affect scalability due to the audio thread needing to complete it's work extremely quickly to avoid glitching. Asynchronous decoding is
305 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
306 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
307 can all run in parallel without needing to worry about the order of execution (how this is achieved is explained below).
308 
309 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
310 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
311 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
312 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
313 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
314 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
315 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
316 frames available starting from the current position.
317 
318 
319 Data Buffers
320 ------------
321 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
322 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
323 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
324 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
325 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.
326 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
327 your data sources.
328 
329 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.
330 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
331 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
332 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).
333 
334 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
335 loading and then the function instantly returns, setting an internal result code to `MA_BUSY`. This result code is returned when the program calls
336 `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
337 completed.
338 
339 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
340 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
341 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
342 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
343 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,
344 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
345 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
346 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
347 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
348 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
349 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
350 final realloc() when the end of the file has been reached.
351 
352 
353 Data Streams
354 ------------
355 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
356 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
357 been read, a job will be posted to load the next page which is done from the VFS.
358 
359 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
360 decoded. When unset, `ma_resource_manager_data_source_init()` will wait until the two pages have been loaded, otherwise it will return immediately.
361 
362 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.
363 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
364 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`
365 will be returned when trying to read frames.
366 
367 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
368 from the same thread that called `ma_resource_manager_data_source_read_pcm_frames()` which should be lock-free.
369 
370 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
371 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
372 uninitialized while pages are in the middle of decoding, they must complete before destroying any underlying object and the job system handles this cleanly.
373 
374 
375 Job Queue
376 ---------
377 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
378 using the algorithm described by Michael and Scott: Nonblocking Algorithms and Preemption-Safe Locking on Multiprogrammed Shared Memory Multiprocessors. In
379 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
380 allocating an index into a fixed sized array, with reference counting for mitigation of the ABA problem. The reference count is 32-bit.
381 
382 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
383 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
384 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
385 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
386 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
387 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
388 multiple threads comes into play when loading multiple sounds at the time time.
389 */
390 MA_API void ma_copy_and_apply_volume_factor_per_channel_f32(float* pFramesOut, const float* pFramesIn, ma_uint64 frameCount, ma_uint32 channels, float* pChannelGains);
391 MA_API void ma_apply_volume_factor_per_channel_f32(float* pFramesOut, ma_uint64 frameCount, ma_uint32 channels, float* pChannelGains);
392 
393 MA_API size_t ma_get_accumulation_bytes_per_sample(ma_format format);
394 MA_API size_t ma_get_accumulation_bytes_per_frame(ma_format format, ma_uint32 channels);
395 
396 
397 typedef struct
398 {
399     ma_uint32 channels;
400     ma_uint32 smoothTimeInFrames;
401 } ma_gainer_config;
402 
403 MA_API ma_gainer_config ma_gainer_config_init(ma_uint32 channels, ma_uint32 smoothTimeInFrames);
404 
405 
406 typedef struct
407 {
408     ma_gainer_config config;
409     ma_uint32 t;
410     float* pOldGains;
411     float* pNewGains;
412 
413     /* Memory management. */
414     void* _pHeap;
415     ma_bool32 _ownsHeap;
416 } ma_gainer;
417 
418 MA_API ma_result ma_gainer_get_heap_size(const ma_gainer_config* pConfig, size_t* pHeapSizeInBytes);
419 MA_API ma_result ma_gainer_init_preallocated(const ma_gainer_config* pConfig, void* pHeap, ma_gainer* pGainer);
420 MA_API ma_result ma_gainer_init(const ma_gainer_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_gainer* pGainer);
421 MA_API void ma_gainer_uninit(ma_gainer* pGainer, const ma_allocation_callbacks* pAllocationCallbacks);
422 MA_API ma_result ma_gainer_process_pcm_frames(ma_gainer* pGainer, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount);
423 MA_API ma_result ma_gainer_set_gain(ma_gainer* pGainer, float newGain);
424 MA_API ma_result ma_gainer_set_gains(ma_gainer* pGainer, float* pNewGains);
425 
426 
427 /*
428 Routing Infrastructure
429 ======================
430 miniaudio's routing infrastructure follows a node graph paradigm. The idea is that you create a
431 node whose outputs are attached to inputs of another node, thereby creating a graph. There are
432 different types of nodes, with each node in the graph processing input data to produce output,
433 which is then fed through the chain. Each node in the graph can apply their own custom effects. At
434 the end of the graph is an endpoint which represents the end of the chain and is where the final
435 output is ultimately extracted from.
436 
437 Each node has a number of input buses and a number of output buses. An output bus from a node is
438 attached to an input bus of another. Multiple nodes can connect their output buses to another
439 node's input bus, in which case their outputs will be mixed before processing by the node.
440 
441 Each input bus must be configured to accept the same number of channels, but input buses and output
442 buses can each have different channel counts, in which case miniaudio will automatically convert
443 the input data to the output channel count before processing. The number of channels of an output
444 bus of one node must match the channel count of the input bus it's attached to. The channel counts
445 cannot be changed after the node has been initialized. If you attempt to attach an output bus to
446 an input bus with a different channel count, attachment will fail.
447 
448 To use a node graph, you first need to initialize a `ma_node_graph` object. This is essentially a
449 container around the entire graph. The `ma_node_graph` object is required for some thread-safety
450 issues which will be explained later. A `ma_node_graph` object is initialized using miniaudio's
451 standard config/init system:
452 
453     ```c
454     ma_node_graph_config nodeGraphConfig = ma_node_graph_config_init(myChannelCount);
455 
456     result = ma_node_graph_init(&nodeGraphConfig, NULL, &nodeGraph);    // Second parameter is a pointer to allocation callbacks.
457     if (result != MA_SUCCESS) {
458         // Failed to initialize node graph.
459     }
460     ```
461 
462 When you initialize the node graph, you're specifying the channel count of the endpoint. The
463 endpoint is a special node which has one input bus and one output bus, both of which have the
464 same channel count, which is specified in the config. Any nodes that connect directly to the
465 endpoint must be configured such that their output buses have the same channel count. When you read
466 audio data from the node graph, it'll have the channel count you specified in the config. To read
467 data from the graph:
468 
469     ```c
470     ma_uint32 framesRead;
471     result = ma_node_graph_read_pcm_frames(&nodeGraph, pFramesOut, frameCount, &framesRead);
472     if (result != MA_SUCCESS) {
473         // Failed to read data from the node graph.
474     }
475     ```
476 
477 When you read audio data, miniaudio starts at the node graph's endpoint node which then pulls in
478 data from it's input attachments, which in turn recusively pull in data from their inputs, and so
479 on. At the very base of the graph there will be some kind of data source node which will have zero
480 inputs and will instead read directly from a data source. The base nodes don't literally need to
481 read from a `ma_data_source` object, but they will always have some kind of underlying object that
482 sources some kind of audio. The `ma_data_source_node` node can be used to read from a
483 `ma_data_source`. Data is always in floating-point format and in the number of channels you
484 specified when the graph was initialized. The sample rate is defined by the underlying data sources.
485 It's up to you to ensure they use a consistent and appropraite sample rate.
486 
487 The `ma_node` API is designed to allow custom nodes to be implemented with relative ease, but
488 miniaudio includes a few stock nodes for common functionality. This is how you would initialize a
489 node which reads directly from a data source (`ma_data_source_node`) which is an example of one
490 of the stock nodes that comes with miniaudio:
491 
492     ```c
493     ma_data_source_node_config config = ma_data_source_node_config_init(pMyDataSource, isLooping);
494 
495     ma_data_source_node dataSourceNode;
496     result = ma_data_source_node_init(&nodeGraph, &config, NULL, &dataSourceNode);
497     if (result != MA_SUCCESS) {
498         // Failed to create data source node.
499     }
500     ```
501 
502 The data source node will use the output channel count to determine the channel count of the output
503 bus. There will be 1 output bus and 0 input buses (data will be drawn directly from the data
504 source). The data source must output to floating-point (ma_format_f32) or else an error will be
505 returned from `ma_data_source_node_init()`.
506 
507 By default the node will not be attached to the graph. To do so, use `ma_node_attach_output_bus()`:
508 
509     ```c
510     result = ma_node_attach_output_bus(&dataSourceNode, 0, ma_node_graph_get_endpoint(&nodeGraph), 0);
511     if (result != MA_SUCCESS) {
512         // Failed to attach node.
513     }
514     ```
515 
516 The code above connects the data source node directly to the endpoint. Since the data source node
517 has only a single output bus, the index will always be 0. Likewise, the endpoint only has a single
518 input bus which means the input bus index will also always be 0.
519 
520 To detach a specific output bus, use `ma_node_detach_output_bus()`. To detach all output buses, use
521 `ma_node_detach_all_output_buses()`. If you want to just move the output bus from one attachment to
522 another, you do not need to detach first. You can just call `ma_node_attach_output_bus()` and it'll
523 deal with it for you.
524 
525 Less frequently you may want to create a specialized node. This will be a node where you implement
526 your own processing callback to apply a custom effect of some kind. This is similar to initalizing
527 one of the stock node types, only this time you need to specify a pointer to a vtable containing a
528 pointer to the processing function and the number of input and output buses. Example:
529 
530     ```c
531     static void my_custom_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut)
532     {
533         // Do some processing of ppFramesIn (one stream of audio data per input bus)
534         const float* pFramesIn_0 = ppFramesIn[0]; // Input bus @ index 0.
535         const float* pFramesIn_1 = ppFramesIn[1]; // Input bus @ index 1.
536         float* pFramesOut_0 = ppFramesOut[0];     // Output bus @ index 0.
537 
538         // Do some processing. On input, `pFrameCountIn` will be the number of input frames in each
539         // buffer in `ppFramesIn` and `pFrameCountOut` will be the capacity of each of the buffers
540         // in `ppFramesOut`. On output, `pFrameCountIn` should be set to the number of input frames
541         // your node consumed and `pFrameCountOut` should be set the number of output frames that
542         // were produced.
543         //
544         // You should process as many frames as you can. If your effect consumes input frames at the
545         // same rate as output frames (always the case, unless you're doing resampling), you need
546         // only look at `ppFramesOut` and process that exact number of frames. If you're doing
547         // resampling, you'll need to be sure to set both `pFrameCountIn` and `pFrameCountOut`
548         // properly.
549     }
550 
551     static ma_node_vtable my_custom_node_vtable =
552     {
553         my_custom_node_process_pcm_frames, // The function that will be called process your custom node. This is where you'd implement your effect processing.
554         NULL,   // Optional. A callback for calculating the number of input frames that are required to process a specified number of output frames.
555         2,      // 2 input buses.
556         1,      // 1 output bus.
557         0       // Default flags.
558     };
559 
560     ...
561 
562     // Each bus needs to have a channel count specified. To do this you need to specify the channel
563     // counts in an array and then pass that into the node config.
564     ma_uint32 inputChannels[2];     // Equal in size to the number of input channels specified in the vtable.
565     ma_uint32 outputChannels[1];    // Equal in size to the number of output channels specicied in the vtable.
566 
567     inputChannels[0]  = channelsIn;
568     inputChannels[1]  = channelsIn;
569     outputChannels[0] = channelsOut;
570 
571     ma_node_config nodeConfig = ma_node_config_init();
572     nodeConfig.vtable          = &my_custom_node_vtable;
573     nodeConfig.pInputChannels  = inputChannels;
574     nodeConfig.pOutputChannels = outputChannels;
575 
576     ma_node_base node;
577     result = ma_node_init(&nodeGraph, &nodeConfig, NULL, &node);
578     if (result != MA_SUCCESS) {
579         // Failed to initialize node.
580     }
581     ```
582 
583 When initializing a custom node, as in the code above, you'll normally just place your vtable in
584 static space. The number of input and output buses are specified as part of the vtable. If you need
585 a variable number of buses on a per-node bases, the vtable should have the relevant bus count set
586 to `MA_NODE_BUS_COUNT_UNKNOWN`. In this case, the bus count should be set in the node config:
587 
588     ```c
589     static ma_node_vtable my_custom_node_vtable =
590     {
591         my_custom_node_process_pcm_frames, // The function that will be called process your custom node. This is where you'd implement your effect processing.
592         NULL,   // Optional. A callback for calculating the number of input frames that are required to process a specified number of output frames.
593         MA_NODE_BUS_COUNT_UNKNOWN,  // The number of input buses is determined on a per-node basis.
594         1,      // 1 output bus.
595         0       // Default flags.
596     };
597 
598     ...
599 
600     ma_node_config nodeConfig = ma_node_config_init();
601     nodeConfig.vtable          = &my_custom_node_vtable;
602     nodeConfig.inputBusCount   = myBusCount;        // <-- Since the vtable specifies MA_NODE_BUS_COUNT_UNKNOWN, the input bus count should be set here.
603     nodeConfig.pInputChannels  = inputChannels;     // <-- Make sure there are nodeConfig.inputBusCount elements in this array.
604     nodeConfig.pOutputChannels = outputChannels;    // <-- The vtable specifies 1 output bus, so there must be 1 element in this array.
605     ```
606 
607 In the above example it's important to never set the `inputBusCount` and `outputBusCount` members
608 to anything other than their defaults if the vtable specifies an explicit count. They can only be
609 set if the vtable specifies MA_NODE_BUS_COUNT_UNKNOWN in the relevant bus count.
610 
611 Most often you'll want to create a structure to encapsulate your node with some extra data. You
612 need to make sure the `ma_node_base` object is your first member of the structure:
613 
614     ```c
615     typedef struct
616     {
617         ma_node_base base; // <-- Make sure this is always the first member.
618         float someCustomData;
619     } my_custom_node;
620     ```
621 
622 By doing this, your object will be compatible with all `ma_node` APIs and you can attach it to the
623 graph just like any other node.
624 
625 In the custom processing callback (`my_custom_node_process_pcm_frames()` in the example above), the
626 number of channels for each bus is what was specified by the config when the node was initialized
627 with `ma_node_init()`. In addition, all attachments to each of the input buses will have been
628 pre-mixed by miniaudio. The config allows you to specify different channel counts for each
629 individual input and output bus. It's up to the effect to handle it appropriate, and if it can't,
630 return an error in it's initialization routine.
631 
632 Custom nodes can be assigned some flags to describe their behaviour. These are set via the vtable
633 and include the following:
634 
635     +-----------------------------------------+---------------------------------------------------+
636     | Flag Name                               | Description                                       |
637     +-----------------------------------------+---------------------------------------------------+
638     | MA_NODE_FLAG_PASSTHROUGH                | Useful for nodes that do not do any kind of audio |
639     |                                         | processing, but are instead used for tracking     |
640     |                                         | time, handling events, etc. Also used by the      |
641     |                                         | internal endpoint node. It reads directly from    |
642     |                                         | the input bus to the output bus. Nodes with this  |
643     |                                         | flag must have exactly 1 input bus and 1 output   |
644     |                                         | bus, and both buses must have the same channel    |
645     |                                         | counts.                                           |
646     +-----------------------------------------+---------------------------------------------------+
647     | MA_NODE_FLAG_CONTINUOUS_PROCESSING      | Causes the processing callback to be called even  |
648     |                                         | when no data is available to be read from input   |
649     |                                         | attachments. This is useful for effects like      |
650     |                                         | echos where there will be a tail of audio data    |
651     |                                         | that still needs to be processed even when the    |
652     |                                         | original data sources have reached their ends.    |
653     +-----------------------------------------+---------------------------------------------------+
654     | MA_NODE_FLAG_ALLOW_NULL_INPUT           | Used in conjunction with                          |
655     |                                         | `MA_NODE_FLAG_CONTINUOUS_PROCESSING`. When this   |
656     |                                         | is set, the `ppFramesIn` parameter of the         |
657     |                                         | processing callback will be set to NULL when      |
658     |                                         | there are no input frames are available. When     |
659     |                                         | this is unset, silence will be posted to the      |
660     |                                         | processing callback.                              |
661     +-----------------------------------------+---------------------------------------------------+
662     | MA_NODE_FLAG_DIFFERENT_PROCESSING_RATES | Used to tell miniaudio that input and output      |
663     |                                         | frames are processed at different rates. You      |
664     |                                         | should set this for any nodes that perform        |
665     |                                         | resampling.                                       |
666     +-----------------------------------------+---------------------------------------------------+
667 
668 
669 If you need to make a copy of an audio stream for effect processing you can use a splitter node
670 called `ma_splitter_node`. This takes has 1 input bus and splits the stream into 2 output buses.
671 You can use it like this:
672 
673     ```c
674     ma_splitter_node_config splitterNodeConfig = ma_splitter_node_config_init(channelsIn, channelsOut);
675 
676     ma_splitter_node splitterNode;
677     result = ma_splitter_node_init(&nodeGraph, &splitterNodeConfig, NULL, &splitterNode);
678     if (result != MA_SUCCESS) {
679         // Failed to create node.
680     }
681 
682     // Attach your output buses to two different input buses (can be on two different nodes).
683     ma_node_attach_output_bus(&splitterNode, 0, ma_node_graph_get_endpoint(&nodeGraph), 0); // Attach directly to the endpoint.
684     ma_node_attach_output_bus(&splitterNode, 1, &myEffectNode,                          0); // Attach to input bus 0 of some effect node.
685     ```
686 
687 The volume of an output bus can be configured on a per-bus basis:
688 
689     ```c
690     ma_node_set_output_bus_volume(&splitterNode, 0, 0.5f);
691     ma_node_set_output_bus_volume(&splitterNode, 1, 0.5f);
692     ```
693 
694 In the code above we're using the splitter node from before and changing the volume of each of the
695 copied streams.
696 
697 You can start, stop and mute a node with the following:
698 
699     ```c
700     ma_node_set_state(&splitterNode, ma_node_state_started);    // The default state.
701     ma_node_set_state(&splitterNode, ma_node_state_stopped);
702     ma_node_set_state(&splitterNode, ma_node_state_muted);
703     ```
704 
705 By default the node is in a started state, but since it won't be connected to anything won't
706 actually be invoked by the node graph until it's connected. When you stop a node, data will not be
707 read from any of it's input connections. You can use this property to stop a group of sounds
708 atomically.
709 
710 You can configure the initial state of a node in it's config:
711 
712     ```c
713     nodeConfig.initialState = ma_node_state_stopped;
714     ```
715 
716 Note that for the stock specialized nodes, all of their configs will have a `nodeConfig` member
717 which is the config to use with the base node. This is where the initial state can be configured
718 for specialized nodes:
719 
720     ```c
721     dataSourceNodeConfig.nodeConfig.initialState = ma_node_state_stopped;
722     ```
723 
724 When using a specialized node like `ma_data_source_node` or `ma_splitter_node`, be sure to not
725 modify the `vtable` member of the `nodeConfig` object.
726 
727 
728 Timing
729 ------
730 The node graph supports starting and stopping nodes at scheduled times. This is especially useful
731 for data source nodes where you want to get the node set up, but only start playback at a specific
732 time. There are two clocks: local and global.
733 
734 A local clock is per-node, whereas the global clock is per graph. Scheduling starts and stops can
735 only be done based on the global clock because the local clock will not be running while the node
736 is stopped. The global clocks advances whenever `ma_node_graph_read_pcm_frames()` is called. On the
737 other hand, the local clock only advances when the node's processing callback is fired, and is
738 advanced based on the output frame count.
739 
740 To retrieve the global time, use `ma_node_graph_get_time()`. The global time can be set with
741 `ma_node_graph_set_time()` which might be useful if you want to do seeking on a global timeline.
742 Getting and setting the local time is similar. Use `ma_node_get_time()` to retrieve the local time,
743 and `ma_node_set_time()` to set the local time. The global and local times will be advanced by the
744 audio thread, so care should be taken to avoid data races. Ideally you should avoid calling these
745 outside of the node processing callbacks which are always run on the audio thread.
746 
747 There is basic support for scheduling the starting and stopping of nodes. You can only schedule one
748 start and one stop at a time. This is mainly intended for putting nodes into a started or stopped
749 state in a frame-exact manner. Without this mechanism, starting and stopping of a node is limited
750 to the resolution of a call to `ma_node_graph_read_pcm_frames()` which would typically be in blocks
751 of several milliseconds. The following APIs can be used for scheduling node states:
752 
753     ```c
754     ma_node_set_state_time()
755     ma_node_get_state_time()
756     ```
757 
758 The time is absolute and must be based on the global clock. An example is below:
759 
760     ```c
761     ma_node_set_state_time(&myNode, ma_node_state_started, sampleRate*1);   // Delay starting to 1 second.
762     ma_node_set_state_time(&myNode, ma_node_state_stopped, sampleRate*5);   // Delay stopping to 5 seconds.
763     ```
764 
765 An example for changing the state using a relative time.
766 
767     ```c
768     ma_node_set_state_time(&myNode, ma_node_state_started, sampleRate*1 + ma_node_graph_get_time(&myNodeGraph));
769     ma_node_set_state_time(&myNode, ma_node_state_stopped, sampleRate*5 + ma_node_graph_get_time(&myNodeGraph));
770     ```
771 
772 Note that due to the nature of multi-threading the times may not be 100% exact. If this is an
773 issue, consider scheduling state changes from within a processing callback. An idea might be to
774 have some kind of passthrough trigger node that is used specifically for tracking time and handling
775 events.
776 
777 
778 
779 Thread Safety and Locking
780 -------------------------
781 When processing audio, it's ideal not to have any kind of locking in the audio thread. Since it's
782 expected that `ma_node_graph_read_pcm_frames()` would be run on the audio thread, it does so
783 without the use of any locks. This section discusses the implementation used by miniaudio and goes
784 over some of the compromises employed by miniaudio to achieve this goal. Note that the current
785 implementation may not be ideal - feedback and critiques are most welcome.
786 
787 The node graph API is not *entirely* lock-free. Only `ma_node_graph_read_pcm_frames()` is expected
788 to be lock-free. Attachment, detachment and uninitialization of nodes use locks to simplify the
789 implementation, but are crafted in a way such that such locking is not required when reading audio
790 data from the graph. Locking in these areas are achieved by means of spinlocks.
791 
792 The main complication with keeping `ma_node_graph_read_pcm_frames()` lock-free stems from the fact
793 that a node can be uninitialized, and it's memory potentially freed, while in the middle of being
794 processed on the audio thread. There are times when the audio thread will be referencing a node,
795 which means the uninitialization process of a node needs to make sure it delays returning until the
796 audio thread is finished so that control is not handed back to the caller thereby giving them a
797 chance to free the node's memory.
798 
799 When the audio thread is processing a node, it does so by reading from each of the output buses of
800 the node. In order for a node to process data for one of it's output buses, it needs to read from
801 each of it's input buses, and so on an so forth. It follows that once all output buses of a node
802 are detached, the node as a whole will be disconnected and no further processing will occur unless
803 it's output buses are reattached, which won't be happening when the node is being uninitialized.
804 By having `ma_node_detach_output_bus()` wait until the audio thread is finished with it, we can
805 simplify a few things, at the expense of making `ma_node_detach_output_bus()` a bit slower. By
806 doing this, the implementation of `ma_node_uninit()` becomes trivial - just detach all output
807 nodes, followed by each of the attachments to each of it's input nodes, and then do any final clean
808 up.
809 
810 With the above design, the worst-case scenario is `ma_node_detach_output_bus()` taking as long as
811 it takes to process the output bus being detached. This will happen if it's called at just the
812 wrong moment where the audio thread has just iterated it and has just started processing. The
813 caller of `ma_node_detach_output_bus()` will stall until the audio thread is finished, which
814 includes the cost of recursively processing it's inputs. This is the biggest compromise made with
815 the approach taken by miniaudio for it's lock-free processing system. The cost of detaching nodes
816 earlier in the pipeline (data sources, for example) will be cheaper than the cost of detaching
817 higher level nodes, such as some kind of final post-processing endpoint. If you need to do mass
818 detachments, detach starting from the lowest level nodes and work your way towards the final
819 endpoint node (but don't try detaching the node graph's endpoint). If the audio thread is not
820 running, detachment will be fast and detachment in any order will be the same. The reason nodes
821 need to wait for their input attachments to complete is due to the potential for desyncs between
822 data sources. If the node was to terminate processing mid way through processing it's inputs,
823 there's a chance that some of the underlying data sources will have been read, but then others not.
824 That will then result in a potential desynchronization when detaching and reattaching higher-level
825 nodes. A possible solution to this is to have an option when detaching to terminate processing
826 before processing all input attachments which should be fairly simple.
827 
828 Another compromise, albeit less significant, is locking when attaching and detaching nodes. This
829 locking is achieved by means of a spinlock in order to reduce memory overhead. A lock is present
830 for each input bus and output bus. When an output bus is connected to an input bus, both the output
831 bus and input bus is locked. This locking is specifically for attaching and detaching across
832 different threads and does not affect `ma_node_graph_read_pcm_frames()` in any way. The locking and
833 unlocking is mostly self-explanatory, but a slightly less intuitive aspect comes into it when
834 considering that iterating over attachments must not break as a result of attaching or detaching a
835 node while iteration is occuring.
836 
837 Attaching and detaching are both quite simple. When an output bus of a node is attached to an input
838 bus of another node, it's added to a linked list. Basically, an input bus is a linked list, where
839 each item in the list is and output bus. We have some intentional (and convenient) restrictions on
840 what can done with the linked list in order to simplify the implementation. First of all, whenever
841 something needs to iterate over the list, it must do so in a forward direction. Backwards iteration
842 is not supported. Also, items can only be added to the start of the list.
843 
844 The linked list is a doubly-linked list where each item in the list (an output bus) holds a pointer
845 to the next item in the list, and another to the previous item. A pointer to the previous item is
846 only required for fast detachment of the node - it is never used in iteration. This is an
847 important property because it means from the perspective of iteration, attaching and detaching of
848 an item can be done with a single atomic assignment. This is exploited by both the attachment and
849 detachment process. When attaching the node, the first thing that is done is the setting of the
850 local "next" and "previous" pointers of the node. After that, the item is "attached" to the list
851 by simply performing an atomic exchange with the head pointer. After that, the node is "attached"
852 to the list from the perspective of iteration. Even though the "previous" pointer of the next item
853 hasn't yet been set, from the perspective of iteration it's been attached because iteration will
854 only be happening in a forward direction which means the "previous" pointer won't actually ever get
855 used. The same general process applies to detachment. See `ma_node_attach_output_bus()` and
856 `ma_node_detach_output_bus()` for the implementation of this mechanism.
857 */
858 
859 
860 /* Must never exceed 254. */
861 #ifndef MA_MAX_NODE_BUS_COUNT
862 #define MA_MAX_NODE_BUS_COUNT       254
863 #endif
864 
865 /* Used internally by miniaudio for memory management. Must never exceed MA_MAX_NODE_BUS_COUNT. */
866 #ifndef MA_MAX_NODE_LOCAL_BUS_COUNT
867 #define MA_MAX_NODE_LOCAL_BUS_COUNT 2
868 #endif
869 
870 /* Use this when the bus count is determined by the node instance rather than the vtable. */
871 #define MA_NODE_BUS_COUNT_UNKNOWN   255
872 
873 typedef struct ma_node_graph ma_node_graph;
874 typedef void ma_node;
875 
876 
877 /* Node flags. */
878 #define MA_NODE_FLAG_PASSTHROUGH                0x00000001
879 #define MA_NODE_FLAG_CONTINUOUS_PROCESSING      0x00000002
880 #define MA_NODE_FLAG_ALLOW_NULL_INPUT           0x00000004
881 #define MA_NODE_FLAG_DIFFERENT_PROCESSING_RATES 0x00000008
882 
883 
884 /* The playback state of a node. Either started or stopped. */
885 typedef enum
886 {
887     ma_node_state_started = 0,
888     ma_node_state_stopped = 1
889 } ma_node_state;
890 
891 
892 typedef struct
893 {
894     /*
895     Extended processing callback. This callback is used for effects that process input and output
896     at different rates (i.e. they perform resampling). This is similar to the simple version, only
897     they take two sepate frame counts: one for input, and one for output.
898 
899     On input, `pFrameCountOut` is equal to the capacity of the output buffer for each bus, whereas
900     `pFrameCountIn` will be equal to the number of PCM frames in each of the buffers in `ppFramesIn`.
901 
902     On output, set `pFrameCountOut` to the number of PCM frames that were actually output and set
903     `pFrameCountIn` to the number of input frames that were consumed.
904     */
905     void (* onProcess)(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut);
906 
907     /*
908     A callback for retrieving the number of a input frames that are required to output the
909     specified number of output frames. You would only want to implement this when the node performs
910     resampling. This is optional, even for nodes that perform resampling, but it does offer a
911     small reduction in latency as it allows miniaudio to calculate the exact number of input frames
912     to read at a time instead of having to estimate.
913     */
914     ma_uint32 (* onGetRequiredInputFrameCount)(ma_node* pNode, ma_uint32 outputFrameCount);
915 
916     /*
917     The number of input buses. This is how many sub-buffers will be contained in the `ppFramesIn`
918     parameters of the callbacks above.
919     */
920     ma_uint8 inputBusCount;
921 
922     /*
923     The number of output buses. This is how many sub-buffers will be contained in the `ppFramesOut`
924     parameters of the callbacks above.
925     */
926     ma_uint8 outputBusCount;
927 
928     /*
929     Flags describing characteristics of the node. This is currently just a placeholder for some
930     ideas for later on.
931     */
932     ma_uint32 flags;
933 } ma_node_vtable;
934 
935 typedef struct
936 {
937     const ma_node_vtable* vtable;       /* Should never be null. Initialization of the node will fail if so. */
938     ma_node_state initialState;         /* Defaults to ma_node_state_started. */
939     ma_uint32 inputBusCount;            /* Only used if the vtable specifies an input bus count of `MA_NODE_BUS_COUNT_UNKNOWN`, otherwise must be set to `MA_NODE_BUS_COUNT_UNKNOWN` (default). */
940     ma_uint32 outputBusCount;           /* Only used if the vtable specifies an output bus count of `MA_NODE_BUS_COUNT_UNKNOWN`, otherwise  be set to `MA_NODE_BUS_COUNT_UNKNOWN` (default). */
941     const ma_uint32* pInputChannels;    /* The number of elements are determined by the input bus count as determined by the vtable, or `inputBusCount` if the vtable specifies `MA_NODE_BUS_COUNT_UNKNOWN`. */
942     const ma_uint32* pOutputChannels;   /* The number of elements are determined by the output bus count as determined by the vtable, or `outputBusCount` if the vtable specifies `MA_NODE_BUS_COUNT_UNKNOWN`. */
943 } ma_node_config;
944 
945 MA_API ma_node_config ma_node_config_init(void);
946 
947 
948 /*
949 A node has multiple output buses. An output bus is attached to an input bus as an item in a linked
950 list. Think of the input bus as a linked list, with the output bus being an item in that list.
951 */
952 #define MA_NODE_OUTPUT_BUS_FLAG_HAS_READ    0x01    /* Whether or not this bus ready to read more data. Only used on nodes with multiple output buses. */
953 
954 typedef struct ma_node_output_bus ma_node_output_bus;
955 struct ma_node_output_bus
956 {
957     /* Immutable. */
958     ma_node* pNode;                             /* The node that owns this output bus. The input node. Will be null for dummy head and tail nodes. */
959     ma_uint8 outputBusIndex;                    /* The index of the output bus on pNode that this output bus represents. */
960     ma_uint8 channels;                          /* The number of channels in the audio stream for this bus. */
961 
962     /* Mutable via multiple threads. Must be used atomically. The weird ordering here is for packing reasons. */
963     MA_ATOMIC ma_uint8 inputNodeInputBusIndex;  /* The index of the input bus on the input. Required for detaching. */
964     MA_ATOMIC ma_uint8 flags;                   /* Some state flags for tracking the read state of the output buffer. */
965     MA_ATOMIC ma_uint16 refCount;               /* Reference count for some thread-safety when detaching. */
966     MA_ATOMIC ma_bool8 isAttached;              /* This is used to prevent iteration of nodes that are in the middle of being detached. Used for thread safety. */
967     MA_ATOMIC ma_spinlock lock;                 /* Unfortunate lock, but significantly simplifies the implementation. Required for thread-safe attaching and detaching. */
968     MA_ATOMIC float volume;                     /* Linear. */
969     MA_ATOMIC ma_node_output_bus* pNext;        /* If null, it's the tail node or detached. */
970     MA_ATOMIC ma_node_output_bus* pPrev;        /* If null, it's the head node or detached. */
971     MA_ATOMIC ma_node* pInputNode;              /* The node that this output bus is attached to. Required for detaching. */
972 };
973 
974 /*
975 A node has multiple input buses. The output buses of a node are connecting to the input busses of
976 another. An input bus is essentially just a linked list of output buses.
977 */
978 typedef struct ma_node_input_bus ma_node_input_bus;
979 struct ma_node_input_bus
980 {
981     /* Mutable via multiple threads. */
982     ma_node_output_bus head;            /* Dummy head node for simplifying some lock-free thread-safety stuff. */
983     MA_ATOMIC ma_uint16 nextCounter;    /* This is used to determine whether or not the input bus is finding the next node in the list. Used for thread safety when detaching output buses. */
984     MA_ATOMIC ma_spinlock lock;         /* Unfortunate lock, but significantly simplifies the implementation. Required for thread-safe attaching and detaching. */
985 
986     /* Set once at startup. */
987     ma_uint8 channels;                  /* The number of channels in the audio stream for this bus. */
988 };
989 
990 
991 typedef struct ma_node_base ma_node_base;
992 struct ma_node_base
993 {
994     /* These variables are set once at startup. */
995     ma_node_graph* pNodeGraph;  /* The graph this node belongs to. */
996     const ma_node_vtable* vtable;
997     float* pCachedData;                     /* Allocated on the heap. Fixed size. Needs to be stored on the heap because reading from output buses is done in separate function calls. */
998     ma_uint16 cachedDataCapInFramesPerBus;  /* The capacity of the input data cache in frames, per bus. */
999 
1000     /* These variables are read and written only from the audio thread. */
1001     ma_uint16 cachedFrameCountOut;
1002     ma_uint16 cachedFrameCountIn;
1003     ma_uint16 consumedFrameCountIn;
1004 
1005     /* These variables are read and written between different threads. */
1006     MA_ATOMIC ma_node_state state;          /* When set to stopped, nothing will be read, regardless of the times in stateTimes. */
1007     MA_ATOMIC ma_uint64 stateTimes[2];      /* Indexed by ma_node_state. Specifies the time based on the global clock that a node should be considered to be in the relevant state. */
1008     MA_ATOMIC ma_uint64 localTime;          /* The node's local clock. This is just a running sum of the number of output frames that have been processed. Can be modified by any thread with `ma_node_set_time()`. */
1009     ma_uint32 inputBusCount;
1010     ma_uint32 outputBusCount;
1011     ma_node_input_bus* pInputBuses;
1012     ma_node_output_bus* pOutputBuses;
1013 
1014     /* Memory management. */
1015     ma_node_input_bus _inputBuses[MA_MAX_NODE_LOCAL_BUS_COUNT];
1016     ma_node_output_bus _outputBuses[MA_MAX_NODE_LOCAL_BUS_COUNT];
1017     void* _pHeap;   /* A heap allocation for internal use only. pInputBuses and/or pOutputBuses will point to this if the bus count exceeds MA_MAX_NODE_LOCAL_BUS_COUNT. */
1018     ma_bool32 _ownsHeap;    /* If set to true, the node owns the heap allocation and _pHeap will be freed in ma_node_uninit(). */
1019 };
1020 
1021 MA_API ma_result ma_node_get_heap_size(const ma_node_config* pConfig, size_t* pHeapSizeInBytes);
1022 MA_API ma_result ma_node_init_preallocated(ma_node_graph* pNodeGraph, const ma_node_config* pConfig, void* pHeap, ma_node* pNode);
1023 MA_API ma_result ma_node_init(ma_node_graph* pNodeGraph, const ma_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_node* pNode);
1024 MA_API void ma_node_uninit(ma_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks);
1025 MA_API ma_node_graph* ma_node_get_node_graph(const ma_node* pNode);
1026 MA_API ma_uint32 ma_node_get_input_bus_count(const ma_node* pNode);
1027 MA_API ma_uint32 ma_node_get_output_bus_count(const ma_node* pNode);
1028 MA_API ma_uint32 ma_node_get_input_channels(const ma_node* pNode, ma_uint32 inputBusIndex);
1029 MA_API ma_uint32 ma_node_get_output_channels(const ma_node* pNode, ma_uint32 outputBusIndex);
1030 MA_API ma_result ma_node_attach_output_bus(ma_node* pNode, ma_uint32 outputBusIndex, ma_node* pOtherNode, ma_uint32 otherNodeInputBusIndex);
1031 MA_API ma_result ma_node_detach_output_bus(ma_node* pNode, ma_uint32 outputBusIndex);
1032 MA_API ma_result ma_node_detach_all_output_buses(ma_node* pNode);
1033 MA_API ma_result ma_node_set_output_bus_volume(ma_node* pNode, ma_uint32 outputBusIndex, float volume);
1034 MA_API float ma_node_get_output_bus_volume(const ma_node* pNode, ma_uint32 outputBusIndex);
1035 MA_API ma_result ma_node_set_state(ma_node* pNode, ma_node_state state);
1036 MA_API ma_node_state ma_node_get_state(const ma_node* pNode);
1037 MA_API ma_result ma_node_set_state_time(ma_node* pNode, ma_node_state state, ma_uint64 globalTime);
1038 MA_API ma_uint64 ma_node_get_state_time(const ma_node* pNode, ma_node_state state);
1039 MA_API ma_node_state ma_node_get_state_by_time(const ma_node* pNode, ma_uint64 globalTime);
1040 MA_API ma_node_state ma_node_get_state_by_time_range(const ma_node* pNode, ma_uint64 globalTimeBeg, ma_uint64 globalTimeEnd);
1041 MA_API ma_uint64 ma_node_get_time(const ma_node* pNode);
1042 MA_API ma_result ma_node_set_time(ma_node* pNode, ma_uint64 localTime);
1043 
1044 
1045 typedef struct
1046 {
1047     ma_uint32 channels;
1048 } ma_node_graph_config;
1049 
1050 MA_API ma_node_graph_config ma_node_graph_config_init(ma_uint32 channels);
1051 
1052 
1053 struct ma_node_graph
1054 {
1055     /* Immutable. */
1056     ma_node_base endpoint;              /* Special node that all nodes eventually connect to. Data is read from this node in ma_node_graph_read_pcm_frames(). */
1057 
1058     /* Read and written by multiple threads. */
1059     MA_ATOMIC ma_bool8 isReading;
1060 };
1061 
1062 MA_API ma_result ma_node_graph_init(const ma_node_graph_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_node_graph* pNodeGraph);
1063 MA_API void ma_node_graph_uninit(ma_node_graph* pNodeGraph, const ma_allocation_callbacks* pAllocationCallbacks);
1064 MA_API ma_node* ma_node_graph_get_endpoint(ma_node_graph* pNodeGraph);
1065 MA_API ma_result ma_node_graph_read_pcm_frames(ma_node_graph* pNodeGraph, void* pFramesOut, ma_uint32 frameCount, ma_uint32* pFramesRead);
1066 MA_API ma_uint32 ma_node_graph_get_channels(const ma_node_graph* pNodeGraph);
1067 MA_API ma_uint64 ma_node_graph_get_time(const ma_node_graph* pNodeGraph);
1068 MA_API ma_result ma_node_graph_set_time(ma_node_graph* pNodeGraph, ma_uint64 globalTime);
1069 
1070 
1071 
1072 /* Data source node. 0 input buses, 1 output bus. Used for reading from a data source. */
1073 typedef struct
1074 {
1075     ma_node_config nodeConfig;
1076     ma_data_source* pDataSource;
1077     ma_bool32 looping;  /* Can be changed after initialization with ma_data_source_node_set_looping(). */
1078 } ma_data_source_node_config;
1079 
1080 MA_API ma_data_source_node_config ma_data_source_node_config_init(ma_data_source* pDataSource, ma_bool32 looping);
1081 
1082 
1083 typedef struct
1084 {
1085     ma_node_base base;
1086     ma_data_source* pDataSource;
1087     MA_ATOMIC ma_bool32 looping;  /* This can be modified and read across different threads. Must be used atomically. */
1088 } ma_data_source_node;
1089 
1090 MA_API ma_result ma_data_source_node_init(ma_node_graph* pNodeGraph, const ma_data_source_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source_node* pDataSourceNode);
1091 MA_API void ma_data_source_node_uninit(ma_data_source_node* pDataSourceNode, const ma_allocation_callbacks* pAllocationCallbacks);
1092 MA_API ma_result ma_data_source_node_set_looping(ma_data_source_node* pDataSourceNode, ma_bool32 looping);
1093 MA_API ma_bool32 ma_data_source_node_is_looping(ma_data_source_node* pDataSourceNode);
1094 
1095 
1096 /* Splitter Node. 1 input, 2 outputs. Used for splitting/copying a stream so it can be as input into two separate output nodes. */
1097 typedef struct
1098 {
1099     ma_node_config nodeConfig;
1100 } ma_splitter_node_config;
1101 
1102 MA_API ma_splitter_node_config ma_splitter_node_config_init(ma_uint32 channels);
1103 
1104 
1105 typedef struct
1106 {
1107     ma_node_base base;
1108 } ma_splitter_node;
1109 
1110 MA_API ma_result ma_splitter_node_init(ma_node_graph* pNodeGraph, const ma_splitter_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_splitter_node* pSplitterNode);
1111 MA_API void ma_splitter_node_uninit(ma_splitter_node* pSplitterNode, const ma_allocation_callbacks* pAllocationCallbacks);
1112 
1113 
1114 
1115 
1116 /*
1117 Resource Manager Data Source Flags
1118 ==================================
1119 The flags below are used for controlling how the resource manager should handle the loading and caching of data sources.
1120 */
1121 #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. */
1122 #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. */
1123 #define MA_DATA_SOURCE_FLAG_ASYNC       0x00000004  /* When set, the resource manager will load the data source asynchronously. */
1124 #define MA_DATA_SOURCE_FLAG_WAIT_INIT   0x00000008  /* When set, waits for initialization of the underlying data source before returning from ma_resource_manager_data_source_init(). */
1125 
1126 
1127 typedef struct ma_resource_manager                  ma_resource_manager;
1128 typedef struct ma_resource_manager_data_buffer_node ma_resource_manager_data_buffer_node;
1129 typedef struct ma_resource_manager_data_buffer      ma_resource_manager_data_buffer;
1130 typedef struct ma_resource_manager_data_stream      ma_resource_manager_data_stream;
1131 typedef struct ma_resource_manager_data_source      ma_resource_manager_data_source;
1132 
1133 
1134 
1135 #ifndef MA_RESOURCE_MANAGER_JOB_QUEUE_CAPACITY
1136 #define MA_RESOURCE_MANAGER_JOB_QUEUE_CAPACITY  1024
1137 #endif
1138 
1139 #define MA_JOB_QUIT                     0x00000000
1140 #define MA_JOB_LOAD_DATA_BUFFER_NODE    0x00000001
1141 #define MA_JOB_FREE_DATA_BUFFER_NODE    0x00000002
1142 #define MA_JOB_PAGE_DATA_BUFFER_NODE    0x00000003
1143 #define MA_JOB_LOAD_DATA_BUFFER         0x00000004
1144 #define MA_JOB_FREE_DATA_BUFFER         0x00000005
1145 #define MA_JOB_LOAD_DATA_STREAM         0x00000006
1146 #define MA_JOB_FREE_DATA_STREAM         0x00000007
1147 #define MA_JOB_PAGE_DATA_STREAM         0x00000008
1148 #define MA_JOB_SEEK_DATA_STREAM         0x00000009
1149 #define MA_JOB_CUSTOM                   0x00000100  /* Number your custom job codes as (MA_JOB_CUSTOM + 0), (MA_JOB_CUSTOM + 1), etc. */
1150 
1151 
1152 /*
1153 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
1154 as the insertion point for an object.
1155 
1156 Slots are reference counted to help mitigate the ABA problem in the lock-free queue we use for tracking jobs.
1157 
1158 The slot index is stored in the low 32 bits. The reference counter is stored in the high 32 bits:
1159 
1160     +-----------------+-----------------+
1161     | 32 Bits         | 32 Bits         |
1162     +-----------------+-----------------+
1163     | Reference Count | Slot Index      |
1164     +-----------------+-----------------+
1165 */
1166 typedef struct
1167 {
1168     struct
1169     {
1170         MA_ATOMIC ma_uint32 bitfield;                           /* Must be used atomically because the allocation and freeing routines need to make copies of this which must never be optimized away by the compiler. */
1171     } groups[MA_RESOURCE_MANAGER_JOB_QUEUE_CAPACITY/32];
1172     ma_uint32 slots[MA_RESOURCE_MANAGER_JOB_QUEUE_CAPACITY];    /* 32 bits for reference counting for ABA mitigation. */
1173     ma_uint32 count;    /* Allocation count. */
1174 } ma_slot_allocator;
1175 
1176 MA_API ma_result ma_slot_allocator_init(ma_slot_allocator* pAllocator);
1177 MA_API ma_result ma_slot_allocator_alloc(ma_slot_allocator* pAllocator, ma_uint64* pSlot);
1178 MA_API ma_result ma_slot_allocator_free(ma_slot_allocator* pAllocator, ma_uint64 slot);
1179 
1180 
1181 
1182 /*
1183 Fence
1184 =====
1185 This locks while the counter is larger than 0. Counter can be incremented and decremented by any
1186 thread, but care needs to be taken when waiting. It is possible for one thread to acquire the
1187 fence just as another thread returns from ma_fence_wait().
1188 
1189 The idea behind a fence is to allow you to wait for a group of operations to complete. When an
1190 operation starts, the counter is incremented which locks the fence. When the operation completes,
1191 the fence will be released which decrements the counter. ma_fence_wait() will block until the
1192 counter hits zero.
1193 */
1194 typedef struct
1195 {
1196     ma_event e;
1197     ma_uint32 counter;
1198 } ma_fence;
1199 
1200 MA_API ma_result ma_fence_init(ma_fence* pFence);
1201 MA_API void ma_fence_uninit(ma_fence* pFence);
1202 MA_API ma_result ma_fence_acquire(ma_fence* pFence);    /* Increment counter. */
1203 MA_API ma_result ma_fence_release(ma_fence* pFence);    /* Decrement counter. */
1204 MA_API ma_result ma_fence_wait(ma_fence* pFence);       /* Wait for counter to reach 0. */
1205 
1206 
1207 
1208 /*
1209 Notification callback for asynchronous operations.
1210 */
1211 typedef void ma_async_notification;
1212 
1213 typedef struct
1214 {
1215     void (* onSignal)(ma_async_notification* pNotification);
1216 } ma_async_notification_callbacks;
1217 
1218 MA_API ma_result ma_async_notification_signal(ma_async_notification* pNotification);
1219 
1220 
1221 /*
1222 Simple polling notification.
1223 
1224 This just sets a variable when the notification has been signalled which is then polled with ma_async_notification_poll_is_signalled()
1225 */
1226 typedef struct
1227 {
1228     ma_async_notification_callbacks cb;
1229     ma_bool32 signalled;
1230 } ma_async_notification_poll;
1231 
1232 MA_API ma_result ma_async_notification_poll_init(ma_async_notification_poll* pNotificationPoll);
1233 MA_API ma_bool32 ma_async_notification_poll_is_signalled(const ma_async_notification_poll* pNotificationPoll);
1234 
1235 
1236 /*
1237 Event Notification
1238 
1239 This notification signals an event internally on the MA_NOTIFICATION_COMPLETE and MA_NOTIFICATION_FAILED codes. All other codes are ignored.
1240 */
1241 typedef struct
1242 {
1243     ma_async_notification_callbacks cb;
1244     ma_event e;
1245 } ma_async_notification_event;
1246 
1247 MA_API ma_result ma_async_notification_event_init(ma_async_notification_event* pNotificationEvent);
1248 MA_API ma_result ma_async_notification_event_uninit(ma_async_notification_event* pNotificationEvent);
1249 MA_API ma_result ma_async_notification_event_wait(ma_async_notification_event* pNotificationEvent);
1250 MA_API ma_result ma_async_notification_event_signal(ma_async_notification_event* pNotificationEvent);
1251 
1252 
1253 typedef struct
1254 {
1255     ma_async_notification* pNotification;
1256     ma_fence* pFence;
1257 } ma_pipeline_stage_notification;
1258 
1259 typedef struct
1260 {
1261     ma_pipeline_stage_notification init;    /* Initialization of the decoder. */
1262     ma_pipeline_stage_notification done;    /* Decoding fully completed. */
1263 } ma_pipeline_notifications;
1264 
1265 MA_API ma_pipeline_notifications ma_pipeline_notifications_init(void);
1266 
1267 
1268 typedef struct
1269 {
1270     union
1271     {
1272         struct
1273         {
1274             ma_uint16 code;
1275             ma_uint16 slot;
1276             ma_uint32 refcount;
1277         } breakup;
1278         ma_uint64 allocation;
1279     } toc;  /* 8 bytes. We encode the job code into the slot allocation data to save space. */
1280     ma_uint64 next;     /* refcount + slot for the next item. Does not include the job code. */
1281     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. */
1282 
1283     union
1284     {
1285         /* Resource Managemer Jobs */
1286         struct
1287         {
1288             ma_resource_manager_data_buffer_node* pDataBufferNode;
1289             char* pFilePath;
1290             wchar_t* pFilePathW;
1291             ma_bool32 decode;                               /* When set to true, the data buffer will be decoded. Otherwise it'll be encoded and will use a decoder for the connector. */
1292             ma_async_notification* pInitNotification;       /* Signalled when the data buffer has been initialized and the format/channels/rate can be retrieved. */
1293             ma_async_notification* pDoneNotification;       /* Signalled when the data buffer has been fully decoded. Will be passed through to MA_JOB_PAGE_DATA_BUFFER_NODE when decoding. */
1294             ma_fence* pInitFence;                           /* Released when initialization of the decoder is complete. */
1295             ma_fence* pDoneFence;                           /* Released if initialization of the decoder fails. Passed through to PAGE_DATA_BUFFER_NODE untouched if init is successful. */
1296         } loadDataBufferNode;
1297         struct
1298         {
1299             ma_resource_manager_data_buffer_node* pDataBufferNode;
1300             ma_async_notification* pDoneNotification;
1301             ma_fence* pDoneFence;
1302         } freeDataBufferNode;
1303         struct
1304         {
1305             ma_resource_manager_data_buffer_node* pDataBufferNode;
1306             ma_decoder* pDecoder;
1307             ma_async_notification* pDoneNotification;       /* Signalled when the data buffer has been fully decoded. */
1308             ma_fence* pDoneFence;                           /* Passed through from LOAD_DATA_BUFFER_NODE and released when the data buffer completes decoding or an error occurs. */
1309         } pageDataBufferNode;
1310 
1311         struct
1312         {
1313             ma_resource_manager_data_buffer* pDataBuffer;
1314             ma_async_notification* pInitNotification;       /* Signalled when the data buffer has been initialized and the format/channels/rate can be retrieved. */
1315             ma_async_notification* pDoneNotification;       /* Signalled when the data buffer has been fully decoded. */
1316             ma_fence* pInitFence;                           /* Released when the data buffer has been initialized and the format/channels/rate can be retrieved. */
1317             ma_fence* pDoneFence;                           /* Released when the data buffer has been fully decoded. */
1318         } loadDataBuffer;
1319         struct
1320         {
1321             ma_resource_manager_data_buffer* pDataBuffer;
1322             ma_async_notification* pDoneNotification;
1323             ma_fence* pDoneFence;
1324         } freeDataBuffer;
1325 
1326         struct
1327         {
1328             ma_resource_manager_data_stream* pDataStream;
1329             char* pFilePath;                            /* Allocated when the job is posted, freed by the job thread after loading. */
1330             wchar_t* pFilePathW;                        /* ^ As above ^. Only used if pFilePath is NULL. */
1331             ma_async_notification* pInitNotification;   /* Signalled after the first two pages have been decoded and frames can be read from the stream. */
1332             ma_fence* pInitFence;
1333         } loadDataStream;
1334         struct
1335         {
1336             ma_resource_manager_data_stream* pDataStream;
1337             ma_async_notification* pDoneNotification;
1338             ma_fence* pDoneFence;
1339         } freeDataStream;
1340         struct
1341         {
1342             ma_resource_manager_data_stream* pDataStream;
1343             ma_uint32 pageIndex;                    /* The index of the page to decode into. */
1344         } pageDataStream;
1345         struct
1346         {
1347             ma_resource_manager_data_stream* pDataStream;
1348             ma_uint64 frameIndex;
1349         } seekDataStream;
1350 
1351         /* Others. */
1352         struct
1353         {
1354             ma_uintptr data0;
1355             ma_uintptr data1;
1356         } custom;
1357     };
1358 } ma_job;
1359 
1360 MA_API ma_job ma_job_init(ma_uint16 code);
1361 
1362 
1363 /*
1364 When set, ma_job_queue_next() will not wait and no semaphore will be signaled in
1365 ma_job_queue_post(). ma_job_queue_next() will return MA_NO_DATA_AVAILABLE if nothing is available.
1366 
1367 This flag should always be used for platforms that do not support multithreading.
1368 */
1369 #define MA_JOB_QUEUE_FLAG_NON_BLOCKING  0x00000001
1370 
1371 typedef struct
1372 {
1373     ma_uint32 flags;    /* Flags passed in at initialization time. */
1374     ma_uint64 head;     /* The first item in the list. Required for removing from the top of the list. */
1375     ma_uint64 tail;     /* The last item in the list. Required for appending to the end of the list. */
1376     ma_semaphore sem;   /* Only used when MA_JOB_QUEUE_FLAG_NON_BLOCKING is unset. */
1377     ma_slot_allocator allocator;
1378     ma_job jobs[MA_RESOURCE_MANAGER_JOB_QUEUE_CAPACITY];
1379 } ma_job_queue;
1380 
1381 MA_API ma_result ma_job_queue_init(ma_uint32 flags, ma_job_queue* pQueue);
1382 MA_API ma_result ma_job_queue_uninit(ma_job_queue* pQueue);
1383 MA_API ma_result ma_job_queue_post(ma_job_queue* pQueue, const ma_job* pJob);
1384 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. */
1385 
1386 
1387 /* 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. */
1388 #ifndef MA_RESOURCE_MANAGER_MAX_JOB_THREAD_COUNT
1389 #define MA_RESOURCE_MANAGER_MAX_JOB_THREAD_COUNT    64
1390 #endif
1391 
1392 /* Indicates ma_resource_manager_next_job() should not block. Only valid when the job thread count is 0. */
1393 #define MA_RESOURCE_MANAGER_FLAG_NON_BLOCKING   0x00000001
1394 
1395 /* Disables any kind of multithreading. Implicitly enables MA_RESOURCE_MANAGER_FLAG_NON_BLOCKING. */
1396 #define MA_RESOURCE_MANAGER_FLAG_NO_THREADING   0x00000002
1397 
1398 
1399 typedef enum
1400 {
1401     ma_resource_manager_data_supply_type_unknown = 0,   /* Used for determining whether or the data supply has been initialized. */
1402     ma_resource_manager_data_supply_type_encoded,       /* Data supply is an encoded buffer. Connector is ma_decoder. */
1403     ma_resource_manager_data_supply_type_decoded,       /* Data supply is a decoded buffer. Connector is ma_audio_buffer. */
1404     ma_resource_manager_data_supply_type_decoded_paged  /* Data supply is a linked list of decoded buffers. Connector is ma_paged_audio_buffer. */
1405 } ma_resource_manager_data_supply_type;
1406 
1407 typedef struct
1408 {
1409     MA_ATOMIC ma_resource_manager_data_supply_type type;  /* Read and written from different threads so needs to be accessed atomically. */
1410     union
1411     {
1412         struct
1413         {
1414             const void* pData;
1415             size_t sizeInBytes;
1416         } encoded;
1417         struct
1418         {
1419             const void* pData;
1420             ma_uint64 totalFrameCount;
1421             ma_uint64 decodedFrameCount;
1422             ma_format format;
1423             ma_uint32 channels;
1424             ma_uint32 sampleRate;
1425         } decoded;
1426         struct
1427         {
1428             ma_paged_audio_buffer_data data;
1429             ma_uint64 decodedFrameCount;
1430             ma_uint32 sampleRate;
1431         } decodedPaged;
1432     };
1433 } ma_resource_manager_data_supply;
1434 
1435 struct ma_resource_manager_data_buffer_node
1436 {
1437     ma_uint32 hashedName32;                         /* The hashed name. This is the key. */
1438     ma_uint32 refCount;
1439     MA_ATOMIC 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. */
1440     ma_uint32 executionCounter;                     /* For allocating execution orders for jobs. */
1441     ma_uint32 executionPointer;                     /* For managing the order of execution for asynchronous jobs relating to this object. Incremented as jobs complete processing. */
1442     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_*()). */
1443     ma_resource_manager_data_supply data;
1444     ma_resource_manager_data_buffer_node* pParent;
1445     ma_resource_manager_data_buffer_node* pChildLo;
1446     ma_resource_manager_data_buffer_node* pChildHi;
1447 };
1448 
1449 struct ma_resource_manager_data_buffer
1450 {
1451     ma_data_source_base ds;                         /* Base data source. A data buffer is a data source. */
1452     ma_resource_manager* pResourceManager;          /* A pointer to the resource manager that owns this buffer. */
1453     ma_resource_manager_data_buffer_node* pNode;    /* The data node. This is reference counted and is what supplies the data. */
1454     ma_uint32 flags;                                /* The flags that were passed used to initialize the buffer. */
1455     ma_uint32 executionCounter;                     /* For allocating execution orders for jobs. */
1456     ma_uint32 executionPointer;                     /* For managing the order of execution for asynchronous jobs relating to this object. Incremented as jobs complete processing. */
1457     ma_uint64 seekTargetInPCMFrames;                /* Only updated by the public API. Never written nor read from the job thread. */
1458     ma_bool32 seekToCursorOnNextRead;               /* On the next read we need to seek to the frame cursor. */
1459     MA_ATOMIC ma_result result;                     /* Keeps track of a result of decoding. Set to MA_BUSY while the buffer is still loading. Set to MA_SUCCESS when loading is finished successfully. Otherwise set to some other code. */
1460     MA_ATOMIC ma_bool32 isLooping;                  /* Can be read and written by different threads at the same time. Must be used atomically. */
1461     ma_bool32 isConnectorInitialized;               /* Used for asynchronous loading to ensure we don't try to initialize the connector multiple times while waiting for the node to fully load. */
1462     union
1463     {
1464         ma_decoder decoder;                 /* Supply type is ma_resource_manager_data_supply_type_encoded */
1465         ma_audio_buffer buffer;             /* Supply type is ma_resource_manager_data_supply_type_decoded */
1466         ma_paged_audio_buffer pagedBuffer;  /* Supply type is ma_resource_manager_data_supply_type_decoded_paged */
1467     } connector;    /* Connects this object to the node's data supply. */
1468 };
1469 
1470 struct ma_resource_manager_data_stream
1471 {
1472     ma_data_source_base ds;                 /* Base data source. A data stream is a data source. */
1473     ma_resource_manager* pResourceManager;  /* A pointer to the resource manager that owns this data stream. */
1474     ma_uint32 flags;                        /* The flags that were passed used to initialize the stream. */
1475     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. */
1476     ma_bool32 isDecoderInitialized;         /* Required for determining whether or not the decoder should be uninitialized in MA_JOB_FREE_DATA_STREAM. */
1477     ma_uint64 totalLengthInPCMFrames;       /* This is calculated when first loaded by the MA_JOB_LOAD_DATA_STREAM. */
1478     ma_uint32 relativeCursor;               /* The playback cursor, relative to the current page. Only ever accessed by the public API. Never accessed by the job thread. */
1479     ma_uint64 absoluteCursor;               /* The playback cursor, in absolute position starting from the start of the file. */
1480     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. */
1481     ma_uint32 executionCounter;             /* For allocating execution orders for jobs. */
1482     ma_uint32 executionPointer;             /* For managing the order of execution for asynchronous jobs relating to this object. Incremented as jobs complete processing. */
1483 
1484     /* Written by the public API, read by the job thread. */
1485     MA_ATOMIC 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. */
1486 
1487     /* Written by the job thread, read by the public API. */
1488     void* pPageData;                        /* Buffer containing the decoded data of each page. Allocated once at initialization time. */
1489     MA_ATOMIC ma_uint32 pageFrameCount[2];  /* The number of valid PCM frames in each page. Used to determine the last valid frame. */
1490 
1491     /* Written and read by both the public API and the job thread. These must be atomic. */
1492     MA_ATOMIC 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. */
1493     MA_ATOMIC ma_bool32 isDecoderAtEnd;     /* Whether or not the decoder has reached the end. */
1494     MA_ATOMIC 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. */
1495     MA_ATOMIC 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. */
1496 };
1497 
1498 struct ma_resource_manager_data_source
1499 {
1500     union
1501     {
1502         ma_resource_manager_data_buffer buffer;
1503         ma_resource_manager_data_stream stream;
1504     };  /* Must be the first item because we need the first item to be the data source callbacks for the buffer or stream. */
1505 
1506     ma_uint32 flags;                /* The flags that were passed in to ma_resource_manager_data_source_init(). */
1507     ma_uint32 executionCounter;     /* For allocating execution orders for jobs. */
1508     ma_uint32 executionPointer;     /* For managing the order of execution for asynchronous jobs relating to this object. Incremented as jobs complete processing. */
1509 };
1510 
1511 typedef struct
1512 {
1513     ma_allocation_callbacks allocationCallbacks;
1514     ma_log* pLog;
1515     ma_format decodedFormat;        /* The decoded format to use. Set to ma_format_unknown (default) to use the file's native format. */
1516     ma_uint32 decodedChannels;      /* The decoded channel count to use. Set to 0 (default) to use the file's native channel count. */
1517     ma_uint32 decodedSampleRate;    /* the decoded sample rate to use. Set to 0 (default) to use the file's native sample rate. */
1518     ma_uint32 jobThreadCount;       /* Set to 0 if you want to self-manage your job threads. Defaults to 1. */
1519     ma_uint32 flags;
1520     ma_vfs* pVFS;                   /* Can be NULL in which case defaults will be used. */
1521     ma_decoding_backend_vtable** ppCustomDecodingBackendVTables;
1522     ma_uint32 customDecodingBackendCount;
1523     void* pCustomDecodingBackendUserData;
1524 } ma_resource_manager_config;
1525 
1526 MA_API ma_resource_manager_config ma_resource_manager_config_init(void);
1527 
1528 struct ma_resource_manager
1529 {
1530     ma_resource_manager_config config;
1531     ma_resource_manager_data_buffer_node* pRootDataBufferNode;      /* The root buffer in the binary tree. */
1532     ma_mutex dataBufferBSTLock;                                     /* For synchronizing access to the data buffer binary tree. */
1533     ma_thread jobThreads[MA_RESOURCE_MANAGER_MAX_JOB_THREAD_COUNT]; /* The threads for executing jobs. */
1534     ma_job_queue jobQueue;                                          /* Lock-free multi-consumer, multi-producer job queue for managing jobs for asynchronous decoding and streaming. */
1535     ma_default_vfs defaultVFS;                                      /* Only used if a custom VFS is not specified. */
1536     ma_log log;                                                     /* Only used if no log was specified in the config. */
1537 };
1538 
1539 /* Init. */
1540 MA_API ma_result ma_resource_manager_init(const ma_resource_manager_config* pConfig, ma_resource_manager* pResourceManager);
1541 MA_API void ma_resource_manager_uninit(ma_resource_manager* pResourceManager);
1542 MA_API ma_log* ma_resource_manager_get_log(ma_resource_manager* pResourceManager);
1543 
1544 /* Registration. */
1545 MA_API ma_result ma_resource_manager_register_file(ma_resource_manager* pResourceManager, const char* pFilePath, ma_uint32 flags);
1546 MA_API ma_result ma_resource_manager_register_file_w(ma_resource_manager* pResourceManager, const wchar_t* pFilePath, ma_uint32 flags);
1547 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. */
1548 MA_API ma_result ma_resource_manager_register_decoded_data_w(ma_resource_manager* pResourceManager, const wchar_t* pName, const void* pData, ma_uint64 frameCount, ma_format format, ma_uint32 channels, ma_uint32 sampleRate);
1549 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. */
1550 MA_API ma_result ma_resource_manager_register_encoded_data_w(ma_resource_manager* pResourceManager, const wchar_t* pName, const void* pData, size_t sizeInBytes);
1551 MA_API ma_result ma_resource_manager_unregister_file(ma_resource_manager* pResourceManager, const char* pFilePath);
1552 MA_API ma_result ma_resource_manager_unregister_file_w(ma_resource_manager* pResourceManager, const wchar_t* pFilePath);
1553 MA_API ma_result ma_resource_manager_unregister_data(ma_resource_manager* pResourceManager, const char* pName);
1554 MA_API ma_result ma_resource_manager_unregister_data_w(ma_resource_manager* pResourceManager, const wchar_t* pName);
1555 
1556 /* Data Buffers. */
1557 MA_API ma_result ma_resource_manager_data_buffer_init(ma_resource_manager* pResourceManager, const char* pFilePath, ma_uint32 flags, const ma_pipeline_notifications* pNotifications, ma_resource_manager_data_buffer* pDataBuffer);
1558 MA_API ma_result ma_resource_manager_data_buffer_init_w(ma_resource_manager* pResourceManager, const wchar_t* pFilePath, ma_uint32 flags, const ma_pipeline_notifications* pNotifications, ma_resource_manager_data_buffer* pDataBuffer);
1559 MA_API ma_result ma_resource_manager_data_buffer_init_copy(ma_resource_manager* pResourceManager, const ma_resource_manager_data_buffer* pExistingDataBuffer, ma_resource_manager_data_buffer* pDataBuffer);
1560 MA_API ma_result ma_resource_manager_data_buffer_uninit(ma_resource_manager_data_buffer* pDataBuffer);
1561 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);
1562 MA_API ma_result ma_resource_manager_data_buffer_seek_to_pcm_frame(ma_resource_manager_data_buffer* pDataBuffer, ma_uint64 frameIndex);
1563 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);
1564 MA_API ma_result ma_resource_manager_data_buffer_get_cursor_in_pcm_frames(ma_resource_manager_data_buffer* pDataBuffer, ma_uint64* pCursor);
1565 MA_API ma_result ma_resource_manager_data_buffer_get_length_in_pcm_frames(ma_resource_manager_data_buffer* pDataBuffer, ma_uint64* pLength);
1566 MA_API ma_result ma_resource_manager_data_buffer_result(const ma_resource_manager_data_buffer* pDataBuffer);
1567 MA_API ma_result ma_resource_manager_data_buffer_set_looping(ma_resource_manager_data_buffer* pDataBuffer, ma_bool32 isLooping);
1568 MA_API ma_result ma_resource_manager_data_buffer_get_looping(const ma_resource_manager_data_buffer* pDataBuffer, ma_bool32* pIsLooping);
1569 MA_API ma_result ma_resource_manager_data_buffer_get_available_frames(ma_resource_manager_data_buffer* pDataBuffer, ma_uint64* pAvailableFrames);
1570 
1571 /* Data Streams. */
1572 MA_API ma_result ma_resource_manager_data_stream_init(ma_resource_manager* pResourceManager, const char* pFilePath, ma_uint32 flags, const ma_pipeline_notifications* pNotifications, ma_resource_manager_data_stream* pDataStream);
1573 MA_API ma_result ma_resource_manager_data_stream_init_w(ma_resource_manager* pResourceManager, const wchar_t* pFilePath, ma_uint32 flags, const ma_pipeline_notifications* pNotifications, ma_resource_manager_data_stream* pDataStream);
1574 MA_API ma_result ma_resource_manager_data_stream_uninit(ma_resource_manager_data_stream* pDataStream);
1575 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);
1576 MA_API ma_result ma_resource_manager_data_stream_seek_to_pcm_frame(ma_resource_manager_data_stream* pDataStream, ma_uint64 frameIndex);
1577 MA_API ma_result ma_resource_manager_data_stream_map(ma_resource_manager_data_stream* pDataStream, void** ppFramesOut, ma_uint64* pFrameCount);
1578 MA_API ma_result ma_resource_manager_data_stream_unmap(ma_resource_manager_data_stream* pDataStream, ma_uint64 frameCount);
1579 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);
1580 MA_API ma_result ma_resource_manager_data_stream_get_cursor_in_pcm_frames(ma_resource_manager_data_stream* pDataStream, ma_uint64* pCursor);
1581 MA_API ma_result ma_resource_manager_data_stream_get_length_in_pcm_frames(ma_resource_manager_data_stream* pDataStream, ma_uint64* pLength);
1582 MA_API ma_result ma_resource_manager_data_stream_result(const ma_resource_manager_data_stream* pDataStream);
1583 MA_API ma_result ma_resource_manager_data_stream_set_looping(ma_resource_manager_data_stream* pDataStream, ma_bool32 isLooping);
1584 MA_API ma_result ma_resource_manager_data_stream_get_looping(const ma_resource_manager_data_stream* pDataStream, ma_bool32* pIsLooping);
1585 MA_API ma_result ma_resource_manager_data_stream_get_available_frames(ma_resource_manager_data_stream* pDataStream, ma_uint64* pAvailableFrames);
1586 
1587 /* Data Sources. */
1588 MA_API ma_result ma_resource_manager_data_source_init(ma_resource_manager* pResourceManager, const char* pName, ma_uint32 flags, const ma_pipeline_notifications* pNotifications, ma_resource_manager_data_source* pDataSource);
1589 MA_API ma_result ma_resource_manager_data_source_init_w(ma_resource_manager* pResourceManager, const wchar_t* pName, ma_uint32 flags, const ma_pipeline_notifications* pNotifications, ma_resource_manager_data_source* pDataSource);
1590 MA_API ma_result ma_resource_manager_data_source_init_copy(ma_resource_manager* pResourceManager, const ma_resource_manager_data_source* pExistingDataSource, ma_resource_manager_data_source* pDataSource);
1591 MA_API ma_result ma_resource_manager_data_source_uninit(ma_resource_manager_data_source* pDataSource);
1592 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);
1593 MA_API ma_result ma_resource_manager_data_source_seek_to_pcm_frame(ma_resource_manager_data_source* pDataSource, ma_uint64 frameIndex);
1594 MA_API ma_result ma_resource_manager_data_source_map(ma_resource_manager_data_source* pDataSource, void** ppFramesOut, ma_uint64* pFrameCount);
1595 MA_API ma_result ma_resource_manager_data_source_unmap(ma_resource_manager_data_source* pDataSource, ma_uint64 frameCount);
1596 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);
1597 MA_API ma_result ma_resource_manager_data_source_get_cursor_in_pcm_frames(ma_resource_manager_data_source* pDataSource, ma_uint64* pCursor);
1598 MA_API ma_result ma_resource_manager_data_source_get_length_in_pcm_frames(ma_resource_manager_data_source* pDataSource, ma_uint64* pLength);
1599 MA_API ma_result ma_resource_manager_data_source_result(const ma_resource_manager_data_source* pDataSource);
1600 MA_API ma_result ma_resource_manager_data_source_set_looping(ma_resource_manager_data_source* pDataSource, ma_bool32 isLooping);
1601 MA_API ma_result ma_resource_manager_data_source_get_looping(const ma_resource_manager_data_source* pDataSource, ma_bool32* pIsLooping);
1602 MA_API ma_result ma_resource_manager_data_source_get_available_frames(ma_resource_manager_data_source* pDataSource, ma_uint64* pAvailableFrames);
1603 
1604 /* Job management. */
1605 MA_API ma_result ma_resource_manager_post_job(ma_resource_manager* pResourceManager, const ma_job* pJob);
1606 MA_API ma_result ma_resource_manager_post_job_quit(ma_resource_manager* pResourceManager);  /* Helper for posting a quit job. */
1607 MA_API ma_result ma_resource_manager_next_job(ma_resource_manager* pResourceManager, ma_job* pJob);
1608 MA_API ma_result ma_resource_manager_process_job(ma_resource_manager* pResourceManager, ma_job* pJob);
1609 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. */
1610 
1611 
1612 /*
1613 Engine
1614 ======
1615 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
1616 (`ma_resource_manager`).
1617 
1618 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
1619 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
1620 `ma_engine_config`. Using this method will require your application to manage groups and sounds on a per `ma_engine` basis.
1621 */
1622 typedef struct ma_engine ma_engine;
1623 typedef struct ma_sound  ma_sound;
1624 
1625 
1626 /* Stereo panner. */
1627 typedef enum
1628 {
1629     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. */
1630     ma_pan_mode_pan             /* A true pan. The sound from one side will "move" to the other side and blend with it. */
1631 } ma_pan_mode;
1632 
1633 typedef struct
1634 {
1635     ma_format format;
1636     ma_uint32 channels;
1637     ma_pan_mode mode;
1638     float pan;
1639 } ma_panner_config;
1640 
1641 MA_API ma_panner_config ma_panner_config_init(ma_format format, ma_uint32 channels);
1642 
1643 
1644 typedef struct
1645 {
1646     ma_format format;
1647     ma_uint32 channels;
1648     ma_pan_mode mode;
1649     float pan;  /* -1..1 where 0 is no pan, -1 is left side, +1 is right side. Defaults to 0. */
1650 } ma_panner;
1651 
1652 MA_API ma_result ma_panner_init(const ma_panner_config* pConfig, ma_panner* pPanner);
1653 MA_API ma_result ma_panner_process_pcm_frames(ma_panner* pPanner, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount);
1654 MA_API void ma_panner_set_mode(ma_panner* pPanner, ma_pan_mode mode);
1655 MA_API void ma_panner_set_pan(ma_panner* pPanner, float pan);
1656 
1657 
1658 
1659 /* Fader. */
1660 typedef struct
1661 {
1662     ma_format format;
1663     ma_uint32 channels;
1664     ma_uint32 sampleRate;
1665 } ma_fader_config;
1666 
1667 MA_API ma_fader_config ma_fader_config_init(ma_format format, ma_uint32 channels, ma_uint32 sampleRate);
1668 
1669 typedef struct
1670 {
1671     ma_fader_config config;
1672     float volumeBeg;            /* If volumeBeg and volumeEnd is equal to 1, no fading happens (ma_fader_process_pcm_frames() will run as a passthrough). */
1673     float volumeEnd;
1674     ma_uint64 lengthInFrames;   /* The total length of the fade. */
1675     ma_uint64 cursorInFrames;   /* The current time in frames. Incremented by ma_fader_process_pcm_frames(). */
1676 } ma_fader;
1677 
1678 MA_API ma_result ma_fader_init(const ma_fader_config* pConfig, ma_fader* pFader);
1679 MA_API ma_result ma_fader_process_pcm_frames(ma_fader* pFader, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount);
1680 MA_API void ma_fader_get_data_format(const ma_fader* pFader, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate);
1681 MA_API void ma_fader_set_fade(ma_fader* pFader, float volumeBeg, float volumeEnd, ma_uint64 lengthInFrames);
1682 MA_API float ma_fader_get_current_volume(ma_fader* pFader);
1683 
1684 
1685 
1686 /* Spatializer. */
1687 typedef struct
1688 {
1689     float x;
1690     float y;
1691     float z;
1692 } ma_vec3f;
1693 
1694 typedef enum
1695 {
1696     ma_attenuation_model_none,          /* No distance attenuation and no spatialization. */
1697     ma_attenuation_model_inverse,       /* Equivalent to OpenAL's AL_INVERSE_DISTANCE_CLAMPED. */
1698     ma_attenuation_model_linear,        /* Linear attenuation. Equivalent to OpenAL's AL_LINEAR_DISTANCE_CLAMPED. */
1699     ma_attenuation_model_exponential    /* Exponential attenuation. Equivalent to OpenAL's AL_EXPONENT_DISTANCE_CLAMPED. */
1700 } ma_attenuation_model;
1701 
1702 typedef enum
1703 {
1704     ma_positioning_absolute,
1705     ma_positioning_relative
1706 } ma_positioning;
1707 
1708 typedef enum
1709 {
1710     ma_handedness_right,
1711     ma_handedness_left
1712 } ma_handedness;
1713 
1714 
1715 typedef struct
1716 {
1717     ma_uint32 channelsOut;
1718     ma_channel* pChannelMapOut;
1719     ma_handedness handedness;   /* Defaults to right. Forward is -1 on the Z axis. In a left handed system, forward is +1 on the Z axis. */
1720     float coneInnerAngleInRadians;
1721     float coneOuterAngleInRadians;
1722     float coneOuterGain;
1723     float speedOfSound;
1724     ma_vec3f worldUp;
1725 } ma_spatializer_listener_config;
1726 
1727 MA_API ma_spatializer_listener_config ma_spatializer_listener_config_init(ma_uint32 channelsOut);
1728 
1729 
1730 typedef struct
1731 {
1732     ma_spatializer_listener_config config;
1733     ma_vec3f position;  /* The absolute position of the listener. */
1734     ma_vec3f direction; /* The direction the listener is facing. The world up vector is config.worldUp. */
1735     ma_vec3f velocity;
1736 
1737     /* Memory management. */
1738     void* _pHeap;
1739     ma_bool32 _ownsHeap;
1740 } ma_spatializer_listener;
1741 
1742 MA_API ma_result ma_spatializer_listener_get_heap_size(const ma_spatializer_listener_config* pConfig, size_t* pHeapSizeInBytes);
1743 MA_API ma_result ma_spatializer_listener_init_preallocated(const ma_spatializer_listener_config* pConfig, void* pHeap, ma_spatializer_listener* pListener);
1744 MA_API ma_result ma_spatializer_listener_init(const ma_spatializer_listener_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_spatializer_listener* pListener);
1745 MA_API void ma_spatializer_listener_uninit(ma_spatializer_listener* pListener, const ma_allocation_callbacks* pAllocationCallbacks);
1746 MA_API ma_channel* ma_spatializer_listener_get_channel_map(ma_spatializer_listener* pListener);
1747 MA_API void ma_spatializer_listener_set_cone(ma_spatializer_listener* pListener, float innerAngleInRadians, float outerAngleInRadians, float outerGain);
1748 MA_API void ma_spatializer_listener_get_cone(const ma_spatializer_listener* pListener, float* pInnerAngleInRadians, float* pOuterAngleInRadians, float* pOuterGain);
1749 MA_API void ma_spatializer_listener_set_position(ma_spatializer_listener* pListener, float x, float y, float z);
1750 MA_API ma_vec3f ma_spatializer_listener_get_position(const ma_spatializer_listener* pListener);
1751 MA_API void ma_spatializer_listener_set_direction(ma_spatializer_listener* pListener, float x, float y, float z);
1752 MA_API ma_vec3f ma_spatializer_listener_get_direction(const ma_spatializer_listener* pListener);
1753 MA_API void ma_spatializer_listener_set_velocity(ma_spatializer_listener* pListener, float x, float y, float z);
1754 MA_API ma_vec3f ma_spatializer_listener_get_velocity(const ma_spatializer_listener* pListener);
1755 MA_API void ma_spatializer_listener_set_speed_of_sound(ma_spatializer_listener* pListener, float speedOfSound);
1756 MA_API float ma_spatializer_listener_get_speed_of_sound(const ma_spatializer_listener* pListener);
1757 MA_API void ma_spatializer_listener_set_world_up(ma_spatializer_listener* pListener, float x, float y, float z);
1758 MA_API ma_vec3f ma_spatializer_listener_get_world_up(const ma_spatializer_listener* pListener);
1759 
1760 
1761 typedef struct
1762 {
1763     ma_uint32 channelsIn;
1764     ma_uint32 channelsOut;
1765     ma_channel* pChannelMapIn;
1766     ma_attenuation_model attenuationModel;
1767     ma_positioning positioning;
1768     ma_handedness handedness;           /* Defaults to right. Forward is -1 on the Z axis. In a left handed system, forward is +1 on the Z axis. */
1769     float minGain;
1770     float maxGain;
1771     float minDistance;
1772     float maxDistance;
1773     float rolloff;
1774     float coneInnerAngleInRadians;
1775     float coneOuterAngleInRadians;
1776     float coneOuterGain;
1777     float dopplerFactor;                /* Set to 0 to disable doppler effect. This will run on a fast path. */
1778     ma_uint32 gainSmoothTimeInFrames;   /* When the gain of a channel changes during spatialization, the transition will be linearly interpolated over this number of frames. */
1779 } ma_spatializer_config;
1780 
1781 MA_API ma_spatializer_config ma_spatializer_config_init(ma_uint32 channelsIn, ma_uint32 channelsOut);
1782 
1783 
1784 typedef struct
1785 {
1786     ma_spatializer_config config;
1787     ma_vec3f position;
1788     ma_vec3f direction;
1789     ma_vec3f velocity;  /* For doppler effect. */
1790     float dopplerPitch; /* Will be updated by ma_spatializer_process_pcm_frames() and can be used by higher level functions to apply a pitch shift for doppler effect. */
1791     ma_gainer gainer;   /* For smooth gain transitions. */
1792     float* pNewChannelGainsOut; /* An offset of _pHeap. Used by ma_spatializer_process_pcm_frames() to store new channel gains. The number of elements in this array is equal to config.channelsOut. */
1793 
1794     /* Memory management. */
1795     void* _pHeap;
1796     ma_bool32 _ownsHeap;
1797 } ma_spatializer;
1798 
1799 MA_API ma_result ma_spatializer_get_heap_size(const ma_spatializer_config* pConfig, size_t* pHeapSizeInBytes);
1800 MA_API ma_result ma_spatializer_init_preallocated(const ma_spatializer_config* pConfig, void* pHeap, ma_spatializer* pSpatializer);
1801 MA_API ma_result ma_spatializer_init(const ma_spatializer_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_spatializer* pSpatializer);
1802 MA_API void ma_spatializer_uninit(ma_spatializer* pSpatializer, const ma_allocation_callbacks* pAllocationCallbacks);
1803 MA_API ma_result ma_spatializer_process_pcm_frames(ma_spatializer* pSpatializer, ma_spatializer_listener* pListener, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount);
1804 MA_API ma_uint32 ma_spatializer_get_input_channels(const ma_spatializer* pSpatializer);
1805 MA_API ma_uint32 ma_spatializer_get_output_channels(const ma_spatializer* pSpatializer);
1806 MA_API void ma_spatializer_set_attenuation_model(ma_spatializer* pSpatializer, ma_attenuation_model attenuationModel);
1807 MA_API ma_attenuation_model ma_spatializer_get_attenuation_model(const ma_spatializer* pSpatializer);
1808 MA_API void ma_spatializer_set_positioning(ma_spatializer* pSpatializer, ma_positioning positioning);
1809 MA_API ma_positioning ma_spatializer_get_positioning(const ma_spatializer* pSpatializer);
1810 MA_API void ma_spatializer_set_rolloff(ma_spatializer* pSpatializer, float rolloff);
1811 MA_API float ma_spatializer_get_rolloff(const ma_spatializer* pSpatializer);
1812 MA_API void ma_spatializer_set_min_gain(ma_spatializer* pSpatializer, float minGain);
1813 MA_API float ma_spatializer_get_min_gain(const ma_spatializer* pSpatializer);
1814 MA_API void ma_spatializer_set_max_gain(ma_spatializer* pSpatializer, float maxGain);
1815 MA_API float ma_spatializer_get_max_gain(const ma_spatializer* pSpatializer);
1816 MA_API void ma_spatializer_set_min_distance(ma_spatializer* pSpatializer, float minDistance);
1817 MA_API float ma_spatializer_get_min_distance(const ma_spatializer* pSpatializer);
1818 MA_API void ma_spatializer_set_max_distance(ma_spatializer* pSpatializer, float maxDistance);
1819 MA_API float ma_spatializer_get_max_distance(const ma_spatializer* pSpatializer);
1820 MA_API void ma_spatializer_set_cone(ma_spatializer* pSpatializer, float innerAngleInRadians, float outerAngleInRadians, float outerGain);
1821 MA_API void ma_spatializer_get_cone(const ma_spatializer* pSpatializer, float* pInnerAngleInRadians, float* pOuterAngleInRadians, float* pOuterGain);
1822 MA_API void ma_spatializer_set_doppler_factor(ma_spatializer* pSpatializer, float dopplerFactor);
1823 MA_API float ma_spatializer_get_doppler_factor(const ma_spatializer* pSpatializer);
1824 MA_API void ma_spatializer_set_position(ma_spatializer* pSpatializer, float x, float y, float z);
1825 MA_API ma_vec3f ma_spatializer_get_position(const ma_spatializer* pSpatializer);
1826 MA_API void ma_spatializer_set_direction(ma_spatializer* pSpatializer, float x, float y, float z);
1827 MA_API ma_vec3f ma_spatializer_get_direction(const ma_spatializer* pSpatializer);
1828 MA_API void ma_spatializer_set_velocity(ma_spatializer* pSpatializer, float x, float y, float z);
1829 MA_API ma_vec3f ma_spatializer_get_velocity(const ma_spatializer* pSpatializer);
1830 
1831 
1832 
1833 /* Sound flags. */
1834 #define MA_SOUND_FLAG_STREAM                MA_DATA_SOURCE_FLAG_STREAM      /* 0x00000001 */
1835 #define MA_SOUND_FLAG_DECODE                MA_DATA_SOURCE_FLAG_DECODE      /* 0x00000002 */
1836 #define MA_SOUND_FLAG_ASYNC                 MA_DATA_SOURCE_FLAG_ASYNC       /* 0x00000004 */
1837 #define MA_SOUND_FLAG_WAIT_INIT             MA_DATA_SOURCE_FLAG_WAIT_INIT   /* 0x00000008 */
1838 #define MA_SOUND_FLAG_NO_DEFAULT_ATTACHMENT 0x00000010  /* Do not attach to the endpoint by default. Useful for when setting up nodes in a complex graph system. */
1839 #define MA_SOUND_FLAG_NO_PITCH              0x00000020  /* Disable pitch shifting with ma_sound_set_pitch() and ma_sound_group_set_pitch(). This is an optimization. */
1840 #define MA_SOUND_FLAG_NO_SPATIALIZATION     0x00000040  /* Disable spatialization. */
1841 
1842 #ifndef MA_ENGINE_MAX_LISTENERS
1843 #define MA_ENGINE_MAX_LISTENERS             4
1844 #endif
1845 
1846 #define MA_LISTENER_INDEX_CLOSEST           ((ma_uint8)-1)
1847 
1848 typedef enum
1849 {
1850     ma_engine_node_type_sound,
1851     ma_engine_node_type_group
1852 } ma_engine_node_type;
1853 
1854 typedef struct
1855 {
1856     ma_engine* pEngine;
1857     ma_engine_node_type type;
1858     ma_uint32 channelsIn;
1859     ma_uint32 channelsOut;
1860     ma_uint32 sampleRate;               /* Only used when the type is set to ma_engine_node_type_sound. */
1861     ma_bool8 isPitchDisabled;           /* Pitching can be explicitly disable with MA_SOUND_FLAG_NO_PITCH to optimize processing. */
1862     ma_bool8 isSpatializationDisabled;  /* Spatialization can be explicitly disabled with MA_SOUND_FLAG_NO_SPATIALIZATION. */
1863     ma_uint8 pinnedListenerIndex;       /* The index of the listener this node should always use for spatialization. If set to MA_LISTENER_INDEX_CLOSEST the engine will use the closest listener. */
1864 } ma_engine_node_config;
1865 
1866 MA_API ma_engine_node_config ma_engine_node_config_init(ma_engine* pEngine, ma_engine_node_type type, ma_uint32 flags);
1867 
1868 
1869 /* Base node object for both ma_sound and ma_sound_group. */
1870 typedef struct
1871 {
1872     ma_node_base baseNode;                          /* Must be the first member for compatiblity with the ma_node API. */
1873     ma_engine* pEngine;                             /* A pointer to the engine. Set based on the value from the config. */
1874     ma_uint32 sampleRate;                           /* The sample rate of the input data. For sounds backed by a data source, this will be the data source's sample rate. Otherwise it'll be the engine's sample rate. */
1875     ma_fader fader;
1876     ma_resampler resampler;                         /* For pitch shift. May change this to ma_linear_resampler later. */
1877     ma_spatializer spatializer;
1878     ma_panner panner;
1879     MA_ATOMIC float pitch;
1880     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. */
1881     float oldDopplerPitch;                          /* For determining whether or not the resampler needs to be updated to take a new doppler pitch into account. */
1882     MA_ATOMIC ma_bool8 isPitchDisabled;             /* When set to true, pitching will be disabled which will allow the resampler to be bypassed to save some computation. */
1883     MA_ATOMIC ma_bool8 isSpatializationDisabled;    /* Set to false by default. When set to false, will not have spatialisation applied. */
1884     MA_ATOMIC ma_uint8 pinnedListenerIndex;         /* The index of the listener this node should always use for spatialization. If set to MA_LISTENER_INDEX_CLOSEST the engine will use the closest listener. */
1885 
1886     /* Memory management. */
1887     ma_bool8 _ownsHeap;
1888     void* _pHeap;
1889 } ma_engine_node;
1890 
1891 MA_API ma_result ma_engine_node_get_heap_size(const ma_engine_node_config* pConfig, size_t* pHeapSizeInBytes);
1892 MA_API ma_result ma_engine_node_init_preallocated(const ma_engine_node_config* pConfig, void* pHeap, ma_engine_node* pEngineNode);
1893 MA_API ma_result ma_engine_node_init(const ma_engine_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_engine_node* pEngineNode);
1894 MA_API void ma_engine_node_uninit(ma_engine_node* pEngineNode, const ma_allocation_callbacks* pAllocationCallbacks);
1895 
1896 
1897 typedef struct
1898 {
1899     const char* pFilePath;                      /* Set this to load from the resource manager. */
1900     const wchar_t* pFilePathW;                  /* Set this to load from the resource manager. */
1901     ma_data_source* pDataSource;                /* Set this to load from an existing data source. */
1902     ma_node* pInitialAttachment;                /* If set, the sound will be attached to an input of this node. This can be set to a ma_sound. If set to NULL, the sound will be attached directly to the endpoint unless MA_SOUND_FLAG_NO_DEFAULT_ATTACHMENT is set in `flags`. */
1903     ma_uint32 initialAttachmentInputBusIndex;   /* The index of the input bus of pInitialAttachment to attach the sound to. */
1904     ma_uint32 channelsIn;                       /* Ignored if using a data source as input (the data source's channel count will be used always). Otherwise, setting to 0 will cause the engine's channel count to be used. */
1905     ma_uint32 channelsOut;                      /* Set this to 0 (default) to use the engine's channel count. */
1906     ma_uint32 flags;                            /* A combination of MA_SOUND_FLAG_* flags. */
1907     ma_fence* pDoneFence;                       /* Released when the resource manager has finished decoding the entire sound. Not used with streams. */
1908 } ma_sound_config;
1909 
1910 MA_API ma_sound_config ma_sound_config_init(void);
1911 
1912 struct ma_sound
1913 {
1914     ma_engine_node engineNode;          /* Must be the first member for compatibility with the ma_node API. */
1915     ma_data_source* pDataSource;
1916     ma_uint64 seekTarget;               /* The PCM frame index to seek to in the mixing thread. Set to (~(ma_uint64)0) to not perform any seeking. */
1917     MA_ATOMIC ma_bool8 isLooping;       /* False by default. */
1918     MA_ATOMIC ma_bool8 atEnd;
1919     ma_bool8 ownsDataSource;
1920 
1921     /*
1922     We're declaring a resource manager data source object here to save us a malloc when loading a
1923     sound via the resource manager, which I *think* will be the most common scenario.
1924     */
1925 #ifndef MA_NO_RESOURCE_MANAGER
1926     ma_resource_manager_data_source* pResourceManagerDataSource;
1927 #endif
1928 };
1929 
1930 /* Structure specifically for sounds played with ma_engine_play_sound(). Making this a separate structure to reduce overhead. */
1931 typedef struct ma_sound_inlined ma_sound_inlined;
1932 struct ma_sound_inlined
1933 {
1934     ma_sound sound;
1935     ma_sound_inlined* pNext;
1936     ma_sound_inlined* pPrev;
1937 };
1938 
1939 /* A sound group is just a sound. */
1940 typedef ma_sound_config ma_sound_group_config;
1941 typedef ma_sound        ma_sound_group;
1942 
1943 MA_API ma_sound_group_config ma_sound_group_config_init(void);
1944 
1945 
1946 typedef struct
1947 {
1948     ma_resource_manager* pResourceManager;  /* Can be null in which case a resource manager will be created for you. */
1949     ma_context* pContext;
1950     ma_device* pDevice;                     /* If set, the caller is responsible for calling ma_engine_data_callback() in the device's data callback. */
1951     ma_log* pLog;                           /* When set to NULL, will use the context's log. */
1952     ma_uint32 listenerCount;                /* Must be between 1 and MA_ENGINE_MAX_LISTENERS. */
1953     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. */
1954     ma_uint32 sampleRate;                   /* The sample rate. When set to 0 will use the native channel count of the device. */
1955     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.*/
1956     ma_uint32 periodSizeInMilliseconds;     /* Used if periodSizeInFrames is unset. */
1957     ma_uint32 gainSmoothTimeInFrames;       /* The number of frames to interpolate the gain of spatialized sounds across. If set to 0, will use gainSmoothTimeInMilliseconds. */
1958     ma_uint32 gainSmoothTimeInMilliseconds; /* When set to 0, gainSmoothTimeInFrames will be used. If both are set to 0, a default value will be used. */
1959     ma_device_id* pPlaybackDeviceID;        /* The ID of the playback device to use with the default listener. */
1960     ma_allocation_callbacks allocationCallbacks;
1961     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(). */
1962     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. */
1963 } ma_engine_config;
1964 
1965 MA_API ma_engine_config ma_engine_config_init(void);
1966 
1967 
1968 struct ma_engine
1969 {
1970     ma_node_graph nodeGraph;                /* An engine is a node graph. It should be able to be plugged into any ma_node_graph API (with a cast) which means this must be the first member of this struct. */
1971     ma_resource_manager* pResourceManager;
1972     ma_device* pDevice;                     /* Optionally set via the config, otherwise allocated by the engine in ma_engine_init(). */
1973     ma_log* pLog;
1974     ma_uint32 listenerCount;
1975     ma_spatializer_listener listeners[MA_ENGINE_MAX_LISTENERS];
1976     ma_allocation_callbacks allocationCallbacks;
1977     ma_bool8 ownsResourceManager;
1978     ma_bool8 ownsDevice;
1979     ma_mutex inlinedSoundLock;              /* For synchronizing access so the inlined sound list. */
1980     ma_sound_inlined* pInlinedSoundHead;    /* The first inlined sound. Inlined sounds are tracked in a linked list. */
1981     MA_ATOMIC ma_uint32 inlinedSoundCount;  /* The total number of allocated inlined sound objects. Used for debugging. */
1982     ma_uint32 gainSmoothTimeInFrames;       /* The number of frames to interpolate the gain of spatialized sounds across. */
1983 };
1984 
1985 MA_API ma_result ma_engine_init(const ma_engine_config* pConfig, ma_engine* pEngine);
1986 MA_API void ma_engine_uninit(ma_engine* pEngine);
1987 MA_API void ma_engine_data_callback(ma_engine* pEngine, void* pOutput, const void* pInput, ma_uint32 frameCount);
1988 MA_API ma_device* ma_engine_get_device(ma_engine* pEngine);
1989 MA_API ma_log* ma_engine_get_log(ma_engine* pEngine);
1990 MA_API ma_node* ma_engine_get_endpoint(ma_engine* pEngine);
1991 MA_API ma_uint64 ma_engine_get_time(const ma_engine* pEngine);
1992 MA_API ma_uint64 ma_engine_set_time(ma_engine* pEngine, ma_uint64 globalTime);
1993 MA_API ma_uint32 ma_engine_get_channels(const ma_engine* pEngine);
1994 MA_API ma_uint32 ma_engine_get_sample_rate(const ma_engine* pEngine);
1995 
1996 MA_API ma_result ma_engine_start(ma_engine* pEngine);
1997 MA_API ma_result ma_engine_stop(ma_engine* pEngine);
1998 MA_API ma_result ma_engine_set_volume(ma_engine* pEngine, float volume);
1999 MA_API ma_result ma_engine_set_gain_db(ma_engine* pEngine, float gainDB);
2000 
2001 MA_API ma_uint32 ma_engine_get_listener_count(const ma_engine* pEngine);
2002 MA_API ma_uint32 ma_engine_find_closest_listener(const ma_engine* pEngine, float absolutePosX, float absolutePosY, float absolutePosZ);
2003 MA_API void ma_engine_listener_set_position(ma_engine* pEngine, ma_uint32 listenerIndex, float x, float y, float z);
2004 MA_API ma_vec3f ma_engine_listener_get_position(const ma_engine* pEngine, ma_uint32 listenerIndex);
2005 MA_API void ma_engine_listener_set_direction(ma_engine* pEngine, ma_uint32 listenerIndex, float x, float y, float z);
2006 MA_API ma_vec3f ma_engine_listener_get_direction(const ma_engine* pEngine, ma_uint32 listenerIndex);
2007 MA_API void ma_engine_listener_set_velocity(ma_engine* pEngine, ma_uint32 listenerIndex, float x, float y, float z);
2008 MA_API ma_vec3f ma_engine_listener_get_velocity(const ma_engine* pEngine, ma_uint32 listenerIndex);
2009 MA_API void ma_engine_listener_set_cone(ma_engine* pEngine, ma_uint32 listenerIndex, float innerAngleInRadians, float outerAngleInRadians, float outerGain);
2010 MA_API void ma_engine_listener_get_cone(const ma_engine* pEngine, ma_uint32 listenerIndex, float* pInnerAngleInRadians, float* pOuterAngleInRadians, float* pOuterGain);
2011 MA_API void ma_engine_listener_set_world_up(ma_engine* pEngine, ma_uint32 listenerIndex, float x, float y, float z);
2012 MA_API ma_vec3f ma_engine_listener_get_world_up(const ma_engine* pEngine, ma_uint32 listenerIndex);
2013 
2014 MA_API ma_result ma_engine_play_sound(ma_engine* pEngine, const char* pFilePath, ma_sound_group* pGroup);   /* Fire and forget. */
2015 
2016 
2017 #ifndef MA_NO_RESOURCE_MANAGER
2018 MA_API ma_result ma_sound_init_from_file(ma_engine* pEngine, const char* pFilePath, ma_uint32 flags, ma_sound_group* pGroup, ma_fence* pDoneFence, ma_sound* pSound);
2019 MA_API ma_result ma_sound_init_from_file_w(ma_engine* pEngine, const wchar_t* pFilePath, ma_uint32 flags, ma_sound_group* pGroup, ma_fence* pDoneFence, ma_sound* pSound);
2020 MA_API ma_result ma_sound_init_copy(ma_engine* pEngine, const ma_sound* pExistingSound, ma_uint32 flags, ma_sound_group* pGroup, ma_sound* pSound);
2021 #endif
2022 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);
2023 MA_API ma_result ma_sound_init_ex(ma_engine* pEngine, const ma_sound_config* pConfig, ma_sound* pSound);
2024 MA_API void ma_sound_uninit(ma_sound* pSound);
2025 MA_API ma_engine* ma_sound_get_engine(const ma_sound* pSound);
2026 MA_API ma_data_source* ma_sound_get_data_source(const ma_sound* pSound);
2027 MA_API ma_result ma_sound_start(ma_sound* pSound);
2028 MA_API ma_result ma_sound_stop(ma_sound* pSound);
2029 MA_API ma_result ma_sound_set_volume(ma_sound* pSound, float volume);
2030 MA_API ma_result ma_sound_set_gain_db(ma_sound* pSound, float gainDB);
2031 MA_API void ma_sound_set_pan(ma_sound* pSound, float pan);
2032 MA_API void ma_sound_set_pan_mode(ma_sound* pSound, ma_pan_mode panMode);
2033 MA_API void ma_sound_set_pitch(ma_sound* pSound, float pitch);
2034 MA_API void ma_sound_set_spatialization_enabled(ma_sound* pSound, ma_bool32 enabled);
2035 MA_API void ma_sound_set_pinned_listener_index(ma_sound* pSound, ma_uint8 listenerIndex);
2036 MA_API ma_uint8 ma_sound_get_pinned_listener_index(const ma_sound* pSound);
2037 MA_API void ma_sound_set_position(ma_sound* pSound, float x, float y, float z);
2038 MA_API ma_vec3f ma_sound_get_position(const ma_sound* pSound);
2039 MA_API void ma_sound_set_direction(ma_sound* pSound, float x, float y, float z);
2040 MA_API ma_vec3f ma_sound_get_direction(const ma_sound* pSound);
2041 MA_API void ma_sound_set_velocity(ma_sound* pSound, float x, float y, float z);
2042 MA_API ma_vec3f ma_sound_get_velocity(const ma_sound* pSound);
2043 MA_API void ma_sound_set_attenuation_model(ma_sound* pSound, ma_attenuation_model attenuationModel);
2044 MA_API ma_attenuation_model ma_sound_get_attenuation_model(const ma_sound* pSound);
2045 MA_API void ma_sound_set_positioning(ma_sound* pSound, ma_positioning positioning);
2046 MA_API ma_positioning ma_sound_get_positioning(const ma_sound* pSound);
2047 MA_API void ma_sound_set_rolloff(ma_sound* pSound, float rolloff);
2048 MA_API float ma_sound_get_rolloff(const ma_sound* pSound);
2049 MA_API void ma_sound_set_min_gain(ma_sound* pSound, float minGain);
2050 MA_API float ma_sound_get_min_gain(const ma_sound* pSound);
2051 MA_API void ma_sound_set_max_gain(ma_sound* pSound, float maxGain);
2052 MA_API float ma_sound_get_max_gain(const ma_sound* pSound);
2053 MA_API void ma_sound_set_min_distance(ma_sound* pSound, float minDistance);
2054 MA_API float ma_sound_get_min_distance(const ma_sound* pSound);
2055 MA_API void ma_sound_set_max_distance(ma_sound* pSound, float maxDistance);
2056 MA_API float ma_sound_get_max_distance(const ma_sound* pSound);
2057 MA_API void ma_sound_set_cone(ma_sound* pSound, float innerAngleInRadians, float outerAngleInRadians, float outerGain);
2058 MA_API void ma_sound_get_cone(const ma_sound* pSound, float* pInnerAngleInRadians, float* pOuterAngleInRadians, float* pOuterGain);
2059 MA_API void ma_sound_set_doppler_factor(ma_sound* pSound, float dopplerFactor);
2060 MA_API float ma_sound_get_doppler_factor(const ma_sound* pSound);
2061 MA_API void ma_sound_set_fade_in_pcm_frames(ma_sound* pSound, float volumeBeg, float volumeEnd, ma_uint64 fadeLengthInFrames);
2062 MA_API void ma_sound_set_fade_in_milliseconds(ma_sound* pSound, float volumeBeg, float volumeEnd, ma_uint64 fadeLengthInMilliseconds);
2063 MA_API float ma_sound_get_current_fade_volume(ma_sound* pSound);
2064 MA_API void ma_sound_set_start_time_in_pcm_frames(ma_sound* pSound, ma_uint64 absoluteGlobalTimeInFrames);
2065 MA_API void ma_sound_set_start_time_in_milliseconds(ma_sound* pSound, ma_uint64 absoluteGlobalTimeInMilliseconds);
2066 MA_API void ma_sound_set_stop_time_in_pcm_frames(ma_sound* pSound, ma_uint64 absoluteGlobalTimeInFrames);
2067 MA_API void ma_sound_set_stop_time_in_milliseconds(ma_sound* pSound, ma_uint64 absoluteGlobalTimeInMilliseconds);
2068 MA_API ma_bool32 ma_sound_is_playing(const ma_sound* pSound);
2069 MA_API ma_uint64 ma_sound_get_time_in_pcm_frames(const ma_sound* pSound);
2070 MA_API void ma_sound_set_looping(ma_sound* pSound, ma_bool8 isLooping);
2071 MA_API ma_bool32 ma_sound_is_looping(const ma_sound* pSound);
2072 MA_API ma_bool32 ma_sound_at_end(const ma_sound* pSound);
2073 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(). */
2074 MA_API ma_result ma_sound_get_data_format(ma_sound* pSound, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate);
2075 MA_API ma_result ma_sound_get_cursor_in_pcm_frames(ma_sound* pSound, ma_uint64* pCursor);
2076 MA_API ma_result ma_sound_get_length_in_pcm_frames(ma_sound* pSound, ma_uint64* pLength);
2077 
2078 MA_API ma_result ma_sound_group_init(ma_engine* pEngine, ma_uint32 flags, ma_sound_group* pParentGroup, ma_sound_group* pGroup);
2079 MA_API ma_result ma_sound_group_init_ex(ma_engine* pEngine, const ma_sound_group_config* pConfig, ma_sound_group* pGroup);
2080 MA_API void ma_sound_group_uninit(ma_sound_group* pGroup);
2081 MA_API ma_engine* ma_sound_group_get_engine(const ma_sound_group* pGroup);
2082 MA_API ma_result ma_sound_group_start(ma_sound_group* pGroup);
2083 MA_API ma_result ma_sound_group_stop(ma_sound_group* pGroup);
2084 MA_API ma_result ma_sound_group_set_volume(ma_sound_group* pGroup, float volume);
2085 MA_API ma_result ma_sound_group_set_gain_db(ma_sound_group* pGroup, float gainDB);
2086 MA_API void ma_sound_group_set_pan(ma_sound_group* pGroup, float pan);
2087 MA_API void ma_sound_group_set_pan_mode(ma_sound_group* pGroup, ma_pan_mode panMode);
2088 MA_API void ma_sound_group_set_pitch(ma_sound_group* pGroup, float pitch);
2089 MA_API void ma_sound_group_set_spatialization_enabled(ma_sound_group* pGroup, ma_bool32 enabled);
2090 MA_API void ma_sound_group_set_pinned_listener_index(ma_sound_group* pGroup, ma_uint8 listenerIndex);
2091 MA_API ma_uint8 ma_sound_group_get_pinned_listener_index(const ma_sound_group* pGroup);
2092 MA_API void ma_sound_group_set_position(ma_sound_group* pGroup, float x, float y, float z);
2093 MA_API ma_vec3f ma_sound_group_get_position(const ma_sound_group* pGroup);
2094 MA_API void ma_sound_group_set_direction(ma_sound_group* pGroup, float x, float y, float z);
2095 MA_API ma_vec3f ma_sound_group_get_direction(const ma_sound_group* pGroup);
2096 MA_API void ma_sound_group_set_velocity(ma_sound_group* pGroup, float x, float y, float z);
2097 MA_API ma_vec3f ma_sound_group_get_velocity(const ma_sound_group* pGroup);
2098 MA_API void ma_sound_group_set_attenuation_model(ma_sound_group* pGroup, ma_attenuation_model attenuationModel);
2099 MA_API ma_attenuation_model ma_sound_group_get_attenuation_model(const ma_sound_group* pGroup);
2100 MA_API void ma_sound_group_set_positioning(ma_sound_group* pGroup, ma_positioning positioning);
2101 MA_API ma_positioning ma_sound_group_get_positioning(const ma_sound_group* pGroup);
2102 MA_API void ma_sound_group_set_rolloff(ma_sound_group* pGroup, float rolloff);
2103 MA_API float ma_sound_group_get_rolloff(const ma_sound_group* pGroup);
2104 MA_API void ma_sound_group_set_min_gain(ma_sound_group* pGroup, float minGain);
2105 MA_API float ma_sound_group_get_min_gain(const ma_sound_group* pGroup);
2106 MA_API void ma_sound_group_set_max_gain(ma_sound_group* pGroup, float maxGain);
2107 MA_API float ma_sound_group_get_max_gain(const ma_sound_group* pGroup);
2108 MA_API void ma_sound_group_set_min_distance(ma_sound_group* pGroup, float minDistance);
2109 MA_API float ma_sound_group_get_min_distance(const ma_sound_group* pGroup);
2110 MA_API void ma_sound_group_set_max_distance(ma_sound_group* pGroup, float maxDistance);
2111 MA_API float ma_sound_group_get_max_distance(const ma_sound_group* pGroup);
2112 MA_API void ma_sound_group_set_cone(ma_sound_group* pGroup, float innerAngleInRadians, float outerAngleInRadians, float outerGain);
2113 MA_API void ma_sound_group_get_cone(const ma_sound_group* pGroup, float* pInnerAngleInRadians, float* pOuterAngleInRadians, float* pOuterGain);
2114 MA_API void ma_sound_group_set_doppler_factor(ma_sound_group* pGroup, float dopplerFactor);
2115 MA_API float ma_sound_group_get_doppler_factor(const ma_sound_group* pGroup);
2116 MA_API void ma_sound_group_set_fade_in_pcm_frames(ma_sound_group* pGroup, float volumeBeg, float volumeEnd, ma_uint64 fadeLengthInFrames);
2117 MA_API void ma_sound_group_set_fade_in_milliseconds(ma_sound_group* pGroup, float volumeBeg, float volumeEnd, ma_uint64 fadeLengthInMilliseconds);
2118 MA_API float ma_sound_group_get_current_fade_volume(ma_sound_group* pGroup);
2119 MA_API void ma_sound_group_set_start_time_in_pcm_frames(ma_sound_group* pGroup, ma_uint64 absoluteGlobalTimeInFrames);
2120 MA_API void ma_sound_group_set_start_time_in_milliseconds(ma_sound_group* pGroup, ma_uint64 absoluteGlobalTimeInMilliseconds);
2121 MA_API void ma_sound_group_set_stop_time_in_pcm_frames(ma_sound_group* pGroup, ma_uint64 absoluteGlobalTimeInFrames);
2122 MA_API void ma_sound_group_set_stop_time_in_milliseconds(ma_sound_group* pGroup, ma_uint64 absoluteGlobalTimeInMilliseconds);
2123 MA_API ma_bool32 ma_sound_group_is_playing(const ma_sound_group* pGroup);
2124 MA_API ma_uint64 ma_sound_group_get_time_in_pcm_frames(const ma_sound_group* pGroup);
2125 
2126 
2127 
2128 /*
2129 Biquad Node
2130 */
2131 typedef struct
2132 {
2133     ma_node_config nodeConfig;
2134     ma_biquad_config biquad;
2135 } ma_biquad_node_config;
2136 
2137 MA_API ma_biquad_node_config ma_biquad_node_config_init(ma_uint32 channels, float b0, float b1, float b2, float a0, float a1, float a2);
2138 
2139 
2140 typedef struct
2141 {
2142     ma_node_base baseNode;
2143     ma_biquad biquad;
2144 } ma_biquad_node;
2145 
2146 MA_API ma_result ma_biquad_node_init(ma_node_graph* pNodeGraph, const ma_biquad_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_biquad_node* pNode);
2147 MA_API ma_result ma_biquad_node_reinit(const ma_biquad_config* pConfig, ma_biquad_node* pNode);
2148 MA_API void ma_biquad_node_uninit(ma_biquad_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks);
2149 
2150 
2151 /*
2152 Low Pass Filter Node
2153 */
2154 typedef struct
2155 {
2156     ma_node_config nodeConfig;
2157     ma_lpf_config lpf;
2158 } ma_lpf_node_config;
2159 
2160 MA_API ma_lpf_node_config ma_lpf_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double cutoffFrequency, ma_uint32 order);
2161 
2162 
2163 typedef struct
2164 {
2165     ma_node_base baseNode;
2166     ma_lpf lpf;
2167 } ma_lpf_node;
2168 
2169 MA_API ma_result ma_lpf_node_init(ma_node_graph* pNodeGraph, const ma_lpf_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_lpf_node* pNode);
2170 MA_API ma_result ma_lpf_node_reinit(const ma_lpf_config* pConfig, ma_lpf_node* pNode);
2171 MA_API void ma_lpf_node_uninit(ma_lpf_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks);
2172 
2173 
2174 /*
2175 High Pass Filter Node
2176 */
2177 typedef struct
2178 {
2179     ma_node_config nodeConfig;
2180     ma_hpf_config hpf;
2181 } ma_hpf_node_config;
2182 
2183 MA_API ma_hpf_node_config ma_hpf_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double cutoffFrequency, ma_uint32 order);
2184 
2185 
2186 typedef struct
2187 {
2188     ma_node_base baseNode;
2189     ma_hpf hpf;
2190 } ma_hpf_node;
2191 
2192 MA_API ma_result ma_hpf_node_init(ma_node_graph* pNodeGraph, const ma_hpf_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_hpf_node* pNode);
2193 MA_API ma_result ma_hpf_node_reinit(const ma_hpf_config* pConfig, ma_hpf_node* pNode);
2194 MA_API void ma_hpf_node_uninit(ma_hpf_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks);
2195 
2196 
2197 /*
2198 Band Pass Filter Node
2199 */
2200 typedef struct
2201 {
2202     ma_node_config nodeConfig;
2203     ma_bpf_config bpf;
2204 } ma_bpf_node_config;
2205 
2206 MA_API ma_bpf_node_config ma_bpf_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double cutoffFrequency, ma_uint32 order);
2207 
2208 
2209 typedef struct
2210 {
2211     ma_node_base baseNode;
2212     ma_bpf bpf;
2213 } ma_bpf_node;
2214 
2215 MA_API ma_result ma_bpf_node_init(ma_node_graph* pNodeGraph, const ma_bpf_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_bpf_node* pNode);
2216 MA_API ma_result ma_bpf_node_reinit(const ma_bpf_config* pConfig, ma_bpf_node* pNode);
2217 MA_API void ma_bpf_node_uninit(ma_bpf_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks);
2218 
2219 
2220 /*
2221 Notching Filter Node
2222 */
2223 typedef struct
2224 {
2225     ma_node_config nodeConfig;
2226     ma_notch_config notch;
2227 } ma_notch_node_config;
2228 
2229 MA_API ma_notch_node_config ma_notch_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double q, double frequency);
2230 
2231 
2232 typedef struct
2233 {
2234     ma_node_base baseNode;
2235     ma_notch2 notch;
2236 } ma_notch_node;
2237 
2238 MA_API ma_result ma_notch_node_init(ma_node_graph* pNodeGraph, const ma_notch_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_notch_node* pNode);
2239 MA_API ma_result ma_notch_node_reinit(const ma_notch_config* pConfig, ma_notch_node* pNode);
2240 MA_API void ma_notch_node_uninit(ma_notch_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks);
2241 
2242 
2243 /*
2244 Peaking Filter Node
2245 */
2246 typedef struct
2247 {
2248     ma_node_config nodeConfig;
2249     ma_peak_config peak;
2250 } ma_peak_node_config;
2251 
2252 MA_API ma_peak_node_config ma_peak_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double gainDB, double q, double frequency);
2253 
2254 
2255 typedef struct
2256 {
2257     ma_node_base baseNode;
2258     ma_peak2 peak;
2259 } ma_peak_node;
2260 
2261 MA_API ma_result ma_peak_node_init(ma_node_graph* pNodeGraph, const ma_peak_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_peak_node* pNode);
2262 MA_API ma_result ma_peak_node_reinit(const ma_peak_config* pConfig, ma_peak_node* pNode);
2263 MA_API void ma_peak_node_uninit(ma_peak_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks);
2264 
2265 
2266 /*
2267 Low Shelf Filter Node
2268 */
2269 typedef struct
2270 {
2271     ma_node_config nodeConfig;
2272     ma_loshelf_config loshelf;
2273 } ma_loshelf_node_config;
2274 
2275 MA_API ma_loshelf_node_config ma_loshelf_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double gainDB, double q, double frequency);
2276 
2277 
2278 typedef struct
2279 {
2280     ma_node_base baseNode;
2281     ma_loshelf2 loshelf;
2282 } ma_loshelf_node;
2283 
2284 MA_API ma_result ma_loshelf_node_init(ma_node_graph* pNodeGraph, const ma_loshelf_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_loshelf_node* pNode);
2285 MA_API ma_result ma_loshelf_node_reinit(const ma_loshelf_config* pConfig, ma_loshelf_node* pNode);
2286 MA_API void ma_loshelf_node_uninit(ma_loshelf_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks);
2287 
2288 
2289 /*
2290 High Shelf Filter Node
2291 */
2292 typedef struct
2293 {
2294     ma_node_config nodeConfig;
2295     ma_hishelf_config hishelf;
2296 } ma_hishelf_node_config;
2297 
2298 MA_API ma_hishelf_node_config ma_hishelf_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double gainDB, double q, double frequency);
2299 
2300 
2301 typedef struct
2302 {
2303     ma_node_base baseNode;
2304     ma_hishelf2 hishelf;
2305 } ma_hishelf_node;
2306 
2307 MA_API ma_result ma_hishelf_node_init(ma_node_graph* pNodeGraph, const ma_hishelf_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_hishelf_node* pNode);
2308 MA_API ma_result ma_hishelf_node_reinit(const ma_hishelf_config* pConfig, ma_hishelf_node* pNode);
2309 MA_API void ma_hishelf_node_uninit(ma_hishelf_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks);
2310 
2311 
2312 
2313 /*
2314 Delay
2315 */
2316 typedef struct
2317 {
2318     ma_uint32 channels;
2319     ma_uint32 sampleRate;
2320     ma_uint32 delayInFrames;
2321     ma_bool32 delayStart;       /* Set to true to delay the start of the output; false otherwise. */
2322     float wet;                  /* 0..1. Default = 1. */
2323     float dry;                  /* 0..1. Default = 1. */
2324     float decay;                /* 0..1. Default = 0 (no feedback). Feedback decay. Use this for echo. */
2325 } ma_delay_config;
2326 
2327 MA_API ma_delay_config ma_delay_config_init(ma_uint32 channels, ma_uint32 sampleRate, ma_uint32 delayInFrames, float decay);
2328 
2329 
2330 typedef struct
2331 {
2332     ma_delay_config config;
2333     ma_uint32 cursor;               /* Feedback is written to this cursor. Always equal or in front of the read cursor. */
2334     ma_uint32 bufferSizeInFrames;   /* The maximum of config.startDelayInFrames and config.feedbackDelayInFrames. */
2335     float* pBuffer;
2336 } ma_delay;
2337 
2338 MA_API ma_result ma_delay_init(const ma_delay_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_delay* pDelay);
2339 MA_API void ma_delay_uninit(ma_delay* pDelay, const ma_allocation_callbacks* pAllocationCallbacks);
2340 MA_API ma_result ma_delay_process_pcm_frames(ma_delay* pDelay, void* pFramesOut, const void* pFramesIn, ma_uint32 frameCount);
2341 MA_API void ma_delay_set_wet(ma_delay* pDelay, float value);
2342 MA_API float ma_delay_get_wet(const ma_delay* pDelay);
2343 MA_API void ma_delay_set_dry(ma_delay* pDelay, float value);
2344 MA_API float ma_delay_get_dry(const ma_delay* pDelay);
2345 MA_API void ma_delay_set_decay(ma_delay* pDelay, float value);
2346 MA_API float ma_delay_get_decay(const ma_delay* pDelay);
2347 
2348 
2349 
2350 typedef struct
2351 {
2352     ma_node_config nodeConfig;
2353     ma_delay_config delay;
2354 } ma_delay_node_config;
2355 
2356 MA_API ma_delay_node_config ma_delay_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, ma_uint32 delayInFrames, float decay);
2357 
2358 
2359 typedef struct
2360 {
2361     ma_node_base baseNode;
2362     ma_delay delay;
2363 } ma_delay_node;
2364 
2365 MA_API ma_result ma_delay_node_init(ma_node_graph* pNodeGraph, const ma_delay_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_delay_node* pDelayNode);
2366 MA_API void ma_delay_node_uninit(ma_delay_node* pDelayNode, const ma_allocation_callbacks* pAllocationCallbacks);
2367 MA_API void ma_delay_node_set_wet(ma_delay_node* pDelayNode, float value);
2368 MA_API float ma_delay_node_get_wet(const ma_delay_node* pDelayNode);
2369 MA_API void ma_delay_node_set_dry(ma_delay_node* pDelayNode, float value);
2370 MA_API float ma_delay_node_get_dry(const ma_delay_node* pDelayNode);
2371 MA_API void ma_delay_node_set_decay(ma_delay_node* pDelayNode, float value);
2372 MA_API float ma_delay_node_get_decay(const ma_delay_node* pDelayNode);
2373 
2374 
2375 #ifdef __cplusplus
2376 }
2377 #endif
2378 #endif  /* miniaudio_engine_h */
2379 
2380 
2381 #if defined(MA_IMPLEMENTATION) || defined(MINIAUDIO_IMPLEMENTATION)
2382 
ma_paged_audio_buffer_data_init(ma_format format,ma_uint32 channels,ma_paged_audio_buffer_data * pData)2383 MA_API ma_result ma_paged_audio_buffer_data_init(ma_format format, ma_uint32 channels, ma_paged_audio_buffer_data* pData)
2384 {
2385     if (pData == NULL) {
2386         return MA_INVALID_ARGS;
2387     }
2388 
2389     MA_ZERO_OBJECT(pData);
2390 
2391     pData->format   = format;
2392     pData->channels = channels;
2393     pData->pTail    = &pData->head;
2394 
2395     return MA_SUCCESS;
2396 }
2397 
ma_paged_audio_buffer_data_uninit(ma_paged_audio_buffer_data * pData,const ma_allocation_callbacks * pAllocationCallbacks)2398 MA_API void ma_paged_audio_buffer_data_uninit(ma_paged_audio_buffer_data* pData, const ma_allocation_callbacks* pAllocationCallbacks)
2399 {
2400     ma_paged_audio_buffer_page* pPage;
2401 
2402     if (pData == NULL) {
2403         return;
2404     }
2405 
2406     /* All pages need to be freed. */
2407     pPage = (ma_paged_audio_buffer_page*)c89atomic_load_ptr(&pData->head.pNext);
2408     while (pPage != NULL) {
2409         ma_paged_audio_buffer_page* pNext = (ma_paged_audio_buffer_page*)c89atomic_load_ptr(&pPage->pNext);
2410 
2411         ma_free(pPage, pAllocationCallbacks);
2412         pPage = pNext;
2413     }
2414 }
2415 
ma_paged_audio_buffer_data_get_head(ma_paged_audio_buffer_data * pData)2416 MA_API ma_paged_audio_buffer_page* ma_paged_audio_buffer_data_get_head(ma_paged_audio_buffer_data* pData)
2417 {
2418     if (pData == NULL) {
2419         return NULL;
2420     }
2421 
2422     return &pData->head;
2423 }
2424 
ma_paged_audio_buffer_data_get_tail(ma_paged_audio_buffer_data * pData)2425 MA_API ma_paged_audio_buffer_page* ma_paged_audio_buffer_data_get_tail(ma_paged_audio_buffer_data* pData)
2426 {
2427     if (pData == NULL) {
2428         return NULL;
2429     }
2430 
2431     return pData->pTail;
2432 }
2433 
ma_paged_audio_buffer_data_get_length_in_pcm_frames(ma_paged_audio_buffer_data * pData,ma_uint64 * pLength)2434 MA_API ma_result ma_paged_audio_buffer_data_get_length_in_pcm_frames(ma_paged_audio_buffer_data* pData, ma_uint64* pLength)
2435 {
2436     ma_paged_audio_buffer_page* pPage;
2437 
2438     if (pLength == NULL) {
2439         return MA_INVALID_ARGS;
2440     }
2441 
2442     *pLength = 0;
2443 
2444     if (pData == NULL) {
2445         return MA_INVALID_ARGS;
2446     }
2447 
2448     /* Calculate the length from the linked list. */
2449     for (pPage = (ma_paged_audio_buffer_page*)c89atomic_load_ptr(&pData->head.pNext); pPage != NULL; pPage = (ma_paged_audio_buffer_page*)c89atomic_load_ptr(&pPage->pNext)) {
2450         *pLength += pPage->sizeInFrames;
2451     }
2452 
2453     return MA_SUCCESS;
2454 }
2455 
ma_paged_audio_buffer_data_allocate_page(ma_paged_audio_buffer_data * pData,ma_uint64 pageSizeInFrames,const void * pInitialData,const ma_allocation_callbacks * pAllocationCallbacks,ma_paged_audio_buffer_page ** ppPage)2456 MA_API ma_result ma_paged_audio_buffer_data_allocate_page(ma_paged_audio_buffer_data* pData, ma_uint64 pageSizeInFrames, const void* pInitialData, const ma_allocation_callbacks* pAllocationCallbacks, ma_paged_audio_buffer_page** ppPage)
2457 {
2458     ma_paged_audio_buffer_page* pPage;
2459     ma_uint64 allocationSize;
2460 
2461     if (ppPage == NULL) {
2462         return MA_INVALID_ARGS;
2463     }
2464 
2465     *ppPage = NULL;
2466 
2467     if (pData == NULL) {
2468         return MA_INVALID_ARGS;
2469     }
2470 
2471     allocationSize = sizeof(*pPage) + (pageSizeInFrames * ma_get_bytes_per_frame(pData->format, pData->channels));
2472     if (allocationSize > MA_SIZE_MAX) {
2473         return MA_OUT_OF_MEMORY;    /* Too big. */
2474     }
2475 
2476     pPage = (ma_paged_audio_buffer_page*)ma_malloc((size_t)allocationSize, pAllocationCallbacks);	/* Safe cast to size_t. */
2477     if (pPage == NULL) {
2478         return MA_OUT_OF_MEMORY;
2479     }
2480 
2481     pPage->pNext = NULL;
2482     pPage->sizeInFrames = pageSizeInFrames;
2483 
2484     if (pInitialData != NULL) {
2485         ma_copy_pcm_frames(pPage->pAudioData, pInitialData, pageSizeInFrames, pData->format, pData->channels);
2486     }
2487 
2488     *ppPage = pPage;
2489 
2490     return MA_SUCCESS;
2491 }
2492 
ma_paged_audio_buffer_data_free_page(ma_paged_audio_buffer_data * pData,ma_paged_audio_buffer_page * pPage,const ma_allocation_callbacks * pAllocationCallbacks)2493 MA_API ma_result ma_paged_audio_buffer_data_free_page(ma_paged_audio_buffer_data* pData, ma_paged_audio_buffer_page* pPage, const ma_allocation_callbacks* pAllocationCallbacks)
2494 {
2495     if (pData == NULL || pPage == NULL) {
2496         return MA_INVALID_ARGS;
2497     }
2498 
2499     /* It's assumed the page is not attached to the list. */
2500     ma_free(pPage, pAllocationCallbacks);
2501 
2502     return MA_SUCCESS;
2503 }
2504 
ma_paged_audio_buffer_data_append_page(ma_paged_audio_buffer_data * pData,ma_paged_audio_buffer_page * pPage)2505 MA_API ma_result ma_paged_audio_buffer_data_append_page(ma_paged_audio_buffer_data* pData, ma_paged_audio_buffer_page* pPage)
2506 {
2507     if (pData == NULL || pPage == NULL) {
2508         return MA_INVALID_ARGS;
2509     }
2510 
2511     /* This function assumes the page has been filled with audio data by this point. As soon as we append, the page will be available for reading. */
2512 
2513     /* First thing to do is update the tail. */
2514     for (;;) {
2515         ma_paged_audio_buffer_page* pOldTail = (ma_paged_audio_buffer_page*)c89atomic_load_ptr(&pData->pTail);
2516         ma_paged_audio_buffer_page* pNewTail = pPage;
2517 
2518         if (c89atomic_compare_exchange_weak_ptr((void**)&pData->pTail, (void**)&pOldTail, pNewTail)) {
2519             /* Here is where we append the page to the list. After this, the page is attached to the list and ready to be read from. */
2520             c89atomic_exchange_ptr(&pOldTail->pNext, pPage);
2521             break;  /* Done. */
2522         }
2523     }
2524 
2525     return MA_SUCCESS;
2526 }
2527 
ma_paged_audio_buffer_data_allocate_and_append_page(ma_paged_audio_buffer_data * pData,ma_uint32 pageSizeInFrames,const void * pInitialData,const ma_allocation_callbacks * pAllocationCallbacks)2528 MA_API ma_result ma_paged_audio_buffer_data_allocate_and_append_page(ma_paged_audio_buffer_data* pData, ma_uint32 pageSizeInFrames, const void* pInitialData, const ma_allocation_callbacks* pAllocationCallbacks)
2529 {
2530     ma_result result;
2531     ma_paged_audio_buffer_page* pPage;
2532 
2533     result = ma_paged_audio_buffer_data_allocate_page(pData, pageSizeInFrames, pInitialData, pAllocationCallbacks, &pPage);
2534     if (result != MA_SUCCESS) {
2535         return result;
2536     }
2537 
2538     return ma_paged_audio_buffer_data_append_page(pData, pPage);    /* <-- Should never fail. */
2539 }
2540 
2541 
ma_paged_audio_buffer_config_init(ma_paged_audio_buffer_data * pData)2542 MA_API ma_paged_audio_buffer_config ma_paged_audio_buffer_config_init(ma_paged_audio_buffer_data* pData)
2543 {
2544     ma_paged_audio_buffer_config config;
2545 
2546     MA_ZERO_OBJECT(&config);
2547     config.pData = pData;
2548 
2549     return config;
2550 }
2551 
2552 
ma_paged_audio_buffer__data_source_on_read(ma_data_source * pDataSource,void * pFramesOut,ma_uint64 frameCount,ma_uint64 * pFramesRead)2553 static ma_result ma_paged_audio_buffer__data_source_on_read(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead)
2554 {
2555     return ma_paged_audio_buffer_read_pcm_frames((ma_paged_audio_buffer*)pDataSource, pFramesOut, frameCount, pFramesRead);
2556 }
2557 
ma_paged_audio_buffer__data_source_on_seek(ma_data_source * pDataSource,ma_uint64 frameIndex)2558 static ma_result ma_paged_audio_buffer__data_source_on_seek(ma_data_source* pDataSource, ma_uint64 frameIndex)
2559 {
2560     return ma_paged_audio_buffer_seek_to_pcm_frame((ma_paged_audio_buffer*)pDataSource, frameIndex);
2561 }
2562 
ma_paged_audio_buffer__data_source_on_get_data_format(ma_data_source * pDataSource,ma_format * pFormat,ma_uint32 * pChannels,ma_uint32 * pSampleRate)2563 static ma_result ma_paged_audio_buffer__data_source_on_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate)
2564 {
2565     ma_paged_audio_buffer* pPagedAudioBuffer = (ma_paged_audio_buffer*)pDataSource;
2566 
2567     *pFormat     = pPagedAudioBuffer->pData->format;
2568     *pChannels   = pPagedAudioBuffer->pData->channels;
2569     *pSampleRate = 0;   /* There is no notion of a sample rate with audio buffers. */
2570 
2571     return MA_SUCCESS;
2572 }
2573 
ma_paged_audio_buffer__data_source_on_get_cursor(ma_data_source * pDataSource,ma_uint64 * pCursor)2574 static ma_result ma_paged_audio_buffer__data_source_on_get_cursor(ma_data_source* pDataSource, ma_uint64* pCursor)
2575 {
2576     return ma_paged_audio_buffer_get_cursor_in_pcm_frames((ma_paged_audio_buffer*)pDataSource, pCursor);
2577 }
2578 
ma_paged_audio_buffer__data_source_on_get_length(ma_data_source * pDataSource,ma_uint64 * pLength)2579 static ma_result ma_paged_audio_buffer__data_source_on_get_length(ma_data_source* pDataSource, ma_uint64* pLength)
2580 {
2581     return ma_paged_audio_buffer_get_length_in_pcm_frames((ma_paged_audio_buffer*)pDataSource, pLength);
2582 }
2583 
2584 static ma_data_source_vtable g_ma_paged_audio_buffer_data_source_vtable =
2585 {
2586     ma_paged_audio_buffer__data_source_on_read,
2587     ma_paged_audio_buffer__data_source_on_seek,
2588     NULL,   /* onMap */
2589     NULL,   /* onUnmap */
2590     ma_paged_audio_buffer__data_source_on_get_data_format,
2591     ma_paged_audio_buffer__data_source_on_get_cursor,
2592     ma_paged_audio_buffer__data_source_on_get_length
2593 };
2594 
ma_paged_audio_buffer_init(const ma_paged_audio_buffer_config * pConfig,ma_paged_audio_buffer * pPagedAudioBuffer)2595 MA_API ma_result ma_paged_audio_buffer_init(const ma_paged_audio_buffer_config* pConfig, ma_paged_audio_buffer* pPagedAudioBuffer)
2596 {
2597     ma_result result;
2598     ma_data_source_config dataSourceConfig;
2599 
2600     if (pPagedAudioBuffer == NULL) {
2601         return MA_INVALID_ARGS;
2602     }
2603 
2604     MA_ZERO_OBJECT(pPagedAudioBuffer);
2605 
2606     /* A config is required for the format and channel count. */
2607     if (pConfig == NULL) {
2608         return MA_INVALID_ARGS;
2609     }
2610 
2611     if (pConfig->pData == NULL) {
2612         return MA_INVALID_ARGS; /* No underlying data specified. */
2613     }
2614 
2615     dataSourceConfig = ma_data_source_config_init();
2616     dataSourceConfig.vtable = &g_ma_paged_audio_buffer_data_source_vtable;
2617 
2618     result = ma_data_source_init(&dataSourceConfig, &pPagedAudioBuffer->ds);
2619     if (result != MA_SUCCESS) {
2620         return result;
2621     }
2622 
2623     pPagedAudioBuffer->pData          = pConfig->pData;
2624     pPagedAudioBuffer->pCurrent       = ma_paged_audio_buffer_data_get_head(pConfig->pData);
2625     pPagedAudioBuffer->relativeCursor = 0;
2626     pPagedAudioBuffer->absoluteCursor = 0;
2627 
2628     return MA_SUCCESS;
2629 }
2630 
ma_paged_audio_buffer_uninit(ma_paged_audio_buffer * pPagedAudioBuffer)2631 MA_API void ma_paged_audio_buffer_uninit(ma_paged_audio_buffer* pPagedAudioBuffer)
2632 {
2633     if (pPagedAudioBuffer == NULL) {
2634         return;
2635     }
2636 
2637     /* Nothing to do. The data needs to be deleted separately. */
2638 }
2639 
ma_paged_audio_buffer_read_pcm_frames(ma_paged_audio_buffer * pPagedAudioBuffer,void * pFramesOut,ma_uint64 frameCount,ma_uint64 * pFramesRead)2640 MA_API ma_result ma_paged_audio_buffer_read_pcm_frames(ma_paged_audio_buffer* pPagedAudioBuffer, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead)
2641 {
2642     ma_result result = MA_SUCCESS;
2643     ma_uint64 totalFramesRead = 0;
2644     ma_format format;
2645     ma_uint32 channels;
2646 
2647     if (pPagedAudioBuffer == NULL) {
2648         return MA_INVALID_ARGS;
2649     }
2650 
2651     format   = pPagedAudioBuffer->pData->format;
2652     channels = pPagedAudioBuffer->pData->channels;
2653 
2654     while (totalFramesRead < frameCount) {
2655         /* Read from the current page. The buffer should never be in a state where this is NULL. */
2656         ma_uint64 framesRemainingInCurrentPage;
2657         ma_uint64 framesRemainingToRead = frameCount - totalFramesRead;
2658         ma_uint64 framesToReadThisIteration;
2659 
2660         MA_ASSERT(pPagedAudioBuffer->pCurrent != NULL);
2661 
2662         framesRemainingInCurrentPage = pPagedAudioBuffer->pCurrent->sizeInFrames - pPagedAudioBuffer->relativeCursor;
2663 
2664         framesToReadThisIteration = ma_min(framesRemainingInCurrentPage, framesRemainingToRead);
2665         ma_copy_pcm_frames(ma_offset_pcm_frames_ptr(pFramesOut, totalFramesRead, format, channels), ma_offset_pcm_frames_ptr(pPagedAudioBuffer->pCurrent->pAudioData, pPagedAudioBuffer->relativeCursor, format, channels), framesToReadThisIteration, format, channels);
2666         totalFramesRead += framesToReadThisIteration;
2667 
2668         pPagedAudioBuffer->absoluteCursor += framesToReadThisIteration;
2669         pPagedAudioBuffer->relativeCursor += framesToReadThisIteration;
2670 
2671         /* Move to the next page if necessary. If there's no more pages, we need to return MA_AT_END. */
2672         MA_ASSERT(pPagedAudioBuffer->relativeCursor <= pPagedAudioBuffer->pCurrent->sizeInFrames);
2673 
2674         if (pPagedAudioBuffer->relativeCursor == pPagedAudioBuffer->pCurrent->sizeInFrames) {
2675             /* We reached the end of the page. Need to move to the next. If there's no more pages, we're done. */
2676             ma_paged_audio_buffer_page* pNext = (ma_paged_audio_buffer_page*)c89atomic_load_ptr(&pPagedAudioBuffer->pCurrent->pNext);
2677             if (pNext == NULL) {
2678                 result = MA_AT_END;
2679                 break;  /* We've reached the end. */
2680             } else {
2681                 pPagedAudioBuffer->pCurrent       = pNext;
2682                 pPagedAudioBuffer->relativeCursor = 0;
2683             }
2684         }
2685     }
2686 
2687     if (pFramesRead != NULL) {
2688         *pFramesRead = totalFramesRead;
2689     }
2690 
2691     return result;
2692 }
2693 
ma_paged_audio_buffer_seek_to_pcm_frame(ma_paged_audio_buffer * pPagedAudioBuffer,ma_uint64 frameIndex)2694 MA_API ma_result ma_paged_audio_buffer_seek_to_pcm_frame(ma_paged_audio_buffer* pPagedAudioBuffer, ma_uint64 frameIndex)
2695 {
2696     if (pPagedAudioBuffer == NULL) {
2697         return MA_INVALID_ARGS;
2698     }
2699 
2700     if (frameIndex == pPagedAudioBuffer->absoluteCursor) {
2701         return MA_SUCCESS;  /* Nothing to do. */
2702     }
2703 
2704     if (frameIndex < pPagedAudioBuffer->absoluteCursor) {
2705         /* Moving backwards. Need to move the cursor back to the start, and then move forward. */
2706         pPagedAudioBuffer->pCurrent       = ma_paged_audio_buffer_data_get_head(pPagedAudioBuffer->pData);
2707         pPagedAudioBuffer->absoluteCursor = 0;
2708         pPagedAudioBuffer->relativeCursor = 0;
2709 
2710         /* Fall through to the forward seeking section below. */
2711     }
2712 
2713     if (frameIndex > pPagedAudioBuffer->absoluteCursor) {
2714         /* Moving forward. */
2715         ma_paged_audio_buffer_page* pPage;
2716         ma_uint64 runningCursor = 0;
2717 
2718         for (pPage = (ma_paged_audio_buffer_page*)c89atomic_load_ptr(&ma_paged_audio_buffer_data_get_head(pPagedAudioBuffer->pData)->pNext); pPage != NULL; pPage = (ma_paged_audio_buffer_page*)c89atomic_load_ptr(&pPage->pNext)) {
2719             ma_uint64 pageRangeBeg = runningCursor;
2720             ma_uint64 pageRangeEnd = pageRangeBeg + pPage->sizeInFrames;
2721 
2722             if (frameIndex >= pageRangeBeg) {
2723                 if (frameIndex < pageRangeEnd || (frameIndex == pageRangeEnd && pPage == (ma_paged_audio_buffer_page*)c89atomic_load_ptr(ma_paged_audio_buffer_data_get_tail(pPagedAudioBuffer->pData)))) {  /* A small edge case - allow seeking to the very end of the buffer. */
2724                     /* We found the page. */
2725                     pPagedAudioBuffer->pCurrent       = pPage;
2726                     pPagedAudioBuffer->absoluteCursor = frameIndex;
2727                     pPagedAudioBuffer->relativeCursor = frameIndex - pageRangeBeg;
2728                     return MA_SUCCESS;
2729                 }
2730             }
2731 
2732             runningCursor = pageRangeEnd;
2733         }
2734 
2735         /* Getting here means we tried seeking too far forward. Don't change any state. */
2736         return MA_BAD_SEEK;
2737     }
2738 
2739     return MA_SUCCESS;
2740 }
2741 
ma_paged_audio_buffer_get_cursor_in_pcm_frames(ma_paged_audio_buffer * pPagedAudioBuffer,ma_uint64 * pCursor)2742 MA_API ma_result ma_paged_audio_buffer_get_cursor_in_pcm_frames(ma_paged_audio_buffer* pPagedAudioBuffer, ma_uint64* pCursor)
2743 {
2744     if (pCursor == NULL) {
2745         return MA_INVALID_ARGS;
2746     }
2747 
2748     *pCursor = 0;   /* Safety. */
2749 
2750     if (pPagedAudioBuffer == NULL) {
2751         return MA_INVALID_ARGS;
2752     }
2753 
2754     *pCursor = pPagedAudioBuffer->absoluteCursor;
2755 
2756     return MA_SUCCESS;
2757 }
2758 
ma_paged_audio_buffer_get_length_in_pcm_frames(ma_paged_audio_buffer * pPagedAudioBuffer,ma_uint64 * pLength)2759 MA_API ma_result ma_paged_audio_buffer_get_length_in_pcm_frames(ma_paged_audio_buffer* pPagedAudioBuffer, ma_uint64* pLength)
2760 {
2761     return ma_paged_audio_buffer_data_get_length_in_pcm_frames(pPagedAudioBuffer->pData, pLength);
2762 }
2763 
2764 
2765 
ma_get_accumulation_bytes_per_sample(ma_format format)2766 MA_API size_t ma_get_accumulation_bytes_per_sample(ma_format format)
2767 {
2768     size_t bytesPerSample[ma_format_count] = {
2769         0,                  /* ma_format_unknown */
2770         sizeof(ma_int16),   /* ma_format_u8  */
2771         sizeof(ma_int32),   /* ma_format_s16 */
2772         sizeof(ma_int64),   /* ma_format_s24 */
2773         sizeof(ma_int64),   /* ma_format_s32 */
2774         sizeof(float)       /* ma_format_f32 */
2775     };
2776 
2777     return bytesPerSample[format];
2778 }
2779 
ma_get_accumulation_bytes_per_frame(ma_format format,ma_uint32 channels)2780 MA_API size_t ma_get_accumulation_bytes_per_frame(ma_format format, ma_uint32 channels)
2781 {
2782     return ma_get_accumulation_bytes_per_sample(format) * channels;
2783 }
2784 
2785 
2786 
ma_gainer_config_init(ma_uint32 channels,ma_uint32 smoothTimeInFrames)2787 MA_API ma_gainer_config ma_gainer_config_init(ma_uint32 channels, ma_uint32 smoothTimeInFrames)
2788 {
2789     ma_gainer_config config;
2790 
2791     MA_ZERO_OBJECT(&config);
2792     config.channels           = channels;
2793     config.smoothTimeInFrames = smoothTimeInFrames;
2794 
2795     return config;
2796 }
2797 
2798 
2799 typedef struct
2800 {
2801     size_t sizeInBytes;
2802     size_t oldGainsOffset;
2803     size_t newGainsOffset;
2804 } ma_gainer_heap_layout;
2805 
ma_gainer_get_heap_layout(const ma_gainer_config * pConfig,ma_gainer_heap_layout * pHeapLayout)2806 static ma_result ma_gainer_get_heap_layout(const ma_gainer_config* pConfig, ma_gainer_heap_layout* pHeapLayout)
2807 {
2808     MA_ASSERT(pHeapLayout != NULL);
2809 
2810     MA_ZERO_OBJECT(pHeapLayout);
2811 
2812     if (pConfig == NULL) {
2813         return MA_INVALID_ARGS;
2814     }
2815 
2816     if (pConfig->channels == 0) {
2817         return MA_INVALID_ARGS;
2818     }
2819 
2820     pHeapLayout->sizeInBytes = 0;
2821 
2822     /* Old gains. */
2823     pHeapLayout->oldGainsOffset = pHeapLayout->sizeInBytes;
2824     pHeapLayout->sizeInBytes += sizeof(float) * pConfig->channels;
2825 
2826     /* New gains. */
2827     pHeapLayout->newGainsOffset = pHeapLayout->sizeInBytes;
2828     pHeapLayout->sizeInBytes += sizeof(float) * pConfig->channels;
2829 
2830     /* Alignment. */
2831     pHeapLayout->sizeInBytes = ma_align_64(pHeapLayout->sizeInBytes);
2832 
2833     return MA_SUCCESS;
2834 }
2835 
2836 
ma_gainer_get_heap_size(const ma_gainer_config * pConfig,size_t * pHeapSizeInBytes)2837 MA_API ma_result ma_gainer_get_heap_size(const ma_gainer_config* pConfig, size_t* pHeapSizeInBytes)
2838 {
2839     ma_result result;
2840     ma_gainer_heap_layout heapLayout;
2841 
2842     if (pHeapSizeInBytes == NULL) {
2843         return MA_INVALID_ARGS;
2844     }
2845 
2846     *pHeapSizeInBytes = 0;
2847 
2848     result = ma_gainer_get_heap_layout(pConfig, &heapLayout);
2849     if (result != MA_SUCCESS) {
2850         return MA_INVALID_ARGS;
2851     }
2852 
2853     *pHeapSizeInBytes = heapLayout.sizeInBytes;
2854 
2855     return MA_SUCCESS;
2856 }
2857 
2858 
ma_gainer_init_preallocated(const ma_gainer_config * pConfig,void * pHeap,ma_gainer * pGainer)2859 MA_API ma_result ma_gainer_init_preallocated(const ma_gainer_config* pConfig, void* pHeap, ma_gainer* pGainer)
2860 {
2861     ma_result result;
2862     ma_gainer_heap_layout heapLayout;
2863     ma_uint32 iChannel;
2864 
2865     if (pGainer == NULL) {
2866         return MA_INVALID_ARGS;
2867     }
2868 
2869     MA_ZERO_OBJECT(pGainer);
2870 
2871     if (pConfig == NULL || pHeap == NULL) {
2872         return MA_INVALID_ARGS;
2873     }
2874 
2875     result = ma_gainer_get_heap_layout(pConfig, &heapLayout);
2876     if (result != MA_SUCCESS) {
2877         return result;
2878     }
2879 
2880     pGainer->_pHeap    = pHeap;
2881     pGainer->pOldGains = (float*)ma_offset_ptr(pHeap, heapLayout.oldGainsOffset);
2882     pGainer->pNewGains = (float*)ma_offset_ptr(pHeap, heapLayout.newGainsOffset);
2883 
2884     pGainer->config = *pConfig;
2885     pGainer->t      = (ma_uint32)-1;  /* No interpolation by default. */
2886 
2887     for (iChannel = 0; iChannel < pConfig->channels; iChannel += 1) {
2888         pGainer->pOldGains[iChannel] = 1;
2889         pGainer->pNewGains[iChannel] = 1;
2890     }
2891 
2892     return MA_SUCCESS;
2893 }
2894 
ma_gainer_init(const ma_gainer_config * pConfig,const ma_allocation_callbacks * pAllocationCallbacks,ma_gainer * pGainer)2895 MA_API ma_result ma_gainer_init(const ma_gainer_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_gainer* pGainer)
2896 {
2897     ma_result result;
2898     size_t heapSizeInBytes;
2899     void* pHeap;
2900 
2901     result = ma_gainer_get_heap_size(pConfig, &heapSizeInBytes);
2902     if (result != MA_SUCCESS) {
2903         return result;  /* Failed to retrieve the size of the heap allocation. */
2904     }
2905 
2906     if (heapSizeInBytes > 0) {
2907         pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks);
2908         if (pHeap == NULL) {
2909             return MA_OUT_OF_MEMORY;
2910         }
2911     } else {
2912         pHeap = NULL;
2913     }
2914 
2915     result = ma_gainer_init_preallocated(pConfig, pHeap, pGainer);
2916     if (result != MA_SUCCESS) {
2917         return result;
2918     }
2919 
2920     pGainer->_ownsHeap = MA_TRUE;
2921     return MA_SUCCESS;
2922 }
2923 
ma_gainer_uninit(ma_gainer * pGainer,const ma_allocation_callbacks * pAllocationCallbacks)2924 MA_API void ma_gainer_uninit(ma_gainer* pGainer, const ma_allocation_callbacks* pAllocationCallbacks)
2925 {
2926     if (pGainer == NULL) {
2927         return;
2928     }
2929 
2930     if (pGainer->_pHeap != NULL && pGainer->_ownsHeap) {
2931         ma_free(pGainer->_pHeap, pAllocationCallbacks);
2932     }
2933 }
2934 
ma_gainer_calculate_current_gain(const ma_gainer * pGainer,ma_uint32 channel)2935 static float ma_gainer_calculate_current_gain(const ma_gainer* pGainer, ma_uint32 channel)
2936 {
2937     float a = (float)pGainer->t / pGainer->config.smoothTimeInFrames;
2938     return ma_mix_f32_fast(pGainer->pOldGains[channel], pGainer->pNewGains[channel], a);
2939 }
2940 
ma_gainer_process_pcm_frames(ma_gainer * pGainer,void * pFramesOut,const void * pFramesIn,ma_uint64 frameCount)2941 MA_API ma_result ma_gainer_process_pcm_frames(ma_gainer* pGainer, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount)
2942 {
2943     ma_uint64 iFrame;
2944     ma_uint32 iChannel;
2945     float* pFramesOutF32 = (float*)pFramesOut;
2946     const float* pFramesInF32 = (const float*)pFramesIn;
2947 
2948     if (pGainer == NULL) {
2949         return MA_INVALID_ARGS;
2950     }
2951 
2952     if (pGainer->t >= pGainer->config.smoothTimeInFrames) {
2953         /* Fast path. No gain calculation required. */
2954         ma_copy_and_apply_volume_factor_per_channel_f32(pFramesOutF32, pFramesInF32, frameCount, pGainer->config.channels, pGainer->pNewGains);
2955 
2956         /* Now that some frames have been processed we need to make sure future changes to the gain are interpolated. */
2957         if (pGainer->t == (ma_uint32)-1) {
2958             pGainer->t = pGainer->config.smoothTimeInFrames;
2959         }
2960     } else {
2961         /* Slow path. Need to interpolate the gain for each channel individually. */
2962 
2963         /* We can allow the input and output buffers to be null in which case we'll just update the internal timer. */
2964         if (pFramesOut != NULL && pFramesIn != NULL) {
2965             float a = (float)pGainer->t / pGainer->config.smoothTimeInFrames;
2966             float d = 1.0f / pGainer->config.smoothTimeInFrames;
2967             ma_uint32 channelCount = pGainer->config.channels;
2968 
2969             for (iFrame = 0; iFrame < frameCount; iFrame += 1) {
2970                 for (iChannel = 0; iChannel < channelCount; iChannel += 1) {
2971                     pFramesOutF32[iChannel] = pFramesInF32[iChannel] * ma_mix_f32_fast(pGainer->pOldGains[iChannel], pGainer->pNewGains[iChannel], a);
2972                 }
2973 
2974                 pFramesOutF32 += channelCount;
2975                 pFramesInF32  += channelCount;
2976 
2977                 a += d;
2978                 if (a > 1) {
2979                     a = 1;
2980                 }
2981             }
2982         }
2983 
2984         pGainer->t = (ma_uint32)ma_min(pGainer->t + frameCount, pGainer->config.smoothTimeInFrames);
2985 
2986     #if 0   /* Reference implementation. */
2987         for (iFrame = 0; iFrame < frameCount; iFrame += 1) {
2988             /* We can allow the input and output buffers to be null in which case we'll just update the internal timer. */
2989             if (pFramesOut != NULL && pFramesIn != NULL) {
2990                 for (iChannel = 0; iChannel < pGainer->config.channels; iChannel += 1) {
2991                     pFramesOutF32[iFrame*pGainer->config.channels + iChannel] = pFramesInF32[iFrame*pGainer->config.channels + iChannel] * ma_gainer_calculate_current_gain(pGainer, iChannel);
2992                 }
2993             }
2994 
2995             /* Move interpolation time forward, but don't go beyond our smoothing time. */
2996             pGainer->t = ma_min(pGainer->t + 1, pGainer->config.smoothTimeInFrames);
2997         }
2998     #endif
2999     }
3000 
3001     return MA_SUCCESS;
3002 }
3003 
ma_gainer_set_gain_by_index(ma_gainer * pGainer,float newGain,ma_uint32 iChannel)3004 static void ma_gainer_set_gain_by_index(ma_gainer* pGainer, float newGain, ma_uint32 iChannel)
3005 {
3006     pGainer->pOldGains[iChannel] = ma_gainer_calculate_current_gain(pGainer, iChannel);
3007     pGainer->pNewGains[iChannel] = newGain;
3008 }
3009 
ma_gainer_reset_smoothing_time(ma_gainer * pGainer)3010 static void ma_gainer_reset_smoothing_time(ma_gainer* pGainer)
3011 {
3012     if (pGainer->t == (ma_uint32)-1) {
3013         pGainer->t = pGainer->config.smoothTimeInFrames;    /* No smoothing required for initial gains setting. */
3014     } else {
3015         pGainer->t = 0;
3016     }
3017 }
3018 
ma_gainer_set_gain(ma_gainer * pGainer,float newGain)3019 MA_API ma_result ma_gainer_set_gain(ma_gainer* pGainer, float newGain)
3020 {
3021     ma_uint32 iChannel;
3022 
3023     if (pGainer == NULL) {
3024         return MA_INVALID_ARGS;
3025     }
3026 
3027     for (iChannel = 0; iChannel < pGainer->config.channels; iChannel += 1) {
3028         ma_gainer_set_gain_by_index(pGainer, newGain, iChannel);
3029     }
3030 
3031     /* The smoothing time needs to be reset to ensure we always interpolate by the configured smoothing time, but only if it's not the first setting. */
3032     ma_gainer_reset_smoothing_time(pGainer);
3033 
3034     return MA_SUCCESS;
3035 }
3036 
ma_gainer_set_gains(ma_gainer * pGainer,float * pNewGains)3037 MA_API ma_result ma_gainer_set_gains(ma_gainer* pGainer, float* pNewGains)
3038 {
3039     ma_uint32 iChannel;
3040 
3041     if (pGainer == NULL || pNewGains == NULL) {
3042         return MA_INVALID_ARGS;
3043     }
3044 
3045     for (iChannel = 0; iChannel < pGainer->config.channels; iChannel += 1) {
3046         ma_gainer_set_gain_by_index(pGainer, pNewGains[iChannel], iChannel);
3047     }
3048 
3049     /* The smoothing time needs to be reset to ensure we always interpolate by the configured smoothing time, but only if it's not the first setting. */
3050     ma_gainer_reset_smoothing_time(pGainer);
3051 
3052     return MA_SUCCESS;
3053 }
3054 
3055 
3056 
3057 
3058 /* 10ms @ 48K = 480. Must never exceed 65535. */
3059 #ifndef MA_DEFAULT_NODE_CACHE_CAP_IN_FRAMES_PER_BUS
3060 #define MA_DEFAULT_NODE_CACHE_CAP_IN_FRAMES_PER_BUS 480
3061 #endif
3062 
3063 
3064 static ma_result ma_node_read_pcm_frames(ma_node* pNode, ma_uint32 outputBusIndex, float* pFramesOut, ma_uint32 frameCount, ma_uint32* pFramesRead, ma_uint64 globalTime);
3065 
3066 
ma_debug_fill_pcm_frames_with_sine_wave(float * pFramesOut,ma_uint32 frameCount,ma_format format,ma_uint32 channels,ma_uint32 sampleRate)3067 MA_API void ma_debug_fill_pcm_frames_with_sine_wave(float* pFramesOut, ma_uint32 frameCount, ma_format format, ma_uint32 channels, ma_uint32 sampleRate)
3068 {
3069     #ifndef MA_NO_GENERATION
3070     {
3071         ma_waveform_config waveformConfig;
3072         ma_waveform waveform;
3073 
3074         waveformConfig = ma_waveform_config_init(format, channels, sampleRate, ma_waveform_type_sine, 1.0, 400);
3075         ma_waveform_init(&waveformConfig, &waveform);
3076         ma_waveform_read_pcm_frames(&waveform, pFramesOut, frameCount);
3077     }
3078     #else
3079     {
3080         (void)pFramesOut;
3081         (void)frameCount;
3082         (void)format;
3083         (void)channels;
3084         (void)sampleRate;
3085         #if defined(MA_DEBUG_OUTPUT)
3086         {
3087             #warning ma_debug_fill_pcm_frames_with_sine_wave() will do nothing because MA_NO_GENERATION is enabled.
3088         }
3089         #endif
3090     }
3091     #endif
3092 }
3093 
3094 
3095 
ma_float_to_fixed_16(float x)3096 static MA_INLINE ma_int16 ma_float_to_fixed_16(float x)
3097 {
3098     return (ma_int16)(x * (1 << 8));
3099 }
3100 
3101 
ma_apply_volume_unclipped_u8(ma_int16 x,ma_int16 volume)3102 static MA_INLINE ma_int16 ma_apply_volume_unclipped_u8(ma_int16 x, ma_int16 volume)
3103 {
3104     return (ma_int16)(((ma_int32)x * (ma_int32)volume) >> 8);
3105 }
3106 
ma_apply_volume_unclipped_s16(ma_int32 x,ma_int16 volume)3107 static MA_INLINE ma_int32 ma_apply_volume_unclipped_s16(ma_int32 x, ma_int16 volume)
3108 {
3109     return (ma_int32)((x * volume) >> 8);
3110 }
3111 
ma_apply_volume_unclipped_s24(ma_int64 x,ma_int16 volume)3112 static MA_INLINE ma_int64 ma_apply_volume_unclipped_s24(ma_int64 x, ma_int16 volume)
3113 {
3114     return (ma_int64)((x * volume) >> 8);
3115 }
3116 
ma_apply_volume_unclipped_s32(ma_int64 x,ma_int16 volume)3117 static MA_INLINE ma_int64 ma_apply_volume_unclipped_s32(ma_int64 x, ma_int16 volume)
3118 {
3119     return (ma_int64)((x * volume) >> 8);
3120 }
3121 
ma_apply_volume_unclipped_f32(float x,float volume)3122 static MA_INLINE float ma_apply_volume_unclipped_f32(float x, float volume)
3123 {
3124     return x * volume;
3125 }
3126 
3127 
3128 
3129 
ma_channel_map_build_shuffle_table(const ma_channel * pChannelMapIn,ma_uint32 channelCountIn,const ma_channel * pChannelMapOut,ma_uint32 channelCountOut,ma_uint8 * pShuffleTable)3130 static ma_result ma_channel_map_build_shuffle_table(const ma_channel* pChannelMapIn, ma_uint32 channelCountIn, const ma_channel* pChannelMapOut, ma_uint32 channelCountOut, ma_uint8* pShuffleTable)
3131 {
3132     ma_uint32 iChannelIn;
3133     ma_uint32 iChannelOut;
3134 
3135     if (pShuffleTable == NULL || channelCountIn == 0 || channelCountIn > MA_MAX_CHANNELS || channelCountOut == 0 || channelCountOut > MA_MAX_CHANNELS) {
3136         return MA_INVALID_ARGS;
3137     }
3138 
3139     /*
3140     When building the shuffle table we just do a 1:1 mapping based on the first occurance of a channel. If the
3141     input channel has more than one occurance of a channel position, the second one will be ignored.
3142     */
3143     for (iChannelOut = 0; iChannelOut < channelCountOut; iChannelOut += 1) {
3144         ma_channel channelOut;
3145 
3146         /* Default to MA_CHANNEL_INDEX_NULL so that if a mapping is not found it'll be set appropriately. */
3147         pShuffleTable[iChannelOut] = MA_CHANNEL_INDEX_NULL;
3148 
3149         channelOut = ma_channel_map_get_channel(pChannelMapOut, channelCountOut, iChannelOut);
3150         for (iChannelIn = 0; iChannelIn < channelCountIn; iChannelIn += 1) {
3151             ma_channel channelIn;
3152 
3153             channelIn = ma_channel_map_get_channel(pChannelMapIn, channelCountIn, iChannelIn);
3154             if (channelOut == channelIn) {
3155                 pShuffleTable[iChannelOut] = (ma_uint8)iChannelIn;
3156                 break;
3157             }
3158 
3159             /*
3160             Getting here means the channels don't exactly match, but we are going to support some
3161             relaxed matching for practicality. If, for example, there are two stereo channel maps,
3162             but one uses front left/right and the other uses side left/right, it makes logical
3163             sense to just map these. The way we'll do it is we'll check if there is a logical
3164             corresponding mapping, and if so, apply it, but we will *not* break from the loop,
3165             thereby giving the loop a chance to find an exact match later which will take priority.
3166             */
3167             switch (channelOut)
3168             {
3169                 /* Left channels. */
3170                 case MA_CHANNEL_FRONT_LEFT:
3171                 case MA_CHANNEL_SIDE_LEFT:
3172                 {
3173                     switch (channelIn) {
3174                         case MA_CHANNEL_FRONT_LEFT:
3175                         case MA_CHANNEL_SIDE_LEFT:
3176                         {
3177                             pShuffleTable[iChannelOut] = (ma_uint8)iChannelIn;
3178                         } break;
3179                     }
3180                 } break;
3181 
3182                 /* Right channels. */
3183                 case MA_CHANNEL_FRONT_RIGHT:
3184                 case MA_CHANNEL_SIDE_RIGHT:
3185                 {
3186                     switch (channelIn) {
3187                         case MA_CHANNEL_FRONT_RIGHT:
3188                         case MA_CHANNEL_SIDE_RIGHT:
3189                         {
3190                             pShuffleTable[iChannelOut] = (ma_uint8)iChannelIn;
3191                         } break;
3192                     }
3193                 } break;
3194 
3195                 default: break;
3196             }
3197         }
3198     }
3199 
3200     return MA_SUCCESS;
3201 }
3202 
ma_channel_map_apply_shuffle_table_f32(float * pFramesOut,ma_uint32 channelsOut,const float * pFramesIn,ma_uint32 channelsIn,ma_uint64 frameCount,const ma_uint8 * pShuffleTable)3203 static ma_result ma_channel_map_apply_shuffle_table_f32(float* pFramesOut, ma_uint32 channelsOut, const float* pFramesIn, ma_uint32 channelsIn, ma_uint64 frameCount, const ma_uint8* pShuffleTable)
3204 {
3205     ma_uint64 iFrame;
3206     ma_uint32 iChannelOut;
3207 
3208     if (pFramesOut == NULL || pFramesIn == NULL || channelsOut == 0 || channelsOut > MA_MAX_CHANNELS || pShuffleTable == NULL) {
3209         return MA_INVALID_ARGS;
3210     }
3211 
3212     for (iFrame = 0; iFrame < frameCount; iFrame += 1) {
3213         for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) {
3214             ma_uint8 iChannelIn = pShuffleTable[iChannelOut];
3215             if (iChannelIn < channelsIn) {  /* For safety, and to deal with MA_CHANNEL_INDEX_NULL. */
3216                 pFramesOut[iChannelOut] = pFramesIn[iChannelIn];
3217             } else {
3218                 pFramesOut[iChannelOut] = 0;
3219             }
3220         }
3221 
3222         pFramesOut += channelsOut;
3223         pFramesIn  += channelsIn;
3224     }
3225 
3226     return MA_SUCCESS;
3227 }
3228 
ma_channel_map_apply_mono_out_f32(float * pFramesOut,const float * pFramesIn,const ma_channel * pChannelMapIn,ma_uint32 channelsIn,ma_uint64 frameCount)3229 static ma_result ma_channel_map_apply_mono_out_f32(float* pFramesOut, const float* pFramesIn, const ma_channel* pChannelMapIn, ma_uint32 channelsIn, ma_uint64 frameCount)
3230 {
3231     ma_uint64 iFrame;
3232     ma_uint32 iChannelIn;
3233     ma_uint32 accumulationCount;
3234 
3235     if (pFramesOut == NULL || pFramesIn == NULL || channelsIn == 0) {
3236         return MA_INVALID_ARGS;
3237     }
3238 
3239     /* In this case the output stream needs to be the average of all channels, ignoring NONE. */
3240 
3241     /* A quick pre-processing step to get the accumulation counter since we're ignoring NONE channels. */
3242     accumulationCount = 0;
3243     for (iChannelIn = 0; iChannelIn < channelsIn; iChannelIn += 1) {
3244         if (ma_channel_map_get_channel(pChannelMapIn, channelsIn, iChannelIn) != MA_CHANNEL_NONE) {
3245             accumulationCount += 1;
3246         }
3247     }
3248 
3249     if (accumulationCount > 0) {    /* <-- Prevent a division by zero. */
3250         for (iFrame = 0; iFrame < frameCount; iFrame += 1) {
3251             float accumulation = 0;
3252 
3253             for (iChannelIn = 0; iChannelIn < channelsIn; iChannelIn += 1) {
3254                 ma_channel channelIn = ma_channel_map_get_channel(pChannelMapIn, channelsIn, iChannelIn);
3255                 if (channelIn != MA_CHANNEL_NONE) {
3256                     accumulation += pFramesIn[iChannelIn];
3257                 }
3258             }
3259 
3260             pFramesOut[0] = accumulation / accumulationCount;
3261             pFramesOut += 1;
3262             pFramesIn  += channelsIn;
3263         }
3264     } else {
3265         ma_silence_pcm_frames(pFramesOut, frameCount, ma_format_f32, 1);
3266     }
3267 
3268     return MA_SUCCESS;
3269 }
3270 
ma_channel_map_apply_mono_in_f32(float * pFramesOut,const ma_channel * pChannelMapOut,ma_uint32 channelsOut,const float * pFramesIn,ma_uint64 frameCount)3271 static ma_result ma_channel_map_apply_mono_in_f32(float* pFramesOut, const ma_channel* pChannelMapOut, ma_uint32 channelsOut, const float* pFramesIn, ma_uint64 frameCount)
3272 {
3273     ma_uint64 iFrame;
3274     ma_uint32 iChannelOut;
3275 
3276     if (pFramesOut == NULL || channelsOut == 0 || pFramesIn == NULL) {
3277         return MA_INVALID_ARGS;
3278     }
3279 
3280     /* In this case we just copy the mono channel to each of the output channels, ignoring NONE. */
3281     for (iFrame = 0; iFrame < frameCount; iFrame += 1) {
3282         for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) {
3283             ma_channel channelOut = ma_channel_map_get_channel(pChannelMapOut, channelsOut, iChannelOut);
3284             if (channelOut != MA_CHANNEL_NONE) {
3285                 pFramesOut[iChannelOut] = pFramesIn[0];
3286             }
3287         }
3288 
3289         pFramesOut += channelsOut;
3290         pFramesIn  += 1;
3291     }
3292 
3293     return MA_SUCCESS;
3294 }
3295 
ma_channel_map_apply_f32(float * pFramesOut,const ma_channel * pChannelMapOut,ma_uint32 channelsOut,const float * pFramesIn,const ma_channel * pChannelMapIn,ma_uint32 channelsIn,ma_uint64 frameCount,ma_channel_mix_mode mode)3296 static void ma_channel_map_apply_f32(float* pFramesOut, const ma_channel* pChannelMapOut, ma_uint32 channelsOut, const float* pFramesIn, const ma_channel* pChannelMapIn, ma_uint32 channelsIn, ma_uint64 frameCount, ma_channel_mix_mode mode)
3297 {
3298     ma_bool32 passthrough = MA_FALSE;
3299 
3300     if (channelsOut == channelsIn) {
3301         if (pChannelMapOut == pChannelMapIn) {
3302             passthrough = MA_TRUE;
3303         } else {
3304             if (ma_channel_map_equal(channelsOut, pChannelMapOut, pChannelMapIn)) {
3305                 passthrough = MA_TRUE;
3306             }
3307         }
3308     }
3309 
3310     /* Optimized Path: Passthrough */
3311     if (passthrough) {
3312         ma_copy_pcm_frames(pFramesOut, pFramesIn, frameCount, ma_format_f32, channelsOut);
3313         return;
3314     }
3315 
3316     /* Special Path: Mono Output. */
3317     if (channelsOut == 1 && (pChannelMapOut == NULL || pChannelMapOut[0] == MA_CHANNEL_MONO)) {
3318         ma_channel_map_apply_mono_out_f32(pFramesOut, pFramesIn, pChannelMapIn, channelsIn, frameCount);
3319         return;
3320     }
3321 
3322     /* Special Path: Mono Input. */
3323     if (channelsIn == 1 && (pChannelMapIn == NULL || pChannelMapIn[0] == MA_CHANNEL_MONO)) {
3324         ma_channel_map_apply_mono_in_f32(pFramesOut, pChannelMapOut, channelsOut, pFramesIn, frameCount);
3325         return;
3326     }
3327 
3328 
3329     if (channelsOut <= MA_MAX_CHANNELS) {
3330         ma_result result;
3331 
3332         if (mode == ma_channel_mix_mode_simple) {
3333             ma_channel shuffleTable[MA_MAX_CHANNELS];
3334 
3335             result = ma_channel_map_build_shuffle_table(pChannelMapIn, channelsIn, pChannelMapOut, channelsOut, shuffleTable);
3336             if (result != MA_SUCCESS) {
3337                 return;
3338             }
3339 
3340             result = ma_channel_map_apply_shuffle_table_f32(pFramesOut, channelsOut, pFramesIn, channelsIn, frameCount, shuffleTable);
3341             if (result != MA_SUCCESS) {
3342                 return;
3343             }
3344         } else {
3345             ma_uint32 iFrame;
3346             ma_uint32 iChannelOut;
3347             ma_uint32 iChannelIn;
3348             float weights[32][32];  /* Do not use MA_MAX_CHANNELS here! */
3349 
3350             /*
3351             If we have a small enough number of channels, pre-compute the weights. Otherwise we'll just need to
3352             fall back to a slower path because otherwise we'll run out of stack space.
3353             */
3354             if (channelsIn <= ma_countof(weights) && channelsOut <= ma_countof(weights)) {
3355                 /* Pre-compute weights. */
3356                 for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) {
3357                     ma_channel channelOut = ma_channel_map_get_channel(pChannelMapOut, channelsOut, iChannelOut);
3358                     for (iChannelIn = 0; iChannelIn < channelsIn; iChannelIn += 1) {
3359                         ma_channel channelIn = ma_channel_map_get_channel(pChannelMapIn, channelsIn, iChannelIn);
3360                         weights[iChannelOut][iChannelIn] = ma_calculate_channel_position_rectangular_weight(channelOut, channelIn);
3361                     }
3362                 }
3363 
3364                 for (iFrame = 0; iFrame < frameCount; iFrame += 1) {
3365                     for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) {
3366                         float accumulation = 0;
3367 
3368                         for (iChannelIn = 0; iChannelIn < channelsIn; iChannelIn += 1) {
3369                             accumulation += pFramesIn[iChannelIn] * weights[iChannelOut][iChannelIn];
3370                         }
3371 
3372                         pFramesOut[iChannelOut] = accumulation;
3373                     }
3374 
3375                     pFramesOut += channelsOut;
3376                     pFramesIn  += channelsIn;
3377                 }
3378             } else {
3379                 /* Cannot pre-compute weights because not enough room in stack-allocated buffer. */
3380                 for (iFrame = 0; iFrame < frameCount; iFrame += 1) {
3381                     for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) {
3382                         float accumulation = 0;
3383                         ma_channel channelOut = ma_channel_map_get_channel(pChannelMapOut, channelsOut, iChannelOut);
3384 
3385                         for (iChannelIn = 0; iChannelIn < channelsIn; iChannelIn += 1) {
3386                             ma_channel channelIn = ma_channel_map_get_channel(pChannelMapIn, channelsIn, iChannelIn);
3387                             accumulation += pFramesIn[iChannelIn] * ma_calculate_channel_position_rectangular_weight(channelOut, channelIn);
3388                         }
3389 
3390                         pFramesOut[iChannelOut] = accumulation;
3391                     }
3392 
3393                     pFramesOut += channelsOut;
3394                     pFramesIn  += channelsIn;
3395                 }
3396             }
3397         }
3398     } else {
3399         /* Fall back to silence. If you hit this, what are you doing with so many channels?! */
3400         ma_silence_pcm_frames(pFramesOut, frameCount, ma_format_f32, channelsOut);
3401     }
3402 }
3403 
3404 
3405 
ma_copy_and_apply_volume_factor_per_channel_f32(float * pFramesOut,const float * pFramesIn,ma_uint64 frameCount,ma_uint32 channels,float * pChannelGains)3406 MA_API void ma_copy_and_apply_volume_factor_per_channel_f32(float* pFramesOut, const float* pFramesIn, ma_uint64 frameCount, ma_uint32 channels, float* pChannelGains)
3407 {
3408     ma_uint64 iFrame;
3409 
3410     if (channels == 2) {
3411         /* TODO: Do an optimized implementation for stereo and mono. Can do a SIMD optimized implementation as well. */
3412     }
3413 
3414     for (iFrame = 0; iFrame < frameCount; iFrame += 1) {
3415         ma_uint32 iChannel;
3416         for (iChannel = 0; iChannel < channels; iChannel += 1) {
3417             pFramesOut[iFrame * channels + iChannel] = pFramesIn[iFrame * channels + iChannel] * pChannelGains[iChannel];
3418         }
3419     }
3420 }
3421 
ma_apply_volume_factor_per_channel_f32(float * pFramesOut,ma_uint64 frameCount,ma_uint32 channels,float * pChannelGains)3422 MA_API void ma_apply_volume_factor_per_channel_f32(float* pFramesOut, ma_uint64 frameCount, ma_uint32 channels, float* pChannelGains)
3423 {
3424     ma_copy_and_apply_volume_factor_per_channel_f32(pFramesOut, pFramesOut, frameCount, channels, pChannelGains);
3425 }
3426 
3427 
3428 /* Not used right now, but leaving here for reference. */
3429 #if 0
3430 static ma_result ma_mix_pcm_frames_u8(ma_int16* pDst, const ma_uint8* pSrc, ma_uint64 frameCount, ma_uint32 channels, float volume)
3431 {
3432     ma_uint64 iSample;
3433     ma_uint64 sampleCount;
3434 
3435     if (pDst == NULL || pSrc == NULL || channels == 0) {
3436         return MA_INVALID_ARGS;
3437     }
3438 
3439     if (volume == 0) {
3440         return MA_SUCCESS;  /* No changes if the volume is 0. */
3441     }
3442 
3443     sampleCount = frameCount * channels;
3444 
3445     if (volume == 1) {
3446         for (iSample = 0; iSample < sampleCount; iSample += 1) {
3447             pDst[iSample] += ma_pcm_sample_u8_to_s16_no_scale(pSrc[iSample]);
3448         }
3449     } else {
3450         ma_int16 volumeFixed = ma_float_to_fixed_16(volume);
3451         for (iSample = 0; iSample < sampleCount; iSample += 1) {
3452             pDst[iSample] += ma_apply_volume_unclipped_u8(ma_pcm_sample_u8_to_s16_no_scale(pSrc[iSample]), volumeFixed);
3453         }
3454     }
3455 
3456     return MA_SUCCESS;
3457 }
3458 
3459 static ma_result ma_mix_pcm_frames_s16(ma_int32* pDst, const ma_int16* pSrc, ma_uint64 frameCount, ma_uint32 channels, float volume)
3460 {
3461     ma_uint64 iSample;
3462     ma_uint64 sampleCount;
3463 
3464     if (pDst == NULL || pSrc == NULL || channels == 0) {
3465         return MA_INVALID_ARGS;
3466     }
3467 
3468     if (volume == 0) {
3469         return MA_SUCCESS;  /* No changes if the volume is 0. */
3470     }
3471 
3472     sampleCount = frameCount * channels;
3473 
3474     if (volume == 1) {
3475         for (iSample = 0; iSample < sampleCount; iSample += 1) {
3476             pDst[iSample] += pSrc[iSample];
3477         }
3478     } else {
3479         ma_int16 volumeFixed = ma_float_to_fixed_16(volume);
3480         for (iSample = 0; iSample < sampleCount; iSample += 1) {
3481             pDst[iSample] += ma_apply_volume_unclipped_s16(pSrc[iSample], volumeFixed);
3482         }
3483     }
3484 
3485     return MA_SUCCESS;
3486 }
3487 
3488 static ma_result ma_mix_pcm_frames_s24(ma_int64* pDst, const ma_uint8* pSrc, ma_uint64 frameCount, ma_uint32 channels, float volume)
3489 {
3490     ma_uint64 iSample;
3491     ma_uint64 sampleCount;
3492 
3493     if (pDst == NULL || pSrc == NULL || channels == 0) {
3494         return MA_INVALID_ARGS;
3495     }
3496 
3497     if (volume == 0) {
3498         return MA_SUCCESS;  /* No changes if the volume is 0. */
3499     }
3500 
3501     sampleCount = frameCount * channels;
3502 
3503     if (volume == 1) {
3504         for (iSample = 0; iSample < sampleCount; iSample += 1) {
3505             pDst[iSample] += ma_pcm_sample_s24_to_s32_no_scale(&pSrc[iSample*3]);
3506         }
3507     } else {
3508         ma_int16 volumeFixed = ma_float_to_fixed_16(volume);
3509         for (iSample = 0; iSample < sampleCount; iSample += 1) {
3510             pDst[iSample] += ma_apply_volume_unclipped_s24(ma_pcm_sample_s24_to_s32_no_scale(&pSrc[iSample*3]), volumeFixed);
3511         }
3512     }
3513 
3514     return MA_SUCCESS;
3515 }
3516 
3517 static ma_result ma_mix_pcm_frames_s32(ma_int64* pDst, const ma_int32* pSrc, ma_uint64 frameCount, ma_uint32 channels, float volume)
3518 {
3519     ma_uint64 iSample;
3520     ma_uint64 sampleCount;
3521 
3522     if (pDst == NULL || pSrc == NULL || channels == 0) {
3523         return MA_INVALID_ARGS;
3524     }
3525 
3526     if (volume == 0) {
3527         return MA_SUCCESS;  /* No changes if the volume is 0. */
3528     }
3529 
3530 
3531     sampleCount = frameCount * channels;
3532 
3533     if (volume == 1) {
3534         for (iSample = 0; iSample < sampleCount; iSample += 1) {
3535             pDst[iSample] += pSrc[iSample];
3536         }
3537     } else {
3538         ma_int16 volumeFixed = ma_float_to_fixed_16(volume);
3539         for (iSample = 0; iSample < sampleCount; iSample += 1) {
3540             pDst[iSample] += ma_apply_volume_unclipped_s32(pSrc[iSample], volumeFixed);
3541         }
3542     }
3543 
3544     return MA_SUCCESS;
3545 }
3546 #endif
3547 
ma_mix_pcm_frames_f32(float * pDst,const float * pSrc,ma_uint64 frameCount,ma_uint32 channels,float volume)3548 static ma_result ma_mix_pcm_frames_f32(float* pDst, const float* pSrc, ma_uint64 frameCount, ma_uint32 channels, float volume)
3549 {
3550     ma_uint64 iSample;
3551     ma_uint64 sampleCount;
3552 
3553     if (pDst == NULL || pSrc == NULL || channels == 0) {
3554         return MA_INVALID_ARGS;
3555     }
3556 
3557     if (volume == 0) {
3558         return MA_SUCCESS;  /* No changes if the volume is 0. */
3559     }
3560 
3561     sampleCount = frameCount * channels;
3562 
3563     if (volume == 1) {
3564         for (iSample = 0; iSample < sampleCount; iSample += 1) {
3565             pDst[iSample] += pSrc[iSample];
3566         }
3567     } else {
3568         for (iSample = 0; iSample < sampleCount; iSample += 1) {
3569             pDst[iSample] += ma_apply_volume_unclipped_f32(pSrc[iSample], volume);
3570         }
3571     }
3572 
3573     return MA_SUCCESS;
3574 }
3575 
3576 #if 0
3577 static ma_result ma_mix_pcm_frames(void* pDst, const void* pSrc, ma_uint64 frameCount, ma_format format, ma_uint32 channels, float volume)
3578 {
3579     ma_result result;
3580 
3581     switch (format)
3582     {
3583         case ma_format_u8:  result = ma_mix_pcm_frames_u8( (ma_int16*)pDst, (const ma_uint8*)pSrc, frameCount, channels, volume); break;
3584         case ma_format_s16: result = ma_mix_pcm_frames_s16((ma_int32*)pDst, (const ma_int16*)pSrc, frameCount, channels, volume); break;
3585         case ma_format_s24: result = ma_mix_pcm_frames_s24((ma_int64*)pDst, (const ma_uint8*)pSrc, frameCount, channels, volume); break;
3586         case ma_format_s32: result = ma_mix_pcm_frames_s32((ma_int64*)pDst, (const ma_int32*)pSrc, frameCount, channels, volume); break;
3587         case ma_format_f32: result = ma_mix_pcm_frames_f32((   float*)pDst, (const    float*)pSrc, frameCount, channels, volume); break;
3588         default: return MA_INVALID_ARGS;    /* Unknown format. */
3589     }
3590 
3591     return result;
3592 }
3593 #endif
3594 
3595 
3596 
ma_node_graph_config_init(ma_uint32 channels)3597 MA_API ma_node_graph_config ma_node_graph_config_init(ma_uint32 channels)
3598 {
3599     ma_node_graph_config config;
3600 
3601     MA_ZERO_OBJECT(&config);
3602     config.channels = channels;
3603 
3604     return config;
3605 }
3606 
3607 
ma_node_graph_set_is_reading(ma_node_graph * pNodeGraph,ma_bool8 isReading)3608 static void ma_node_graph_set_is_reading(ma_node_graph* pNodeGraph, ma_bool8 isReading)
3609 {
3610     MA_ASSERT(pNodeGraph != NULL);
3611     c89atomic_exchange_8(&pNodeGraph->isReading, isReading);
3612 }
3613 
3614 #if 0
3615 static ma_bool8 ma_node_graph_is_reading(ma_node_graph* pNodeGraph)
3616 {
3617     MA_ASSERT(pNodeGraph != NULL);
3618     return c89atomic_load_8(&pNodeGraph->isReading);
3619 }
3620 #endif
3621 
3622 
ma_node_graph_endpoint_process_pcm_frames(ma_node * pNode,const float ** ppFramesIn,ma_uint32 * pFrameCountIn,float ** ppFramesOut,ma_uint32 * pFrameCountOut)3623 static void ma_node_graph_endpoint_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut)
3624 {
3625     MA_ASSERT(pNode != NULL);
3626     MA_ASSERT(ma_node_get_input_bus_count(pNode)  == 1);
3627     MA_ASSERT(ma_node_get_output_bus_count(pNode) == 1);
3628 
3629     /* Input channel count needs to be the same as the output channel count. */
3630     MA_ASSERT(ma_node_get_input_channels(pNode, 0) == ma_node_get_output_channels(pNode, 0));
3631 
3632     /* We don't need to do anything here because it's a passthrough. */
3633     (void)pNode;
3634     (void)ppFramesIn;
3635     (void)pFrameCountIn;
3636     (void)ppFramesOut;
3637     (void)pFrameCountOut;
3638 
3639 #if 0
3640     /* The data has already been mixed. We just need to move it to the output buffer. */
3641     if (ppFramesIn != NULL) {
3642         ma_copy_pcm_frames(ppFramesOut[0], ppFramesIn[0], *pFrameCountOut, ma_format_f32, ma_node_get_output_channels(pNode, 0));
3643     }
3644 #endif
3645 }
3646 
3647 static ma_node_vtable g_node_graph_endpoint_vtable =
3648 {
3649     ma_node_graph_endpoint_process_pcm_frames,
3650     NULL,   /* onGetRequiredInputFrameCount */
3651     1,      /* 1 input bus. */
3652     1,      /* 1 output bus. */
3653     MA_NODE_FLAG_PASSTHROUGH    /* Flags. The endpoint is a passthrough. */
3654 };
3655 
ma_node_graph_init(const ma_node_graph_config * pConfig,const ma_allocation_callbacks * pAllocationCallbacks,ma_node_graph * pNodeGraph)3656 MA_API ma_result ma_node_graph_init(const ma_node_graph_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_node_graph* pNodeGraph)
3657 {
3658     ma_result result;
3659     ma_node_config endpointConfig;
3660 
3661     if (pNodeGraph == NULL) {
3662         return MA_INVALID_ARGS;
3663     }
3664 
3665     MA_ZERO_OBJECT(pNodeGraph);
3666 
3667     endpointConfig = ma_node_config_init();
3668     endpointConfig.vtable          = &g_node_graph_endpoint_vtable;
3669     endpointConfig.pInputChannels  = &pConfig->channels;
3670     endpointConfig.pOutputChannels = &pConfig->channels;
3671 
3672     result = ma_node_init(pNodeGraph, &endpointConfig, pAllocationCallbacks, &pNodeGraph->endpoint);
3673     if (result != MA_SUCCESS) {
3674         return result;
3675     }
3676 
3677     return MA_SUCCESS;
3678 }
3679 
ma_node_graph_uninit(ma_node_graph * pNodeGraph,const ma_allocation_callbacks * pAllocationCallbacks)3680 MA_API void ma_node_graph_uninit(ma_node_graph* pNodeGraph, const ma_allocation_callbacks* pAllocationCallbacks)
3681 {
3682     if (pNodeGraph == NULL) {
3683         return;
3684     }
3685 
3686     ma_node_uninit(&pNodeGraph->endpoint, pAllocationCallbacks);
3687 }
3688 
ma_node_graph_get_endpoint(ma_node_graph * pNodeGraph)3689 MA_API ma_node* ma_node_graph_get_endpoint(ma_node_graph* pNodeGraph)
3690 {
3691     if (pNodeGraph == NULL) {
3692         return NULL;
3693     }
3694 
3695     return &pNodeGraph->endpoint;
3696 }
3697 
ma_node_graph_read_pcm_frames(ma_node_graph * pNodeGraph,void * pFramesOut,ma_uint32 frameCount,ma_uint32 * pFramesRead)3698 MA_API ma_result ma_node_graph_read_pcm_frames(ma_node_graph* pNodeGraph, void* pFramesOut, ma_uint32 frameCount, ma_uint32* pFramesRead)
3699 {
3700     ma_result result = MA_SUCCESS;
3701     ma_uint32 totalFramesRead;
3702     ma_uint32 channels;
3703 
3704     if (pFramesRead != NULL) {
3705         *pFramesRead = 0;   /* Safety. */
3706     }
3707 
3708     if (pNodeGraph == NULL) {
3709         return MA_INVALID_ARGS;
3710     }
3711 
3712     channels = ma_node_get_output_channels(&pNodeGraph->endpoint, 0);
3713 
3714 
3715     /* We'll be nice and try to do a full read of all frameCount frames. */
3716     totalFramesRead = 0;
3717     while (totalFramesRead < frameCount) {
3718         ma_uint32 framesJustRead;
3719         ma_uint32 framesToRead = frameCount - totalFramesRead;
3720 
3721         ma_node_graph_set_is_reading(pNodeGraph, MA_TRUE);
3722         {
3723             result = ma_node_read_pcm_frames(&pNodeGraph->endpoint, 0, (float*)ma_offset_pcm_frames_ptr(pFramesOut, totalFramesRead, ma_format_f32, channels), framesToRead, &framesJustRead, ma_node_get_time(&pNodeGraph->endpoint));
3724         }
3725         ma_node_graph_set_is_reading(pNodeGraph, MA_FALSE);
3726 
3727         totalFramesRead += framesJustRead;
3728 
3729         if (result != MA_SUCCESS) {
3730             break;
3731         }
3732 
3733         /* Abort if we weren't able to read any frames or else we risk getting stuck in a loop. */
3734         if (framesJustRead == 0) {
3735             break;
3736         }
3737     }
3738 
3739     /* Let's go ahead and silence any leftover frames just for some added safety to ensure the caller doesn't try emitting garbage out of the speakers. */
3740     if (totalFramesRead < frameCount) {
3741         ma_silence_pcm_frames(ma_offset_pcm_frames_ptr(pFramesOut, totalFramesRead, ma_format_f32, channels), (frameCount - totalFramesRead), ma_format_f32, channels);
3742     }
3743 
3744     if (pFramesRead != NULL) {
3745         *pFramesRead = totalFramesRead;
3746     }
3747 
3748     return result;
3749 }
3750 
ma_node_graph_get_channels(const ma_node_graph * pNodeGraph)3751 MA_API ma_uint32 ma_node_graph_get_channels(const ma_node_graph* pNodeGraph)
3752 {
3753     if (pNodeGraph == NULL) {
3754         return 0;
3755     }
3756 
3757     return ma_node_get_output_channels(&pNodeGraph->endpoint, 0);
3758 }
3759 
ma_node_graph_get_time(const ma_node_graph * pNodeGraph)3760 MA_API ma_uint64 ma_node_graph_get_time(const ma_node_graph* pNodeGraph)
3761 {
3762     if (pNodeGraph == NULL) {
3763         return 0;
3764     }
3765 
3766     return ma_node_get_time(&pNodeGraph->endpoint); /* Global time is just the local time of the endpoint. */
3767 }
3768 
ma_node_graph_set_time(ma_node_graph * pNodeGraph,ma_uint64 globalTime)3769 MA_API ma_result ma_node_graph_set_time(ma_node_graph* pNodeGraph, ma_uint64 globalTime)
3770 {
3771     if (pNodeGraph == NULL) {
3772         return MA_INVALID_ARGS;
3773     }
3774 
3775     return ma_node_set_time(&pNodeGraph->endpoint, globalTime); /* Global time is just the local time of the endpoint. */
3776 }
3777 
3778 
3779 
ma_node_output_bus_init(ma_node * pNode,ma_uint32 outputBusIndex,ma_uint32 channels,ma_node_output_bus * pOutputBus)3780 static ma_result ma_node_output_bus_init(ma_node* pNode, ma_uint32 outputBusIndex, ma_uint32 channels, ma_node_output_bus* pOutputBus)
3781 {
3782     MA_ASSERT(pOutputBus != NULL);
3783     MA_ASSERT(outputBusIndex < MA_MAX_NODE_BUS_COUNT);
3784     MA_ASSERT(outputBusIndex < ma_node_get_output_bus_count(pNode));
3785     MA_ASSERT(channels < 256);
3786 
3787     MA_ZERO_OBJECT(pOutputBus);
3788 
3789     if (channels == 0) {
3790         return MA_INVALID_ARGS;
3791     }
3792 
3793     pOutputBus->pNode          = pNode;
3794     pOutputBus->outputBusIndex = (ma_uint8)outputBusIndex;
3795     pOutputBus->channels       = (ma_uint8)channels;
3796     pOutputBus->flags          = MA_NODE_OUTPUT_BUS_FLAG_HAS_READ; /* <-- Important that this flag is set by default. */
3797     pOutputBus->volume         = 1;
3798 
3799     return MA_SUCCESS;
3800 }
3801 
ma_node_output_bus_lock(ma_node_output_bus * pOutputBus)3802 static void ma_node_output_bus_lock(ma_node_output_bus* pOutputBus)
3803 {
3804     ma_spinlock_lock(&pOutputBus->lock);
3805 }
3806 
ma_node_output_bus_unlock(ma_node_output_bus * pOutputBus)3807 static void ma_node_output_bus_unlock(ma_node_output_bus* pOutputBus)
3808 {
3809     ma_spinlock_unlock(&pOutputBus->lock);
3810 }
3811 
3812 
ma_node_output_bus_get_channels(const ma_node_output_bus * pOutputBus)3813 static ma_uint32 ma_node_output_bus_get_channels(const ma_node_output_bus* pOutputBus)
3814 {
3815     return pOutputBus->channels;
3816 }
3817 
3818 
ma_node_output_bus_set_has_read(ma_node_output_bus * pOutputBus,ma_bool32 hasRead)3819 static void ma_node_output_bus_set_has_read(ma_node_output_bus* pOutputBus, ma_bool32 hasRead)
3820 {
3821     if (hasRead) {
3822         c89atomic_fetch_or_8(&pOutputBus->flags, MA_NODE_OUTPUT_BUS_FLAG_HAS_READ);
3823     } else {
3824         c89atomic_fetch_and_8(&pOutputBus->flags, (ma_uint8)~MA_NODE_OUTPUT_BUS_FLAG_HAS_READ);
3825     }
3826 }
3827 
ma_node_output_bus_has_read(ma_node_output_bus * pOutputBus)3828 static ma_bool32 ma_node_output_bus_has_read(ma_node_output_bus* pOutputBus)
3829 {
3830     return (c89atomic_load_8(&pOutputBus->flags) & MA_NODE_OUTPUT_BUS_FLAG_HAS_READ) != 0;
3831 }
3832 
3833 
ma_node_output_bus_set_is_attached(ma_node_output_bus * pOutputBus,ma_bool8 isAttached)3834 static void ma_node_output_bus_set_is_attached(ma_node_output_bus* pOutputBus, ma_bool8 isAttached)
3835 {
3836     c89atomic_exchange_8(&pOutputBus->isAttached, isAttached);
3837 }
3838 
ma_node_output_bus_is_attached(ma_node_output_bus * pOutputBus)3839 static ma_bool8 ma_node_output_bus_is_attached(ma_node_output_bus* pOutputBus)
3840 {
3841     return c89atomic_load_8(&pOutputBus->isAttached);
3842 }
3843 
3844 
ma_node_output_bus_set_volume(ma_node_output_bus * pOutputBus,float volume)3845 static ma_result ma_node_output_bus_set_volume(ma_node_output_bus* pOutputBus, float volume)
3846 {
3847     MA_ASSERT(pOutputBus != NULL);
3848 
3849     if (volume < 0.0f) {
3850         volume = 0.0f;
3851     }
3852 
3853     c89atomic_exchange_f32(&pOutputBus->volume, volume);
3854 
3855     return MA_SUCCESS;
3856 }
3857 
ma_node_output_bus_get_volume(const ma_node_output_bus * pOutputBus)3858 static float ma_node_output_bus_get_volume(const ma_node_output_bus* pOutputBus)
3859 {
3860     return c89atomic_load_f32((float*)&pOutputBus->volume);
3861 }
3862 
3863 
ma_node_input_bus_init(ma_uint32 channels,ma_node_input_bus * pInputBus)3864 static ma_result ma_node_input_bus_init(ma_uint32 channels, ma_node_input_bus* pInputBus)
3865 {
3866     MA_ASSERT(pInputBus != NULL);
3867     MA_ASSERT(channels < 256);
3868 
3869     MA_ZERO_OBJECT(pInputBus);
3870 
3871     if (channels == 0) {
3872         return MA_INVALID_ARGS;
3873     }
3874 
3875     pInputBus->channels = (ma_uint8)channels;
3876 
3877     return MA_SUCCESS;
3878 }
3879 
ma_node_input_bus_lock(ma_node_input_bus * pInputBus)3880 static void ma_node_input_bus_lock(ma_node_input_bus* pInputBus)
3881 {
3882     ma_spinlock_lock(&pInputBus->lock);
3883 }
3884 
ma_node_input_bus_unlock(ma_node_input_bus * pInputBus)3885 static void ma_node_input_bus_unlock(ma_node_input_bus* pInputBus)
3886 {
3887     ma_spinlock_unlock(&pInputBus->lock);
3888 }
3889 
3890 
ma_node_input_bus_next_begin(ma_node_input_bus * pInputBus)3891 static void ma_node_input_bus_next_begin(ma_node_input_bus* pInputBus)
3892 {
3893     c89atomic_fetch_add_16(&pInputBus->nextCounter, 1);
3894 }
3895 
ma_node_input_bus_next_end(ma_node_input_bus * pInputBus)3896 static void ma_node_input_bus_next_end(ma_node_input_bus* pInputBus)
3897 {
3898     c89atomic_fetch_sub_16(&pInputBus->nextCounter, 1);
3899 }
3900 
ma_node_input_bus_get_next_counter(ma_node_input_bus * pInputBus)3901 static ma_uint16 ma_node_input_bus_get_next_counter(ma_node_input_bus* pInputBus)
3902 {
3903     return c89atomic_load_16(&pInputBus->nextCounter);
3904 }
3905 
3906 
ma_node_input_bus_get_channels(const ma_node_input_bus * pInputBus)3907 static ma_uint32 ma_node_input_bus_get_channels(const ma_node_input_bus* pInputBus)
3908 {
3909     return pInputBus->channels;
3910 }
3911 
3912 
ma_node_input_bus_detach__no_output_bus_lock(ma_node_input_bus * pInputBus,ma_node_output_bus * pOutputBus)3913 static void ma_node_input_bus_detach__no_output_bus_lock(ma_node_input_bus* pInputBus, ma_node_output_bus* pOutputBus)
3914 {
3915     MA_ASSERT(pInputBus  != NULL);
3916     MA_ASSERT(pOutputBus != NULL);
3917 
3918     /*
3919     Mark the output bus as detached first. This will prevent future iterations on the audio thread
3920     from iterating this output bus.
3921     */
3922     ma_node_output_bus_set_is_attached(pOutputBus, MA_FALSE);
3923 
3924     /*
3925     We cannot use the output bus lock here since it'll be getting used at a higher level, but we do
3926     still need to use the input bus lock since we'll be updating pointers on two different output
3927     buses. The same rules apply here as the attaching case. Although we're using a lock here, we're
3928     *not* using a lock when iterating over the list in the audio thread. We therefore need to craft
3929     this in a way such that the iteration on the audio thread doesn't break.
3930 
3931     The the first thing to do is swap out the "next" pointer of the previous output bus with the
3932     new "next" output bus. This is the operation that matters for iteration on the audio thread.
3933     After that, the previous pointer on the new "next" pointer needs to be updated, after which
3934     point the linked list will be in a good state.
3935     */
3936     ma_node_input_bus_lock(pInputBus);
3937     {
3938         ma_node_output_bus* pOldPrev = (ma_node_output_bus*)c89atomic_load_ptr(&pOutputBus->pPrev);
3939         ma_node_output_bus* pOldNext = (ma_node_output_bus*)c89atomic_load_ptr(&pOutputBus->pNext);
3940 
3941         if (pOldPrev != NULL) {
3942             c89atomic_exchange_ptr(&pOldPrev->pNext, pOldNext); /* <-- This is where the output bus is detached from the list. */
3943         }
3944         if (pOldNext != NULL) {
3945             c89atomic_exchange_ptr(&pOldNext->pPrev, pOldPrev); /* <-- This is required for detachment. */
3946         }
3947     }
3948     ma_node_input_bus_unlock(pInputBus);
3949 
3950     /* At this point the output bus is detached and the linked list is completely unaware of it. Reset some data for safety. */
3951     c89atomic_exchange_ptr(&pOutputBus->pNext, NULL);   /* Using atomic exchanges here, mainly for the benefit of analysis tools which don't always recognize spinlocks. */
3952     c89atomic_exchange_ptr(&pOutputBus->pPrev, NULL);   /* As above. */
3953     pOutputBus->pInputNode             = NULL;
3954     pOutputBus->inputNodeInputBusIndex = 0;
3955 
3956 
3957     /*
3958     For thread-safety reasons, we don't want to be returning from this straight away. We need to
3959     wait for the audio thread to finish with the output bus. There's two things we need to wait
3960     for. The first is the part that selects the next output bus in the list, and the other is the
3961     part that reads from the output bus. Basically all we're doing is waiting for the input bus
3962     to stop referencing the output bus.
3963 
3964     We're doing this part last because we want the section above to run while the audio thread
3965     is finishing up with the output bus, just for efficiency reasons. We marked the output bus as
3966     detached right at the top of this function which is going to prevent the audio thread from
3967     iterating the output bus again.
3968     */
3969 
3970     /* Part 1: Wait for the current iteration to complete. */
3971     while (ma_node_input_bus_get_next_counter(pInputBus) > 0) {
3972         ma_yield();
3973     }
3974 
3975     /* Part 2: Wait for any reads to complete. */
3976     while (c89atomic_load_16(&pOutputBus->refCount) > 0) {
3977         ma_yield();
3978     }
3979 
3980     /*
3981     At this point we're done detaching and we can be guaranteed that the audio thread is not going
3982     to attempt to reference this output bus again (until attached again).
3983     */
3984 }
3985 
3986 #if 0   /* Not used at the moment, but leaving here in case I need it later. */
3987 static void ma_node_input_bus_detach(ma_node_input_bus* pInputBus, ma_node_output_bus* pOutputBus)
3988 {
3989     MA_ASSERT(pInputBus  != NULL);
3990     MA_ASSERT(pOutputBus != NULL);
3991 
3992     ma_node_output_bus_lock(pOutputBus);
3993     {
3994         ma_node_input_bus_detach__no_output_bus_lock(pInputBus, pOutputBus);
3995     }
3996     ma_node_output_bus_unlock(pOutputBus);
3997 }
3998 #endif
3999 
ma_node_input_bus_attach(ma_node_input_bus * pInputBus,ma_node_output_bus * pOutputBus,ma_node * pNewInputNode,ma_uint32 inputNodeInputBusIndex)4000 static void ma_node_input_bus_attach(ma_node_input_bus* pInputBus, ma_node_output_bus* pOutputBus, ma_node* pNewInputNode, ma_uint32 inputNodeInputBusIndex)
4001 {
4002     MA_ASSERT(pInputBus  != NULL);
4003     MA_ASSERT(pOutputBus != NULL);
4004 
4005     ma_node_output_bus_lock(pOutputBus);
4006     {
4007         ma_node_output_bus* pOldInputNode = (ma_node_output_bus*)c89atomic_load_ptr(&pOutputBus->pInputNode);
4008 
4009         /* Detach from any existing attachment first if necessary. */
4010         if (pOldInputNode != NULL) {
4011             ma_node_input_bus_detach__no_output_bus_lock(pInputBus, pOutputBus);
4012         }
4013 
4014         /*
4015         At this point we can be sure the output bus is not attached to anything. The linked list in the
4016         old input bus has been updated so that pOutputBus will not get iterated again.
4017         */
4018         pOutputBus->pInputNode             = pNewInputNode;                     /* No need for an atomic assignment here because modification of this variable always happens within a lock. */
4019         pOutputBus->inputNodeInputBusIndex = (ma_uint8)inputNodeInputBusIndex;  /* As above. */
4020 
4021         /*
4022         Now we need to attach the output bus to the linked list. This involves updating two pointers on
4023         two different output buses so I'm going to go ahead and keep this simple and just use a lock.
4024         There are ways to do this without a lock, but it's just too hard to maintain for it's value.
4025 
4026         Although we're locking here, it's important to remember that we're *not* locking when iterating
4027         and reading audio data since that'll be running on the audio thread. As a result we need to be
4028         careful how we craft this so that we don't break iteration. What we're going to do is always
4029         attach the new item so that it becomes the first item in the list. That way, as we're iterating
4030         we won't break any links in the list and iteration will continue safely. The detaching case will
4031         also be crafted in a way as to not break list iteration. It's important to remember to use
4032         atomic exchanges here since no locking is happening on the audio thread during iteration.
4033         */
4034         ma_node_input_bus_lock(pInputBus);
4035         {
4036             ma_node_output_bus* pNewPrev = &pInputBus->head;
4037             ma_node_output_bus* pNewNext = (ma_node_output_bus*)c89atomic_load_ptr(&pInputBus->head.pNext);
4038 
4039             /* Update the local output bus. */
4040             c89atomic_exchange_ptr(&pOutputBus->pPrev, pNewPrev);
4041             c89atomic_exchange_ptr(&pOutputBus->pNext, pNewNext);
4042 
4043             /* Update the other output buses to point back to the local output bus. */
4044             c89atomic_exchange_ptr(&pInputBus->head.pNext, pOutputBus); /* <-- This is where the output bus is actually attached to the input bus. */
4045 
4046             /* Do the previous pointer last. This is only used for detachment. */
4047             if (pNewNext != NULL) {
4048                 c89atomic_exchange_ptr(&pNewNext->pPrev,  pOutputBus);
4049             }
4050         }
4051         ma_node_input_bus_unlock(pInputBus);
4052 
4053         /*
4054         Mark the node as attached last. This is used to controlling whether or the output bus will be
4055         iterated on the audio thread. Mainly required for detachment purposes.
4056         */
4057         ma_node_output_bus_set_is_attached(pOutputBus, MA_TRUE);
4058     }
4059     ma_node_output_bus_unlock(pOutputBus);
4060 }
4061 
ma_node_input_bus_next(ma_node_input_bus * pInputBus,ma_node_output_bus * pOutputBus)4062 static ma_node_output_bus* ma_node_input_bus_next(ma_node_input_bus* pInputBus, ma_node_output_bus* pOutputBus)
4063 {
4064     ma_node_output_bus* pNext;
4065 
4066     MA_ASSERT(pInputBus != NULL);
4067 
4068     if (pOutputBus == NULL) {
4069         return NULL;
4070     }
4071 
4072     ma_node_input_bus_next_begin(pInputBus);
4073     {
4074         pNext = pOutputBus;
4075         for (;;) {
4076             pNext = (ma_node_output_bus*)c89atomic_load_ptr(&pNext->pNext);
4077             if (pNext == NULL) {
4078                 break;      /* Reached the end. */
4079             }
4080 
4081             if (ma_node_output_bus_is_attached(pNext) == MA_FALSE) {
4082                 continue;   /* The node is not attached. Keep checking. */
4083             }
4084 
4085             /* The next node has been selected. */
4086             break;
4087         }
4088 
4089         /* We need to increment the reference count of the selected node. */
4090         if (pNext != NULL) {
4091             c89atomic_fetch_add_16(&pNext->refCount, 1);
4092         }
4093 
4094         /* The previous node is no longer being referenced. */
4095         c89atomic_fetch_sub_16(&pOutputBus->refCount, 1);
4096     }
4097     ma_node_input_bus_next_end(pInputBus);
4098 
4099     return pNext;
4100 }
4101 
ma_node_input_bus_first(ma_node_input_bus * pInputBus)4102 static ma_node_output_bus* ma_node_input_bus_first(ma_node_input_bus* pInputBus)
4103 {
4104     return ma_node_input_bus_next(pInputBus, &pInputBus->head);
4105 }
4106 
4107 
4108 
ma_node_input_bus_read_pcm_frames(ma_node * pInputNode,ma_node_input_bus * pInputBus,float * pFramesOut,ma_uint32 frameCount,ma_uint32 * pFramesRead,ma_uint64 globalTime)4109 static ma_result ma_node_input_bus_read_pcm_frames(ma_node* pInputNode, ma_node_input_bus* pInputBus, float* pFramesOut, ma_uint32 frameCount, ma_uint32* pFramesRead, ma_uint64 globalTime)
4110 {
4111     ma_result result = MA_SUCCESS;
4112     ma_node_output_bus* pOutputBus;
4113     ma_node_output_bus* pFirst;
4114     ma_uint32 inputChannels;
4115 
4116     /*
4117     This will be called from the audio thread which means we can't be doing any locking. Basically,
4118     this function will not perfom any locking, whereas attaching and detaching will, but crafted in
4119     such a way that we don't need to perform any locking here. The important thing to remember is
4120     to always iterate in a forward direction.
4121 
4122     In order to process any data we need to first read from all input buses. That's where this
4123     function comes in. This iterates over each of the attachments and accumulates/mixes them. We
4124     also convert the channels to the nodes output channel count before mixing. We want to do this
4125     channel conversion so that the caller of this function can invoke the processing callback
4126     without having to do it themselves.
4127 
4128     When we iterate over each of the attachments on the input bus, we need to read as much data as
4129     we can from each of them so that we don't end up with holes between each of the attachments. To
4130     do this, we need to read from each attachment in a loop and read as many frames as we can, up
4131     to `frameCount`.
4132     */
4133     MA_ASSERT(pInputNode  != NULL);
4134     MA_ASSERT(pFramesRead != NULL); /* pFramesRead is critical and must always be specified. On input it's undefined and on output it'll be set to the number of frames actually read. */
4135 
4136     *pFramesRead = 0;   /* Safety. */
4137 
4138     inputChannels = ma_node_input_bus_get_channels(pInputBus);
4139 
4140     /*
4141     We need to be careful with how we call ma_node_input_bus_first() and ma_node_input_bus_next(). They
4142     are both critical to our lock-free thread-safety system. We can only call ma_node_input_bus_first()
4143     once per iteration, however we have an optimization to checks whether or not it's the first item in
4144     the list. We therefore need to store a pointer to the first item rather than repeatedly calling
4145     ma_node_input_bus_first(). It's safe to keep hold of this point, so long as we don't dereference it
4146     after calling ma_node_input_bus_next(), which we won't be.
4147     */
4148     pFirst = ma_node_input_bus_first(pInputBus);
4149     if (pFirst == NULL) {
4150         return MA_SUCCESS;  /* No attachments. Read nothing. */
4151     }
4152 
4153     for (pOutputBus = pFirst; pOutputBus != NULL; pOutputBus = ma_node_input_bus_next(pInputBus, pOutputBus)) {
4154         ma_uint32 framesProcessed = 0;
4155 
4156         MA_ASSERT(pOutputBus->pNode != NULL);
4157 
4158         if (pFramesOut != NULL) {
4159             /* Read. */
4160             float temp[MA_DATA_CONVERTER_STACK_BUFFER_SIZE / sizeof(float)];
4161             ma_uint32 tempCapInFrames = ma_countof(temp) / inputChannels;
4162             float volume = ma_node_output_bus_get_volume(pOutputBus);
4163 
4164             while (framesProcessed < frameCount) {
4165                 float* pRunningFramesOut;
4166                 ma_uint32 framesToRead;
4167                 ma_uint32 framesJustRead;
4168 
4169                 framesToRead = frameCount - framesProcessed;
4170                 if (framesToRead > tempCapInFrames) {
4171                     framesToRead = tempCapInFrames;
4172                 }
4173 
4174                 pRunningFramesOut = ma_offset_pcm_frames_ptr_f32(pFramesOut, framesProcessed, inputChannels);
4175 
4176                 if (pOutputBus == pFirst) {
4177                     /* Fast path. First attachment. We just read straight into the output buffer (no mixing required). */
4178                     result = ma_node_read_pcm_frames(pOutputBus->pNode, pOutputBus->outputBusIndex, pRunningFramesOut, framesToRead, &framesJustRead, globalTime + framesProcessed);
4179                     if (result == MA_SUCCESS || result == MA_AT_END) {
4180                         /* Apply volume, if necessary. */
4181                         if (volume != 1) {
4182                             ma_apply_volume_factor_f32(pRunningFramesOut, framesJustRead * inputChannels, volume);
4183                         }
4184                     }
4185                 } else {
4186                     /* Slow path. Not the first attachment. Mixing required. */
4187                     result = ma_node_read_pcm_frames(pOutputBus->pNode, pOutputBus->outputBusIndex, temp, framesToRead, &framesJustRead, globalTime + framesProcessed);
4188                     if (result == MA_SUCCESS || result == MA_AT_END) {
4189                         /* Apply volume, if necessary. */
4190                         if (volume != 1) {
4191                             ma_apply_volume_factor_f32(temp, framesJustRead * inputChannels, volume);
4192                         }
4193 
4194                         ma_mix_pcm_frames_f32(pRunningFramesOut, temp, framesJustRead, inputChannels, /*volume*/1);
4195                     }
4196                 }
4197 
4198                 framesProcessed += framesJustRead;
4199 
4200                 /* If we reached the end or otherwise failed to read any data we need to finish up with this output node. */
4201                 if (result != MA_SUCCESS) {
4202                     break;
4203                 }
4204 
4205                 /* If we didn't read anything, abort so we don't get stuck in a loop. */
4206                 if (framesJustRead == 0) {
4207                     break;
4208                 }
4209             }
4210 
4211             /* If it's the first attachment we didn't do any mixing. Any leftover samples need to be silenced. */
4212             if (pOutputBus == pFirst && framesProcessed < frameCount) {
4213                 ma_silence_pcm_frames(ma_offset_pcm_frames_ptr(pFramesOut, framesProcessed, ma_format_f32, inputChannels), (frameCount - framesProcessed), ma_format_f32, inputChannels);
4214             }
4215         } else {
4216             /* Seek. */
4217             ma_node_read_pcm_frames(pOutputBus->pNode, pOutputBus->outputBusIndex, NULL, frameCount, &framesProcessed, globalTime);
4218         }
4219     }
4220 
4221     /* In this path we always "process" the entire amount. */
4222     *pFramesRead = frameCount;
4223 
4224     return result;
4225 }
4226 
4227 
ma_node_config_init(void)4228 MA_API ma_node_config ma_node_config_init(void)
4229 {
4230     ma_node_config config;
4231 
4232     MA_ZERO_OBJECT(&config);
4233     config.initialState   = ma_node_state_started;    /* Nodes are started by default. */
4234     config.inputBusCount  = MA_NODE_BUS_COUNT_UNKNOWN;
4235     config.outputBusCount = MA_NODE_BUS_COUNT_UNKNOWN;
4236 
4237     return config;
4238 }
4239 
4240 
4241 
4242 static ma_result ma_node_detach_full(ma_node* pNode);
4243 
ma_node_get_cached_input_ptr(ma_node * pNode,ma_uint32 inputBusIndex)4244 static float* ma_node_get_cached_input_ptr(ma_node* pNode, ma_uint32 inputBusIndex)
4245 {
4246     ma_node_base* pNodeBase = (ma_node_base*)pNode;
4247     ma_uint32 iInputBus;
4248     float* pBasePtr;
4249 
4250     MA_ASSERT(pNodeBase != NULL);
4251 
4252     /* Input data is stored at the front of the buffer. */
4253     pBasePtr = pNodeBase->pCachedData;
4254     for (iInputBus = 0; iInputBus < inputBusIndex; iInputBus += 1) {
4255         pBasePtr += pNodeBase->cachedDataCapInFramesPerBus * ma_node_input_bus_get_channels(&pNodeBase->pInputBuses[iInputBus]);
4256     }
4257 
4258     return pBasePtr;
4259 }
4260 
ma_node_get_cached_output_ptr(ma_node * pNode,ma_uint32 outputBusIndex)4261 static float* ma_node_get_cached_output_ptr(ma_node* pNode, ma_uint32 outputBusIndex)
4262 {
4263     ma_node_base* pNodeBase = (ma_node_base*)pNode;
4264     ma_uint32 iInputBus;
4265     ma_uint32 iOutputBus;
4266     float* pBasePtr;
4267 
4268     MA_ASSERT(pNodeBase != NULL);
4269 
4270     /* Cached output data starts after the input data. */
4271     pBasePtr = pNodeBase->pCachedData;
4272     for (iInputBus = 0; iInputBus < ma_node_get_input_bus_count(pNodeBase); iInputBus += 1) {
4273         pBasePtr += pNodeBase->cachedDataCapInFramesPerBus * ma_node_input_bus_get_channels(&pNodeBase->pInputBuses[iInputBus]);
4274     }
4275 
4276     for (iOutputBus = 0; iOutputBus < outputBusIndex; iOutputBus += 1) {
4277         pBasePtr += pNodeBase->cachedDataCapInFramesPerBus * ma_node_output_bus_get_channels(&pNodeBase->pOutputBuses[iOutputBus]);
4278     }
4279 
4280     return pBasePtr;
4281 }
4282 
4283 
4284 typedef struct
4285 {
4286     size_t sizeInBytes;
4287     size_t inputBusOffset;
4288     size_t outputBusOffset;
4289     size_t cachedDataOffset;
4290     ma_uint32 inputBusCount;    /* So it doesn't have to be calculated twice. */
4291     ma_uint32 outputBusCount;   /* So it doesn't have to be calculated twice. */
4292 } ma_node_heap_layout;
4293 
ma_node_translate_bus_counts(const ma_node_config * pConfig,ma_uint32 * pInputBusCount,ma_uint32 * pOutputBusCount)4294 static ma_result ma_node_translate_bus_counts(const ma_node_config* pConfig, ma_uint32* pInputBusCount, ma_uint32* pOutputBusCount)
4295 {
4296     ma_uint32 inputBusCount;
4297     ma_uint32 outputBusCount;
4298 
4299     MA_ASSERT(pConfig != NULL);
4300     MA_ASSERT(pInputBusCount  != NULL);
4301     MA_ASSERT(pOutputBusCount != NULL);
4302 
4303     /* Bus counts are determined by the vtable, unless they're set to `MA_NODE_BUS_COUNT_UNKNWON`, in which case they're taken from the config. */
4304     if (pConfig->vtable->inputBusCount == MA_NODE_BUS_COUNT_UNKNOWN) {
4305         inputBusCount = pConfig->inputBusCount;
4306     } else {
4307         inputBusCount = pConfig->vtable->inputBusCount;
4308 
4309         if (pConfig->inputBusCount != MA_NODE_BUS_COUNT_UNKNOWN && pConfig->inputBusCount != pConfig->vtable->inputBusCount) {
4310             return MA_INVALID_ARGS; /* Invalid configuration. You must not specify a conflicting bus count between the node's config and the vtable. */
4311         }
4312     }
4313 
4314     if (pConfig->vtable->outputBusCount == MA_NODE_BUS_COUNT_UNKNOWN) {
4315         outputBusCount = pConfig->outputBusCount;
4316     } else {
4317         outputBusCount = pConfig->vtable->outputBusCount;
4318 
4319         if (pConfig->outputBusCount != MA_NODE_BUS_COUNT_UNKNOWN && pConfig->outputBusCount != pConfig->vtable->outputBusCount) {
4320             return MA_INVALID_ARGS; /* Invalid configuration. You must not specify a conflicting bus count between the node's config and the vtable. */
4321         }
4322     }
4323 
4324     /* Bus counts must be within limits. */
4325     if (inputBusCount > MA_MAX_NODE_BUS_COUNT || outputBusCount > MA_MAX_NODE_BUS_COUNT) {
4326         return MA_INVALID_ARGS;
4327     }
4328 
4329 
4330     /* We must have channel counts for each bus. */
4331     if ((inputBusCount > 0 && pConfig->pInputChannels == NULL) || (outputBusCount > 0 && pConfig->pOutputChannels == NULL)) {
4332         return MA_INVALID_ARGS; /* You must specify channel counts for each input and output bus. */
4333     }
4334 
4335 
4336     /* Some special rules for passthrough nodes. */
4337     if ((pConfig->vtable->flags & MA_NODE_FLAG_PASSTHROUGH) != 0) {
4338         if (pConfig->vtable->inputBusCount != 1 || pConfig->vtable->outputBusCount != 1) {
4339             return MA_INVALID_ARGS; /* Passthrough nodes must have exactly 1 input bus and 1 output bus. */
4340         }
4341 
4342         if (pConfig->pInputChannels[0] != pConfig->pOutputChannels[0]) {
4343             return MA_INVALID_ARGS; /* Passthrough nodes must have the same number of channels between input and output nodes. */
4344         }
4345     }
4346 
4347 
4348     *pInputBusCount  = inputBusCount;
4349     *pOutputBusCount = outputBusCount;
4350 
4351     return MA_SUCCESS;
4352 }
4353 
ma_node_get_heap_layout(const ma_node_config * pConfig,ma_node_heap_layout * pHeapLayout)4354 static ma_result ma_node_get_heap_layout(const ma_node_config* pConfig, ma_node_heap_layout* pHeapLayout)
4355 {
4356     ma_result result;
4357     ma_uint32 inputBusCount;
4358     ma_uint32 outputBusCount;
4359 
4360     MA_ASSERT(pHeapLayout != NULL);
4361 
4362     MA_ZERO_OBJECT(pHeapLayout);
4363 
4364     if (pConfig == NULL || pConfig->vtable == NULL || pConfig->vtable->onProcess == NULL) {
4365         return MA_INVALID_ARGS;
4366     }
4367 
4368     result = ma_node_translate_bus_counts(pConfig, &inputBusCount, &outputBusCount);
4369     if (result != MA_SUCCESS) {
4370         return result;
4371     }
4372 
4373     pHeapLayout->sizeInBytes = 0;
4374 
4375     /* Input buses. */
4376     if (inputBusCount > MA_MAX_NODE_LOCAL_BUS_COUNT) {
4377         pHeapLayout->inputBusOffset = pHeapLayout->sizeInBytes;
4378         pHeapLayout->sizeInBytes += ma_align_64(sizeof(ma_node_input_bus) * inputBusCount);
4379     } else {
4380         pHeapLayout->inputBusOffset = MA_SIZE_MAX;  /* MA_SIZE_MAX indicates that no heap allocation is required for the input bus. */
4381     }
4382 
4383     /* Output buses. */
4384     if (outputBusCount > MA_MAX_NODE_LOCAL_BUS_COUNT) {
4385         pHeapLayout->outputBusOffset = pHeapLayout->sizeInBytes;
4386         pHeapLayout->sizeInBytes += ma_align_64(sizeof(ma_node_output_bus) * outputBusCount);
4387     } else {
4388         pHeapLayout->outputBusOffset = MA_SIZE_MAX;
4389     }
4390 
4391     /*
4392     Cached audio data.
4393 
4394     We need to allocate memory for a caching both input and output data. We have an optimization
4395     where no caching is necessary for specific conditions:
4396 
4397         - The node has 0 inputs and 1 output.
4398 
4399     When a node meets the above conditions, no cache is allocated.
4400 
4401     The size choice for this buffer is a little bit finicky. We don't want to be too wasteful by
4402     allocating too much, but at the same time we want it be large enough so that enough frames can
4403     be processed for each call to ma_node_read_pcm_frames() so that it keeps things efficient. For
4404     now I'm going with 10ms @ 48K which is 480 frames per bus. This is configurable at compile
4405     time. It might also be worth investigating whether or not this can be configured at run time.
4406     */
4407     if (inputBusCount == 0 && outputBusCount == 1) {
4408         /* Fast path. No cache needed. */
4409         pHeapLayout->cachedDataOffset = MA_SIZE_MAX;
4410     } else {
4411         /* Slow path. Cache needed. */
4412         size_t cachedDataSizeInBytes = 0;
4413         ma_uint32 iBus;
4414 
4415         MA_ASSERT(MA_DEFAULT_NODE_CACHE_CAP_IN_FRAMES_PER_BUS <= 0xFFFF);    /* Clamped to 16 bits. */
4416 
4417         for (iBus = 0; iBus < inputBusCount; iBus += 1) {
4418             cachedDataSizeInBytes += MA_DEFAULT_NODE_CACHE_CAP_IN_FRAMES_PER_BUS * ma_get_bytes_per_frame(ma_format_f32, pConfig->pInputChannels[iBus]);
4419         }
4420 
4421         for (iBus = 0; iBus < outputBusCount; iBus += 1) {
4422             cachedDataSizeInBytes += MA_DEFAULT_NODE_CACHE_CAP_IN_FRAMES_PER_BUS * ma_get_bytes_per_frame(ma_format_f32, pConfig->pOutputChannels[iBus]);
4423         }
4424 
4425         pHeapLayout->cachedDataOffset = pHeapLayout->sizeInBytes;
4426         pHeapLayout->sizeInBytes += ma_align_64(cachedDataSizeInBytes);
4427     }
4428 
4429 
4430     /*
4431     Not technically part of the heap, but we can output the input and output bus counts so we can
4432     avoid a redundant call to ma_node_translate_bus_counts().
4433     */
4434     pHeapLayout->inputBusCount  = inputBusCount;
4435     pHeapLayout->outputBusCount = outputBusCount;
4436 
4437     return MA_SUCCESS;
4438 }
4439 
ma_node_get_heap_size(const ma_node_config * pConfig,size_t * pHeapSizeInBytes)4440 MA_API ma_result ma_node_get_heap_size(const ma_node_config* pConfig, size_t* pHeapSizeInBytes)
4441 {
4442     ma_result result;
4443     ma_node_heap_layout heapLayout;
4444 
4445     if (pHeapSizeInBytes == NULL) {
4446         return MA_INVALID_ARGS;
4447     }
4448 
4449     *pHeapSizeInBytes = 0;
4450 
4451     result = ma_node_get_heap_layout(pConfig, &heapLayout);
4452     if (result != MA_SUCCESS) {
4453         return result;
4454     }
4455 
4456     *pHeapSizeInBytes = heapLayout.sizeInBytes;
4457 
4458     return MA_SUCCESS;
4459 }
4460 
ma_node_init_preallocated(ma_node_graph * pNodeGraph,const ma_node_config * pConfig,void * pHeap,ma_node * pNode)4461 MA_API ma_result ma_node_init_preallocated(ma_node_graph* pNodeGraph, const ma_node_config* pConfig, void* pHeap, ma_node* pNode)
4462 {
4463     ma_node_base* pNodeBase = (ma_node_base*)pNode;
4464     ma_result result;
4465     ma_node_heap_layout heapLayout;
4466     ma_uint32 iInputBus;
4467     ma_uint32 iOutputBus;
4468 
4469     if (pNodeBase == NULL) {
4470         return MA_INVALID_ARGS;
4471     }
4472 
4473     MA_ZERO_OBJECT(pNodeBase);
4474 
4475     result = ma_node_get_heap_layout(pConfig, &heapLayout);
4476     if (result != MA_SUCCESS) {
4477         return result;
4478     }
4479 
4480     pNodeBase->_pHeap         = pHeap;
4481     pNodeBase->pNodeGraph     = pNodeGraph;
4482     pNodeBase->vtable         = pConfig->vtable;
4483     pNodeBase->state          = pConfig->initialState;
4484     pNodeBase->stateTimes[ma_node_state_started] = 0;
4485     pNodeBase->stateTimes[ma_node_state_stopped] = (ma_uint64)(ma_int64)-1; /* Weird casting for VC6 compatibility. */
4486     pNodeBase->inputBusCount  = heapLayout.inputBusCount;
4487     pNodeBase->outputBusCount = heapLayout.outputBusCount;
4488 
4489     if (heapLayout.inputBusOffset != MA_SIZE_MAX) {
4490         pNodeBase->pInputBuses = (ma_node_input_bus*)ma_offset_ptr(pHeap, heapLayout.inputBusOffset);
4491     } else {
4492         pNodeBase->pInputBuses = pNodeBase->_inputBuses;
4493     }
4494 
4495     if (heapLayout.outputBusOffset != MA_SIZE_MAX) {
4496         pNodeBase->pOutputBuses = (ma_node_output_bus*)ma_offset_ptr(pHeap, heapLayout.inputBusOffset);
4497     } else {
4498         pNodeBase->pOutputBuses = pNodeBase->_outputBuses;
4499     }
4500 
4501     if (heapLayout.cachedDataOffset != MA_SIZE_MAX) {
4502         pNodeBase->pCachedData = (float*)ma_offset_ptr(pHeap, heapLayout.cachedDataOffset);
4503         pNodeBase->cachedDataCapInFramesPerBus = MA_DEFAULT_NODE_CACHE_CAP_IN_FRAMES_PER_BUS;
4504     } else {
4505         pNodeBase->pCachedData = NULL;
4506     }
4507 
4508 
4509 
4510     /* We need to run an initialization step for each input and output bus. */
4511     for (iInputBus = 0; iInputBus < ma_node_get_input_bus_count(pNodeBase); iInputBus += 1) {
4512         result = ma_node_input_bus_init(pConfig->pInputChannels[iInputBus], &pNodeBase->pInputBuses[iInputBus]);
4513         if (result != MA_SUCCESS) {
4514             return result;
4515         }
4516     }
4517 
4518     for (iOutputBus = 0; iOutputBus < ma_node_get_output_bus_count(pNodeBase); iOutputBus += 1) {
4519         result = ma_node_output_bus_init(pNodeBase, iOutputBus, pConfig->pOutputChannels[iOutputBus], &pNodeBase->pOutputBuses[iOutputBus]);
4520         if (result != MA_SUCCESS) {
4521             return result;
4522         }
4523     }
4524 
4525 
4526     /* The cached data needs to be initialized to silence (or a sine wave tone if we're debugging). */
4527     if (pNodeBase->pCachedData != NULL) {
4528         ma_uint32 iBus;
4529 
4530     #if 1   /* Toggle this between 0 and 1 to turn debugging on or off. 1 = fill with a sine wave for debugging; 0 = fill with silence. */
4531         /* For safety we'll go ahead and default the buffer to silence. */
4532         for (iBus = 0; iBus < ma_node_get_input_bus_count(pNodeBase); iBus += 1) {
4533             ma_silence_pcm_frames(ma_node_get_cached_input_ptr(pNode, iBus), pNodeBase->cachedDataCapInFramesPerBus, ma_format_f32, ma_node_input_bus_get_channels(&pNodeBase->pInputBuses[iBus]));
4534         }
4535         for (iBus = 0; iBus < ma_node_get_output_bus_count(pNodeBase); iBus += 1) {
4536             ma_silence_pcm_frames(ma_node_get_cached_output_ptr(pNode, iBus), pNodeBase->cachedDataCapInFramesPerBus, ma_format_f32, ma_node_output_bus_get_channels(&pNodeBase->pOutputBuses[iBus]));
4537         }
4538     #else
4539         /* For debugging. Default to a sine wave. */
4540         for (iBus = 0; iBus < ma_node_get_input_bus_count(pNodeBase); iBus += 1) {
4541             ma_debug_fill_pcm_frames_with_sine_wave(ma_node_get_cached_input_ptr(pNode, iBus), pNodeBase->cachedDataCapInFramesPerBus, ma_format_f32, ma_node_input_bus_get_channels(&pNodeBase->pInputBuses[iBus]), 48000);
4542         }
4543         for (iBus = 0; iBus < ma_node_get_output_bus_count(pNodeBase); iBus += 1) {
4544             ma_debug_fill_pcm_frames_with_sine_wave(ma_node_get_cached_output_ptr(pNode, iBus), pNodeBase->cachedDataCapInFramesPerBus, ma_format_f32, ma_node_output_bus_get_channels(&pNodeBase->pOutputBuses[iBus]), 48000);
4545         }
4546     #endif
4547     }
4548 
4549     return MA_SUCCESS;
4550 }
4551 
ma_node_init(ma_node_graph * pNodeGraph,const ma_node_config * pConfig,const ma_allocation_callbacks * pAllocationCallbacks,ma_node * pNode)4552 MA_API ma_result ma_node_init(ma_node_graph* pNodeGraph, const ma_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_node* pNode)
4553 {
4554     ma_result result;
4555     size_t heapSizeInBytes;
4556     void* pHeap;
4557 
4558     result = ma_node_get_heap_size(pConfig, &heapSizeInBytes);
4559     if (result != MA_SUCCESS) {
4560         return result;
4561     }
4562 
4563     if (heapSizeInBytes > 0) {
4564         pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks);
4565         if (pHeap == NULL) {
4566             return MA_OUT_OF_MEMORY;
4567         }
4568     } else {
4569         pHeap = NULL;
4570     }
4571 
4572     result = ma_node_init_preallocated(pNodeGraph, pConfig, pHeap, pNode);
4573     if (result != MA_SUCCESS) {
4574         ma_free(pHeap, pAllocationCallbacks);
4575         return result;
4576     }
4577 
4578     ((ma_node_base*)pNode)->_ownsHeap = MA_TRUE;
4579     return MA_SUCCESS;
4580 }
4581 
ma_node_uninit(ma_node * pNode,const ma_allocation_callbacks * pAllocationCallbacks)4582 MA_API void ma_node_uninit(ma_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks)
4583 {
4584     ma_node_base* pNodeBase = (ma_node_base*)pNode;
4585 
4586     if (pNodeBase == NULL) {
4587         return;
4588     }
4589 
4590     /*
4591     The first thing we need to do is fully detach the node. This will detach all inputs and
4592     outputs. We need to do this first because it will sever the connection with the node graph and
4593     allow us to complete uninitialization without needing to worry about thread-safety with the
4594     audio thread. The detachment process will wait for any local processing of the node to finish.
4595     */
4596     ma_node_detach_full(pNode);
4597 
4598     /*
4599     At this point the node should be completely unreferenced by the node graph and we can finish up
4600     the uninitialization process without needing to worry about thread-safety.
4601     */
4602     if (pNodeBase->_pHeap != NULL && pNodeBase->_ownsHeap) {
4603         ma_free(pNodeBase->_pHeap, pAllocationCallbacks);
4604         pNodeBase->_pHeap;
4605     }
4606 }
4607 
ma_node_get_node_graph(const ma_node * pNode)4608 MA_API ma_node_graph* ma_node_get_node_graph(const ma_node* pNode)
4609 {
4610     if (pNode == NULL) {
4611         return NULL;
4612     }
4613 
4614     return ((const ma_node_base*)pNode)->pNodeGraph;
4615 }
4616 
ma_node_get_input_bus_count(const ma_node * pNode)4617 MA_API ma_uint32 ma_node_get_input_bus_count(const ma_node* pNode)
4618 {
4619     if (pNode == NULL) {
4620         return 0;
4621     }
4622 
4623     return ((ma_node_base*)pNode)->inputBusCount;
4624 }
4625 
ma_node_get_output_bus_count(const ma_node * pNode)4626 MA_API ma_uint32 ma_node_get_output_bus_count(const ma_node* pNode)
4627 {
4628     if (pNode == NULL) {
4629         return 0;
4630     }
4631 
4632     return ((ma_node_base*)pNode)->outputBusCount;
4633 }
4634 
4635 
ma_node_get_input_channels(const ma_node * pNode,ma_uint32 inputBusIndex)4636 MA_API ma_uint32 ma_node_get_input_channels(const ma_node* pNode, ma_uint32 inputBusIndex)
4637 {
4638     const ma_node_base* pNodeBase = (const ma_node_base*)pNode;
4639 
4640     if (pNode == NULL) {
4641         return 0;
4642     }
4643 
4644     if (inputBusIndex >= ma_node_get_input_bus_count(pNode)) {
4645         return 0;   /* Invalid bus index. */
4646     }
4647 
4648     return ma_node_input_bus_get_channels(&pNodeBase->pInputBuses[inputBusIndex]);
4649 }
4650 
ma_node_get_output_channels(const ma_node * pNode,ma_uint32 outputBusIndex)4651 MA_API ma_uint32 ma_node_get_output_channels(const ma_node* pNode, ma_uint32 outputBusIndex)
4652 {
4653     const ma_node_base* pNodeBase = (const ma_node_base*)pNode;
4654 
4655     if (pNode == NULL) {
4656         return 0;
4657     }
4658 
4659     if (outputBusIndex >= ma_node_get_output_bus_count(pNode)) {
4660         return 0;   /* Invalid bus index. */
4661     }
4662 
4663     return ma_node_output_bus_get_channels(&pNodeBase->pOutputBuses[outputBusIndex]);
4664 }
4665 
4666 
ma_node_detach_full(ma_node * pNode)4667 static ma_result ma_node_detach_full(ma_node* pNode)
4668 {
4669     ma_node_base* pNodeBase = (ma_node_base*)pNode;
4670     ma_uint32 iInputBus;
4671 
4672     if (pNodeBase == NULL) {
4673         return MA_INVALID_ARGS;
4674     }
4675 
4676     /*
4677     Make sure the node is completely detached first. This will not return until the output bus is
4678     guaranteed to no longer be referenced by the audio thread.
4679     */
4680     ma_node_detach_all_output_buses(pNode);
4681 
4682     /*
4683     At this point all output buses will have been detached from the graph and we can be guaranteed
4684     that none of it's input nodes will be getting processed by the graph. We can detach these
4685     without needing to worry about the audio thread touching them.
4686     */
4687     for (iInputBus = 0; iInputBus < ma_node_get_input_bus_count(pNode); iInputBus += 1) {
4688         ma_node_input_bus* pInputBus;
4689         ma_node_output_bus* pOutputBus;
4690 
4691         pInputBus = &pNodeBase->pInputBuses[iInputBus];
4692 
4693         /*
4694         This is important. We cannot be using ma_node_input_bus_first() or ma_node_input_bus_next(). Those
4695         functions are specifically for the audio thread. We'll instead just manually iterate using standard
4696         linked list logic. We don't need to worry about the audio thread referencing these because the step
4697         above severed the connection to the graph.
4698         */
4699         for (pOutputBus = (ma_node_output_bus*)c89atomic_load_ptr(&pInputBus->head.pNext); pOutputBus != NULL; pOutputBus = (ma_node_output_bus*)c89atomic_load_ptr(&pOutputBus->pNext)) {
4700             ma_node_detach_output_bus(pOutputBus->pNode, pOutputBus->outputBusIndex);   /* This won't do any waiting in practice and should be efficient. */
4701         }
4702     }
4703 
4704     return MA_SUCCESS;
4705 }
4706 
ma_node_detach_output_bus(ma_node * pNode,ma_uint32 outputBusIndex)4707 MA_API ma_result ma_node_detach_output_bus(ma_node* pNode, ma_uint32 outputBusIndex)
4708 {
4709     ma_result result = MA_SUCCESS;
4710     ma_node_base* pNodeBase = (ma_node_base*)pNode;
4711     ma_node_base* pInputNodeBase;
4712 
4713     if (pNode == NULL) {
4714         return MA_INVALID_ARGS;
4715     }
4716 
4717     if (outputBusIndex >= ma_node_get_output_bus_count(pNode)) {
4718         return MA_INVALID_ARGS; /* Invalid output bus index. */
4719     }
4720 
4721     /* We need to lock the output bus because we need to inspect the input node and grab it's input bus. */
4722     ma_node_output_bus_lock(&pNodeBase->pOutputBuses[outputBusIndex]);
4723     {
4724         pInputNodeBase = (ma_node_base*)pNodeBase->pOutputBuses[outputBusIndex].pInputNode;
4725         if (pInputNodeBase != NULL) {
4726             ma_node_input_bus_detach__no_output_bus_lock(&pInputNodeBase->pInputBuses[pNodeBase->pOutputBuses[outputBusIndex].inputNodeInputBusIndex], &pNodeBase->pOutputBuses[outputBusIndex]);
4727         }
4728     }
4729     ma_node_output_bus_unlock(&pNodeBase->pOutputBuses[outputBusIndex]);
4730 
4731     return result;
4732 }
4733 
ma_node_detach_all_output_buses(ma_node * pNode)4734 MA_API ma_result ma_node_detach_all_output_buses(ma_node* pNode)
4735 {
4736     ma_uint32 iOutputBus;
4737 
4738     if (pNode == NULL) {
4739         return MA_INVALID_ARGS;
4740     }
4741 
4742     for (iOutputBus = 0; iOutputBus < ma_node_get_output_bus_count(pNode); iOutputBus += 1) {
4743         ma_node_detach_output_bus(pNode, iOutputBus);
4744     }
4745 
4746     return MA_SUCCESS;
4747 }
4748 
ma_node_attach_output_bus(ma_node * pNode,ma_uint32 outputBusIndex,ma_node * pOtherNode,ma_uint32 otherNodeInputBusIndex)4749 MA_API ma_result ma_node_attach_output_bus(ma_node* pNode, ma_uint32 outputBusIndex, ma_node* pOtherNode, ma_uint32 otherNodeInputBusIndex)
4750 {
4751     ma_node_base* pNodeBase  = (ma_node_base*)pNode;
4752     ma_node_base* pOtherNodeBase = (ma_node_base*)pOtherNode;
4753 
4754     if (pNodeBase == NULL || pOtherNodeBase == NULL) {
4755         return MA_INVALID_ARGS;
4756     }
4757 
4758     if (pNodeBase == pOtherNodeBase) {
4759         return MA_INVALID_OPERATION;    /* Cannot attach a node to itself. */
4760     }
4761 
4762     if (outputBusIndex >= ma_node_get_output_bus_count(pNode) || otherNodeInputBusIndex >= ma_node_get_input_bus_count(pOtherNode)) {
4763         return MA_INVALID_OPERATION;    /* Invalid bus index. */
4764     }
4765 
4766     /* The output channel count of the output node must be the same as the input channel count of the input node. */
4767     if (ma_node_get_output_channels(pNode, outputBusIndex) != ma_node_get_input_channels(pOtherNode, otherNodeInputBusIndex)) {
4768         return MA_INVALID_OPERATION;    /* Channel count is incompatible. */
4769     }
4770 
4771     /* This will deal with detaching if the output bus is already attached to something. */
4772     ma_node_input_bus_attach(&pOtherNodeBase->pInputBuses[otherNodeInputBusIndex], &pNodeBase->pOutputBuses[outputBusIndex], pOtherNode, otherNodeInputBusIndex);
4773 
4774     return MA_SUCCESS;
4775 }
4776 
ma_node_set_output_bus_volume(ma_node * pNode,ma_uint32 outputBusIndex,float volume)4777 MA_API ma_result ma_node_set_output_bus_volume(ma_node* pNode, ma_uint32 outputBusIndex, float volume)
4778 {
4779     ma_node_base* pNodeBase = (ma_node_base*)pNode;
4780 
4781     if (pNodeBase == NULL) {
4782         return MA_INVALID_ARGS;
4783     }
4784 
4785     if (outputBusIndex >= ma_node_get_output_bus_count(pNode)) {
4786         return MA_INVALID_ARGS; /* Invalid bus index. */
4787     }
4788 
4789     return ma_node_output_bus_set_volume(&pNodeBase->pOutputBuses[outputBusIndex], volume);
4790 }
4791 
ma_node_get_output_bus_volume(const ma_node * pNode,ma_uint32 outputBusIndex)4792 MA_API float ma_node_get_output_bus_volume(const ma_node* pNode, ma_uint32 outputBusIndex)
4793 {
4794     const ma_node_base* pNodeBase = (const ma_node_base*)pNode;
4795 
4796     if (pNodeBase == NULL) {
4797         return 0;
4798     }
4799 
4800     if (outputBusIndex >= ma_node_get_output_bus_count(pNode)) {
4801         return 0;   /* Invalid bus index. */
4802     }
4803 
4804     return ma_node_output_bus_get_volume(&pNodeBase->pOutputBuses[outputBusIndex]);
4805 }
4806 
ma_node_set_state(ma_node * pNode,ma_node_state state)4807 MA_API ma_result ma_node_set_state(ma_node* pNode, ma_node_state state)
4808 {
4809     ma_node_base* pNodeBase = (ma_node_base*)pNode;
4810 
4811     if (pNodeBase == NULL) {
4812         return MA_INVALID_ARGS;
4813     }
4814 
4815     c89atomic_exchange_i32(&pNodeBase->state, state);
4816 
4817     return MA_SUCCESS;
4818 }
4819 
ma_node_get_state(const ma_node * pNode)4820 MA_API ma_node_state ma_node_get_state(const ma_node* pNode)
4821 {
4822     const ma_node_base* pNodeBase = (const ma_node_base*)pNode;
4823 
4824     if (pNodeBase == NULL) {
4825         return ma_node_state_stopped;
4826     }
4827 
4828     return (ma_node_state)c89atomic_load_i32(&pNodeBase->state);
4829 }
4830 
ma_node_set_state_time(ma_node * pNode,ma_node_state state,ma_uint64 globalTime)4831 MA_API ma_result ma_node_set_state_time(ma_node* pNode, ma_node_state state, ma_uint64 globalTime)
4832 {
4833     if (pNode == NULL) {
4834         return MA_INVALID_ARGS;
4835     }
4836 
4837     /* Validation check for safety since we'll be using this as an index into stateTimes[]. */
4838     if (state != ma_node_state_started && state != ma_node_state_stopped) {
4839         return MA_INVALID_ARGS;
4840     }
4841 
4842     c89atomic_exchange_64(&((ma_node_base*)pNode)->stateTimes[state], globalTime);
4843 
4844     return MA_SUCCESS;
4845 }
4846 
ma_node_get_state_time(const ma_node * pNode,ma_node_state state)4847 MA_API ma_uint64 ma_node_get_state_time(const ma_node* pNode, ma_node_state state)
4848 {
4849     if (pNode == NULL) {
4850         return 0;
4851     }
4852 
4853     /* Validation check for safety since we'll be using this as an index into stateTimes[]. */
4854     if (state != ma_node_state_started && state != ma_node_state_stopped) {
4855         return 0;
4856     }
4857 
4858     return c89atomic_load_64(&((ma_node_base*)pNode)->stateTimes[state]);
4859 }
4860 
ma_node_get_state_by_time(const ma_node * pNode,ma_uint64 globalTime)4861 MA_API ma_node_state ma_node_get_state_by_time(const ma_node* pNode, ma_uint64 globalTime)
4862 {
4863     if (pNode == NULL) {
4864         return ma_node_state_stopped;
4865     }
4866 
4867     return ma_node_get_state_by_time_range(pNode, globalTime, globalTime);
4868 }
4869 
ma_node_get_state_by_time_range(const ma_node * pNode,ma_uint64 globalTimeBeg,ma_uint64 globalTimeEnd)4870 MA_API ma_node_state ma_node_get_state_by_time_range(const ma_node* pNode, ma_uint64 globalTimeBeg, ma_uint64 globalTimeEnd)
4871 {
4872     ma_node_state state;
4873 
4874     if (pNode == NULL) {
4875         return ma_node_state_stopped;
4876     }
4877 
4878     state = ma_node_get_state(pNode);
4879 
4880     /* An explicitly stopped node is always stopped. */
4881     if (state == ma_node_state_stopped) {
4882         return ma_node_state_stopped;
4883     }
4884 
4885     /*
4886     Getting here means the node is marked as started, but it may still not be truly started due to
4887     it's start time not having been reached yet. Also, the stop time may have also been reached in
4888     which case it'll be considered stopped.
4889     */
4890     if (ma_node_get_state_time(pNode, ma_node_state_started) > globalTimeBeg) {
4891         return ma_node_state_stopped;   /* Start time has not yet been reached. */
4892     }
4893 
4894     if (ma_node_get_state_time(pNode, ma_node_state_stopped) <= globalTimeEnd) {
4895         return ma_node_state_stopped;   /* Stop time has been reached. */
4896     }
4897 
4898     /* Getting here means the node is marked as started and is within it's start/stop times. */
4899     return ma_node_state_started;
4900 }
4901 
ma_node_get_time(const ma_node * pNode)4902 MA_API ma_uint64 ma_node_get_time(const ma_node* pNode)
4903 {
4904     if (pNode == NULL) {
4905         return 0;
4906     }
4907 
4908     return c89atomic_load_64(&((ma_node_base*)pNode)->localTime);
4909 }
4910 
ma_node_set_time(ma_node * pNode,ma_uint64 localTime)4911 MA_API ma_result ma_node_set_time(ma_node* pNode, ma_uint64 localTime)
4912 {
4913     if (pNode == NULL) {
4914         return MA_INVALID_ARGS;
4915     }
4916 
4917     c89atomic_exchange_64(&((ma_node_base*)pNode)->localTime, localTime);
4918 
4919     return MA_SUCCESS;
4920 }
4921 
4922 
4923 
ma_node_process_pcm_frames_internal(ma_node * pNode,const float ** ppFramesIn,ma_uint32 * pFrameCountIn,float ** ppFramesOut,ma_uint32 * pFrameCountOut)4924 static void ma_node_process_pcm_frames_internal(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut)
4925 {
4926     ma_node_base* pNodeBase = (ma_node_base*)pNode;
4927 
4928     MA_ASSERT(pNode != NULL);
4929 
4930     if (pNodeBase->vtable->onProcess) {
4931         pNodeBase->vtable->onProcess(pNode, ppFramesIn, pFrameCountIn, ppFramesOut, pFrameCountOut);
4932     }
4933 }
4934 
ma_node_read_pcm_frames(ma_node * pNode,ma_uint32 outputBusIndex,float * pFramesOut,ma_uint32 frameCount,ma_uint32 * pFramesRead,ma_uint64 globalTime)4935 static ma_result ma_node_read_pcm_frames(ma_node* pNode, ma_uint32 outputBusIndex, float* pFramesOut, ma_uint32 frameCount, ma_uint32* pFramesRead, ma_uint64 globalTime)
4936 {
4937     ma_node_base* pNodeBase = (ma_node_base*)pNode;
4938     ma_result result = MA_SUCCESS;
4939     ma_uint32 iInputBus;
4940     ma_uint32 iOutputBus;
4941     ma_uint32 inputBusCount;
4942     ma_uint32 outputBusCount;
4943     ma_uint32 totalFramesRead = 0;
4944     float* ppFramesIn[MA_MAX_NODE_BUS_COUNT];
4945     float* ppFramesOut[MA_MAX_NODE_BUS_COUNT];
4946     ma_uint64 globalTimeBeg;
4947     ma_uint64 globalTimeEnd;
4948     ma_uint64 startTime;
4949     ma_uint64 stopTime;
4950     ma_uint32 timeOffsetBeg;
4951     ma_uint32 timeOffsetEnd;
4952     ma_uint32 frameCountIn;
4953     ma_uint32 frameCountOut;
4954 
4955     /*
4956     pFramesRead is mandatory. It must be used to determine how many frames were read. It's normal and
4957     expected that the number of frames read may be different to that requested. Therefore, the caller
4958     must look at this value to correctly determine how many frames were read.
4959     */
4960     MA_ASSERT(pFramesRead != NULL); /* <-- If you've triggered this assert, you're using this function wrong. You *must* use this variable and inspect it after the call returns. */
4961     if (pFramesRead == NULL) {
4962         return MA_INVALID_ARGS;
4963     }
4964 
4965     *pFramesRead = 0;   /* Safety. */
4966 
4967     if (pNodeBase == NULL) {
4968         return MA_INVALID_ARGS;
4969     }
4970 
4971     if (outputBusIndex >= ma_node_get_output_bus_count(pNodeBase)) {
4972         return MA_INVALID_ARGS; /* Invalid output bus index. */
4973     }
4974 
4975     /* Don't do anything if we're in a stopped state. */
4976     if (ma_node_get_state_by_time_range(pNode, globalTime, globalTime + frameCount) != ma_node_state_started) {
4977         return MA_SUCCESS;  /* We're in a stopped state. This is not an error - we just need to not read anything. */
4978     }
4979 
4980 
4981     globalTimeBeg = globalTime;
4982     globalTimeEnd = globalTime + frameCount;
4983     startTime = ma_node_get_state_time(pNode, ma_node_state_started);
4984     stopTime  = ma_node_get_state_time(pNode, ma_node_state_stopped);
4985 
4986     /*
4987     At this point we know that we are inside our start/stop times. However, we may need to adjust
4988     our frame count and output pointer to accomodate since we could be straddling the time period
4989     that this function is getting called for.
4990 
4991     It's possible (and likely) that the start time does not line up with the output buffer. We
4992     therefore need to offset it by a number of frames to accomodate. The same thing applies for
4993     the stop time.
4994     */
4995     timeOffsetBeg = (globalTimeBeg < startTime) ? (ma_uint32)(globalTimeEnd - startTime) : 0;
4996     timeOffsetEnd = (globalTimeEnd > stopTime)  ? (ma_uint32)(globalTimeEnd - stopTime)  : 0;
4997 
4998     /* Trim based on the start offset. We need to silence the start of the buffer. */
4999     if (timeOffsetBeg > 0) {
5000         ma_silence_pcm_frames(pFramesOut, timeOffsetBeg, ma_format_f32, ma_node_get_output_channels(pNode, outputBusIndex));
5001         pFramesOut += timeOffsetBeg * ma_node_get_output_channels(pNode, outputBusIndex);
5002         frameCount -= timeOffsetBeg;
5003     }
5004 
5005     /* Trim based on the end offset. We don't need to silence the tail section because we'll just have a reduced value written to pFramesRead. */
5006     if (timeOffsetEnd > 0) {
5007         frameCount -= timeOffsetEnd;
5008     }
5009 
5010 
5011     /* We run on different paths depending on the bus counts. */
5012     inputBusCount  = ma_node_get_input_bus_count(pNode);
5013     outputBusCount = ma_node_get_output_bus_count(pNode);
5014 
5015     /*
5016     Run a simplified path when there are no inputs and one output. In this case there's nothing to
5017     actually read and we can go straight to output. This is a very common scenario because the vast
5018     majority of data source nodes will use this setup so this optimization I think is worthwhile.
5019     */
5020     if (inputBusCount == 0 && outputBusCount == 1) {
5021         /* Fast path. No need to read from input and no need for any caching. */
5022         frameCountIn  = 0;
5023         frameCountOut = frameCount;    /* Just read as much as we can. The callback will return what was actually read. */
5024 
5025         /* Don't do anything if our read counter is ahead of the node graph. That means we're */
5026         ppFramesOut[0] = pFramesOut;
5027         ma_node_process_pcm_frames_internal(pNode, NULL, &frameCountIn, ppFramesOut, &frameCountOut);
5028         totalFramesRead = frameCountOut;
5029     } else {
5030         /* Slow path. Need to read input data. */
5031         if ((pNodeBase->vtable->flags & MA_NODE_FLAG_PASSTHROUGH) != 0) {
5032             /*
5033             Fast path. We're running a passthrough. We need to read directly into the output buffer, but
5034             still fire the callback so that event handling and trigger nodes can do their thing. Since
5035             it's a passthrough there's no need for any kind of caching logic.
5036             */
5037             MA_ASSERT(outputBusCount == inputBusCount);
5038             MA_ASSERT(outputBusCount == 1);
5039             MA_ASSERT(outputBusIndex == 0);
5040 
5041             /* We just read directly from input bus to output buffer, and then afterwards fire the callback. */
5042             ppFramesOut[0] = pFramesOut;
5043             ppFramesIn[0] = ppFramesOut[0];
5044 
5045             result = ma_node_input_bus_read_pcm_frames(pNodeBase, &pNodeBase->pInputBuses[0], ppFramesIn[0], frameCount, &totalFramesRead, globalTime);
5046             if (result == MA_SUCCESS) {
5047                 /* Even though it's a passthrough, we still need to fire the callback. */
5048                 frameCountIn  = totalFramesRead;
5049                 frameCountOut = totalFramesRead;
5050 
5051                 if (totalFramesRead > 0) {
5052                     ma_node_process_pcm_frames_internal(pNode, (const float**)ppFramesIn, &frameCountIn, ppFramesOut, &frameCountOut);  /* From GCC: expected 'const float **' but argument is of type 'float **'. Shouldn't this be implicit? Excplicit cast to silence the warning. */
5053                 }
5054 
5055                 /*
5056                 A passthrough should never have modified the input and output frame counts. If you're
5057                 triggering these assers you need to fix your processing callback.
5058                 */
5059                 MA_ASSERT(frameCountIn  == totalFramesRead);
5060                 MA_ASSERT(frameCountOut == totalFramesRead);
5061             }
5062         } else {
5063             /* Slow path. Need to do caching. */
5064             ma_uint32 framesToProcessIn;
5065             ma_uint32 framesToProcessOut;
5066             ma_bool32 consumeNullInput = MA_FALSE;
5067 
5068             /*
5069             We use frameCount as a basis for the number of frames to read since that's what's being
5070             requested, however we still need to clamp it to whatever can fit in the cache.
5071 
5072             This will also be used as the basis for determining how many input frames to read. This is
5073             not ideal because it can result in too many input frames being read which introduces latency.
5074             To solve this, nodes can implement an optional callback called onGetRequiredInputFrameCount
5075             which is used as hint to miniaudio as to how many input frames it needs to read at a time. This
5076             callback is completely optional, and if it's not set, miniaudio will assume `frameCount`.
5077 
5078             This function will be called multiple times for each period of time, once for each output node.
5079             We cannot read from each input node each time this function is called. Instead we need to check
5080             whether or not this is first output bus to be read from for this time period, and if so, read
5081             from our input data.
5082 
5083             To determine whether or not we're ready to read data, we check a flag. There will be one flag
5084             for each output. When the flag is set, it means data has been read previously and that we're
5085             ready to advance time forward for our input nodes by reading fresh data.
5086             */
5087             framesToProcessOut = frameCount;
5088             if (framesToProcessOut > pNodeBase->cachedDataCapInFramesPerBus) {
5089                 framesToProcessOut = pNodeBase->cachedDataCapInFramesPerBus;
5090             }
5091 
5092             framesToProcessIn  = frameCount;
5093             if (pNodeBase->vtable->onGetRequiredInputFrameCount) {
5094                 framesToProcessIn = pNodeBase->vtable->onGetRequiredInputFrameCount(pNode, framesToProcessOut);
5095             }
5096             if (framesToProcessIn > pNodeBase->cachedDataCapInFramesPerBus) {
5097                 framesToProcessIn = pNodeBase->cachedDataCapInFramesPerBus;
5098             }
5099 
5100 
5101             MA_ASSERT(framesToProcessIn  <= 0xFFFF);
5102             MA_ASSERT(framesToProcessOut <= 0xFFFF);
5103 
5104             if (ma_node_output_bus_has_read(&pNodeBase->pOutputBuses[outputBusIndex])) {
5105                 /* Getting here means we need to do another round of processing. */
5106                 pNodeBase->cachedFrameCountOut = 0;
5107 
5108                 /*
5109                 We need to prepare our output frame pointers for processing. In the same iteration we need
5110                 to mark every output bus as unread so that future calls to this function for different buses
5111                 for the current time period don't pull in data when they should instead be reading from cache.
5112                 */
5113                 for (iOutputBus = 0; iOutputBus < outputBusCount; iOutputBus += 1) {
5114                     ma_node_output_bus_set_has_read(&pNodeBase->pOutputBuses[iOutputBus], MA_FALSE); /* <-- This is what tells the next calls to this function for other output buses for this time period to read from cache instead of pulling in more data. */
5115                     ppFramesOut[iOutputBus] = ma_node_get_cached_output_ptr(pNode, iOutputBus);
5116                 }
5117 
5118                 /* We only need to read from input buses if there isn't already some data in the cache. */
5119                 if (pNodeBase->cachedFrameCountIn == 0) {
5120                     ma_uint32 maxFramesReadIn = 0;
5121 
5122                     /* Here is where we pull in data from the input buses. This is what will trigger an advance in time. */
5123                     for (iInputBus = 0; iInputBus < inputBusCount; iInputBus += 1) {
5124                         ma_uint32 framesRead;
5125 
5126                         /* The first thing to do is get the offset within our bulk allocation to store this input data. */
5127                         ppFramesIn[iInputBus] = ma_node_get_cached_input_ptr(pNode, iInputBus);
5128 
5129                         /* Once we've determined our destination pointer we can read. Note that we must inspect the number of frames read and fill any leftovers with silence for safety. */
5130                         result = ma_node_input_bus_read_pcm_frames(pNodeBase, &pNodeBase->pInputBuses[iInputBus], ppFramesIn[iInputBus], framesToProcessIn, &framesRead, globalTime);
5131                         if (result != MA_SUCCESS) {
5132                             /* It doesn't really matter if we fail because we'll just fill with silence. */
5133                             framesRead = 0; /* Just for safety, but I don't think it's really needed. */
5134                         }
5135 
5136                         /* TODO: Minor optimization opportunity here. If no frames were read and the buffer is already filled with silence, no need to re-silence it. */
5137                         /* Any leftover frames need to silenced for safety. */
5138                         if (framesRead < framesToProcessIn) {
5139                             ma_silence_pcm_frames(ppFramesIn[iInputBus] + (framesRead * ma_node_get_input_channels(pNodeBase, iInputBus)), (framesToProcessIn - framesRead), ma_format_f32, ma_node_get_input_channels(pNodeBase, iInputBus));
5140                         }
5141 
5142                         maxFramesReadIn = ma_max(maxFramesReadIn, framesRead);
5143                     }
5144 
5145                     /* This was a fresh load of input data so reset our consumption counter. */
5146                     pNodeBase->consumedFrameCountIn = 0;
5147 
5148                     /*
5149                     We don't want to keep processing if there's nothing to process, so set the number of cached
5150                     input frames to the maximum number we read from each attachment (the lesser will be padded
5151                     with silence). If we didn't read anything, this will be set to 0 and the entire buffer will
5152                     have been assigned to silence. This being equal to 0 is an important property for us because
5153                     it allows us to detect when NULL can be passed into the processing callback for the input
5154                     buffer for the purpose of continuous processing.
5155                     */
5156                     pNodeBase->cachedFrameCountIn = (ma_uint16)maxFramesReadIn;
5157                 } else {
5158                     /* We don't need to read anything, but we do need to prepare our input frame pointers. */
5159                     for (iInputBus = 0; iInputBus < inputBusCount; iInputBus += 1) {
5160                         ppFramesIn[iInputBus] = ma_node_get_cached_input_ptr(pNode, iInputBus) + (pNodeBase->consumedFrameCountIn * ma_node_get_input_channels(pNodeBase, iInputBus));
5161                     }
5162                 }
5163 
5164                 /*
5165                 At this point we have our input data so now we need to do some processing. Sneaky little
5166                 optimization here - we can set the pointer to the output buffer for this output bus so
5167                 that the final copy into the output buffer is done directly by onProcess().
5168                 */
5169                 if (pFramesOut != NULL) {
5170                     ppFramesOut[outputBusIndex] = pFramesOut;
5171                 }
5172 
5173 
5174                 /* Give the processing function the entire capacity of the output buffer. */
5175                 frameCountOut = framesToProcessOut;
5176 
5177                 /*
5178                 We need to treat nodes with continuous processing a little differently. For these ones,
5179                 we always want to fire the callback with the requested number of frames, regardless of
5180                 pNodeBase->cachedFrameCountIn, which could be 0. Also, we want to check if we can pass
5181                 in NULL for the input buffer to the callback.
5182                 */
5183                 if ((pNodeBase->vtable->flags & MA_NODE_FLAG_CONTINUOUS_PROCESSING) != 0) {
5184                     /* We're using continuous processing. Make sure we specify the whole frame count at all times. */
5185                     frameCountIn = framesToProcessIn;    /* Give the processing function as much input data as we've got in the buffer. */
5186 
5187                     if ((pNodeBase->vtable->flags & MA_NODE_FLAG_ALLOW_NULL_INPUT) != 0 && pNodeBase->consumedFrameCountIn == 0 && pNodeBase->cachedFrameCountIn == 0) {
5188                         consumeNullInput = MA_TRUE;
5189                     } else {
5190                         consumeNullInput = MA_FALSE;
5191                     }
5192                 } else {
5193                     frameCountIn = pNodeBase->cachedFrameCountIn;  /* Give the processing function as much valid input data as we've got. */
5194                     consumeNullInput = MA_FALSE;
5195                 }
5196 
5197                 /*
5198                 Process data slightly differently depending on whether or not we're consuming NULL
5199                 input (checked just above).
5200                 */
5201                 if (consumeNullInput) {
5202                     ma_node_process_pcm_frames_internal(pNode, NULL, &frameCountIn, ppFramesOut, &frameCountOut);
5203                 } else {
5204                     /*
5205                     We want to skip processing if there's no input data, but we can only do that safely if
5206                     we know that there is no chance of any output frames being produced. If continuous
5207                     processing is being used, this won't be a problem because the input frame count will
5208                     always be non-0. However, if continuous processing is *not* enabled and input and output
5209                     data is processed at different rates, we still need to process that last input frame
5210                     because there could be a few excess output frames needing to be produced from cached
5211                     data. The `MA_NODE_FLAG_DIFFERENT_PROCESSING_RATES` flag is used as the indicator for
5212                     determining whether or not we need to process the node even when there are no input
5213                     frames available right now.
5214                     */
5215                     if (frameCountIn > 0 || (pNodeBase->vtable->flags & MA_NODE_FLAG_DIFFERENT_PROCESSING_RATES) != 0) {
5216                         ma_node_process_pcm_frames_internal(pNode, (const float**)ppFramesIn, &frameCountIn, ppFramesOut, &frameCountOut);    /* From GCC: expected 'const float **' but argument is of type 'float **'. Shouldn't this be implicit? Excplicit cast to silence the warning. */
5217                     }
5218                 }
5219 
5220                 /*
5221                 Thanks to our sneaky optimization above we don't need to do any data copying directly into
5222                 the output buffer - the onProcess() callback just did that for us. We do, however, need to
5223                 apply the number of input and output frames that were processed. Note that due to continuous
5224                 processing above, we need to do explicit checks here. If we just consumed a NULL input
5225                 buffer it means that no actual input data was processed from the internal buffers and we
5226                 don't want to be modifying any counters.
5227                 */
5228                 if (consumeNullInput == MA_FALSE) {
5229                     pNodeBase->consumedFrameCountIn += (ma_uint16)frameCountIn;
5230                     pNodeBase->cachedFrameCountIn   -= (ma_uint16)frameCountIn;
5231                 }
5232 
5233                 /* The cached output frame count is always equal to what we just read. */
5234                 pNodeBase->cachedFrameCountOut = (ma_uint16)frameCountOut;
5235             } else {
5236                 /*
5237                 We're not needing to read anything from the input buffer so just read directly from our
5238                 already-processed data.
5239                 */
5240                 if (pFramesOut != NULL) {
5241                     ma_copy_pcm_frames(pFramesOut, ma_node_get_cached_output_ptr(pNodeBase, outputBusIndex), pNodeBase->cachedFrameCountOut, ma_format_f32, ma_node_get_output_channels(pNodeBase, outputBusIndex));
5242                 }
5243             }
5244 
5245             /* The number of frames read is always equal to the number of cached output frames. */
5246             totalFramesRead = pNodeBase->cachedFrameCountOut;
5247 
5248             /* Now that we've read the data, make sure our read flag is set. */
5249             ma_node_output_bus_set_has_read(&pNodeBase->pOutputBuses[outputBusIndex], MA_TRUE);
5250         }
5251     }
5252 
5253     /* Advance our local time forward. */
5254     c89atomic_fetch_add_64(&pNodeBase->localTime, (ma_uint64)totalFramesRead);
5255 
5256     *pFramesRead = totalFramesRead + timeOffsetBeg; /* Must include the silenced section at the start of the buffer. */
5257     return result;
5258 }
5259 
5260 
5261 
5262 
5263 /* Data source node. */
ma_data_source_node_config_init(ma_data_source * pDataSource,ma_bool32 looping)5264 MA_API ma_data_source_node_config ma_data_source_node_config_init(ma_data_source* pDataSource, ma_bool32 looping)
5265 {
5266     ma_data_source_node_config config;
5267 
5268     MA_ZERO_OBJECT(&config);
5269     config.nodeConfig  = ma_node_config_init();
5270     config.pDataSource = pDataSource;
5271     config.looping     = looping;
5272 
5273     return config;
5274 }
5275 
5276 
ma_data_source_node_process_pcm_frames(ma_node * pNode,const float ** ppFramesIn,ma_uint32 * pFrameCountIn,float ** ppFramesOut,ma_uint32 * pFrameCountOut)5277 static void ma_data_source_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut)
5278 {
5279     ma_data_source_node* pDataSourceNode = (ma_data_source_node*)pNode;
5280     ma_format format;
5281     ma_uint32 channels;
5282     ma_uint32 frameCount;
5283     ma_uint64 framesRead = 0;
5284 
5285     MA_ASSERT(pDataSourceNode != NULL);
5286     MA_ASSERT(pDataSourceNode->pDataSource != NULL);
5287     MA_ASSERT(ma_node_get_input_bus_count(pDataSourceNode)  == 0);
5288     MA_ASSERT(ma_node_get_output_bus_count(pDataSourceNode) == 1);
5289 
5290     /* We don't want to read from ppFramesIn at all. Instead we read from the data source. */
5291     (void)ppFramesIn;
5292     (void)pFrameCountIn;
5293 
5294     frameCount = *pFrameCountOut;
5295 
5296     if (ma_data_source_get_data_format(pDataSourceNode->pDataSource, &format, &channels, NULL) == MA_SUCCESS) { /* <-- Don't care about sample rate here. */
5297         /* The node graph system requires samples be in floating point format. This is checked in ma_data_source_node_init(). */
5298         MA_ASSERT(format == ma_format_f32);
5299         (void)format;   /* Just to silence some static analysis tools. */
5300 
5301         ma_data_source_read_pcm_frames(pDataSourceNode->pDataSource, ppFramesOut[0], frameCount, &framesRead, c89atomic_load_32(&pDataSourceNode->looping));
5302     }
5303 
5304     *pFrameCountOut = (ma_uint32)framesRead;
5305 }
5306 
5307 static ma_node_vtable g_ma_data_source_node_vtable =
5308 {
5309     ma_data_source_node_process_pcm_frames,
5310     NULL,   /* onGetRequiredInputFrameCount */
5311     0,      /* 0 input buses. */
5312     1,      /* 1 output bus. */
5313     0
5314 };
5315 
ma_data_source_node_init(ma_node_graph * pNodeGraph,const ma_data_source_node_config * pConfig,const ma_allocation_callbacks * pAllocationCallbacks,ma_data_source_node * pDataSourceNode)5316 MA_API ma_result ma_data_source_node_init(ma_node_graph* pNodeGraph, const ma_data_source_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source_node* pDataSourceNode)
5317 {
5318     ma_result result;
5319     ma_format format;   /* For validating the format, which must be ma_format_f32. */
5320     ma_uint32 channels; /* For specifying the channel count of the output bus. */
5321     ma_node_config baseConfig;
5322 
5323     if (pDataSourceNode == NULL) {
5324         return MA_INVALID_ARGS;
5325     }
5326 
5327     MA_ZERO_OBJECT(pDataSourceNode);
5328 
5329     if (pConfig == NULL) {
5330         return MA_INVALID_ARGS;
5331     }
5332 
5333     result = ma_data_source_get_data_format(pConfig->pDataSource, &format, &channels, NULL);    /* Don't care about sample rate. This will check pDataSource for NULL. */
5334     if (result != MA_SUCCESS) {
5335         return result;
5336     }
5337 
5338     MA_ASSERT(format == ma_format_f32); /* <-- If you've triggered this it means your data source is not outputting floating-point samples. You must configure your data source to use ma_format_f32. */
5339     if (format != ma_format_f32) {
5340         return MA_INVALID_ARGS; /* Invalid format. */
5341     }
5342 
5343     /* The channel count is defined by the data source. If the caller has manually changed the channels we just ignore it. */
5344     baseConfig = pConfig->nodeConfig;
5345     baseConfig.vtable = &g_ma_data_source_node_vtable;  /* Explicitly set the vtable here to prevent callers from setting it incorrectly. */
5346 
5347     /*
5348     The channel count is defined by the data source. It is invalid for the caller to manually set
5349     the channel counts in the config. `ma_data_source_node_config_init()` will have defaulted the
5350     channel count pointer to NULL which is how it must remain. If you trigger any of these asserts
5351     it means you're explicitly setting the channel count. Instead, configure the output channel
5352     count of your data source to be the necessary channel count.
5353     */
5354     if (baseConfig.pOutputChannels != NULL) {
5355         return MA_INVALID_ARGS;
5356     }
5357 
5358     baseConfig.pOutputChannels = &channels;
5359 
5360     result = ma_node_init(pNodeGraph, &baseConfig, pAllocationCallbacks, &pDataSourceNode->base);
5361     if (result != MA_SUCCESS) {
5362         return result;
5363     }
5364 
5365     pDataSourceNode->pDataSource = pConfig->pDataSource;
5366     pDataSourceNode->looping     = pConfig->looping;
5367 
5368     return MA_SUCCESS;
5369 }
5370 
ma_data_source_node_uninit(ma_data_source_node * pDataSourceNode,const ma_allocation_callbacks * pAllocationCallbacks)5371 MA_API void ma_data_source_node_uninit(ma_data_source_node* pDataSourceNode, const ma_allocation_callbacks* pAllocationCallbacks)
5372 {
5373     ma_node_uninit(&pDataSourceNode->base, pAllocationCallbacks);
5374 }
5375 
ma_data_source_node_set_looping(ma_data_source_node * pDataSourceNode,ma_bool32 looping)5376 MA_API ma_result ma_data_source_node_set_looping(ma_data_source_node* pDataSourceNode, ma_bool32 looping)
5377 {
5378     if (pDataSourceNode == NULL) {
5379         return MA_INVALID_ARGS;
5380     }
5381 
5382     c89atomic_exchange_32(&pDataSourceNode->looping, looping);
5383 
5384     return MA_SUCCESS;
5385 }
5386 
ma_data_source_node_is_looping(ma_data_source_node * pDataSourceNode)5387 MA_API ma_bool32 ma_data_source_node_is_looping(ma_data_source_node* pDataSourceNode)
5388 {
5389     if (pDataSourceNode == NULL) {
5390         return MA_FALSE;
5391     }
5392 
5393     return c89atomic_load_32(&pDataSourceNode->looping);
5394 }
5395 
5396 
5397 
5398 /* Splitter Node. */
ma_splitter_node_config_init(ma_uint32 channels)5399 MA_API ma_splitter_node_config ma_splitter_node_config_init(ma_uint32 channels)
5400 {
5401     ma_splitter_node_config config;
5402     ma_uint32 inputChannels[1];
5403     ma_uint32 outputChannels[2];
5404 
5405     /* Same channel count between inputs and outputs are required for splitters. */
5406     inputChannels[0]  = channels;
5407     outputChannels[0] = channels;
5408     outputChannels[1] = channels;
5409 
5410     MA_ZERO_OBJECT(&config);
5411     config.nodeConfig = ma_node_config_init();
5412     config.nodeConfig.pInputChannels  = inputChannels;
5413     config.nodeConfig.pOutputChannels = outputChannels;
5414 
5415     return config;
5416 }
5417 
5418 
ma_splitter_node_process_pcm_frames(ma_node * pNode,const float ** ppFramesIn,ma_uint32 * pFrameCountIn,float ** ppFramesOut,ma_uint32 * pFrameCountOut)5419 static void ma_splitter_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut)
5420 {
5421     ma_node_base* pNodeBase = (ma_node_base*)pNode;
5422     ma_uint32 iOutputBus;
5423     ma_uint32 channels;
5424 
5425     MA_ASSERT(pNodeBase != NULL);
5426     MA_ASSERT(ma_node_get_input_bus_count(pNodeBase)  == 1);
5427     MA_ASSERT(ma_node_get_output_bus_count(pNodeBase) >= 2);
5428 
5429     /* We don't need to consider the input frame count - it'll be the same as the output frame count and we process everything. */
5430     (void)pFrameCountIn;
5431 
5432     /* NOTE: This assumes the same number of channels for all inputs and outputs. This was checked in ma_splitter_node_init(). */
5433     channels = ma_node_get_input_channels(pNodeBase, 0);
5434 
5435     /* Splitting is just copying the first input bus and copying it over to each output bus. */
5436     for (iOutputBus = 0; iOutputBus < ma_node_get_output_bus_count(pNodeBase); iOutputBus += 1) {
5437         ma_copy_pcm_frames(ppFramesOut[iOutputBus], ppFramesIn[0], *pFrameCountOut, ma_format_f32, channels);
5438     }
5439 }
5440 
5441 static ma_node_vtable g_ma_splitter_node_vtable =
5442 {
5443     ma_splitter_node_process_pcm_frames,
5444     NULL,   /* onGetRequiredInputFrameCount */
5445     1,      /* 1 input bus. */
5446     2,      /* 2 output buses. */
5447     0
5448 };
5449 
ma_splitter_node_init(ma_node_graph * pNodeGraph,const ma_splitter_node_config * pConfig,const ma_allocation_callbacks * pAllocationCallbacks,ma_splitter_node * pSplitterNode)5450 MA_API ma_result ma_splitter_node_init(ma_node_graph* pNodeGraph, const ma_splitter_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_splitter_node* pSplitterNode)
5451 {
5452     ma_result result;
5453     ma_node_config baseConfig;
5454 
5455     if (pSplitterNode == NULL) {
5456         return MA_INVALID_ARGS;
5457     }
5458 
5459     MA_ZERO_OBJECT(pSplitterNode);
5460 
5461     if (pConfig == NULL) {
5462         return MA_INVALID_ARGS;
5463     }
5464 
5465     if (pConfig->nodeConfig.pInputChannels == NULL || pConfig->nodeConfig.pOutputChannels == NULL) {
5466         return MA_INVALID_ARGS; /* No channel counts specified. */
5467     }
5468 
5469     /* Splitters require the same number of channels between inputs and outputs. */
5470     if (pConfig->nodeConfig.pInputChannels[0] != pConfig->nodeConfig.pOutputChannels[0]) {
5471         return MA_INVALID_ARGS;
5472     }
5473 
5474     baseConfig = pConfig->nodeConfig;
5475     baseConfig.vtable = &g_ma_splitter_node_vtable;
5476 
5477     result = ma_node_init(pNodeGraph, &baseConfig, pAllocationCallbacks, &pSplitterNode->base);
5478     if (result != MA_SUCCESS) {
5479         return result;  /* Failed to initialize the base node. */
5480     }
5481 
5482     return MA_SUCCESS;
5483 }
5484 
ma_splitter_node_uninit(ma_splitter_node * pSplitterNode,const ma_allocation_callbacks * pAllocationCallbacks)5485 MA_API void ma_splitter_node_uninit(ma_splitter_node* pSplitterNode, const ma_allocation_callbacks* pAllocationCallbacks)
5486 {
5487     ma_node_uninit(pSplitterNode, pAllocationCallbacks);
5488 }
5489 
5490 
5491 
5492 
5493 
5494 #ifndef MA_RESOURCE_MANAGER_PAGE_SIZE_IN_MILLISECONDS
5495 #define MA_RESOURCE_MANAGER_PAGE_SIZE_IN_MILLISECONDS   1000
5496 #endif
5497 
5498 
ma_ffs_32(ma_uint32 x)5499 static ma_uint32 ma_ffs_32(ma_uint32 x)
5500 {
5501     ma_uint32 i;
5502 
5503     /* Just a naive implementation just to get things working for now. Will optimize this later. */
5504     for (i = 0; i < 32; i += 1) {
5505         if ((x & (1 << i)) != 0) {
5506             return i;
5507         }
5508     }
5509 
5510     return i;
5511 }
5512 
5513 
5514 
5515 #if 0
5516 static void ma_accumulate_and_clip_u8(ma_uint8* pDst, const ma_int16* pSrc, ma_uint64 count)
5517 {
5518     ma_uint64 iSample;
5519 
5520     MA_ASSERT(pDst != NULL);
5521     MA_ASSERT(pSrc != NULL);
5522 
5523     for (iSample = 0; iSample < count; iSample += 1) {
5524         pDst[iSample] = ma_clip_u8(ma_pcm_sample_u8_to_s16_no_scale(pDst[iSample]) + pSrc[iSample]);
5525     }
5526 }
5527 
5528 static void ma_accumulate_and_clip_s16(ma_int16* pDst, const ma_int32* pSrc, ma_uint64 count)
5529 {
5530     ma_uint64 iSample;
5531 
5532     MA_ASSERT(pDst != NULL);
5533     MA_ASSERT(pSrc != NULL);
5534 
5535     for (iSample = 0; iSample < count; iSample += 1) {
5536         pDst[iSample] = ma_clip_s16(pDst[iSample] + pSrc[iSample]);
5537     }
5538 }
5539 
5540 static void ma_accumulate_and_clip_s24(ma_uint8* pDst, const ma_int64* pSrc, ma_uint64 count)
5541 {
5542     ma_uint64 iSample;
5543 
5544     MA_ASSERT(pDst != NULL);
5545     MA_ASSERT(pSrc != NULL);
5546 
5547     for (iSample = 0; iSample < count; iSample += 1) {
5548         ma_int64 s = ma_clip_s24(ma_pcm_sample_s24_to_s32_no_scale(&pDst[iSample*3]) + pSrc[iSample]);
5549         pDst[iSample*3 + 0] = (ma_uint8)((s & 0x000000FF) >>  0);
5550         pDst[iSample*3 + 1] = (ma_uint8)((s & 0x0000FF00) >>  8);
5551         pDst[iSample*3 + 2] = (ma_uint8)((s & 0x00FF0000) >> 16);
5552     }
5553 }
5554 
5555 static void ma_accumulate_and_clip_s32(ma_int32* pDst, const ma_int64* pSrc, ma_uint64 count)
5556 {
5557     ma_uint64 iSample;
5558 
5559     MA_ASSERT(pDst != NULL);
5560     MA_ASSERT(pSrc != NULL);
5561 
5562     for (iSample = 0; iSample < count; iSample += 1) {
5563         pDst[iSample] = ma_clip_s32(pDst[iSample] + pSrc[iSample]);
5564     }
5565 }
5566 
5567 static void ma_accumulate_and_clip_f32(float* pDst, const float* pSrc, ma_uint64 count)
5568 {
5569     ma_uint64 iSample;
5570 
5571     MA_ASSERT(pDst != NULL);
5572     MA_ASSERT(pSrc != NULL);
5573 
5574     for (iSample = 0; iSample < count; iSample += 1) {
5575         pDst[iSample] = ma_clip_f32(pDst[iSample] + pSrc[iSample]);
5576     }
5577 }
5578 #endif
5579 
5580 
ma_clip_samples_u8(ma_uint8 * pDst,const ma_int16 * pSrc,ma_uint64 count)5581 static void ma_clip_samples_u8(ma_uint8* pDst, const ma_int16* pSrc, ma_uint64 count)
5582 {
5583     ma_uint64 iSample;
5584 
5585     MA_ASSERT(pDst != NULL);
5586     MA_ASSERT(pSrc != NULL);
5587 
5588     for (iSample = 0; iSample < count; iSample += 1) {
5589         pDst[iSample] = ma_clip_u8(pSrc[iSample]);
5590     }
5591 }
5592 
ma_clip_samples_s16(ma_int16 * pDst,const ma_int32 * pSrc,ma_uint64 count)5593 static void ma_clip_samples_s16(ma_int16* pDst, const ma_int32* pSrc, ma_uint64 count)
5594 {
5595     ma_uint64 iSample;
5596 
5597     MA_ASSERT(pDst != NULL);
5598     MA_ASSERT(pSrc != NULL);
5599 
5600     for (iSample = 0; iSample < count; iSample += 1) {
5601         pDst[iSample] = ma_clip_s16(pSrc[iSample]);
5602     }
5603 }
5604 
ma_clip_samples_s24(ma_uint8 * pDst,const ma_int64 * pSrc,ma_uint64 count)5605 static void ma_clip_samples_s24(ma_uint8* pDst, const ma_int64* pSrc, ma_uint64 count)
5606 {
5607     ma_uint64 iSample;
5608 
5609     MA_ASSERT(pDst != NULL);
5610     MA_ASSERT(pSrc != NULL);
5611 
5612     for (iSample = 0; iSample < count; iSample += 1) {
5613         ma_int64 s = ma_clip_s24(pSrc[iSample]);
5614         pDst[iSample*3 + 0] = (ma_uint8)((s & 0x000000FF) >>  0);
5615         pDst[iSample*3 + 1] = (ma_uint8)((s & 0x0000FF00) >>  8);
5616         pDst[iSample*3 + 2] = (ma_uint8)((s & 0x00FF0000) >> 16);
5617     }
5618 }
5619 
ma_clip_samples_s32(ma_int32 * pDst,const ma_int64 * pSrc,ma_uint64 count)5620 static void ma_clip_samples_s32(ma_int32* pDst, const ma_int64* pSrc, ma_uint64 count)
5621 {
5622     ma_uint64 iSample;
5623 
5624     MA_ASSERT(pDst != NULL);
5625     MA_ASSERT(pSrc != NULL);
5626 
5627     for (iSample = 0; iSample < count; iSample += 1) {
5628         pDst[iSample] = ma_clip_s32(pSrc[iSample]);
5629     }
5630 }
5631 
ma_clip_samples_f32_ex(float * pDst,const float * pSrc,ma_uint64 count)5632 static void ma_clip_samples_f32_ex(float* pDst, const float* pSrc, ma_uint64 count)
5633 {
5634     ma_uint64 iSample;
5635 
5636     MA_ASSERT(pDst != NULL);
5637     MA_ASSERT(pSrc != NULL);
5638 
5639     for (iSample = 0; iSample < count; iSample += 1) {
5640         pDst[iSample] = ma_clip_f32(pSrc[iSample]);
5641     }
5642 }
5643 
5644 
5645 
ma_volume_and_clip_samples_u8(ma_uint8 * pDst,const ma_int16 * pSrc,ma_uint64 count,float volume)5646 static void ma_volume_and_clip_samples_u8(ma_uint8* pDst, const ma_int16* pSrc, ma_uint64 count, float volume)
5647 {
5648     ma_uint64 iSample;
5649     ma_int16  volumeFixed;
5650 
5651     MA_ASSERT(pDst != NULL);
5652     MA_ASSERT(pSrc != NULL);
5653 
5654     volumeFixed = ma_float_to_fixed_16(volume);
5655 
5656     for (iSample = 0; iSample < count; iSample += 1) {
5657         pDst[iSample] = ma_clip_u8(ma_apply_volume_unclipped_u8(pSrc[iSample], volumeFixed));
5658     }
5659 }
5660 
ma_volume_and_clip_samples_s16(ma_int16 * pDst,const ma_int32 * pSrc,ma_uint64 count,float volume)5661 static void ma_volume_and_clip_samples_s16(ma_int16* pDst, const ma_int32* pSrc, ma_uint64 count, float volume)
5662 {
5663     ma_uint64 iSample;
5664     ma_int16  volumeFixed;
5665 
5666     MA_ASSERT(pDst != NULL);
5667     MA_ASSERT(pSrc != NULL);
5668 
5669     volumeFixed = ma_float_to_fixed_16(volume);
5670 
5671     for (iSample = 0; iSample < count; iSample += 1) {
5672         pDst[iSample] = ma_clip_s16(ma_apply_volume_unclipped_s16(pSrc[iSample], volumeFixed));
5673     }
5674 }
5675 
ma_volume_and_clip_samples_s24(ma_uint8 * pDst,const ma_int64 * pSrc,ma_uint64 count,float volume)5676 static void ma_volume_and_clip_samples_s24(ma_uint8* pDst, const ma_int64* pSrc, ma_uint64 count, float volume)
5677 {
5678     ma_uint64 iSample;
5679     ma_int16  volumeFixed;
5680 
5681     MA_ASSERT(pDst != NULL);
5682     MA_ASSERT(pSrc != NULL);
5683 
5684     volumeFixed = ma_float_to_fixed_16(volume);
5685 
5686     for (iSample = 0; iSample < count; iSample += 1) {
5687         ma_int64 s = ma_clip_s24(ma_apply_volume_unclipped_s24(pSrc[iSample], volumeFixed));
5688         pDst[iSample*3 + 0] = (ma_uint8)((s & 0x000000FF) >>  0);
5689         pDst[iSample*3 + 1] = (ma_uint8)((s & 0x0000FF00) >>  8);
5690         pDst[iSample*3 + 2] = (ma_uint8)((s & 0x00FF0000) >> 16);
5691     }
5692 }
5693 
ma_volume_and_clip_samples_s32(ma_int32 * pDst,const ma_int64 * pSrc,ma_uint64 count,float volume)5694 static void ma_volume_and_clip_samples_s32(ma_int32* pDst, const ma_int64* pSrc, ma_uint64 count, float volume)
5695 {
5696     ma_uint64 iSample;
5697     ma_int16  volumeFixed;
5698 
5699     MA_ASSERT(pDst != NULL);
5700     MA_ASSERT(pSrc != NULL);
5701 
5702     volumeFixed = ma_float_to_fixed_16(volume);
5703 
5704     for (iSample = 0; iSample < count; iSample += 1) {
5705         pDst[iSample] = ma_clip_s32(ma_apply_volume_unclipped_s32(pSrc[iSample], volumeFixed));
5706     }
5707 }
5708 
ma_volume_and_clip_samples_f32(float * pDst,const float * pSrc,ma_uint64 count,float volume)5709 static void ma_volume_and_clip_samples_f32(float* pDst, const float* pSrc, ma_uint64 count, float volume)
5710 {
5711     ma_uint64 iSample;
5712 
5713     MA_ASSERT(pDst != NULL);
5714     MA_ASSERT(pSrc != NULL);
5715 
5716     /* For the f32 case we need to make sure this supports in-place processing where the input and output buffers are the same. */
5717 
5718     for (iSample = 0; iSample < count; iSample += 1) {
5719         pDst[iSample] = ma_clip_f32(ma_apply_volume_unclipped_f32(pSrc[iSample], volume));
5720     }
5721 }
5722 
ma_clip_pcm_frames(void * pDst,const void * pSrc,ma_uint64 frameCount,ma_format format,ma_uint32 channels)5723 static void ma_clip_pcm_frames(void* pDst, const void* pSrc, ma_uint64 frameCount, ma_format format, ma_uint32 channels)
5724 {
5725     ma_uint64 sampleCount;
5726 
5727     MA_ASSERT(pDst != NULL);
5728     MA_ASSERT(pSrc != NULL);
5729 
5730     sampleCount = frameCount * channels;
5731 
5732     switch (format) {
5733         case ma_format_u8:  ma_clip_samples_u8( (ma_uint8*)pDst, (const ma_int16*)pSrc, sampleCount); break;
5734         case ma_format_s16: ma_clip_samples_s16((ma_int16*)pDst, (const ma_int32*)pSrc, sampleCount); break;
5735         case ma_format_s24: ma_clip_samples_s24((ma_uint8*)pDst, (const ma_int64*)pSrc, sampleCount); break;
5736         case ma_format_s32: ma_clip_samples_s32((ma_int32*)pDst, (const ma_int64*)pSrc, sampleCount); break;
5737         case ma_format_f32: ma_clip_samples_f32_ex((float*)pDst, (const    float*)pSrc, sampleCount); break;
5738 
5739         /* 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. */
5740         case ma_format_unknown:
5741         case ma_format_count:
5742             break;
5743     }
5744 }
5745 
ma_volume_and_clip_pcm_frames(void * pDst,const void * pSrc,ma_uint64 frameCount,ma_format format,ma_uint32 channels,float volume)5746 static void ma_volume_and_clip_pcm_frames(void* pDst, const void* pSrc, ma_uint64 frameCount, ma_format format, ma_uint32 channels, float volume)
5747 {
5748     MA_ASSERT(pDst != NULL);
5749     MA_ASSERT(pSrc != NULL);
5750 
5751     if (volume == 1) {
5752         ma_clip_pcm_frames(pDst, pSrc, frameCount, format, channels);   /* Optimized case for volume = 1. */
5753     } else if (volume == 0) {
5754         ma_silence_pcm_frames(pDst, frameCount, format, channels);      /* Optimized case for volume = 0. */
5755     } else {
5756         ma_uint64 sampleCount = frameCount * channels;
5757 
5758         switch (format) {
5759             case ma_format_u8:  ma_volume_and_clip_samples_u8( (ma_uint8*)pDst, (const ma_int16*)pSrc, sampleCount, volume); break;
5760             case ma_format_s16: ma_volume_and_clip_samples_s16((ma_int16*)pDst, (const ma_int32*)pSrc, sampleCount, volume); break;
5761             case ma_format_s24: ma_volume_and_clip_samples_s24((ma_uint8*)pDst, (const ma_int64*)pSrc, sampleCount, volume); break;
5762             case ma_format_s32: ma_volume_and_clip_samples_s32((ma_int32*)pDst, (const ma_int64*)pSrc, sampleCount, volume); break;
5763             case ma_format_f32: ma_volume_and_clip_samples_f32((   float*)pDst, (const    float*)pSrc, sampleCount, volume); break;
5764 
5765             /* 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. */
5766             case ma_format_unknown:
5767             case ma_format_count:
5768                 break;
5769         }
5770     }
5771 }
5772 
5773 
5774 /* Not used at the moment, but leaving it here in case I want to use it again later. */
5775 #if 0
5776 static void ma_clipped_accumulate_u8(ma_uint8* pDst, const ma_uint8* pSrc, ma_uint64 sampleCount)
5777 {
5778     ma_uint64 iSample;
5779 
5780     MA_ASSERT(pDst != NULL);
5781     MA_ASSERT(pSrc != NULL);
5782 
5783     for (iSample = 0; iSample < sampleCount; iSample += 1) {
5784         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]));
5785     }
5786 }
5787 
5788 static void ma_clipped_accumulate_s16(ma_int16* pDst, const ma_int16* pSrc, ma_uint64 sampleCount)
5789 {
5790     ma_uint64 iSample;
5791 
5792     MA_ASSERT(pDst != NULL);
5793     MA_ASSERT(pSrc != NULL);
5794 
5795     for (iSample = 0; iSample < sampleCount; iSample += 1) {
5796         pDst[iSample] = ma_clip_s16((ma_int32)pDst[iSample] + (ma_int32)pSrc[iSample]);
5797     }
5798 }
5799 
5800 static void ma_clipped_accumulate_s24(ma_uint8* pDst, const ma_uint8* pSrc, ma_uint64 sampleCount)
5801 {
5802     ma_uint64 iSample;
5803 
5804     MA_ASSERT(pDst != NULL);
5805     MA_ASSERT(pSrc != NULL);
5806 
5807     for (iSample = 0; iSample < sampleCount; iSample += 1) {
5808         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]));
5809         pDst[iSample*3 + 0] = (ma_uint8)((s & 0x000000FF) >>  0);
5810         pDst[iSample*3 + 1] = (ma_uint8)((s & 0x0000FF00) >>  8);
5811         pDst[iSample*3 + 2] = (ma_uint8)((s & 0x00FF0000) >> 16);
5812     }
5813 }
5814 
5815 static void ma_clipped_accumulate_s32(ma_int32* pDst, const ma_int32* pSrc, ma_uint64 sampleCount)
5816 {
5817     ma_uint64 iSample;
5818 
5819     MA_ASSERT(pDst != NULL);
5820     MA_ASSERT(pSrc != NULL);
5821 
5822     for (iSample = 0; iSample < sampleCount; iSample += 1) {
5823         pDst[iSample] = ma_clip_s32((ma_int64)pDst[iSample] + (ma_int64)pSrc[iSample]);
5824     }
5825 }
5826 
5827 static void ma_clipped_accumulate_f32(float* pDst, const float* pSrc, ma_uint64 sampleCount)
5828 {
5829     ma_uint64 iSample;
5830 
5831     MA_ASSERT(pDst != NULL);
5832     MA_ASSERT(pSrc != NULL);
5833 
5834     for (iSample = 0; iSample < sampleCount; iSample += 1) {
5835         pDst[iSample] = ma_clip_f32(pDst[iSample] + pSrc[iSample]);
5836     }
5837 }
5838 
5839 static void ma_clipped_accumulate_pcm_frames(void* pDst, const void* pSrc, ma_uint64 frameCount, ma_format format, ma_uint32 channels)
5840 {
5841     ma_uint64 sampleCount;
5842 
5843     MA_ASSERT(pDst != NULL);
5844     MA_ASSERT(pSrc != NULL);
5845 
5846     sampleCount = frameCount * channels;
5847 
5848     switch (format) {
5849         case ma_format_u8:  ma_clipped_accumulate_u8( (ma_uint8*)pDst, (const ma_uint8*)pSrc, sampleCount); break;
5850         case ma_format_s16: ma_clipped_accumulate_s16((ma_int16*)pDst, (const ma_int16*)pSrc, sampleCount); break;
5851         case ma_format_s24: ma_clipped_accumulate_s24((ma_uint8*)pDst, (const ma_uint8*)pSrc, sampleCount); break;
5852         case ma_format_s32: ma_clipped_accumulate_s32((ma_int32*)pDst, (const ma_int32*)pSrc, sampleCount); break;
5853         case ma_format_f32: ma_clipped_accumulate_f32((   float*)pDst, (const    float*)pSrc, sampleCount); break;
5854 
5855         /* 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. */
5856         case ma_format_unknown:
5857         case ma_format_count:
5858             break;
5859     }
5860 }
5861 #endif
5862 
5863 
5864 /* Not used right now, but leaving here for reference. */
5865 #if 0
5866 static void ma_unclipped_accumulate_u8(ma_int16* pDst, const ma_uint8* pSrc, ma_uint64 sampleCount)
5867 {
5868     ma_uint64 iSample;
5869 
5870     MA_ASSERT(pDst != NULL);
5871     MA_ASSERT(pSrc != NULL);
5872 
5873     for (iSample = 0; iSample < sampleCount; iSample += 1) {
5874         pDst[iSample] = pDst[iSample] + ma_pcm_sample_u8_to_s16_no_scale(pSrc[iSample]);
5875     }
5876 }
5877 
5878 static void ma_unclipped_accumulate_s16(ma_int32* pDst, const ma_int16* pSrc, ma_uint64 sampleCount)
5879 {
5880     ma_uint64 iSample;
5881 
5882     MA_ASSERT(pDst != NULL);
5883     MA_ASSERT(pSrc != NULL);
5884 
5885     for (iSample = 0; iSample < sampleCount; iSample += 1) {
5886         pDst[iSample] = (ma_int32)pDst[iSample] + (ma_int32)pSrc[iSample];
5887     }
5888 }
5889 
5890 static void ma_unclipped_accumulate_s24(ma_int64* pDst, const ma_uint8* pSrc, ma_uint64 sampleCount)
5891 {
5892     ma_uint64 iSample;
5893 
5894     MA_ASSERT(pDst != NULL);
5895     MA_ASSERT(pSrc != NULL);
5896 
5897     for (iSample = 0; iSample < sampleCount; iSample += 1) {
5898         pDst[iSample] = pDst[iSample] + ma_pcm_sample_s24_to_s32_no_scale(&pSrc[iSample*3]);
5899     }
5900 }
5901 
5902 static void ma_unclipped_accumulate_s32(ma_int64* pDst, const ma_int32* pSrc, ma_uint64 sampleCount)
5903 {
5904     ma_uint64 iSample;
5905 
5906     MA_ASSERT(pDst != NULL);
5907     MA_ASSERT(pSrc != NULL);
5908 
5909     for (iSample = 0; iSample < sampleCount; iSample += 1) {
5910         pDst[iSample] = (ma_int64)pDst[iSample] + (ma_int64)pSrc[iSample];
5911     }
5912 }
5913 
5914 static void ma_unclipped_accumulate_f32(float* pDst, const float* pSrc, ma_uint64 sampleCount)
5915 {
5916     ma_uint64 iSample;
5917 
5918     MA_ASSERT(pDst != NULL);
5919     MA_ASSERT(pSrc != NULL);
5920 
5921     for (iSample = 0; iSample < sampleCount; iSample += 1) {
5922         pDst[iSample] = pDst[iSample] + pSrc[iSample];
5923     }
5924 }
5925 
5926 static void ma_unclipped_accumulate_pcm_frames(void* pDst, const void* pSrc, ma_uint64 frameCount, ma_format format, ma_uint32 channels)
5927 {
5928     ma_uint64 sampleCount;
5929 
5930     MA_ASSERT(pDst != NULL);
5931     MA_ASSERT(pSrc != NULL);
5932 
5933     sampleCount = frameCount * channels;
5934 
5935     switch (format) {
5936         case ma_format_u8:  ma_unclipped_accumulate_u8( (ma_int16*)pDst, (const ma_uint8*)pSrc, sampleCount); break;
5937         case ma_format_s16: ma_unclipped_accumulate_s16((ma_int32*)pDst, (const ma_int16*)pSrc, sampleCount); break;
5938         case ma_format_s24: ma_unclipped_accumulate_s24((ma_int64*)pDst, (const ma_uint8*)pSrc, sampleCount); break;
5939         case ma_format_s32: ma_unclipped_accumulate_s32((ma_int64*)pDst, (const ma_int32*)pSrc, sampleCount); break;
5940         case ma_format_f32: ma_unclipped_accumulate_f32((   float*)pDst, (const    float*)pSrc, sampleCount); break;
5941 
5942         /* 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. */
5943         case ma_format_unknown:
5944         case ma_format_count:
5945             break;
5946     }
5947 }
5948 #endif
5949 
5950 
5951 /* Not used right now, but leaving here for reference. */
5952 #if 0
5953 static void ma_volume_and_accumulate_and_clip_u8(ma_uint8* pDst, const ma_int16* pSrc, ma_uint64 count, float volume)
5954 {
5955     ma_uint64 iSample;
5956     ma_int16  volumeFixed;
5957 
5958     MA_ASSERT(pDst != NULL);
5959     MA_ASSERT(pSrc != NULL);
5960 
5961     volumeFixed = ma_float_to_fixed_16(volume);
5962 
5963     for (iSample = 0; iSample < count; iSample += 1) {
5964         pDst[iSample] = ma_clip_u8(ma_pcm_sample_u8_to_s16_no_scale(pDst[iSample]) + ma_apply_volume_unclipped_u8(pSrc[iSample], volumeFixed));
5965     }
5966 }
5967 
5968 static void ma_volume_and_accumulate_and_clip_s16(ma_int16* pDst, const ma_int32* pSrc, ma_uint64 count, float volume)
5969 {
5970     ma_uint64 iSample;
5971     ma_int16  volumeFixed;
5972 
5973     MA_ASSERT(pDst != NULL);
5974     MA_ASSERT(pSrc != NULL);
5975 
5976     volumeFixed = ma_float_to_fixed_16(volume);
5977 
5978     for (iSample = 0; iSample < count; iSample += 1) {
5979         pDst[iSample] = ma_clip_s16(pDst[iSample] + ma_apply_volume_unclipped_s16(pSrc[iSample], volumeFixed));
5980     }
5981 }
5982 
5983 static void ma_volume_and_accumulate_and_clip_s24(ma_uint8* pDst, const ma_int64* pSrc, ma_uint64 count, float volume)
5984 {
5985     ma_uint64 iSample;
5986     ma_int16  volumeFixed;
5987 
5988     MA_ASSERT(pDst != NULL);
5989     MA_ASSERT(pSrc != NULL);
5990 
5991     volumeFixed = ma_float_to_fixed_16(volume);
5992 
5993     for (iSample = 0; iSample < count; iSample += 1) {
5994         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));
5995         pDst[iSample*3 + 0] = (ma_uint8)((s & 0x000000FF) >>  0);
5996         pDst[iSample*3 + 1] = (ma_uint8)((s & 0x0000FF00) >>  8);
5997         pDst[iSample*3 + 2] = (ma_uint8)((s & 0x00FF0000) >> 16);
5998     }
5999 }
6000 
6001 static void ma_volume_and_accumulate_and_clip_s32(ma_int32* dst, const ma_int64* src, ma_uint64 count, float volume)
6002 {
6003     ma_uint64 iSample;
6004     ma_int16  volumeFixed;
6005 
6006     MA_ASSERT(dst != NULL);
6007     MA_ASSERT(src != NULL);
6008 
6009     volumeFixed = ma_float_to_fixed_16(volume);
6010 
6011     for (iSample = 0; iSample < count; iSample += 1) {
6012         dst[iSample] = ma_clip_s32(dst[iSample] + ma_apply_volume_unclipped_s32(src[iSample], volumeFixed));
6013     }
6014 }
6015 
6016 static void ma_volume_and_accumulate_and_clip_f32(float* pDst, const float* pSrc, ma_uint64 count, float volume)
6017 {
6018     ma_uint64 iSample;
6019 
6020     MA_ASSERT(pDst != NULL);
6021     MA_ASSERT(pSrc != NULL);
6022 
6023     for (iSample = 0; iSample < count; iSample += 1) {
6024         pDst[iSample] = ma_clip_f32(pDst[iSample] + ma_apply_volume_unclipped_f32(pSrc[iSample], volume));
6025     }
6026 }
6027 
6028 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)
6029 {
6030     ma_uint64 sampleCount;
6031 
6032     if (pDst == NULL || pSrc == NULL) {
6033         return MA_INVALID_ARGS;
6034     }
6035 
6036     /* The output buffer cannot be the same as the accumulation buffer. */
6037     if (pDst == pSrc) {
6038         return MA_INVALID_OPERATION;
6039     }
6040 
6041     /* No-op if there's no volume. */
6042     if (volume == 0) {
6043         return MA_SUCCESS;
6044     }
6045 
6046     sampleCount = frameCount * channels;
6047 
6048     /* No need for volume control if the volume is 1. */
6049     if (volume == 1) {
6050         switch (format) {
6051             case ma_format_u8:  ma_accumulate_and_clip_u8( pDst, pSrc, sampleCount); break;
6052             case ma_format_s16: ma_accumulate_and_clip_s16(pDst, pSrc, sampleCount); break;
6053             case ma_format_s24: ma_accumulate_and_clip_s24(pDst, pSrc, sampleCount); break;
6054             case ma_format_s32: ma_accumulate_and_clip_s32(pDst, pSrc, sampleCount); break;
6055             case ma_format_f32: ma_accumulate_and_clip_f32(pDst, pSrc, sampleCount); break;
6056             default: return MA_INVALID_ARGS;    /* Unknown format. */
6057         }
6058     } else {
6059         /* Getting here means the volume is not 0 nor 1. */
6060         MA_ASSERT(volume != 0 && volume != 1);
6061 
6062         switch (format) {
6063             case ma_format_u8:  ma_volume_and_accumulate_and_clip_u8( pDst, pSrc, sampleCount, volume); break;
6064             case ma_format_s16: ma_volume_and_accumulate_and_clip_s16(pDst, pSrc, sampleCount, volume); break;
6065             case ma_format_s24: ma_volume_and_accumulate_and_clip_s24(pDst, pSrc, sampleCount, volume); break;
6066             case ma_format_s32: ma_volume_and_accumulate_and_clip_s32(pDst, pSrc, sampleCount, volume); break;
6067             case ma_format_f32: ma_volume_and_accumulate_and_clip_f32(pDst, pSrc, sampleCount, volume); break;
6068             default: return MA_INVALID_ARGS;        /* Unknown format. */
6069         }
6070     }
6071 
6072     return MA_SUCCESS;
6073 }
6074 #endif
6075 
6076 
6077 /* Not used right now, but leaving here for reference. */
6078 #if 0
6079 static void ma_mix_accumulation_buffers_u8(ma_int16* pDst, const ma_int16* pSrc, ma_uint64 sampleCount, float volume)
6080 {
6081     ma_uint64 iSample;
6082     ma_int16  volumeFixed;
6083 
6084     MA_ASSERT(pDst != NULL);
6085     MA_ASSERT(pSrc != NULL);
6086 
6087     volumeFixed = ma_float_to_fixed_16(volume);
6088 
6089     for (iSample = 0; iSample < sampleCount; iSample += 1) {
6090         pDst[iSample] += ma_apply_volume_unclipped_u8(pSrc[iSample], volumeFixed);
6091     }
6092 }
6093 
6094 static void ma_mix_accumulation_buffers_s16(ma_int32* pDst, const ma_int32* pSrc, ma_uint64 sampleCount, float volume)
6095 {
6096     ma_uint64 iSample;
6097     ma_int16  volumeFixed;
6098 
6099     MA_ASSERT(pDst != NULL);
6100     MA_ASSERT(pSrc != NULL);
6101 
6102     volumeFixed = ma_float_to_fixed_16(volume);
6103 
6104     for (iSample = 0; iSample < sampleCount; iSample += 1) {
6105         pDst[iSample] += ma_apply_volume_unclipped_s16(pSrc[iSample], volumeFixed);
6106     }
6107 }
6108 
6109 static void ma_mix_accumulation_buffers_s24(ma_int64* pDst, const ma_int64* pSrc, ma_uint64 sampleCount, float volume)
6110 {
6111     ma_uint64 iSample;
6112     ma_int16  volumeFixed;
6113 
6114     MA_ASSERT(pDst != NULL);
6115     MA_ASSERT(pSrc != NULL);
6116 
6117     volumeFixed = ma_float_to_fixed_16(volume);
6118 
6119     for (iSample = 0; iSample < sampleCount; iSample += 1) {
6120         pDst[iSample] += ma_apply_volume_unclipped_s24(pSrc[iSample], volumeFixed);
6121     }
6122 }
6123 
6124 static void ma_mix_accumulation_buffers_s32(ma_int64* pDst, const ma_int64* pSrc, ma_uint64 sampleCount, float volume)
6125 {
6126     ma_uint64 iSample;
6127     ma_int16  volumeFixed;
6128 
6129     MA_ASSERT(pDst != NULL);
6130     MA_ASSERT(pSrc != NULL);
6131 
6132     volumeFixed = ma_float_to_fixed_16(volume);
6133 
6134     for (iSample = 0; iSample < sampleCount; iSample += 1) {
6135         pDst[iSample] += ma_apply_volume_unclipped_s32(pSrc[iSample], volumeFixed);
6136     }
6137 }
6138 
6139 static void ma_mix_accumulation_buffers_f32(float* pDst, const float* pSrc, ma_uint64 sampleCount, float volume)
6140 {
6141     ma_uint64 iSample;
6142 
6143     MA_ASSERT(pDst != NULL);
6144     MA_ASSERT(pSrc != NULL);
6145 
6146     for (iSample = 0; iSample < sampleCount; iSample += 1) {
6147         pDst[iSample] += ma_apply_volume_unclipped_f32(pSrc[iSample], volume);
6148     }
6149 }
6150 
6151 static void ma_mix_accumulation_buffers(void* pDst, const void* pSrc, ma_uint64 frameCount, ma_format formatIn, ma_uint32 channelsIn, float volume)
6152 {
6153     ma_uint64 sampleCount;
6154 
6155     MA_ASSERT(pDst != NULL);
6156     MA_ASSERT(pSrc != NULL);
6157 
6158     sampleCount = frameCount * channelsIn;
6159 
6160     switch (formatIn)
6161     {
6162         case ma_format_u8:  ma_mix_accumulation_buffers_u8( (ma_int16*)pDst, (const ma_int16*)pSrc, sampleCount, volume); break;
6163         case ma_format_s16: ma_mix_accumulation_buffers_s16((ma_int32*)pDst, (const ma_int32*)pSrc, sampleCount, volume); break;
6164         case ma_format_s24: ma_mix_accumulation_buffers_s24((ma_int64*)pDst, (const ma_int64*)pSrc, sampleCount, volume); break;
6165         case ma_format_s32: ma_mix_accumulation_buffers_s32((ma_int64*)pDst, (const ma_int64*)pSrc, sampleCount, volume); break;
6166         case ma_format_f32: ma_mix_accumulation_buffers_f32((   float*)pDst, (const    float*)pSrc, sampleCount, volume); break;
6167         default: break;
6168     }
6169 }
6170 
6171 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)
6172 {
6173     if (formatOut == formatIn && channelsOut == channelsIn) {
6174         /* Fast path. No conversion required. */
6175         ma_mix_accumulation_buffers(pDst, pSrc, frameCount, formatIn, channelsIn, volume);
6176     } else {
6177         /* 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. */
6178         ma_uint8  clippedSrcBuffer[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; /* formatIn, channelsIn */
6179         ma_uint32 clippedSrcBufferCapInFrames = sizeof(clippedSrcBuffer) / ma_get_bytes_per_frame(formatIn, channelsIn);
6180         ma_uint64 totalFramesProcessed = 0;
6181         /* */ void* pRunningDst = pDst;
6182         const void* pRunningSrc = pSrc;
6183 
6184         while (totalFramesProcessed < frameCount) {
6185             ma_uint64 framesToProcess = frameCount - totalFramesProcessed;
6186             if (framesToProcess > clippedSrcBufferCapInFrames) {
6187                 framesToProcess = clippedSrcBufferCapInFrames;
6188             }
6189 
6190             /* Volume and clip. */
6191             ma_volume_and_clip_pcm_frames(clippedSrcBuffer, pRunningSrc, framesToProcess, formatIn, channelsIn, volume);
6192 
6193             /* Mix. */
6194             ma_mix_pcm_frames_ex(pRunningDst, formatOut, channelsOut, clippedSrcBuffer, formatIn, channelsIn, framesToProcess, 1);
6195 
6196             totalFramesProcessed += framesToProcess;
6197             pRunningDst = ma_offset_ptr(pRunningDst, framesToProcess * ma_get_accumulation_bytes_per_frame(formatOut, channelsOut));
6198             pRunningSrc = ma_offset_ptr(pRunningSrc, framesToProcess * ma_get_accumulation_bytes_per_frame(formatIn,  channelsIn ));
6199         }
6200     }
6201 }
6202 #endif
6203 
6204 
6205 
6206 
ma_slot_allocator_init(ma_slot_allocator * pAllocator)6207 MA_API ma_result ma_slot_allocator_init(ma_slot_allocator* pAllocator)
6208 {
6209     if (pAllocator == NULL) {
6210         return MA_INVALID_ARGS;
6211     }
6212 
6213     MA_ZERO_OBJECT(pAllocator);
6214 
6215     return MA_SUCCESS;
6216 }
6217 
ma_slot_allocator_alloc(ma_slot_allocator * pAllocator,ma_uint64 * pSlot)6218 MA_API ma_result ma_slot_allocator_alloc(ma_slot_allocator* pAllocator, ma_uint64* pSlot)
6219 {
6220     ma_uint32 capacity;
6221     ma_uint32 iAttempt;
6222     const ma_uint32 maxAttempts = 2;    /* The number of iterations to perform until returning MA_OUT_OF_MEMORY if no slots can be found. */
6223 
6224     if (pAllocator == NULL || pSlot == NULL) {
6225         return MA_INVALID_ARGS;
6226     }
6227 
6228     capacity = ma_countof(pAllocator->groups) * 32;
6229 
6230     for (iAttempt = 0; iAttempt < maxAttempts; iAttempt += 1) {
6231         /* We need to acquire a suitable bitfield first. This is a bitfield that's got an available slot within it. */
6232         ma_uint32 iGroup;
6233         for (iGroup = 0; iGroup < ma_countof(pAllocator->groups); iGroup += 1) {
6234             /* CAS */
6235             for (;;) {
6236                 ma_uint32 oldBitfield;
6237                 ma_uint32 newBitfield;
6238                 ma_uint32 bitOffset;
6239 
6240                 oldBitfield = c89atomic_load_32(&pAllocator->groups[iGroup].bitfield);  /* <-- This copy must happen. The compiler must not optimize this away. */
6241 
6242                 /* Fast check to see if anything is available. */
6243                 if (oldBitfield == 0xFFFFFFFF) {
6244                     break;  /* No available bits in this bitfield. */
6245                 }
6246 
6247                 bitOffset = ma_ffs_32(~oldBitfield);
6248                 MA_ASSERT(bitOffset < 32);
6249 
6250                 newBitfield = oldBitfield | (1 << bitOffset);
6251 
6252                 if (c89atomic_compare_and_swap_32(&pAllocator->groups[iGroup].bitfield, oldBitfield, newBitfield) == oldBitfield) {
6253                     ma_uint32 slotIndex;
6254 
6255                     /* Increment the counter as soon as possible to have other threads report out-of-memory sooner than later. */
6256                     c89atomic_fetch_add_32(&pAllocator->count, 1);
6257 
6258                     /* The slot index is required for constructing the output value. */
6259                     slotIndex = (iGroup << 5) + bitOffset;  /* iGroup << 5 = iGroup * 32 */
6260 
6261                     /* Increment the reference count before constructing the output value. */
6262                     pAllocator->slots[slotIndex] += 1;
6263 
6264                     /* Construct the output value. */
6265                     *pSlot = ((ma_uint64)pAllocator->slots[slotIndex] << 32 | slotIndex);
6266 
6267                     return MA_SUCCESS;
6268                 }
6269             }
6270         }
6271 
6272         /* 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. */
6273         if (pAllocator->count < capacity) {
6274             ma_yield();
6275         } else {
6276             return MA_OUT_OF_MEMORY;
6277         }
6278     }
6279 
6280     /* We couldn't find a slot within the maximum number of attempts. */
6281     return MA_OUT_OF_MEMORY;
6282 }
6283 
ma_slot_allocator_free(ma_slot_allocator * pAllocator,ma_uint64 slot)6284 MA_API ma_result ma_slot_allocator_free(ma_slot_allocator* pAllocator, ma_uint64 slot)
6285 {
6286     ma_uint32 iGroup;
6287     ma_uint32 iBit;
6288 
6289     if (pAllocator == NULL) {
6290         return MA_INVALID_ARGS;
6291     }
6292 
6293     iGroup = (slot & 0xFFFFFFFF) >> 5;   /* slot / 32 */
6294     iBit   = (slot & 0xFFFFFFFF) & 31;   /* slot % 32 */
6295 
6296     if (iGroup >= ma_countof(pAllocator->groups)) {
6297         return MA_INVALID_ARGS;
6298     }
6299 
6300     MA_ASSERT(iBit < 32);   /* This must be true due to the logic we used to actually calculate it. */
6301 
6302     while (pAllocator->count > 0) {
6303         /* CAS */
6304         ma_uint32 oldBitfield;
6305         ma_uint32 newBitfield;
6306 
6307         oldBitfield = c89atomic_load_32(&pAllocator->groups[iGroup].bitfield);  /* <-- This copy must happen. The compiler must not optimize this away. */
6308         newBitfield = oldBitfield & ~(1 << iBit);
6309 
6310         if (c89atomic_compare_and_swap_32(&pAllocator->groups[iGroup].bitfield, oldBitfield, newBitfield) == oldBitfield) {
6311             c89atomic_fetch_sub_32(&pAllocator->count, 1);
6312             return MA_SUCCESS;
6313         }
6314     }
6315 
6316     /* Getting here means there are no allocations available for freeing. */
6317     return MA_INVALID_OPERATION;
6318 }
6319 
6320 
6321 
6322 #define MA_FENCE_COUNTER_MAX    0x7FFFFFFF
6323 
ma_fence_init(ma_fence * pFence)6324 MA_API ma_result ma_fence_init(ma_fence* pFence)
6325 {
6326     ma_result result;
6327 
6328     if (pFence == NULL) {
6329         return MA_INVALID_ARGS;
6330     }
6331 
6332     MA_ZERO_OBJECT(pFence);
6333     pFence->counter = 0;
6334 
6335     result = ma_event_init(&pFence->e);
6336     if (result != MA_SUCCESS) {
6337         return result;
6338     }
6339 
6340     return MA_SUCCESS;
6341 }
6342 
ma_fence_uninit(ma_fence * pFence)6343 MA_API void ma_fence_uninit(ma_fence* pFence)
6344 {
6345     if (pFence == NULL) {
6346         return;
6347     }
6348 
6349     ma_event_uninit(&pFence->e);
6350 
6351     MA_ZERO_OBJECT(pFence);
6352 }
6353 
ma_fence_acquire(ma_fence * pFence)6354 MA_API ma_result ma_fence_acquire(ma_fence* pFence)
6355 {
6356     if (pFence == NULL) {
6357         return MA_INVALID_ARGS;
6358     }
6359 
6360     for (;;) {
6361         ma_uint32 oldCounter = c89atomic_load_32(&pFence->counter);
6362         ma_uint32 newCounter = oldCounter + 1;
6363 
6364         /* Make sure we're not about to exceed our maximum value. */
6365         if (newCounter > MA_FENCE_COUNTER_MAX) {
6366             MA_ASSERT(MA_FALSE);
6367             return MA_OUT_OF_RANGE;
6368         }
6369 
6370         if (c89atomic_compare_exchange_weak_32(&pFence->counter, &oldCounter, newCounter)) {
6371             return MA_SUCCESS;
6372         } else {
6373             if (oldCounter == MA_FENCE_COUNTER_MAX) {
6374                 MA_ASSERT(MA_FALSE);
6375                 return MA_OUT_OF_RANGE; /* The other thread took the last available slot. Abort. */
6376             }
6377         }
6378     }
6379 
6380     /* Should never get here. */
6381     /*return MA_SUCCESS;*/
6382 }
6383 
ma_fence_release(ma_fence * pFence)6384 MA_API ma_result ma_fence_release(ma_fence* pFence)
6385 {
6386     if (pFence == NULL) {
6387         return MA_INVALID_ARGS;
6388     }
6389 
6390     for (;;) {
6391         ma_uint32 oldCounter = c89atomic_load_32(&pFence->counter);
6392         ma_uint32 newCounter = oldCounter - 1;
6393 
6394         if (oldCounter == 0) {
6395             MA_ASSERT(MA_FALSE);
6396             return MA_INVALID_OPERATION;    /* Acquire/release mismatch. */
6397         }
6398 
6399         if (c89atomic_compare_exchange_weak_32(&pFence->counter, &oldCounter, newCounter)) {
6400             if (newCounter == 0) {
6401                 ma_event_signal(&pFence->e);    /* <-- ma_fence_wait() will be waiting on this. */
6402             }
6403 
6404             return MA_SUCCESS;
6405         } else {
6406             if (oldCounter == 0) {
6407                 MA_ASSERT(MA_FALSE);
6408                 return MA_INVALID_OPERATION;    /* Another thread has taken the 0 slot. Acquire/release mismatch. */
6409             }
6410         }
6411     }
6412 
6413     /* Should never get here. */
6414     /*return MA_SUCCESS;*/
6415 }
6416 
ma_fence_wait(ma_fence * pFence)6417 MA_API ma_result ma_fence_wait(ma_fence* pFence)
6418 {
6419     if (pFence == NULL) {
6420         return MA_INVALID_ARGS;
6421     }
6422 
6423     for (;;) {
6424         ma_result result;
6425         ma_uint32 counter;
6426 
6427         counter = c89atomic_load_32(&pFence->counter);
6428         if (counter == 0) {
6429             /*
6430             Counter has hit zero. By the time we get here some other thread may have acquired the
6431             fence again, but that is where the caller needs to take care with how they se the fence.
6432             */
6433             return MA_SUCCESS;
6434         }
6435 
6436         /* Getting here means the counter is > 0. We'll need to wait for something to happen. */
6437         result = ma_event_wait(&pFence->e);
6438         if (result != MA_SUCCESS) {
6439             return result;
6440         }
6441     }
6442 
6443     /* Should never get here. */
6444     /*return MA_INVALID_OPERATION;*/
6445 }
6446 
6447 
6448 
ma_async_notification_signal(ma_async_notification * pNotification)6449 MA_API ma_result ma_async_notification_signal(ma_async_notification* pNotification)
6450 {
6451     ma_async_notification_callbacks* pNotificationCallbacks = (ma_async_notification_callbacks*)pNotification;
6452 
6453     if (pNotification == NULL) {
6454         return MA_INVALID_ARGS;
6455     }
6456 
6457     if (pNotificationCallbacks->onSignal == NULL) {
6458         return MA_NOT_IMPLEMENTED;
6459     }
6460 
6461     pNotificationCallbacks->onSignal(pNotification);
6462     return MA_INVALID_ARGS;
6463 }
6464 
6465 
ma_async_notification_poll__on_signal(ma_async_notification * pNotification)6466 static void ma_async_notification_poll__on_signal(ma_async_notification* pNotification)
6467 {
6468     ((ma_async_notification_poll*)pNotification)->signalled = MA_TRUE;
6469 }
6470 
ma_async_notification_poll_init(ma_async_notification_poll * pNotificationPoll)6471 MA_API ma_result ma_async_notification_poll_init(ma_async_notification_poll* pNotificationPoll)
6472 {
6473     if (pNotificationPoll == NULL) {
6474         return MA_INVALID_ARGS;
6475     }
6476 
6477     pNotificationPoll->cb.onSignal = ma_async_notification_poll__on_signal;
6478     pNotificationPoll->signalled = MA_FALSE;
6479 
6480     return MA_SUCCESS;
6481 }
6482 
ma_async_notification_poll_is_signalled(const ma_async_notification_poll * pNotificationPoll)6483 MA_API ma_bool32 ma_async_notification_poll_is_signalled(const ma_async_notification_poll* pNotificationPoll)
6484 {
6485     if (pNotificationPoll == NULL) {
6486         return MA_FALSE;
6487     }
6488 
6489     return pNotificationPoll->signalled;
6490 }
6491 
6492 
ma_async_notification_event__on_signal(ma_async_notification * pNotification)6493 static void ma_async_notification_event__on_signal(ma_async_notification* pNotification)
6494 {
6495     ma_async_notification_event_signal((ma_async_notification_event*)pNotification);
6496 }
6497 
ma_async_notification_event_init(ma_async_notification_event * pNotificationEvent)6498 MA_API ma_result ma_async_notification_event_init(ma_async_notification_event* pNotificationEvent)
6499 {
6500     ma_result result;
6501 
6502     if (pNotificationEvent == NULL) {
6503         return MA_INVALID_ARGS;
6504     }
6505 
6506     pNotificationEvent->cb.onSignal = ma_async_notification_event__on_signal;
6507 
6508     result = ma_event_init(&pNotificationEvent->e);
6509     if (result != MA_SUCCESS) {
6510         return result;
6511     }
6512 
6513     return MA_SUCCESS;
6514 }
6515 
ma_async_notification_event_uninit(ma_async_notification_event * pNotificationEvent)6516 MA_API ma_result ma_async_notification_event_uninit(ma_async_notification_event* pNotificationEvent)
6517 {
6518     if (pNotificationEvent == NULL) {
6519         return MA_INVALID_ARGS;
6520     }
6521 
6522     ma_event_uninit(&pNotificationEvent->e);
6523     return MA_SUCCESS;
6524 }
6525 
ma_async_notification_event_wait(ma_async_notification_event * pNotificationEvent)6526 MA_API ma_result ma_async_notification_event_wait(ma_async_notification_event* pNotificationEvent)
6527 {
6528     if (pNotificationEvent == NULL) {
6529         return MA_INVALID_ARGS;
6530     }
6531 
6532     return ma_event_wait(&pNotificationEvent->e);
6533 }
6534 
ma_async_notification_event_signal(ma_async_notification_event * pNotificationEvent)6535 MA_API ma_result ma_async_notification_event_signal(ma_async_notification_event* pNotificationEvent)
6536 {
6537     if (pNotificationEvent == NULL) {
6538         return MA_INVALID_ARGS;
6539     }
6540 
6541     return ma_event_signal(&pNotificationEvent->e);
6542 }
6543 
6544 
6545 
ma_pipeline_notifications_init(void)6546 MA_API ma_pipeline_notifications ma_pipeline_notifications_init(void)
6547 {
6548     ma_pipeline_notifications notifications;
6549 
6550     MA_ZERO_OBJECT(&notifications);
6551 
6552     return notifications;
6553 }
6554 
ma_pipeline_notifications_signal_all_notifications(const ma_pipeline_notifications * pPipelineNotifications)6555 static void ma_pipeline_notifications_signal_all_notifications(const ma_pipeline_notifications* pPipelineNotifications)
6556 {
6557     if (pPipelineNotifications == NULL) {
6558         return;
6559     }
6560 
6561     if (pPipelineNotifications->init.pNotification) { ma_async_notification_signal(pPipelineNotifications->init.pNotification); }
6562     if (pPipelineNotifications->done.pNotification) { ma_async_notification_signal(pPipelineNotifications->done.pNotification); }
6563 }
6564 
ma_pipeline_notifications_acquire_all_fences(const ma_pipeline_notifications * pPipelineNotifications)6565 static void ma_pipeline_notifications_acquire_all_fences(const ma_pipeline_notifications* pPipelineNotifications)
6566 {
6567     if (pPipelineNotifications == NULL) {
6568         return;
6569     }
6570 
6571     if (pPipelineNotifications->init.pFence != NULL) { ma_fence_acquire(pPipelineNotifications->init.pFence); }
6572     if (pPipelineNotifications->done.pFence != NULL) { ma_fence_acquire(pPipelineNotifications->done.pFence); }
6573 }
6574 
ma_pipeline_notifications_release_all_fences(const ma_pipeline_notifications * pPipelineNotifications)6575 static void ma_pipeline_notifications_release_all_fences(const ma_pipeline_notifications* pPipelineNotifications)
6576 {
6577     if (pPipelineNotifications == NULL) {
6578         return;
6579     }
6580 
6581     if (pPipelineNotifications->init.pFence != NULL) { ma_fence_release(pPipelineNotifications->init.pFence); }
6582     if (pPipelineNotifications->done.pFence != NULL) { ma_fence_release(pPipelineNotifications->done.pFence); }
6583 }
6584 
6585 
6586 
6587 #define MA_JOB_ID_NONE      ~((ma_uint64)0)
6588 #define MA_JOB_SLOT_NONE    (ma_uint16)(~0)
6589 
ma_job_extract_refcount(ma_uint64 toc)6590 static MA_INLINE ma_uint32 ma_job_extract_refcount(ma_uint64 toc)
6591 {
6592     return (ma_uint32)(toc >> 32);
6593 }
6594 
ma_job_extract_slot(ma_uint64 toc)6595 static MA_INLINE ma_uint16 ma_job_extract_slot(ma_uint64 toc)
6596 {
6597     return (ma_uint16)(toc & 0x0000FFFF);
6598 }
6599 
ma_job_extract_code(ma_uint64 toc)6600 static MA_INLINE ma_uint16 ma_job_extract_code(ma_uint64 toc)
6601 {
6602     return (ma_uint16)((toc & 0xFFFF0000) >> 16);
6603 }
6604 
ma_job_toc_to_allocation(ma_uint64 toc)6605 static MA_INLINE ma_uint64 ma_job_toc_to_allocation(ma_uint64 toc)
6606 {
6607     return ((ma_uint64)ma_job_extract_refcount(toc) << 32) | (ma_uint64)ma_job_extract_slot(toc);
6608 }
6609 
6610 
ma_job_init(ma_uint16 code)6611 MA_API ma_job ma_job_init(ma_uint16 code)
6612 {
6613     ma_job job;
6614 
6615     MA_ZERO_OBJECT(&job);
6616     job.toc.breakup.code = code;
6617     job.toc.breakup.slot = MA_JOB_SLOT_NONE;    /* Temp value. Will be allocated when posted to a queue. */
6618     job.next             = MA_JOB_ID_NONE;
6619 
6620     return job;
6621 }
6622 
6623 
6624 /*
6625 Lock free queue implementation based on the paper by Michael and Scott: Nonblocking Algorithms and Preemption-Safe Locking on Multiprogrammed Shared Memory Multiprocessors
6626 */
ma_job_queue_init(ma_uint32 flags,ma_job_queue * pQueue)6627 MA_API ma_result ma_job_queue_init(ma_uint32 flags, ma_job_queue* pQueue)
6628 {
6629     if (pQueue == NULL) {
6630         return MA_INVALID_ARGS;
6631     }
6632 
6633     MA_ZERO_OBJECT(pQueue);
6634     pQueue->flags = flags;
6635 
6636     ma_slot_allocator_init(&pQueue->allocator); /* Will not fail. */
6637 
6638     /* We need a semaphore if we're running in synchronous mode. */
6639     if ((pQueue->flags & MA_JOB_QUEUE_FLAG_NON_BLOCKING) == 0) {
6640         ma_semaphore_init(0, &pQueue->sem);
6641     }
6642 
6643     /*
6644     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
6645     just a dummy item for giving us the first item in the list which is stored in the "next" member.
6646     */
6647     ma_slot_allocator_alloc(&pQueue->allocator, &pQueue->head);  /* Will never fail. */
6648     pQueue->jobs[ma_job_extract_slot(pQueue->head)].next = MA_JOB_ID_NONE;
6649     pQueue->tail = pQueue->head;
6650 
6651     return MA_SUCCESS;
6652 }
6653 
ma_job_queue_uninit(ma_job_queue * pQueue)6654 MA_API ma_result ma_job_queue_uninit(ma_job_queue* pQueue)
6655 {
6656     if (pQueue == NULL) {
6657         return MA_INVALID_ARGS;
6658     }
6659 
6660     /* All we need to do is uninitialize the semaphore. */
6661     if ((pQueue->flags & MA_JOB_QUEUE_FLAG_NON_BLOCKING) == 0) {
6662         ma_semaphore_uninit(&pQueue->sem);
6663     }
6664 
6665     return MA_SUCCESS;
6666 }
6667 
ma_job_queue_post(ma_job_queue * pQueue,const ma_job * pJob)6668 MA_API ma_result ma_job_queue_post(ma_job_queue* pQueue, const ma_job* pJob)
6669 {
6670     ma_result result;
6671     ma_uint64 slot;
6672     ma_uint64 tail;
6673     ma_uint64 next;
6674 
6675     if (pQueue == NULL || pJob == NULL) {
6676         return MA_INVALID_ARGS;
6677     }
6678 
6679     /* We need a new slot. */
6680     result = ma_slot_allocator_alloc(&pQueue->allocator, &slot);
6681     if (result != MA_SUCCESS) {
6682         return result;  /* Probably ran out of slots. If so, MA_OUT_OF_MEMORY will be returned. */
6683     }
6684 
6685     /* At this point we should have a slot to place the job. */
6686     MA_ASSERT(ma_job_extract_slot(slot) < MA_RESOURCE_MANAGER_JOB_QUEUE_CAPACITY);
6687 
6688     /* We need to put the job into memory before we do anything. */
6689     pQueue->jobs[ma_job_extract_slot(slot)]                  = *pJob;
6690     pQueue->jobs[ma_job_extract_slot(slot)].toc.allocation   = slot;                    /* This will overwrite the job code. */
6691     pQueue->jobs[ma_job_extract_slot(slot)].toc.breakup.code = pJob->toc.breakup.code;  /* The job code needs to be applied again because the line above overwrote it. */
6692     pQueue->jobs[ma_job_extract_slot(slot)].next             = MA_JOB_ID_NONE;          /* Reset for safety. */
6693 
6694     /* 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. */
6695     for (;;) {
6696         tail = pQueue->tail;
6697         next = pQueue->jobs[ma_job_extract_slot(tail)].next;
6698 
6699         if (ma_job_toc_to_allocation(tail) == ma_job_toc_to_allocation(pQueue->tail)) {
6700             if (ma_job_extract_slot(next) == 0xFFFF) {
6701                 if (c89atomic_compare_and_swap_64(&pQueue->jobs[ma_job_extract_slot(tail)].next, next, slot) == next) {
6702                     break;
6703                 }
6704             } else {
6705                 c89atomic_compare_and_swap_64(&pQueue->tail, tail, next);
6706             }
6707         }
6708     }
6709     c89atomic_compare_and_swap_64(&pQueue->tail, tail, slot);
6710 
6711 
6712     /* Signal the semaphore as the last step if we're using synchronous mode. */
6713     if ((pQueue->flags & MA_JOB_QUEUE_FLAG_NON_BLOCKING) == 0) {
6714         ma_semaphore_release(&pQueue->sem);
6715     }
6716 
6717     return MA_SUCCESS;
6718 }
6719 
ma_job_queue_next(ma_job_queue * pQueue,ma_job * pJob)6720 MA_API ma_result ma_job_queue_next(ma_job_queue* pQueue, ma_job* pJob)
6721 {
6722     ma_uint64 head;
6723     ma_uint64 tail;
6724     ma_uint64 next;
6725 
6726     if (pQueue == NULL || pJob == NULL) {
6727         return MA_INVALID_ARGS;
6728     }
6729 
6730     /* If we're running in synchronous mode we'll need to wait on a semaphore. */
6731     if ((pQueue->flags & MA_JOB_QUEUE_FLAG_NON_BLOCKING) == 0) {
6732         ma_semaphore_wait(&pQueue->sem);
6733     }
6734 
6735     /* Now we need to remove the root item from the list. This must be done without locking. */
6736     for (;;) {
6737         head = pQueue->head;
6738         tail = pQueue->tail;
6739         next = pQueue->jobs[ma_job_extract_slot(head)].next;
6740 
6741         if (ma_job_toc_to_allocation(head) == ma_job_toc_to_allocation(pQueue->head)) {
6742             if (ma_job_toc_to_allocation(head) == ma_job_toc_to_allocation(tail)) {
6743                 if (ma_job_extract_slot(next) == 0xFFFF) {
6744                     return MA_NO_DATA_AVAILABLE;
6745                 }
6746                 c89atomic_compare_and_swap_64(&pQueue->tail, tail, next);
6747             } else {
6748                 *pJob = pQueue->jobs[ma_job_extract_slot(next)];
6749                 if (c89atomic_compare_and_swap_64(&pQueue->head, head, next) == head) {
6750                     break;
6751                 }
6752             }
6753         }
6754     }
6755 
6756     ma_slot_allocator_free(&pQueue->allocator, head);
6757 
6758     /*
6759     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
6760     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
6761     possible.
6762     */
6763     if (pJob->toc.breakup.code == MA_JOB_QUIT) {
6764         ma_job_queue_post(pQueue, pJob);
6765         return MA_CANCELLED;    /* Return a cancelled status just in case the thread is checking return codes and not properly checking for a quit job. */
6766     }
6767 
6768     return MA_SUCCESS;
6769 }
6770 
ma_job_queue_free(ma_job_queue * pQueue,ma_job * pJob)6771 MA_API ma_result ma_job_queue_free(ma_job_queue* pQueue, ma_job* pJob)
6772 {
6773     if (pQueue == NULL || pJob == NULL) {
6774         return MA_INVALID_ARGS;
6775     }
6776 
6777     return ma_slot_allocator_free(&pQueue->allocator, ma_job_toc_to_allocation(pJob->toc.allocation));
6778 }
6779 
6780 
6781 
6782 
6783 
6784 #ifndef MA_DEFAULT_HASH_SEED
6785 #define MA_DEFAULT_HASH_SEED    42
6786 #endif
6787 
6788 /* MurmurHash3. Based on code from https://github.com/PeterScott/murmur3/blob/master/murmur3.c (public domain). */
6789 #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)))
6790     #pragma GCC diagnostic push
6791     #pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
6792 #endif
6793 
ma_rotl32(ma_uint32 x,ma_int8 r)6794 static MA_INLINE ma_uint32 ma_rotl32(ma_uint32 x, ma_int8 r)
6795 {
6796     return (x << r) | (x >> (32 - r));
6797 }
6798 
ma_hash_getblock(const ma_uint32 * blocks,int i)6799 static MA_INLINE ma_uint32 ma_hash_getblock(const ma_uint32* blocks, int i)
6800 {
6801     if (ma_is_little_endian()) {
6802         return blocks[i];
6803     } else {
6804         return ma_swap_endian_uint32(blocks[i]);
6805     }
6806 }
6807 
ma_hash_fmix32(ma_uint32 h)6808 static MA_INLINE ma_uint32 ma_hash_fmix32(ma_uint32 h)
6809 {
6810     h ^= h >> 16;
6811     h *= 0x85ebca6b;
6812     h ^= h >> 13;
6813     h *= 0xc2b2ae35;
6814     h ^= h >> 16;
6815 
6816     return h;
6817 }
6818 
ma_hash_32(const void * key,int len,ma_uint32 seed)6819 static ma_uint32 ma_hash_32(const void* key, int len, ma_uint32 seed)
6820 {
6821     const ma_uint8* data = (const ma_uint8*)key;
6822     const ma_uint32* blocks;
6823     const ma_uint8* tail;
6824     const int nblocks = len / 4;
6825     ma_uint32 h1 = seed;
6826     ma_uint32 c1 = 0xcc9e2d51;
6827     ma_uint32 c2 = 0x1b873593;
6828     ma_uint32 k1;
6829     int i;
6830 
6831     blocks = (const ma_uint32 *)(data + nblocks*4);
6832 
6833     for(i = -nblocks; i; i++) {
6834         k1 = ma_hash_getblock(blocks,i);
6835 
6836         k1 *= c1;
6837         k1 = ma_rotl32(k1, 15);
6838         k1 *= c2;
6839 
6840         h1 ^= k1;
6841         h1 = ma_rotl32(h1, 13);
6842         h1 = h1*5 + 0xe6546b64;
6843     }
6844 
6845 
6846     tail = (const ma_uint8*)(data + nblocks*4);
6847 
6848     k1 = 0;
6849     switch(len & 3) {
6850         case 3: k1 ^= tail[2] << 16;
6851         case 2: k1 ^= tail[1] << 8;
6852         case 1: k1 ^= tail[0];
6853                 k1 *= c1; k1 = ma_rotl32(k1, 15); k1 *= c2; h1 ^= k1;
6854     };
6855 
6856 
6857     h1 ^= len;
6858     h1  = ma_hash_fmix32(h1);
6859 
6860     return h1;
6861 }
6862 
6863 #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)))
6864     #pragma GCC diagnostic push
6865 #endif
6866 /* End MurmurHash3 */
6867 
ma_hash_string_32(const char * str)6868 static ma_uint32 ma_hash_string_32(const char* str)
6869 {
6870     return ma_hash_32(str, (int)strlen(str), MA_DEFAULT_HASH_SEED);
6871 }
6872 
ma_hash_string_w_32(const wchar_t * str)6873 static ma_uint32 ma_hash_string_w_32(const wchar_t* str)
6874 {
6875     return ma_hash_32(str, (int)wcslen(str) * sizeof(*str), MA_DEFAULT_HASH_SEED);
6876 }
6877 
6878 
6879 
6880 
6881 /*
6882 Basic BST Functions
6883 */
ma_resource_manager_data_buffer_node_search(ma_resource_manager * pResourceManager,ma_uint32 hashedName32,ma_resource_manager_data_buffer_node ** ppDataBufferNode)6884 static ma_result ma_resource_manager_data_buffer_node_search(ma_resource_manager* pResourceManager, ma_uint32 hashedName32, ma_resource_manager_data_buffer_node** ppDataBufferNode)
6885 {
6886     ma_resource_manager_data_buffer_node* pCurrentNode;
6887 
6888     MA_ASSERT(pResourceManager != NULL);
6889     MA_ASSERT(ppDataBufferNode != NULL);
6890 
6891     pCurrentNode = pResourceManager->pRootDataBufferNode;
6892     while (pCurrentNode != NULL) {
6893         if (hashedName32 == pCurrentNode->hashedName32) {
6894             break;  /* Found. */
6895         } else if (hashedName32 < pCurrentNode->hashedName32) {
6896             pCurrentNode = pCurrentNode->pChildLo;
6897         } else {
6898             pCurrentNode = pCurrentNode->pChildHi;
6899         }
6900     }
6901 
6902     *ppDataBufferNode = pCurrentNode;
6903 
6904     if (pCurrentNode == NULL) {
6905         return MA_DOES_NOT_EXIST;
6906     } else {
6907         return MA_SUCCESS;
6908     }
6909 }
6910 
ma_resource_manager_data_buffer_node_insert_point(ma_resource_manager * pResourceManager,ma_uint32 hashedName32,ma_resource_manager_data_buffer_node ** ppInsertPoint)6911 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)
6912 {
6913     ma_result result = MA_SUCCESS;
6914     ma_resource_manager_data_buffer_node* pCurrentNode;
6915 
6916     MA_ASSERT(pResourceManager != NULL);
6917     MA_ASSERT(ppInsertPoint    != NULL);
6918 
6919     *ppInsertPoint = NULL;
6920 
6921     if (pResourceManager->pRootDataBufferNode == NULL) {
6922         return MA_SUCCESS;  /* No items. */
6923     }
6924 
6925     /* 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. */
6926     pCurrentNode = pResourceManager->pRootDataBufferNode;
6927     while (pCurrentNode != NULL) {
6928         if (hashedName32 == pCurrentNode->hashedName32) {
6929             result = MA_ALREADY_EXISTS;
6930             break;
6931         } else {
6932             if (hashedName32 < pCurrentNode->hashedName32) {
6933                 if (pCurrentNode->pChildLo == NULL) {
6934                     result = MA_SUCCESS;
6935                     break;
6936                 } else {
6937                     pCurrentNode = pCurrentNode->pChildLo;
6938                 }
6939             } else {
6940                 if (pCurrentNode->pChildHi == NULL) {
6941                     result = MA_SUCCESS;
6942                     break;
6943                 } else {
6944                     pCurrentNode = pCurrentNode->pChildHi;
6945                 }
6946             }
6947         }
6948     }
6949 
6950     *ppInsertPoint = pCurrentNode;
6951     return result;
6952 }
6953 
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)6954 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)
6955 {
6956     MA_ASSERT(pResourceManager != NULL);
6957     MA_ASSERT(pDataBufferNode  != NULL);
6958 
6959     /* The key must have been set before calling this function. */
6960     MA_ASSERT(pDataBufferNode->hashedName32 != 0);
6961 
6962     if (pInsertPoint == NULL) {
6963         /* It's the first node. */
6964         pResourceManager->pRootDataBufferNode = pDataBufferNode;
6965     } else {
6966         /* It's not the first node. It needs to be inserted. */
6967         if (pDataBufferNode->hashedName32 < pInsertPoint->hashedName32) {
6968             MA_ASSERT(pInsertPoint->pChildLo == NULL);
6969             pInsertPoint->pChildLo = pDataBufferNode;
6970         } else {
6971             MA_ASSERT(pInsertPoint->pChildHi == NULL);
6972             pInsertPoint->pChildHi = pDataBufferNode;
6973         }
6974     }
6975 
6976     pDataBufferNode->pParent = pInsertPoint;
6977 
6978     return MA_SUCCESS;
6979 }
6980 
6981 #if 0   /* Unused for now. */
6982 static ma_result ma_resource_manager_data_buffer_node_insert(ma_resource_manager* pResourceManager, ma_resource_manager_data_buffer_node* pDataBufferNode)
6983 {
6984     ma_result result;
6985     ma_resource_manager_data_buffer_node* pInsertPoint;
6986 
6987     MA_ASSERT(pResourceManager != NULL);
6988     MA_ASSERT(pDataBufferNode  != NULL);
6989 
6990     result = ma_resource_manager_data_buffer_node_insert_point(pResourceManager, pDataBufferNode->hashedName32, &pInsertPoint);
6991     if (result != MA_SUCCESS) {
6992         return MA_INVALID_ARGS;
6993     }
6994 
6995     return ma_resource_manager_data_buffer_node_insert_at(pResourceManager, pDataBufferNode, pInsertPoint);
6996 }
6997 #endif
6998 
ma_resource_manager_data_buffer_node_find_min(ma_resource_manager_data_buffer_node * pDataBufferNode)6999 static MA_INLINE ma_resource_manager_data_buffer_node* ma_resource_manager_data_buffer_node_find_min(ma_resource_manager_data_buffer_node* pDataBufferNode)
7000 {
7001     ma_resource_manager_data_buffer_node* pCurrentNode;
7002 
7003     MA_ASSERT(pDataBufferNode != NULL);
7004 
7005     pCurrentNode = pDataBufferNode;
7006     while (pCurrentNode->pChildLo != NULL) {
7007         pCurrentNode = pCurrentNode->pChildLo;
7008     }
7009 
7010     return pCurrentNode;
7011 }
7012 
ma_resource_manager_data_buffer_node_find_max(ma_resource_manager_data_buffer_node * pDataBufferNode)7013 static MA_INLINE ma_resource_manager_data_buffer_node* ma_resource_manager_data_buffer_node_find_max(ma_resource_manager_data_buffer_node* pDataBufferNode)
7014 {
7015     ma_resource_manager_data_buffer_node* pCurrentNode;
7016 
7017     MA_ASSERT(pDataBufferNode != NULL);
7018 
7019     pCurrentNode = pDataBufferNode;
7020     while (pCurrentNode->pChildHi != NULL) {
7021         pCurrentNode = pCurrentNode->pChildHi;
7022     }
7023 
7024     return pCurrentNode;
7025 }
7026 
ma_resource_manager_data_buffer_node_find_inorder_successor(ma_resource_manager_data_buffer_node * pDataBufferNode)7027 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)
7028 {
7029     MA_ASSERT(pDataBufferNode           != NULL);
7030     MA_ASSERT(pDataBufferNode->pChildHi != NULL);
7031 
7032     return ma_resource_manager_data_buffer_node_find_min(pDataBufferNode->pChildHi);
7033 }
7034 
ma_resource_manager_data_buffer_node_find_inorder_predecessor(ma_resource_manager_data_buffer_node * pDataBufferNode)7035 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)
7036 {
7037     MA_ASSERT(pDataBufferNode           != NULL);
7038     MA_ASSERT(pDataBufferNode->pChildLo != NULL);
7039 
7040     return ma_resource_manager_data_buffer_node_find_max(pDataBufferNode->pChildLo);
7041 }
7042 
ma_resource_manager_data_buffer_node_remove(ma_resource_manager * pResourceManager,ma_resource_manager_data_buffer_node * pDataBufferNode)7043 static ma_result ma_resource_manager_data_buffer_node_remove(ma_resource_manager* pResourceManager, ma_resource_manager_data_buffer_node* pDataBufferNode)
7044 {
7045     MA_ASSERT(pResourceManager != NULL);
7046     MA_ASSERT(pDataBufferNode  != NULL);
7047 
7048     if (pDataBufferNode->pChildLo == NULL) {
7049         if (pDataBufferNode->pChildHi == NULL) {
7050             /* Simple case - deleting a buffer with no children. */
7051             if (pDataBufferNode->pParent == NULL) {
7052                 MA_ASSERT(pResourceManager->pRootDataBufferNode == pDataBufferNode);    /* There is only a single buffer in the tree which should be equal to the root node. */
7053                 pResourceManager->pRootDataBufferNode = NULL;
7054             } else {
7055                 if (pDataBufferNode->pParent->pChildLo == pDataBufferNode) {
7056                     pDataBufferNode->pParent->pChildLo = NULL;
7057                 } else {
7058                     pDataBufferNode->pParent->pChildHi = NULL;
7059                 }
7060             }
7061         } else {
7062             /* Node has one child - pChildHi != NULL. */
7063             pDataBufferNode->pChildHi->pParent = pDataBufferNode->pParent;
7064 
7065             if (pDataBufferNode->pParent == NULL) {
7066                 MA_ASSERT(pResourceManager->pRootDataBufferNode == pDataBufferNode);
7067                 pResourceManager->pRootDataBufferNode = pDataBufferNode->pChildHi;
7068             } else {
7069                 if (pDataBufferNode->pParent->pChildLo == pDataBufferNode) {
7070                     pDataBufferNode->pParent->pChildLo = pDataBufferNode->pChildHi;
7071                 } else {
7072                     pDataBufferNode->pParent->pChildHi = pDataBufferNode->pChildHi;
7073                 }
7074             }
7075         }
7076     } else {
7077         if (pDataBufferNode->pChildHi == NULL) {
7078             /* Node has one child - pChildLo != NULL. */
7079             pDataBufferNode->pChildLo->pParent = pDataBufferNode->pParent;
7080 
7081             if (pDataBufferNode->pParent == NULL) {
7082                 MA_ASSERT(pResourceManager->pRootDataBufferNode == pDataBufferNode);
7083                 pResourceManager->pRootDataBufferNode = pDataBufferNode->pChildLo;
7084             } else {
7085                 if (pDataBufferNode->pParent->pChildLo == pDataBufferNode) {
7086                     pDataBufferNode->pParent->pChildLo = pDataBufferNode->pChildLo;
7087                 } else {
7088                     pDataBufferNode->pParent->pChildHi = pDataBufferNode->pChildLo;
7089                 }
7090             }
7091         } else {
7092             /* Complex case - deleting a node with two children. */
7093             ma_resource_manager_data_buffer_node* pReplacementDataBufferNode;
7094 
7095             /* 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. */
7096             pReplacementDataBufferNode = ma_resource_manager_data_buffer_node_find_inorder_successor(pDataBufferNode);
7097             MA_ASSERT(pReplacementDataBufferNode != NULL);
7098 
7099             /*
7100             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
7101             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
7102             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
7103             replacement node and reinserting it into the same position as the deleted node.
7104             */
7105             MA_ASSERT(pReplacementDataBufferNode->pParent  != NULL);  /* The replacement node should never be the root which means it should always have a parent. */
7106             MA_ASSERT(pReplacementDataBufferNode->pChildLo == NULL);  /* Because we used in-order successor. This would be pChildHi == NULL if we used in-order predecessor. */
7107 
7108             if (pReplacementDataBufferNode->pChildHi == NULL) {
7109                 if (pReplacementDataBufferNode->pParent->pChildLo == pReplacementDataBufferNode) {
7110                     pReplacementDataBufferNode->pParent->pChildLo = NULL;
7111                 } else {
7112                     pReplacementDataBufferNode->pParent->pChildHi = NULL;
7113                 }
7114             } else {
7115                 pReplacementDataBufferNode->pChildHi->pParent = pReplacementDataBufferNode->pParent;
7116                 if (pReplacementDataBufferNode->pParent->pChildLo == pReplacementDataBufferNode) {
7117                     pReplacementDataBufferNode->pParent->pChildLo = pReplacementDataBufferNode->pChildHi;
7118                 } else {
7119                     pReplacementDataBufferNode->pParent->pChildHi = pReplacementDataBufferNode->pChildHi;
7120                 }
7121             }
7122 
7123 
7124             /* 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 */
7125             if (pDataBufferNode->pParent != NULL) {
7126                 if (pDataBufferNode->pParent->pChildLo == pDataBufferNode) {
7127                     pDataBufferNode->pParent->pChildLo = pReplacementDataBufferNode;
7128                 } else {
7129                     pDataBufferNode->pParent->pChildHi = pReplacementDataBufferNode;
7130                 }
7131             }
7132 
7133             /* Now need to update the replacement node's pointers. */
7134             pReplacementDataBufferNode->pParent  = pDataBufferNode->pParent;
7135             pReplacementDataBufferNode->pChildLo = pDataBufferNode->pChildLo;
7136             pReplacementDataBufferNode->pChildHi = pDataBufferNode->pChildHi;
7137 
7138             /* Now the children of the replacement node need to have their parent pointers updated. */
7139             if (pReplacementDataBufferNode->pChildLo != NULL) {
7140                 pReplacementDataBufferNode->pChildLo->pParent = pReplacementDataBufferNode;
7141             }
7142             if (pReplacementDataBufferNode->pChildHi != NULL) {
7143                 pReplacementDataBufferNode->pChildHi->pParent = pReplacementDataBufferNode;
7144             }
7145 
7146             /* Now the root node needs to be updated. */
7147             if (pResourceManager->pRootDataBufferNode == pDataBufferNode) {
7148                 pResourceManager->pRootDataBufferNode = pReplacementDataBufferNode;
7149             }
7150         }
7151     }
7152 
7153     return MA_SUCCESS;
7154 }
7155 
7156 #if 0   /* Unused for now. */
7157 static ma_result ma_resource_manager_data_buffer_node_remove_by_key(ma_resource_manager* pResourceManager, ma_uint32 hashedName32)
7158 {
7159     ma_result result;
7160     ma_resource_manager_data_buffer_node* pDataBufferNode;
7161 
7162     result = ma_resource_manager_data_buffer_search(pResourceManager, hashedName32, &pDataBufferNode);
7163     if (result != MA_SUCCESS) {
7164         return result;  /* Could not find the data buffer. */
7165     }
7166 
7167     return ma_resource_manager_data_buffer_remove(pResourceManager, pDataBufferNode);
7168 }
7169 #endif
7170 
ma_resource_manager_data_buffer_node_get_data_supply_type(ma_resource_manager_data_buffer_node * pDataBufferNode)7171 static ma_resource_manager_data_supply_type ma_resource_manager_data_buffer_node_get_data_supply_type(ma_resource_manager_data_buffer_node* pDataBufferNode)
7172 {
7173     return (ma_resource_manager_data_supply_type)c89atomic_load_i32(&pDataBufferNode->data.type);
7174 }
7175 
ma_resource_manager_data_buffer_node_set_data_supply_type(ma_resource_manager_data_buffer_node * pDataBufferNode,ma_resource_manager_data_supply_type supplyType)7176 static void ma_resource_manager_data_buffer_node_set_data_supply_type(ma_resource_manager_data_buffer_node* pDataBufferNode, ma_resource_manager_data_supply_type supplyType)
7177 {
7178     c89atomic_exchange_i32(&pDataBufferNode->data.type, supplyType);
7179 }
7180 
ma_resource_manager_data_buffer_node_increment_ref(ma_resource_manager * pResourceManager,ma_resource_manager_data_buffer_node * pDataBufferNode,ma_uint32 * pNewRefCount)7181 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)
7182 {
7183     ma_uint32 refCount;
7184 
7185     MA_ASSERT(pResourceManager != NULL);
7186     MA_ASSERT(pDataBufferNode  != NULL);
7187 
7188     (void)pResourceManager;
7189 
7190     refCount = c89atomic_fetch_add_32(&pDataBufferNode->refCount, 1) + 1;
7191 
7192     if (pNewRefCount != NULL) {
7193         *pNewRefCount = refCount;
7194     }
7195 
7196     return MA_SUCCESS;
7197 }
7198 
ma_resource_manager_data_buffer_node_decrement_ref(ma_resource_manager * pResourceManager,ma_resource_manager_data_buffer_node * pDataBufferNode,ma_uint32 * pNewRefCount)7199 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)
7200 {
7201     ma_uint32 refCount;
7202 
7203     MA_ASSERT(pResourceManager != NULL);
7204     MA_ASSERT(pDataBufferNode  != NULL);
7205 
7206     (void)pResourceManager;
7207 
7208     refCount = c89atomic_fetch_sub_32(&pDataBufferNode->refCount, 1) - 1;
7209 
7210     if (pNewRefCount != NULL) {
7211         *pNewRefCount = refCount;
7212     }
7213 
7214     return MA_SUCCESS;
7215 }
7216 
ma_resource_manager_data_buffer_node_free(ma_resource_manager * pResourceManager,ma_resource_manager_data_buffer_node * pDataBufferNode)7217 static void ma_resource_manager_data_buffer_node_free(ma_resource_manager* pResourceManager, ma_resource_manager_data_buffer_node* pDataBufferNode)
7218 {
7219     MA_ASSERT(pResourceManager != NULL);
7220     MA_ASSERT(pDataBufferNode  != NULL);
7221 
7222     if (pDataBufferNode->isDataOwnedByResourceManager) {
7223         if (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBufferNode) == ma_resource_manager_data_supply_type_encoded) {
7224             ma_free((void*)pDataBufferNode->data.encoded.pData, &pResourceManager->config.allocationCallbacks/*, MA_ALLOCATION_TYPE_ENCODED_BUFFER*/);
7225             pDataBufferNode->data.encoded.pData       = NULL;
7226             pDataBufferNode->data.encoded.sizeInBytes = 0;
7227         } else if (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBufferNode) == ma_resource_manager_data_supply_type_decoded) {
7228             ma_free((void*)pDataBufferNode->data.decoded.pData, &pResourceManager->config.allocationCallbacks/*, MA_ALLOCATION_TYPE_DECODED_BUFFER*/);
7229             pDataBufferNode->data.decoded.pData           = NULL;
7230             pDataBufferNode->data.decoded.totalFrameCount = 0;
7231         } else if (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBufferNode) == ma_resource_manager_data_supply_type_decoded_paged) {
7232             ma_paged_audio_buffer_data_uninit(&pDataBufferNode->data.decodedPaged.data, &pResourceManager->config.allocationCallbacks);
7233         } else {
7234             /* Should never hit this if the node was successfully initialized. */
7235             MA_ASSERT(pDataBufferNode->result != MA_SUCCESS);
7236         }
7237     }
7238 
7239     /* The data buffer itself needs to be freed. */
7240     ma_free(pDataBufferNode, &pResourceManager->config.allocationCallbacks/*, MA_ALLOCATION_TYPE_RESOURCE_MANAGER_DATA_BUFFER*/);
7241 }
7242 
ma_resource_manager_data_buffer_node_result(const ma_resource_manager_data_buffer_node * pDataBufferNode)7243 static ma_result ma_resource_manager_data_buffer_node_result(const ma_resource_manager_data_buffer_node* pDataBufferNode)
7244 {
7245     MA_ASSERT(pDataBufferNode != NULL);
7246 
7247     return c89atomic_load_i32((ma_result*)&pDataBufferNode->result);    /* Need a naughty const-cast here. */
7248 }
7249 
7250 
ma_resource_manager_is_threading_enabled(const ma_resource_manager * pResourceManager)7251 static ma_bool32 ma_resource_manager_is_threading_enabled(const ma_resource_manager* pResourceManager)
7252 {
7253     MA_ASSERT(pResourceManager != NULL);
7254 
7255     return (pResourceManager->config.flags & MA_RESOURCE_MANAGER_FLAG_NO_THREADING) == 0;
7256 }
7257 
7258 
7259 typedef struct
7260 {
7261     union
7262     {
7263         ma_async_notification_event e;
7264         ma_async_notification_poll p;
7265     };  /* Must be the first member. */
7266     ma_resource_manager* pResourceManager;
7267 } ma_resource_manager_inline_notification;
7268 
ma_resource_manager_inline_notification_init(ma_resource_manager * pResourceManager,ma_resource_manager_inline_notification * pNotification)7269 static ma_result ma_resource_manager_inline_notification_init(ma_resource_manager* pResourceManager, ma_resource_manager_inline_notification* pNotification)
7270 {
7271     MA_ASSERT(pResourceManager != NULL);
7272     MA_ASSERT(pNotification    != NULL);
7273 
7274     pNotification->pResourceManager = pResourceManager;
7275 
7276     if (ma_resource_manager_is_threading_enabled(pResourceManager)) {
7277         return ma_async_notification_event_init(&pNotification->e);
7278     } else {
7279         return ma_async_notification_poll_init(&pNotification->p);
7280     }
7281 }
7282 
ma_resource_manager_inline_notification_uninit(ma_resource_manager_inline_notification * pNotification)7283 static void ma_resource_manager_inline_notification_uninit(ma_resource_manager_inline_notification* pNotification)
7284 {
7285     MA_ASSERT(pNotification != NULL);
7286 
7287     if (ma_resource_manager_is_threading_enabled(pNotification->pResourceManager)) {
7288         ma_async_notification_event_uninit(&pNotification->e);
7289     } else {
7290         /* No need to uninitialize a polling notification. */
7291     }
7292 }
7293 
ma_resource_manager_inline_notification_wait(ma_resource_manager_inline_notification * pNotification)7294 static void ma_resource_manager_inline_notification_wait(ma_resource_manager_inline_notification* pNotification)
7295 {
7296     MA_ASSERT(pNotification != NULL);
7297 
7298     if (ma_resource_manager_is_threading_enabled(pNotification->pResourceManager)) {
7299         ma_async_notification_event_wait(&pNotification->e);
7300     } else {
7301         while (ma_async_notification_poll_is_signalled(&pNotification->p) == MA_FALSE) {
7302             ma_result result = ma_resource_manager_process_next_job(pNotification->pResourceManager);
7303             if (result == MA_NO_DATA_AVAILABLE || result == MA_JOB_QUIT) {
7304                 break;
7305             }
7306         }
7307     }
7308 }
7309 
ma_resource_manager_inline_notification_wait_and_uninit(ma_resource_manager_inline_notification * pNotification)7310 static void ma_resource_manager_inline_notification_wait_and_uninit(ma_resource_manager_inline_notification* pNotification)
7311 {
7312     ma_resource_manager_inline_notification_wait(pNotification);
7313     ma_resource_manager_inline_notification_uninit(pNotification);
7314 }
7315 
7316 
ma_resource_manager_data_buffer_bst_lock(ma_resource_manager * pResourceManager)7317 static void ma_resource_manager_data_buffer_bst_lock(ma_resource_manager* pResourceManager)
7318 {
7319     MA_ASSERT(pResourceManager != NULL);
7320 
7321     if (ma_resource_manager_is_threading_enabled(pResourceManager)) {
7322         ma_mutex_lock(&pResourceManager->dataBufferBSTLock);
7323     } else {
7324         /* Threading not enabled. Do nothing. */
7325     }
7326 }
7327 
ma_resource_manager_data_buffer_bst_unlock(ma_resource_manager * pResourceManager)7328 static void ma_resource_manager_data_buffer_bst_unlock(ma_resource_manager* pResourceManager)
7329 {
7330     MA_ASSERT(pResourceManager != NULL);
7331 
7332     if (ma_resource_manager_is_threading_enabled(pResourceManager)) {
7333         ma_mutex_unlock(&pResourceManager->dataBufferBSTLock);
7334     } else {
7335         /* Threading not enabled. Do nothing. */
7336     }
7337 }
7338 
ma_resource_manager_job_thread(void * pUserData)7339 static ma_thread_result MA_THREADCALL ma_resource_manager_job_thread(void* pUserData)
7340 {
7341     ma_resource_manager* pResourceManager = (ma_resource_manager*)pUserData;
7342     MA_ASSERT(pResourceManager != NULL);
7343 
7344     for (;;) {
7345         ma_result result;
7346         ma_job job;
7347 
7348         result = ma_resource_manager_next_job(pResourceManager, &job);
7349         if (result != MA_SUCCESS) {
7350             break;
7351         }
7352 
7353         /* Terminate if we got a quit message. */
7354         if (job.toc.breakup.code == MA_JOB_QUIT) {
7355             break;
7356         }
7357 
7358         ma_resource_manager_process_job(pResourceManager, &job);
7359     }
7360 
7361     return (ma_thread_result)0;
7362 }
7363 
7364 
ma_resource_manager_config_init(void)7365 MA_API ma_resource_manager_config ma_resource_manager_config_init(void)
7366 {
7367     ma_resource_manager_config config;
7368 
7369     MA_ZERO_OBJECT(&config);
7370     config.decodedFormat     = ma_format_unknown;
7371     config.decodedChannels   = 0;
7372     config.decodedSampleRate = 0;
7373     config.jobThreadCount    = 1;   /* A single miniaudio-managed job thread by default. */
7374 
7375     return config;
7376 }
7377 
7378 
ma_resource_manager_init(const ma_resource_manager_config * pConfig,ma_resource_manager * pResourceManager)7379 MA_API ma_result ma_resource_manager_init(const ma_resource_manager_config* pConfig, ma_resource_manager* pResourceManager)
7380 {
7381     ma_result result;
7382     ma_uint32 jobQueueFlags;
7383     ma_uint32 iJobThread;
7384 
7385     if (pResourceManager == NULL) {
7386         return MA_INVALID_ARGS;
7387     }
7388 
7389     MA_ZERO_OBJECT(pResourceManager);
7390 
7391     if (pConfig == NULL) {
7392         return MA_INVALID_ARGS;
7393     }
7394 
7395     if (pConfig->jobThreadCount > ma_countof(pResourceManager->jobThreads)) {
7396         return MA_INVALID_ARGS; /* Requesting too many job threads. */
7397     }
7398 
7399     pResourceManager->config = *pConfig;
7400     ma_allocation_callbacks_init_copy(&pResourceManager->config.allocationCallbacks, &pConfig->allocationCallbacks);
7401 
7402     /* Get the log set up early so we can start using it as soon as possible. */
7403     if (pResourceManager->config.pLog == NULL) {
7404         result = ma_log_init(&pResourceManager->config.allocationCallbacks, &pResourceManager->log);
7405         if (result == MA_SUCCESS) {
7406             pResourceManager->config.pLog = &pResourceManager->log;
7407         } else {
7408             pResourceManager->config.pLog = NULL;   /* Logging is unavailable. */
7409         }
7410     }
7411 
7412     if (pResourceManager->config.pVFS == NULL) {
7413         result = ma_default_vfs_init(&pResourceManager->defaultVFS, &pResourceManager->config.allocationCallbacks);
7414         if (result != MA_SUCCESS) {
7415             return result;  /* Failed to initialize the default file system. */
7416         }
7417 
7418         pResourceManager->config.pVFS = &pResourceManager->defaultVFS;
7419     }
7420 
7421     /* We need to force MA_RESOURCE_MANAGER_FLAG_NON_BLOCKING if MA_RESOURCE_MANAGER_FLAG_NO_THREADING is set. */
7422     if ((pResourceManager->config.flags & MA_RESOURCE_MANAGER_FLAG_NO_THREADING) != 0) {
7423         pResourceManager->config.flags |= MA_RESOURCE_MANAGER_FLAG_NON_BLOCKING;
7424 
7425         /* We cannot allow job threads when MA_RESOURCE_MANAGER_FLAG_NO_THREADING has been set. This is an invalid use case. */
7426         if (pResourceManager->config.jobThreadCount > 0) {
7427             return MA_INVALID_ARGS;
7428         }
7429     }
7430 
7431     /* Job queue. */
7432     jobQueueFlags = 0;
7433     if ((pResourceManager->config.flags & MA_RESOURCE_MANAGER_FLAG_NON_BLOCKING) != 0) {
7434         if (pResourceManager->config.jobThreadCount > 0) {
7435             return MA_INVALID_ARGS; /* Non-blocking mode is only valid for self-managed job threads. */
7436         }
7437 
7438         jobQueueFlags |= MA_JOB_QUEUE_FLAG_NON_BLOCKING;
7439     }
7440 
7441     result = ma_job_queue_init(jobQueueFlags, &pResourceManager->jobQueue);
7442     if (result != MA_SUCCESS) {
7443         return result;
7444     }
7445 
7446 
7447     /* Custom decoding backends. */
7448     if (pConfig->ppCustomDecodingBackendVTables != NULL && pConfig->customDecodingBackendCount > 0) {
7449         size_t sizeInBytes = sizeof(*pResourceManager->config.ppCustomDecodingBackendVTables) * pConfig->customDecodingBackendCount;
7450 
7451         pResourceManager->config.ppCustomDecodingBackendVTables = (ma_decoding_backend_vtable**)ma_malloc(sizeInBytes, &pResourceManager->config.allocationCallbacks);
7452         if (pResourceManager->config.ppCustomDecodingBackendVTables == NULL) {
7453             ma_job_queue_uninit(&pResourceManager->jobQueue);
7454             return MA_OUT_OF_MEMORY;
7455         }
7456 
7457         MA_COPY_MEMORY(pResourceManager->config.ppCustomDecodingBackendVTables, pConfig->ppCustomDecodingBackendVTables, sizeInBytes);
7458 
7459         pResourceManager->config.customDecodingBackendCount     = pConfig->customDecodingBackendCount;
7460         pResourceManager->config.pCustomDecodingBackendUserData = pConfig->pCustomDecodingBackendUserData;
7461     }
7462 
7463 
7464 
7465     /* Here is where we initialize our threading stuff. We don't do this if we don't support threading. */
7466     if (ma_resource_manager_is_threading_enabled(pResourceManager)) {
7467         /* Data buffer lock. */
7468         result = ma_mutex_init(&pResourceManager->dataBufferBSTLock);
7469         if (result != MA_SUCCESS) {
7470             ma_job_queue_uninit(&pResourceManager->jobQueue);
7471             return result;
7472         }
7473 
7474         /* Create the job threads last to ensure the threads has access to valid data. */
7475         for (iJobThread = 0; iJobThread < pResourceManager->config.jobThreadCount; iJobThread += 1) {
7476             result = ma_thread_create(&pResourceManager->jobThreads[iJobThread], ma_thread_priority_normal, 0, ma_resource_manager_job_thread, pResourceManager, &pResourceManager->config.allocationCallbacks);
7477             if (result != MA_SUCCESS) {
7478                 ma_mutex_uninit(&pResourceManager->dataBufferBSTLock);
7479                 ma_job_queue_uninit(&pResourceManager->jobQueue);
7480                 return result;
7481             }
7482         }
7483     }
7484 
7485     return MA_SUCCESS;
7486 }
7487 
7488 
ma_resource_manager_delete_all_data_buffer_nodes(ma_resource_manager * pResourceManager)7489 static void ma_resource_manager_delete_all_data_buffer_nodes(ma_resource_manager* pResourceManager)
7490 {
7491     MA_ASSERT(pResourceManager);
7492 
7493     /* If everything was done properly, there shouldn't be any active data buffers. */
7494     while (pResourceManager->pRootDataBufferNode != NULL) {
7495         ma_resource_manager_data_buffer_node* pDataBufferNode = pResourceManager->pRootDataBufferNode;
7496         ma_resource_manager_data_buffer_node_remove(pResourceManager, pDataBufferNode);
7497 
7498         /* The data buffer has been removed from the BST, so now we need to free it's data. */
7499         ma_resource_manager_data_buffer_node_free(pResourceManager, pDataBufferNode);
7500     }
7501 }
7502 
ma_resource_manager_uninit(ma_resource_manager * pResourceManager)7503 MA_API void ma_resource_manager_uninit(ma_resource_manager* pResourceManager)
7504 {
7505     ma_uint32 iJobThread;
7506 
7507     if (pResourceManager == NULL) {
7508         return;
7509     }
7510 
7511     /*
7512     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
7513     queue which means it will never not be returned after being encounted for the first time which means all threads will eventually receive it.
7514     */
7515     ma_resource_manager_post_job_quit(pResourceManager);
7516 
7517     /* Wait for every job to finish before continuing to ensure nothing is sill trying to access any of our objects below. */
7518     if (ma_resource_manager_is_threading_enabled(pResourceManager)) {
7519         for (iJobThread = 0; iJobThread < pResourceManager->config.jobThreadCount; iJobThread += 1) {
7520             ma_thread_wait(&pResourceManager->jobThreads[iJobThread]);
7521         }
7522     }
7523 
7524     /* At this point the thread should have returned and no other thread should be accessing our data. We can now delete all data buffers. */
7525     ma_resource_manager_delete_all_data_buffer_nodes(pResourceManager);
7526 
7527     /* The job queue is no longer needed. */
7528     ma_job_queue_uninit(&pResourceManager->jobQueue);
7529 
7530     /* We're no longer doing anything with data buffers so the lock can now be uninitialized. */
7531     if (ma_resource_manager_is_threading_enabled(pResourceManager)) {
7532         ma_mutex_uninit(&pResourceManager->dataBufferBSTLock);
7533     }
7534 
7535     ma_free(pResourceManager->config.ppCustomDecodingBackendVTables, &pResourceManager->config.allocationCallbacks);
7536 
7537     if (pResourceManager->config.pLog == &pResourceManager->log) {
7538         ma_log_uninit(&pResourceManager->log);
7539     }
7540 }
7541 
ma_resource_manager_get_log(ma_resource_manager * pResourceManager)7542 MA_API ma_log* ma_resource_manager_get_log(ma_resource_manager* pResourceManager)
7543 {
7544     if (pResourceManager == NULL) {
7545         return NULL;
7546     }
7547 
7548     return pResourceManager->config.pLog;
7549 }
7550 
7551 
ma_resource_manager__init_decoder_config(ma_resource_manager * pResourceManager)7552 static ma_decoder_config ma_resource_manager__init_decoder_config(ma_resource_manager* pResourceManager)
7553 {
7554     ma_decoder_config config;
7555 
7556     config = ma_decoder_config_init(pResourceManager->config.decodedFormat, pResourceManager->config.decodedChannels, pResourceManager->config.decodedSampleRate);
7557     config.allocationCallbacks    = pResourceManager->config.allocationCallbacks;
7558     config.ppCustomBackendVTables = pResourceManager->config.ppCustomDecodingBackendVTables;
7559     config.customBackendCount     = pResourceManager->config.customDecodingBackendCount;
7560     config.pCustomBackendUserData = pResourceManager->config.pCustomDecodingBackendUserData;
7561 
7562     return config;
7563 }
7564 
ma_resource_manager__init_decoder(ma_resource_manager * pResourceManager,const char * pFilePath,const wchar_t * pFilePathW,ma_decoder * pDecoder)7565 static ma_result ma_resource_manager__init_decoder(ma_resource_manager* pResourceManager, const char* pFilePath, const wchar_t* pFilePathW, ma_decoder* pDecoder)
7566 {
7567     ma_result result;
7568     ma_decoder_config config;
7569 
7570     MA_ASSERT(pResourceManager != NULL);
7571     MA_ASSERT(pFilePath        != NULL || pFilePathW != NULL);
7572     MA_ASSERT(pDecoder         != NULL);
7573 
7574     config = ma_resource_manager__init_decoder_config(pResourceManager);
7575 
7576     if (pFilePath != NULL) {
7577         result = ma_decoder_init_vfs(pResourceManager->config.pVFS, pFilePath, &config, pDecoder);
7578         if (result != MA_SUCCESS) {
7579             ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_WARNING, "Failed to load file \"%s\". %s.\n", pFilePath, ma_result_description(result));
7580             return result;
7581         }
7582     } else {
7583         result = ma_decoder_init_vfs_w(pResourceManager->config.pVFS, pFilePathW, &config, pDecoder);
7584         if (result != MA_SUCCESS) {
7585             ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_WARNING, "Failed to load file \"%ls\". %s.\n", pFilePathW, ma_result_description(result));
7586             return result;
7587         }
7588     }
7589 
7590     return MA_SUCCESS;
7591 }
7592 
ma_resource_manager_data_buffer_init_connector(ma_resource_manager_data_buffer * pDataBuffer,ma_async_notification * pInitNotification,ma_fence * pInitFence)7593 static ma_result ma_resource_manager_data_buffer_init_connector(ma_resource_manager_data_buffer* pDataBuffer, ma_async_notification* pInitNotification, ma_fence* pInitFence)
7594 {
7595     ma_result result;
7596 
7597     MA_ASSERT(pDataBuffer != NULL);
7598     MA_ASSERT(pDataBuffer->isConnectorInitialized == MA_FALSE);
7599 
7600     /* The underlying data buffer must be initialized before we'll be able to know how to initialize the backend. */
7601     result = ma_resource_manager_data_buffer_node_result(pDataBuffer->pNode);
7602     if (result != MA_SUCCESS && result != MA_BUSY) {
7603         return result;  /* The data buffer is in an erroneous state. */
7604     }
7605 
7606     /*
7607     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
7608     "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
7609     an ma_audio_buffer. This enables us to use memory mapping when mixing which saves us a bit of data movement overhead.
7610     */
7611     switch (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBuffer->pNode))
7612     {
7613         case ma_resource_manager_data_supply_type_encoded:          /* Connector is a decoder. */
7614         {
7615             ma_decoder_config config;
7616             config = ma_resource_manager__init_decoder_config(pDataBuffer->pResourceManager);
7617             result = ma_decoder_init_memory(pDataBuffer->pNode->data.encoded.pData, pDataBuffer->pNode->data.encoded.sizeInBytes, &config, &pDataBuffer->connector.decoder);
7618         } break;
7619 
7620         case ma_resource_manager_data_supply_type_decoded:          /* Connector is an audio buffer. */
7621         {
7622             ma_audio_buffer_config config;
7623             config = ma_audio_buffer_config_init(pDataBuffer->pNode->data.decoded.format, pDataBuffer->pNode->data.decoded.channels, pDataBuffer->pNode->data.decoded.totalFrameCount, pDataBuffer->pNode->data.decoded.pData, NULL);
7624             result = ma_audio_buffer_init(&config, &pDataBuffer->connector.buffer);
7625         } break;
7626 
7627         case ma_resource_manager_data_supply_type_decoded_paged:    /* Connector is a paged audio buffer. */
7628         {
7629             ma_paged_audio_buffer_config config;
7630             config = ma_paged_audio_buffer_config_init(&pDataBuffer->pNode->data.decodedPaged.data);
7631             result = ma_paged_audio_buffer_init(&config, &pDataBuffer->connector.pagedBuffer);
7632         } break;
7633 
7634         case ma_resource_manager_data_supply_type_unknown:
7635         default:
7636         {
7637             /* Unknown data supply type. Should never happen. Need to post an error here. */
7638             return MA_INVALID_ARGS;
7639         };
7640     }
7641 
7642     /*
7643     Initialization of the connector is when we can fire the init notification. This will give the application access to
7644     the format/channels/rate of the data source.
7645     */
7646     if (result == MA_SUCCESS) {
7647         pDataBuffer->isConnectorInitialized = MA_TRUE;
7648 
7649         if (pInitNotification != NULL) {
7650             ma_async_notification_signal(pInitNotification);
7651         }
7652 
7653         if (pInitFence != NULL) {
7654             ma_fence_release(pInitFence);
7655         }
7656     }
7657 
7658     /* 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. */
7659     return result;
7660 }
7661 
ma_resource_manager_data_buffer_uninit_connector(ma_resource_manager * pResourceManager,ma_resource_manager_data_buffer * pDataBuffer)7662 static ma_result ma_resource_manager_data_buffer_uninit_connector(ma_resource_manager* pResourceManager, ma_resource_manager_data_buffer* pDataBuffer)
7663 {
7664     MA_ASSERT(pResourceManager != NULL);
7665     MA_ASSERT(pDataBuffer      != NULL);
7666 
7667     switch (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBuffer->pNode))
7668     {
7669         case ma_resource_manager_data_supply_type_encoded:          /* Connector is a decoder. */
7670         {
7671             ma_decoder_uninit(&pDataBuffer->connector.decoder);
7672         } break;
7673 
7674         case ma_resource_manager_data_supply_type_decoded:          /* Connector is an audio buffer. */
7675         {
7676             ma_audio_buffer_uninit(&pDataBuffer->connector.buffer);
7677         } break;
7678 
7679         case ma_resource_manager_data_supply_type_decoded_paged:    /* Connector is a paged audio buffer. */
7680         {
7681             ma_paged_audio_buffer_uninit(&pDataBuffer->connector.pagedBuffer);
7682         } break;
7683 
7684         case ma_resource_manager_data_supply_type_unknown:
7685         default:
7686         {
7687             /* Unknown data supply type. Should never happen. Need to post an error here. */
7688             return MA_INVALID_ARGS;
7689         };
7690     }
7691 
7692     return MA_SUCCESS;
7693 }
7694 
ma_resource_manager_data_buffer_node_next_execution_order(ma_resource_manager_data_buffer_node * pDataBufferNode)7695 static ma_uint32 ma_resource_manager_data_buffer_node_next_execution_order(ma_resource_manager_data_buffer_node* pDataBufferNode)
7696 {
7697     MA_ASSERT(pDataBufferNode != NULL);
7698     return c89atomic_fetch_add_32(&pDataBufferNode->executionCounter, 1);
7699 }
7700 
ma_resource_manager_data_buffer_get_connector(ma_resource_manager_data_buffer * pDataBuffer)7701 static ma_data_source* ma_resource_manager_data_buffer_get_connector(ma_resource_manager_data_buffer* pDataBuffer)
7702 {
7703     switch (pDataBuffer->pNode->data.type)
7704     {
7705         case ma_resource_manager_data_supply_type_encoded:       return &pDataBuffer->connector.decoder;
7706         case ma_resource_manager_data_supply_type_decoded:       return &pDataBuffer->connector.buffer;
7707         case ma_resource_manager_data_supply_type_decoded_paged: return &pDataBuffer->connector.pagedBuffer;
7708 
7709         case ma_resource_manager_data_supply_type_unknown:
7710         default:
7711         {
7712             ma_log_postf(ma_resource_manager_get_log(pDataBuffer->pResourceManager), MA_LOG_LEVEL_ERROR, "Failed to retrieve data buffer connector. Unknown data supply type.\n");
7713             return NULL;
7714         };
7715     };
7716 }
7717 
ma_resource_manager_data_buffer_node_init_supply_encoded(ma_resource_manager * pResourceManager,ma_resource_manager_data_buffer_node * pDataBufferNode,const char * pFilePath,const wchar_t * pFilePathW)7718 static ma_result ma_resource_manager_data_buffer_node_init_supply_encoded(ma_resource_manager* pResourceManager, ma_resource_manager_data_buffer_node* pDataBufferNode, const char* pFilePath, const wchar_t* pFilePathW)
7719 {
7720     ma_result result;
7721     size_t dataSizeInBytes;
7722     void* pData;
7723 
7724     MA_ASSERT(pResourceManager != NULL);
7725     MA_ASSERT(pDataBufferNode  != NULL);
7726     MA_ASSERT(pFilePath != NULL || pFilePathW != NULL);
7727 
7728     result = ma_vfs_open_and_read_file_ex(pResourceManager->config.pVFS, pFilePath, pFilePathW, &pData, &dataSizeInBytes, &pResourceManager->config.allocationCallbacks, MA_ALLOCATION_TYPE_ENCODED_BUFFER);
7729     if (result != MA_SUCCESS) {
7730         if (pFilePath != NULL) {
7731             ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_WARNING, "Failed to load file \"%s\". %s.\n", pFilePath, ma_result_description(result));
7732         } else {
7733             ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_WARNING, "Failed to load file \"%ls\". %s.\n", pFilePathW, ma_result_description(result));
7734         }
7735 
7736         return result;
7737     }
7738 
7739     pDataBufferNode->data.encoded.pData       = pData;
7740     pDataBufferNode->data.encoded.sizeInBytes = dataSizeInBytes;
7741     ma_resource_manager_data_buffer_node_set_data_supply_type(pDataBufferNode, ma_resource_manager_data_supply_type_encoded);  /* <-- Must be set last. */
7742 
7743     return MA_SUCCESS;
7744 }
7745 
ma_resource_manager_data_buffer_node_init_supply_decoded(ma_resource_manager * pResourceManager,ma_resource_manager_data_buffer_node * pDataBufferNode,const char * pFilePath,const wchar_t * pFilePathW,ma_decoder ** ppDecoder)7746 static ma_result ma_resource_manager_data_buffer_node_init_supply_decoded(ma_resource_manager* pResourceManager, ma_resource_manager_data_buffer_node* pDataBufferNode, const char* pFilePath, const wchar_t* pFilePathW, ma_decoder** ppDecoder)
7747 {
7748     ma_result result = MA_SUCCESS;
7749     ma_decoder* pDecoder;
7750     ma_uint64 totalFrameCount;
7751 
7752     MA_ASSERT(pResourceManager != NULL);
7753     MA_ASSERT(pDataBufferNode  != NULL);
7754     MA_ASSERT(ppDecoder         != NULL);
7755     MA_ASSERT(pFilePath != NULL || pFilePathW != NULL);
7756 
7757     *ppDecoder = NULL;  /* For safety. */
7758 
7759     pDecoder = (ma_decoder*)ma_malloc(sizeof(*pDecoder), &pResourceManager->config.allocationCallbacks/*, MA_ALLOCATION_TYPE_DECODER*/);
7760     if (pDecoder == NULL) {
7761         return MA_OUT_OF_MEMORY;
7762     }
7763 
7764     result = ma_resource_manager__init_decoder(pResourceManager, pFilePath, pFilePathW, pDecoder);
7765     if (result != MA_SUCCESS) {
7766         ma_free(pDecoder, &pResourceManager->config.allocationCallbacks/*, MA_ALLOCATION_TYPE_DECODER*/);
7767         return result;
7768     }
7769 
7770     /*
7771     At this point we have the decoder and we now need to initialize the data supply. This will
7772     be either a decoded buffer, or a decoded paged buffer. A regular buffer is just one big heap
7773     allocated buffer, whereas a paged buffer is a linked list of paged-sized buffers. The latter
7774     is used when the length of a sound is unknown until a full decode has been performed.
7775     */
7776     totalFrameCount = ma_decoder_get_length_in_pcm_frames(pDecoder);
7777     if (totalFrameCount > 0) {
7778         /* It's a known length. The data supply is a regular decoded buffer. */
7779         ma_uint64 dataSizeInBytes;
7780         void* pData;
7781 
7782         dataSizeInBytes = totalFrameCount * ma_get_bytes_per_frame(pDecoder->outputFormat, pDecoder->outputChannels);
7783         if (dataSizeInBytes > MA_SIZE_MAX) {
7784             ma_decoder_uninit(pDecoder);
7785             ma_free(pDecoder, &pResourceManager->config.allocationCallbacks/*, MA_ALLOCATION_TYPE_DECODER*/);
7786             return MA_TOO_BIG;
7787         }
7788 
7789         pData = ma_malloc((size_t)dataSizeInBytes, &pResourceManager->config.allocationCallbacks/*, MA_ALLOCATION_TYPE_DECODED_BUFFER*/);
7790         if (pData == NULL) {
7791             ma_decoder_uninit(pDecoder);
7792             ma_free(pDecoder, &pResourceManager->config.allocationCallbacks/*, MA_ALLOCATION_TYPE_DECODER*/);
7793             return MA_OUT_OF_MEMORY;
7794         }
7795 
7796         /* The buffer needs to be initialized to silence in case the caller reads from it. */
7797         ma_silence_pcm_frames(pData, totalFrameCount, pDecoder->outputFormat, pDecoder->outputChannels);
7798 
7799         /* Data has been allocated and the data supply can now be initialized. */
7800         pDataBufferNode->data.decoded.pData             = pData;
7801         pDataBufferNode->data.decoded.totalFrameCount   = totalFrameCount;
7802         pDataBufferNode->data.decoded.format            = pDecoder->outputFormat;
7803         pDataBufferNode->data.decoded.channels          = pDecoder->outputChannels;
7804         pDataBufferNode->data.decoded.sampleRate        = pDecoder->outputSampleRate;
7805         pDataBufferNode->data.decoded.decodedFrameCount = 0;
7806         ma_resource_manager_data_buffer_node_set_data_supply_type(pDataBufferNode, ma_resource_manager_data_supply_type_decoded);  /* <-- Must be set last. */
7807     } else {
7808         /*
7809         It's an unknown length. The data supply is a paged decoded buffer. Setting this up is
7810         actually easier than the non-paged decoded buffer because we just need to initialize
7811         a ma_paged_audio_buffer object.
7812         */
7813         result = ma_paged_audio_buffer_data_init(pDecoder->outputFormat, pDecoder->outputChannels, &pDataBufferNode->data.decodedPaged.data);
7814         if (result != MA_SUCCESS) {
7815             ma_decoder_uninit(pDecoder);
7816             ma_free(pDecoder, &pResourceManager->config.allocationCallbacks/*, MA_ALLOCATION_TYPE_DECODER*/);
7817             return result;
7818         }
7819 
7820         pDataBufferNode->data.decodedPaged.sampleRate        = pDecoder->outputSampleRate;
7821         pDataBufferNode->data.decodedPaged.decodedFrameCount = 0;
7822         ma_resource_manager_data_buffer_node_set_data_supply_type(pDataBufferNode, ma_resource_manager_data_supply_type_decoded_paged);  /* <-- Must be set last. */
7823     }
7824 
7825     *ppDecoder = pDecoder;
7826 
7827     return MA_SUCCESS;
7828 }
7829 
ma_resource_manager_data_buffer_node_decode_next_page(ma_resource_manager * pResourceManager,ma_resource_manager_data_buffer_node * pDataBufferNode,ma_decoder * pDecoder)7830 static ma_result ma_resource_manager_data_buffer_node_decode_next_page(ma_resource_manager* pResourceManager, ma_resource_manager_data_buffer_node* pDataBufferNode, ma_decoder* pDecoder)
7831 {
7832     ma_result result = MA_SUCCESS;
7833     ma_uint64 pageSizeInFrames;
7834     ma_uint64 framesToTryReading;
7835     ma_uint64 framesRead;
7836 
7837     MA_ASSERT(pResourceManager != NULL);
7838     MA_ASSERT(pDataBufferNode  != NULL);
7839     MA_ASSERT(pDecoder         != NULL);
7840 
7841     /* We need to know the size of a page in frames to know how many frames to decode. */
7842     pageSizeInFrames = MA_RESOURCE_MANAGER_PAGE_SIZE_IN_MILLISECONDS * (pDecoder->outputSampleRate/1000);
7843     framesToTryReading = pageSizeInFrames;
7844 
7845     /*
7846     Here is where we do the decoding of the next page. We'll run a slightly different path depending
7847     on whether or not we're using a flat or paged buffer because the allocation of the page differs
7848     between the two. For a flat buffer it's an offset to an already-allocated buffer. For a paged
7849     buffer, we need to allocate a new page and attach it to the linked list.
7850     */
7851     switch (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBufferNode))
7852     {
7853         case ma_resource_manager_data_supply_type_decoded:
7854         {
7855             /* The destination buffer is an offset to the existing buffer. Don't read more than we originally retrieved when we first initialized the decoder. */
7856             void* pDst;
7857             ma_uint64 framesRemaining = pDataBufferNode->data.decoded.totalFrameCount - pDataBufferNode->data.decoded.decodedFrameCount;
7858             if (framesToTryReading > framesRemaining) {
7859                 framesToTryReading = framesRemaining;
7860             }
7861 
7862             pDst = ma_offset_ptr(
7863                 pDataBufferNode->data.decoded.pData,
7864                 pDataBufferNode->data.decoded.decodedFrameCount * ma_get_bytes_per_frame(pDataBufferNode->data.decoded.format, pDataBufferNode->data.decoded.channels)
7865             );
7866             MA_ASSERT(pDst != NULL);
7867 
7868             framesRead = ma_decoder_read_pcm_frames(pDecoder, pDst, framesToTryReading);
7869             if (framesRead > 0) {
7870                 pDataBufferNode->data.decoded.decodedFrameCount += framesRead;
7871             }
7872         } break;
7873 
7874         case ma_resource_manager_data_supply_type_decoded_paged:
7875         {
7876             /* The destination buffer is a freshly allocated page. */
7877             ma_paged_audio_buffer_page* pPage;
7878 
7879             result = ma_paged_audio_buffer_data_allocate_page(&pDataBufferNode->data.decodedPaged.data, framesToTryReading, NULL, &pResourceManager->config.allocationCallbacks, &pPage);
7880             if (result != MA_SUCCESS) {
7881                 return result;
7882             }
7883 
7884             framesRead = ma_decoder_read_pcm_frames(pDecoder, pPage->pAudioData, framesToTryReading);
7885             if (framesRead > 0) {
7886                 pPage->sizeInFrames = framesRead;
7887 
7888                 result = ma_paged_audio_buffer_data_append_page(&pDataBufferNode->data.decodedPaged.data, pPage);
7889                 if (result == MA_SUCCESS) {
7890                     pDataBufferNode->data.decodedPaged.decodedFrameCount += framesRead;
7891                 } else {
7892                     /* Failed to append the page. Just abort and set the status to MA_AT_END. */
7893                     ma_paged_audio_buffer_data_free_page(&pDataBufferNode->data.decodedPaged.data, pPage, &pResourceManager->config.allocationCallbacks);
7894                     result = MA_AT_END;
7895                 }
7896             } else {
7897                 /* No frames were read. Free the page and just set the status to MA_AT_END. */
7898                 ma_paged_audio_buffer_data_free_page(&pDataBufferNode->data.decodedPaged.data, pPage, &pResourceManager->config.allocationCallbacks);
7899                 result = MA_AT_END;
7900             }
7901         } break;
7902 
7903         case ma_resource_manager_data_supply_type_encoded:
7904         case ma_resource_manager_data_supply_type_unknown:
7905         default:
7906         {
7907             /* Unexpected data supply type. */
7908             ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_ERROR, "Unexpected data supply type (%d) when decoding page.", ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBufferNode));
7909             return MA_ERROR;
7910         };
7911     }
7912 
7913     if (result == MA_SUCCESS && framesRead == 0) {
7914         result = MA_AT_END;
7915     }
7916 
7917     return result;
7918 }
7919 
ma_resource_manager_data_buffer_node_acquire(ma_resource_manager * pResourceManager,const char * pFilePath,const wchar_t * pFilePathW,ma_uint32 hashedName32,ma_uint32 flags,const ma_resource_manager_data_supply * pExistingData,ma_fence * pInitFence,ma_fence * pDoneFence,ma_resource_manager_data_buffer_node ** ppDataBufferNode)7920 static ma_result ma_resource_manager_data_buffer_node_acquire(ma_resource_manager* pResourceManager, const char* pFilePath, const wchar_t* pFilePathW, ma_uint32 hashedName32, ma_uint32 flags, const ma_resource_manager_data_supply* pExistingData, ma_fence* pInitFence, ma_fence* pDoneFence, ma_resource_manager_data_buffer_node** ppDataBufferNode)
7921 {
7922     ma_result result = MA_SUCCESS;
7923     ma_bool32 nodeAlreadyExists = MA_FALSE;
7924     ma_resource_manager_data_buffer_node* pDataBufferNode = NULL;
7925     ma_resource_manager_inline_notification initNotification;   /* Used when the WAIT_INIT flag is set. */
7926 
7927     if (ppDataBufferNode != NULL) {
7928         *ppDataBufferNode = NULL;   /* Safety. */
7929     }
7930 
7931     if (pResourceManager == NULL || (pFilePath == NULL && pFilePathW == NULL && hashedName32 == 0)) {
7932         return MA_INVALID_ARGS;
7933     }
7934 
7935     /* If we're specifying existing data, it must be valid. */
7936     if (pExistingData != NULL && pExistingData->type == ma_resource_manager_data_supply_type_unknown) {
7937         return MA_INVALID_ARGS;
7938     }
7939 
7940     /* If we don't support threading, remove the ASYNC flag to make the rest of this a bit simpler. */
7941     if (ma_resource_manager_is_threading_enabled(pResourceManager) == MA_FALSE) {
7942         flags &= ~MA_DATA_SOURCE_FLAG_ASYNC;
7943     }
7944 
7945     if (hashedName32 == 0) {
7946         if (pFilePath != NULL) {
7947             hashedName32 = ma_hash_string_32(pFilePath);
7948         } else {
7949             hashedName32 = ma_hash_string_w_32(pFilePathW);
7950         }
7951     }
7952 
7953     /*
7954     Here is where we either increment the node's reference count or allocate a new one and add it
7955     to the BST. When allocating a new node, we need to make sure the LOAD_DATA_BUFFER_NODE job is
7956     posted inside the critical section just in case the caller immediately uninitializes the node
7957     as this will ensure the FREE_DATA_BUFFER_NODE job is given an execution order such that the
7958     node is not uninitialized before initialization.
7959     */
7960     ma_resource_manager_data_buffer_bst_lock(pResourceManager);
7961     {
7962         ma_resource_manager_data_buffer_node* pInsertPoint;
7963 
7964         result = ma_resource_manager_data_buffer_node_insert_point(pResourceManager, hashedName32, &pInsertPoint);
7965         if (result == MA_ALREADY_EXISTS) {
7966             /* The node already exists. We just need to increment the reference count. */
7967             pDataBufferNode = pInsertPoint;
7968 
7969             result = ma_resource_manager_data_buffer_node_increment_ref(pResourceManager, pDataBufferNode, NULL);
7970             if (result != MA_SUCCESS) {
7971                 goto early_exit;  /* Should never happen. Failed to increment the reference count. */
7972             }
7973 
7974             nodeAlreadyExists = MA_TRUE;    /* This is used later on, outside of this critical section, to determine whether or not a synchronous load should happen now. */
7975         } else {
7976             /*
7977             The node does not already exist. We need to post a LOAD_DATA_BUFFER_NODE job here. This
7978             needs to be done inside the critical section to ensure an uninitialization of the node
7979             does not occur before initialization on another thread.
7980             */
7981             pDataBufferNode = (ma_resource_manager_data_buffer_node*)ma_malloc(sizeof(*pDataBufferNode), &pResourceManager->config.allocationCallbacks);
7982             if (pDataBufferNode == NULL) {
7983                 result = MA_OUT_OF_MEMORY;
7984                 goto early_exit;
7985             }
7986 
7987             MA_ZERO_OBJECT(pDataBufferNode);
7988             pDataBufferNode->hashedName32 = hashedName32;
7989             pDataBufferNode->refCount     = 1;        /* Always set to 1 by default (this is our first reference). */
7990 
7991             if (pExistingData == NULL) {
7992                 pDataBufferNode->data.type    = ma_resource_manager_data_supply_type_unknown;    /* <-- We won't know this until we start decoding. */
7993                 pDataBufferNode->result       = MA_BUSY;  /* Must be set to MA_BUSY before we leave the critical section, so might as well do it now. */
7994                 pDataBufferNode->isDataOwnedByResourceManager = MA_TRUE;
7995             } else {
7996                 pDataBufferNode->data         = *pExistingData;
7997                 pDataBufferNode->result       = MA_SUCCESS;   /* Not loading asynchronously, so just set the status */
7998                 pDataBufferNode->isDataOwnedByResourceManager = MA_FALSE;
7999             }
8000 
8001             result = ma_resource_manager_data_buffer_node_insert_at(pResourceManager, pDataBufferNode, pInsertPoint);
8002             if (result != MA_SUCCESS) {
8003                 ma_free(pDataBufferNode, &pResourceManager->config.allocationCallbacks/*, MA_ALLOCATION_TYPE_RESOURCE_MANAGER_DATA_BUFFER*/);
8004                 goto early_exit;  /* Should never happen. Failed to insert the data buffer into the BST. */
8005             }
8006 
8007             /*
8008             Here is where we'll post the job, but only if we're loading asynchronously. If we're
8009             loading synchronously we'll defer loading to a later stage, outside of the critical
8010             section.
8011             */
8012             if (pDataBufferNode->isDataOwnedByResourceManager && (flags & MA_DATA_SOURCE_FLAG_ASYNC) != 0) {
8013                 /* Loading asynchronously. Post the job. */
8014                 ma_job job;
8015                 char* pFilePathCopy = NULL;
8016                 wchar_t* pFilePathWCopy = NULL;
8017 
8018                 /* 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. */
8019                 if (pFilePath != NULL) {
8020                     pFilePathCopy = ma_copy_string(pFilePath, &pResourceManager->config.allocationCallbacks/*, MA_ALLOCATION_TYPE_TRANSIENT_STRING*/);
8021                 } else {
8022                     pFilePathWCopy = ma_copy_string_w(pFilePathW, &pResourceManager->config.allocationCallbacks/*, MA_ALLOCATION_TYPE_TRANSIENT_STRING*/);
8023                 }
8024 
8025                 if (pFilePathCopy == NULL && pFilePathWCopy == NULL) {
8026                     result = MA_OUT_OF_MEMORY;
8027                     goto done;
8028                 }
8029 
8030                 if ((flags & MA_DATA_SOURCE_FLAG_WAIT_INIT) != 0) {
8031                     ma_resource_manager_inline_notification_init(pResourceManager, &initNotification);
8032                 }
8033 
8034                 /* Acquire init and done fences before posting the job. These will be unacquired by the job thread. */
8035                 if (pInitFence != NULL) { ma_fence_acquire(pInitFence); }
8036                 if (pDoneFence != NULL) { ma_fence_acquire(pDoneFence); }
8037 
8038                 /* We now have everything we need to post the job to the job thread. */
8039                 job = ma_job_init(MA_JOB_LOAD_DATA_BUFFER_NODE);
8040                 job.order = ma_resource_manager_data_buffer_node_next_execution_order(pDataBufferNode);
8041                 job.loadDataBufferNode.pDataBufferNode   = pDataBufferNode;
8042                 job.loadDataBufferNode.pFilePath         = pFilePathCopy;
8043                 job.loadDataBufferNode.pFilePathW        = pFilePathWCopy;
8044                 job.loadDataBufferNode.decode            =  (flags & MA_DATA_SOURCE_FLAG_DECODE   ) != 0;
8045                 job.loadDataBufferNode.pInitNotification = ((flags & MA_DATA_SOURCE_FLAG_WAIT_INIT) != 0) ? &initNotification : NULL;
8046                 job.loadDataBufferNode.pDoneNotification = NULL;
8047                 job.loadDataBufferNode.pInitFence        = pInitFence;
8048                 job.loadDataBufferNode.pDoneFence        = pDoneFence;
8049 
8050                 result = ma_resource_manager_post_job(pResourceManager, &job);
8051                 if (result != MA_SUCCESS) {
8052                     /* Failed to post job. Probably ran out of memory. */
8053                     ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_ERROR, "Failed to post MA_JOB_LOAD_DATA_BUFFER_NODE job. %s.\n", ma_result_description(result));
8054 
8055                     /*
8056                     Fences were acquired before posting the job, but since the job was not able to
8057                     be posted, we need to make sure we release them so nothing gets stuck waiting.
8058                     */
8059                     if (pInitFence != NULL) { ma_fence_release(pInitFence); }
8060                     if (pDoneFence != NULL) { ma_fence_release(pDoneFence); }
8061 
8062                     ma_free(pFilePathCopy,  &pResourceManager->config.allocationCallbacks/*, MA_ALLOCATION_TYPE_TRANSIENT_STRING*/);
8063                     ma_free(pFilePathWCopy, &pResourceManager->config.allocationCallbacks/*, MA_ALLOCATION_TYPE_TRANSIENT_STRING*/);
8064                     goto done;
8065                 }
8066             }
8067         }
8068     }
8069     ma_resource_manager_data_buffer_bst_unlock(pResourceManager);
8070 
8071 early_exit:
8072     if (result != MA_SUCCESS) {
8073         return result;
8074     }
8075 
8076     /*
8077     If we're loading synchronously, we'll need to load everything now. When loading asynchronously,
8078     a job will have been posted inside the BST critical section so that an uninitialization can be
8079     allocated an appropriate execution order thereby preventing it from being uninitialized before
8080     the node is initialized by the decoding thread(s).
8081     */
8082     if (nodeAlreadyExists == MA_FALSE) {    /* Don't need to try loading anything if the node already exists. */
8083         if (pFilePath == NULL && pFilePathW == NULL) {
8084             /*
8085             If this path is hit, it means a buffer is being copied (i.e. initialized from only the
8086             hashed name), but that node has been freed in the meantime, probably from some other
8087             thread. This is an invalid operation.
8088             */
8089             ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_WARNING, "Cloning data buffer node failed because the source node was released. The source node must remain valid until the cloning has completed.\n");
8090             result = MA_INVALID_OPERATION;
8091             goto done;
8092         }
8093 
8094         if (pDataBufferNode->isDataOwnedByResourceManager) {
8095             if ((flags & MA_DATA_SOURCE_FLAG_ASYNC) == 0) {
8096                 /* Loading synchronously. Load the sound in it's entirety here. */
8097                 if ((flags & MA_DATA_SOURCE_FLAG_DECODE) == 0) {
8098                     /* No decoding. This is the simple case - just store the file contents in memory. */
8099                     result = ma_resource_manager_data_buffer_node_init_supply_encoded(pResourceManager, pDataBufferNode, pFilePath, pFilePathW);
8100                     if (result != MA_SUCCESS) {
8101                         goto done;
8102                     }
8103                 } else {
8104                     /* Decoding. We do this the same way as we do when loading asynchronously. */
8105                     ma_decoder* pDecoder;
8106                     result = ma_resource_manager_data_buffer_node_init_supply_decoded(pResourceManager, pDataBufferNode, pFilePath, pFilePathW, &pDecoder);
8107                     if (result != MA_SUCCESS) {
8108                         goto done;
8109                     }
8110 
8111                     /* We have the decoder, now decode page by page just like we do when loading asynchronously. */
8112                     for (;;) {
8113                         /* Decode next page. */
8114                         result = ma_resource_manager_data_buffer_node_decode_next_page(pResourceManager, pDataBufferNode, pDecoder);
8115                         if (result != MA_SUCCESS) {
8116                             break;  /* Will return MA_AT_END when the last page has been decoded. */
8117                         }
8118                     }
8119 
8120                     /* Reaching the end needs to be considered successful. */
8121                     if (result == MA_AT_END) {
8122                         result  = MA_SUCCESS;
8123                     }
8124 
8125                     /*
8126                     At this point the data buffer is either fully decoded or some error occurred. Either
8127                     way, the decoder is no longer necessary.
8128                     */
8129                     ma_decoder_uninit(pDecoder);
8130                     ma_free(pDecoder, &pResourceManager->config.allocationCallbacks);
8131                 }
8132 
8133                 /* Getting here means we were successful. Make sure the status of the node is updated accordingly. */
8134                 c89atomic_exchange_i32(&pDataBufferNode->result, result);
8135             } else {
8136                 /* Loading asynchronously. We may need to wait for initialization. */
8137                 if ((flags & MA_DATA_SOURCE_FLAG_WAIT_INIT) != 0) {
8138                     ma_resource_manager_inline_notification_wait(&initNotification);
8139                 }
8140             }
8141         } else {
8142             /* The data is not managed by the resource manager so there's nothing else to do. */
8143             MA_ASSERT(pExistingData != NULL);
8144         }
8145     }
8146 
8147 done:
8148     /* If we failed to initialize the data buffer we need to free it. */
8149     if (result != MA_SUCCESS) {
8150         if (nodeAlreadyExists == MA_FALSE) {
8151             ma_resource_manager_data_buffer_node_remove(pResourceManager, pDataBufferNode);
8152             ma_free(pDataBufferNode, &pResourceManager->config.allocationCallbacks/*, MA_ALLOCATION_TYPE_RESOURCE_MANAGER_DATA_BUFFER*/);
8153         }
8154     }
8155 
8156     /*
8157     The init notification needs to be uninitialized. This will be used if the node does not already
8158     exist, and we've specified ASYNC | WAIT_INIT.
8159     */
8160     if (nodeAlreadyExists == MA_FALSE && pDataBufferNode->isDataOwnedByResourceManager && (flags & MA_DATA_SOURCE_FLAG_ASYNC) != 0) {
8161         if ((flags & MA_DATA_SOURCE_FLAG_WAIT_INIT) != 0) {
8162             ma_resource_manager_inline_notification_uninit(&initNotification);
8163         }
8164     }
8165 
8166     if (ppDataBufferNode != NULL) {
8167         *ppDataBufferNode = pDataBufferNode;
8168     }
8169 
8170     return result;
8171 }
8172 
ma_resource_manager_data_buffer_node_unacquire(ma_resource_manager * pResourceManager,ma_resource_manager_data_buffer_node * pDataBufferNode,const char * pName,const wchar_t * pNameW)8173 static ma_result ma_resource_manager_data_buffer_node_unacquire(ma_resource_manager* pResourceManager, ma_resource_manager_data_buffer_node* pDataBufferNode, const char* pName, const wchar_t* pNameW)
8174 {
8175     ma_result result = MA_SUCCESS;
8176     ma_uint32 refCount = 0xFFFFFFFF; /* The new reference count of the node after decrementing. Initialize to non-0 to be safe we don't fall into the freeing path. */
8177     ma_uint32 hashedName32 = 0;
8178 
8179     if (pResourceManager == NULL) {
8180         return MA_INVALID_ARGS;
8181     }
8182 
8183     if (pDataBufferNode == NULL) {
8184         if (pName == NULL && pNameW == NULL) {
8185             return MA_INVALID_ARGS;
8186         }
8187 
8188         if (pName != NULL) {
8189             hashedName32 = ma_hash_string_32(pName);
8190         } else {
8191             hashedName32 = ma_hash_string_w_32(pNameW);
8192         }
8193     }
8194 
8195     /*
8196     The first thing to do is decrement the reference counter of the node. Then, if the reference
8197     count is zero, we need to free the node. If the node is still in the process of loading, we'll
8198     need to post a job to the job queue to free the node. Otherwise we'll just do it here.
8199     */
8200     ma_resource_manager_data_buffer_bst_lock(pResourceManager);
8201     {
8202         /* Might need to find the node. Must be done inside the critical section. */
8203         if (pDataBufferNode == NULL) {
8204             result = ma_resource_manager_data_buffer_node_search(pResourceManager, hashedName32, &pDataBufferNode);
8205             if (result != MA_SUCCESS) {
8206                 goto stage2;    /* Couldn't find the node. */
8207             }
8208         }
8209 
8210         result = ma_resource_manager_data_buffer_node_decrement_ref(pResourceManager, pDataBufferNode, &refCount);
8211         if (result != MA_SUCCESS) {
8212             goto stage2;    /* Should never happen. */
8213         }
8214 
8215         if (refCount == 0) {
8216             result = ma_resource_manager_data_buffer_node_remove(pResourceManager, pDataBufferNode);
8217             if (result != MA_SUCCESS) {
8218                 goto stage2;  /* An error occurred when trying to remove the data buffer. This should never happen. */
8219             }
8220         }
8221     }
8222     ma_resource_manager_data_buffer_bst_unlock(pResourceManager);
8223 
8224 stage2:
8225     if (result != MA_SUCCESS) {
8226         return result;
8227     }
8228 
8229     /*
8230     Here is where we need to free the node. We don't want to do this inside the critical section
8231     above because we want to keep that as small as possible for multi-threaded efficiency.
8232     */
8233     if (refCount == 0) {
8234         if (ma_resource_manager_data_buffer_node_result(pDataBufferNode) == MA_BUSY) {
8235             /* The sound is still loading. We need to delay the freeing of the node to a safe time. */
8236             ma_job job;
8237 
8238 			/* We need to mark the node as unavailable for the sake of the resource manager worker threads. */
8239 			c89atomic_exchange_i32(&pDataBufferNode->result, MA_UNAVAILABLE);
8240 
8241 			job = ma_job_init(MA_JOB_FREE_DATA_BUFFER_NODE);
8242             job.order = ma_resource_manager_data_buffer_node_next_execution_order(pDataBufferNode);
8243             job.freeDataBufferNode.pDataBufferNode = pDataBufferNode;
8244 
8245             result = ma_resource_manager_post_job(pResourceManager, &job);
8246             if (result != MA_SUCCESS) {
8247                 ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_ERROR, "Failed to post MA_JOB_FREE_DATA_BUFFER_NODE job. %s.\n", ma_result_description(result));
8248                 return result;
8249             }
8250 
8251             /* If we don't support threading, process the job queue here. */
8252             if (ma_resource_manager_is_threading_enabled(pResourceManager) == MA_FALSE) {
8253                 while (ma_resource_manager_data_buffer_node_result(pDataBufferNode) == MA_BUSY) {
8254                     result = ma_resource_manager_process_next_job(pResourceManager);
8255                     if (result == MA_NO_DATA_AVAILABLE || result == MA_JOB_QUIT) {
8256                         result = MA_SUCCESS;
8257                         break;
8258                     }
8259                 }
8260             } else {
8261                 /* Threading is enabled. The job queue will deal with the rest of the cleanup from here. */
8262             }
8263         } else {
8264             /* The sound isn't loading so we can just free the node here. */
8265             ma_resource_manager_data_buffer_node_free(pResourceManager, pDataBufferNode);
8266         }
8267     }
8268 
8269     return result;
8270 }
8271 
8272 
8273 
ma_resource_manager_data_buffer_next_execution_order(ma_resource_manager_data_buffer * pDataBuffer)8274 static ma_uint32 ma_resource_manager_data_buffer_next_execution_order(ma_resource_manager_data_buffer* pDataBuffer)
8275 {
8276     MA_ASSERT(pDataBuffer != NULL);
8277     return c89atomic_fetch_add_32(&pDataBuffer->executionCounter, 1);
8278 }
8279 
ma_resource_manager_data_buffer_cb__read_pcm_frames(ma_data_source * pDataSource,void * pFramesOut,ma_uint64 frameCount,ma_uint64 * pFramesRead)8280 static ma_result ma_resource_manager_data_buffer_cb__read_pcm_frames(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead)
8281 {
8282     return ma_resource_manager_data_buffer_read_pcm_frames((ma_resource_manager_data_buffer*)pDataSource, pFramesOut, frameCount, pFramesRead);
8283 }
8284 
ma_resource_manager_data_buffer_cb__seek_to_pcm_frame(ma_data_source * pDataSource,ma_uint64 frameIndex)8285 static ma_result ma_resource_manager_data_buffer_cb__seek_to_pcm_frame(ma_data_source* pDataSource, ma_uint64 frameIndex)
8286 {
8287     return ma_resource_manager_data_buffer_seek_to_pcm_frame((ma_resource_manager_data_buffer*)pDataSource, frameIndex);
8288 }
8289 
ma_resource_manager_data_buffer_cb__get_data_format(ma_data_source * pDataSource,ma_format * pFormat,ma_uint32 * pChannels,ma_uint32 * pSampleRate)8290 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)
8291 {
8292     return ma_resource_manager_data_buffer_get_data_format((ma_resource_manager_data_buffer*)pDataSource, pFormat, pChannels, pSampleRate);
8293 }
8294 
ma_resource_manager_data_buffer_cb__get_cursor_in_pcm_frames(ma_data_source * pDataSource,ma_uint64 * pCursor)8295 static ma_result ma_resource_manager_data_buffer_cb__get_cursor_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pCursor)
8296 {
8297     return ma_resource_manager_data_buffer_get_cursor_in_pcm_frames((ma_resource_manager_data_buffer*)pDataSource, pCursor);
8298 }
8299 
ma_resource_manager_data_buffer_cb__get_length_in_pcm_frames(ma_data_source * pDataSource,ma_uint64 * pLength)8300 static ma_result ma_resource_manager_data_buffer_cb__get_length_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pLength)
8301 {
8302     return ma_resource_manager_data_buffer_get_length_in_pcm_frames((ma_resource_manager_data_buffer*)pDataSource, pLength);
8303 }
8304 
8305 static ma_data_source_vtable g_ma_resource_manager_data_buffer_vtable =
8306 {
8307     ma_resource_manager_data_buffer_cb__read_pcm_frames,
8308     ma_resource_manager_data_buffer_cb__seek_to_pcm_frame,
8309     NULL,   /* onMap */
8310     NULL,   /* onUnmap */
8311     ma_resource_manager_data_buffer_cb__get_data_format,
8312     ma_resource_manager_data_buffer_cb__get_cursor_in_pcm_frames,
8313     ma_resource_manager_data_buffer_cb__get_length_in_pcm_frames
8314 };
8315 
ma_resource_manager_data_buffer_init_internal(ma_resource_manager * pResourceManager,const char * pFilePath,const wchar_t * pFilePathW,ma_uint32 hashedName32,ma_uint32 flags,const ma_pipeline_notifications * pNotifications,ma_resource_manager_data_buffer * pDataBuffer)8316 static ma_result ma_resource_manager_data_buffer_init_internal(ma_resource_manager* pResourceManager, const char* pFilePath, const wchar_t* pFilePathW, ma_uint32 hashedName32, ma_uint32 flags, const ma_pipeline_notifications* pNotifications, ma_resource_manager_data_buffer* pDataBuffer)
8317 {
8318     ma_result result = MA_SUCCESS;
8319     ma_resource_manager_data_buffer_node* pDataBufferNode;
8320     ma_data_source_config dataSourceConfig;
8321     ma_bool32 async;
8322     ma_pipeline_notifications notifications;
8323 
8324     if (pNotifications != NULL) {
8325         notifications = *pNotifications;
8326         pNotifications = NULL;  /* From here on out we should be referencing `notifications` instead of `pNotifications`. Set this to NULL to catch errors at testing time. */
8327     } else {
8328         MA_ZERO_OBJECT(&notifications);
8329     }
8330 
8331     if (pDataBuffer == NULL) {
8332         ma_pipeline_notifications_signal_all_notifications(&notifications);
8333         return MA_INVALID_ARGS;
8334     }
8335 
8336     MA_ZERO_OBJECT(pDataBuffer);
8337 
8338     /* For safety, always remove the ASYNC flag if threading is disabled on the resource manager. */
8339     if (ma_resource_manager_is_threading_enabled(pResourceManager) == MA_FALSE) {
8340         flags &= ~MA_DATA_SOURCE_FLAG_ASYNC;
8341     }
8342 
8343     async = (flags & MA_DATA_SOURCE_FLAG_ASYNC) != 0;
8344 
8345     /*
8346     Fences need to be acquired before doing anything. These must be aquired and released outside of
8347     the node to ensure there's no holes where ma_fence_wait() could prematurely return before the
8348     data buffer has completed initialization.
8349 
8350     When loading asynchronously, the node acquisition routine below will acquire the fences on this
8351     thread and then release them on the async thread when the operation is complete.
8352 
8353     These fences are always released at the "done" tag at the end of this function. They'll be
8354     acquired a second if loading asynchronously. This double acquisition system is just done to
8355     simplify code maintanence.
8356     */
8357     ma_pipeline_notifications_acquire_all_fences(&notifications);
8358     {
8359         /* We first need to acquire a node. If ASYNC is not set, this will not return until the entire sound has been loaded. */
8360         result = ma_resource_manager_data_buffer_node_acquire(pResourceManager, pFilePath, pFilePathW, hashedName32, flags, NULL, notifications.init.pFence, notifications.done.pFence, &pDataBufferNode);
8361         if (result != MA_SUCCESS) {
8362             ma_pipeline_notifications_signal_all_notifications(&notifications);
8363             goto done;
8364         }
8365 
8366         dataSourceConfig = ma_data_source_config_init();
8367         dataSourceConfig.vtable = &g_ma_resource_manager_data_buffer_vtable;
8368 
8369         result = ma_data_source_init(&dataSourceConfig, &pDataBuffer->ds);
8370         if (result != MA_SUCCESS) {
8371             ma_resource_manager_data_buffer_node_unacquire(pResourceManager, pDataBufferNode, NULL, NULL);
8372             ma_pipeline_notifications_signal_all_notifications(&notifications);
8373             goto done;
8374         }
8375 
8376         pDataBuffer->pResourceManager = pResourceManager;
8377         pDataBuffer->pNode  = pDataBufferNode;
8378         pDataBuffer->flags  = flags;
8379         pDataBuffer->result = MA_BUSY;  /* Always default to MA_BUSY for safety. It'll be overwritten when loading completes or an error occurs. */
8380 
8381         /* If we're loading asynchronously we need to post a job to the job queue to initialize the connector. */
8382         if (async == MA_FALSE || ma_resource_manager_data_buffer_node_result(pDataBufferNode) == MA_SUCCESS) {
8383             /* Loading synchronously or the data has already been fully loaded. We can just initialize the connector from here without a job. */
8384             result = ma_resource_manager_data_buffer_init_connector(pDataBuffer, NULL, NULL);
8385             c89atomic_exchange_i32(&pDataBuffer->result, result);
8386 
8387             ma_pipeline_notifications_signal_all_notifications(&notifications);
8388             goto done;
8389         } else {
8390             /* The node's data supply isn't initialized yet. The caller has requested that we load asynchronously so we need to post a job to do this. */
8391             ma_job job;
8392             ma_resource_manager_inline_notification initNotification;   /* Used when the WAIT_INIT flag is set. */
8393 
8394             if ((flags & MA_DATA_SOURCE_FLAG_WAIT_INIT) != 0) {
8395                 ma_resource_manager_inline_notification_init(pResourceManager, &initNotification);
8396             }
8397 
8398             /*
8399             The status of the data buffer needs to be set to MA_BUSY before posting the job so that the
8400             worker thread is aware of it's busy state. If the LOAD_DATA_BUFFER job sees a status other
8401             than MA_BUSY, it'll assume an error and fall through to an early exit.
8402             */
8403             c89atomic_exchange_i32(&pDataBuffer->result, MA_BUSY);
8404 
8405             /* Acquire fences a second time. These will be released by the async thread. */
8406             ma_pipeline_notifications_acquire_all_fences(&notifications);
8407 
8408             job = ma_job_init(MA_JOB_LOAD_DATA_BUFFER);
8409             job.order = ma_resource_manager_data_buffer_next_execution_order(pDataBuffer);
8410             job.loadDataBuffer.pDataBuffer       = pDataBuffer;
8411             job.loadDataBuffer.pInitNotification = ((flags & MA_DATA_SOURCE_FLAG_WAIT_INIT) != 0) ? &initNotification : notifications.init.pNotification;
8412             job.loadDataBuffer.pDoneNotification = notifications.done.pNotification;
8413             job.loadDataBuffer.pInitFence        = notifications.init.pFence;
8414             job.loadDataBuffer.pDoneFence        = notifications.done.pFence;
8415 
8416             result = ma_resource_manager_post_job(pResourceManager, &job);
8417             if (result != MA_SUCCESS) {
8418                 /* We failed to post the job. Most likely there isn't enough room in the queue's buffer. */
8419                 ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_ERROR, "Failed to post MA_JOB_LOAD_DATA_BUFFER job. %s.\n", ma_result_description(result));
8420                 c89atomic_exchange_i32(&pDataBuffer->result, result);
8421 
8422                 /* Release the fences after the result has been set on the data buffer. */
8423                 ma_pipeline_notifications_release_all_fences(&notifications);
8424             } else {
8425                 if ((flags & MA_DATA_SOURCE_FLAG_WAIT_INIT) != 0) {
8426                     ma_resource_manager_inline_notification_wait(&initNotification);
8427 
8428                     if (notifications.init.pNotification != NULL) {
8429                         ma_async_notification_signal(notifications.init.pNotification);
8430                     }
8431 
8432                     /* NOTE: Do not release the init fence here. It will have been done by the job. */
8433 
8434 				    /* Make sure we return an error if initialization failed on the async thread. */
8435 				    result = ma_resource_manager_data_buffer_result(pDataBuffer);
8436 				    if (result == MA_BUSY) {
8437 					    result  = MA_SUCCESS;
8438 				    }
8439                 }
8440             }
8441 
8442             if ((flags & MA_DATA_SOURCE_FLAG_WAIT_INIT) != 0) {
8443                 ma_resource_manager_inline_notification_uninit(&initNotification);
8444             }
8445         }
8446 
8447         if (result != MA_SUCCESS) {
8448             ma_resource_manager_data_buffer_node_unacquire(pResourceManager, pDataBufferNode, NULL, NULL);
8449             goto done;
8450         }
8451     }
8452 done:
8453     ma_pipeline_notifications_release_all_fences(&notifications);
8454 
8455     return result;
8456 }
8457 
ma_resource_manager_data_buffer_init(ma_resource_manager * pResourceManager,const char * pFilePath,ma_uint32 flags,const ma_pipeline_notifications * pNotifications,ma_resource_manager_data_buffer * pDataBuffer)8458 MA_API ma_result ma_resource_manager_data_buffer_init(ma_resource_manager* pResourceManager, const char* pFilePath, ma_uint32 flags, const ma_pipeline_notifications* pNotifications, ma_resource_manager_data_buffer* pDataBuffer)
8459 {
8460     return ma_resource_manager_data_buffer_init_internal(pResourceManager, pFilePath, NULL, 0, flags, pNotifications, pDataBuffer);
8461 }
8462 
ma_resource_manager_data_buffer_init_w(ma_resource_manager * pResourceManager,const wchar_t * pFilePath,ma_uint32 flags,const ma_pipeline_notifications * pNotifications,ma_resource_manager_data_buffer * pDataBuffer)8463 MA_API ma_result ma_resource_manager_data_buffer_init_w(ma_resource_manager* pResourceManager, const wchar_t* pFilePath, ma_uint32 flags, const ma_pipeline_notifications* pNotifications, ma_resource_manager_data_buffer* pDataBuffer)
8464 {
8465     return ma_resource_manager_data_buffer_init_internal(pResourceManager, NULL, pFilePath, 0, flags, pNotifications, pDataBuffer);
8466 }
8467 
ma_resource_manager_data_buffer_init_copy(ma_resource_manager * pResourceManager,const ma_resource_manager_data_buffer * pExistingDataBuffer,ma_resource_manager_data_buffer * pDataBuffer)8468 MA_API ma_result ma_resource_manager_data_buffer_init_copy(ma_resource_manager* pResourceManager, const ma_resource_manager_data_buffer* pExistingDataBuffer, ma_resource_manager_data_buffer* pDataBuffer)
8469 {
8470     if (pExistingDataBuffer == NULL) {
8471         return MA_INVALID_ARGS;
8472     }
8473 
8474     MA_ASSERT(pExistingDataBuffer->pNode != NULL);  /* <-- If you've triggered this, you've passed in an invalid existing data buffer. */
8475 
8476     return ma_resource_manager_data_buffer_init_internal(pResourceManager, NULL, NULL, pExistingDataBuffer->pNode->hashedName32, pExistingDataBuffer->flags, NULL, pDataBuffer);
8477 }
8478 
ma_resource_manager_data_buffer_uninit_internal(ma_resource_manager_data_buffer * pDataBuffer)8479 static ma_result ma_resource_manager_data_buffer_uninit_internal(ma_resource_manager_data_buffer* pDataBuffer)
8480 {
8481     MA_ASSERT(pDataBuffer != NULL);
8482 
8483     /* The connector should be uninitialized first. */
8484     ma_resource_manager_data_buffer_uninit_connector(pDataBuffer->pResourceManager, pDataBuffer);
8485 
8486     /* With the connector uninitialized we can unacquire the node. */
8487     ma_resource_manager_data_buffer_node_unacquire(pDataBuffer->pResourceManager, pDataBuffer->pNode, NULL, NULL);
8488 
8489     /* The base data source needs to be uninitialized as well. */
8490     ma_data_source_uninit(&pDataBuffer->ds);
8491 
8492     return MA_SUCCESS;
8493 }
8494 
ma_resource_manager_data_buffer_uninit(ma_resource_manager_data_buffer * pDataBuffer)8495 MA_API ma_result ma_resource_manager_data_buffer_uninit(ma_resource_manager_data_buffer* pDataBuffer)
8496 {
8497     ma_result result;
8498 
8499     if (pDataBuffer == NULL) {
8500         return MA_INVALID_ARGS;
8501     }
8502 
8503     if (ma_resource_manager_data_buffer_result(pDataBuffer) == MA_SUCCESS) {
8504         /* The data buffer can be deleted synchronously. */
8505         return ma_resource_manager_data_buffer_uninit_internal(pDataBuffer);
8506     } else {
8507         /*
8508         The data buffer needs to be deleted asynchronously because it's still loading. With the status set to MA_UNAVAILABLE, no more pages will
8509         be loaded and the uninitialization should happen fairly quickly. Since the caller owns the data buffer, we need to wait for this event
8510         to get processed before returning.
8511         */
8512         ma_resource_manager_inline_notification notification;
8513         ma_job job;
8514 
8515         /*
8516         We need to mark the node as unavailable so we don't try reading from it anymore, but also to
8517         let the loading thread know that it needs to abort it's loading procedure.
8518         */
8519         c89atomic_exchange_i32(&pDataBuffer->result, MA_UNAVAILABLE);
8520 
8521         result = ma_resource_manager_inline_notification_init(pDataBuffer->pResourceManager, &notification);
8522         if (result != MA_SUCCESS) {
8523             return result;  /* Failed to create the notification. This should rarely, if ever, happen. */
8524         }
8525 
8526         job = ma_job_init(MA_JOB_FREE_DATA_BUFFER);
8527         job.order = ma_resource_manager_data_buffer_next_execution_order(pDataBuffer);
8528         job.freeDataBuffer.pDataBuffer       = pDataBuffer;
8529         job.freeDataBuffer.pDoneNotification = &notification;
8530         job.freeDataBuffer.pDoneFence        = NULL;
8531 
8532         result = ma_resource_manager_post_job(pDataBuffer->pResourceManager, &job);
8533         if (result != MA_SUCCESS) {
8534             ma_resource_manager_inline_notification_uninit(&notification);
8535             return result;
8536         }
8537 
8538         ma_resource_manager_inline_notification_wait_and_uninit(&notification);
8539     }
8540 
8541     return result;
8542 }
8543 
ma_resource_manager_data_buffer_read_pcm_frames(ma_resource_manager_data_buffer * pDataBuffer,void * pFramesOut,ma_uint64 frameCount,ma_uint64 * pFramesRead)8544 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)
8545 {
8546     ma_result result = MA_SUCCESS;
8547     ma_uint64 framesRead = 0;
8548     ma_bool32 isLooping;
8549     ma_bool32 isDecodedBufferBusy = MA_FALSE;
8550 
8551     /* Safety. */
8552     if (pFramesRead != NULL) {
8553         *pFramesRead = 0;
8554     }
8555 
8556     /*
8557     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
8558     it's been uninitialized or is in the process of uninitializing.
8559     */
8560     MA_ASSERT(ma_resource_manager_data_buffer_node_result(pDataBuffer->pNode) != MA_UNAVAILABLE);
8561 
8562     /* If the node is not initialized we need to abort with a busy code. */
8563     if (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBuffer->pNode) == ma_resource_manager_data_supply_type_unknown) {
8564         return MA_BUSY; /* Still loading. */
8565     }
8566 
8567     if (pDataBuffer->seekToCursorOnNextRead) {
8568         pDataBuffer->seekToCursorOnNextRead = MA_FALSE;
8569 
8570         result = ma_data_source_seek_to_pcm_frame(ma_resource_manager_data_buffer_get_connector(pDataBuffer), pDataBuffer->seekTargetInPCMFrames);
8571         if (result != MA_SUCCESS) {
8572             return result;
8573         }
8574     }
8575 
8576     result = ma_resource_manager_data_buffer_get_looping(pDataBuffer, &isLooping);
8577     if (result != MA_SUCCESS) {
8578         return result;
8579     }
8580 
8581     /*
8582     For decoded buffers (not paged) we need to check beforehand how many frames we have available. We cannot
8583     exceed this amount. We'll read as much as we can, and then return MA_BUSY.
8584     */
8585     if (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBuffer->pNode) == ma_resource_manager_data_supply_type_decoded) {
8586         ma_uint64 availableFrames;
8587 
8588         isDecodedBufferBusy = (ma_resource_manager_data_buffer_node_result(pDataBuffer->pNode) == MA_BUSY);
8589 
8590         if (ma_resource_manager_data_buffer_get_available_frames(pDataBuffer, &availableFrames) == MA_SUCCESS) {
8591             /* Don't try reading more than the available frame count. */
8592             if (frameCount > availableFrames) {
8593                 frameCount = availableFrames;
8594 
8595                 /*
8596                 If there's no frames available we want to set the status to MA_AT_END. The logic below
8597                 will check if the node is busy, and if so, change it to MA_BUSY. The reason we do this
8598                 is because we don't want to call `ma_data_source_read_pcm_frames()` if the frame count
8599                 is 0 because that'll result in a situation where it's possible MA_AT_END won't get
8600                 returned.
8601                 */
8602                 if (frameCount == 0) {
8603                     result = MA_AT_END;
8604                 }
8605             } else {
8606                 isDecodedBufferBusy = MA_FALSE; /* We have enough frames available in the buffer to avoid a MA_BUSY status. */
8607             }
8608         }
8609     }
8610 
8611     /* Don't attempt to read anything if we've got no frames available. */
8612     if (frameCount > 0) {
8613         result = ma_data_source_read_pcm_frames(ma_resource_manager_data_buffer_get_connector(pDataBuffer), pFramesOut, frameCount, &framesRead, isLooping);
8614     }
8615 
8616     /*
8617     If we returned MA_AT_END, but the node is still loading, we don't want to return that code or else the caller will interpret the sound
8618     as at the end and terminate decoding.
8619     */
8620     if (result == MA_AT_END) {
8621         if (ma_resource_manager_data_buffer_node_result(pDataBuffer->pNode) == MA_BUSY) {
8622             result = MA_BUSY;
8623         }
8624     }
8625 
8626     if (isDecodedBufferBusy) {
8627         result = MA_BUSY;
8628     }
8629 
8630     if (pFramesRead != NULL) {
8631         *pFramesRead = framesRead;
8632     }
8633 
8634     return result;
8635 }
8636 
ma_resource_manager_data_buffer_seek_to_pcm_frame(ma_resource_manager_data_buffer * pDataBuffer,ma_uint64 frameIndex)8637 MA_API ma_result ma_resource_manager_data_buffer_seek_to_pcm_frame(ma_resource_manager_data_buffer* pDataBuffer, ma_uint64 frameIndex)
8638 {
8639     ma_result result;
8640 
8641     /* We cannot be using the data source after it's been uninitialized. */
8642     MA_ASSERT(ma_resource_manager_data_buffer_node_result(pDataBuffer->pNode) != MA_UNAVAILABLE);
8643 
8644     /* If we haven't yet got a connector we need to abort. */
8645     if (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBuffer->pNode) == ma_resource_manager_data_supply_type_unknown) {
8646         pDataBuffer->seekTargetInPCMFrames = frameIndex;
8647         pDataBuffer->seekToCursorOnNextRead = MA_TRUE;
8648         return MA_BUSY; /* Still loading. */
8649     }
8650 
8651     result = ma_data_source_seek_to_pcm_frame(ma_resource_manager_data_buffer_get_connector(pDataBuffer), frameIndex);
8652     if (result != MA_SUCCESS) {
8653         return result;
8654     }
8655 
8656     pDataBuffer->seekTargetInPCMFrames = ~(ma_uint64)0; /* <-- For identification purposes. */
8657     pDataBuffer->seekToCursorOnNextRead = MA_FALSE;
8658 
8659     return MA_SUCCESS;
8660 }
8661 
ma_resource_manager_data_buffer_get_data_format(ma_resource_manager_data_buffer * pDataBuffer,ma_format * pFormat,ma_uint32 * pChannels,ma_uint32 * pSampleRate)8662 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)
8663 {
8664     /* We cannot be using the data source after it's been uninitialized. */
8665     MA_ASSERT(ma_resource_manager_data_buffer_node_result(pDataBuffer->pNode) != MA_UNAVAILABLE);
8666 
8667     switch (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBuffer->pNode))
8668     {
8669         case ma_resource_manager_data_supply_type_encoded:
8670         {
8671             return ma_data_source_get_data_format(&pDataBuffer->connector.decoder, pFormat, pChannels, pSampleRate);
8672         };
8673 
8674         case ma_resource_manager_data_supply_type_decoded:
8675         {
8676             *pFormat     = pDataBuffer->pNode->data.decoded.format;
8677             *pChannels   = pDataBuffer->pNode->data.decoded.channels;
8678             *pSampleRate = pDataBuffer->pNode->data.decoded.sampleRate;
8679             return MA_SUCCESS;
8680         };
8681 
8682         case ma_resource_manager_data_supply_type_decoded_paged:
8683         {
8684             *pFormat     = pDataBuffer->pNode->data.decodedPaged.data.format;
8685             *pChannels   = pDataBuffer->pNode->data.decodedPaged.data.channels;
8686             *pSampleRate = pDataBuffer->pNode->data.decodedPaged.sampleRate;
8687             return MA_SUCCESS;
8688         };
8689 
8690         case ma_resource_manager_data_supply_type_unknown:
8691         {
8692             return MA_BUSY; /* Still loading. */
8693         };
8694 
8695         default:
8696         {
8697             /* Unknown supply type. Should never hit this. */
8698             return MA_INVALID_ARGS;
8699         }
8700     }
8701 }
8702 
ma_resource_manager_data_buffer_get_cursor_in_pcm_frames(ma_resource_manager_data_buffer * pDataBuffer,ma_uint64 * pCursor)8703 MA_API ma_result ma_resource_manager_data_buffer_get_cursor_in_pcm_frames(ma_resource_manager_data_buffer* pDataBuffer, ma_uint64* pCursor)
8704 {
8705     /* We cannot be using the data source after it's been uninitialized. */
8706     MA_ASSERT(ma_resource_manager_data_buffer_node_result(pDataBuffer->pNode) != MA_UNAVAILABLE);
8707 
8708     if (pDataBuffer == NULL || pCursor == NULL) {
8709         return MA_INVALID_ARGS;
8710     }
8711 
8712     *pCursor = 0;
8713 
8714     switch (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBuffer->pNode))
8715     {
8716         case ma_resource_manager_data_supply_type_encoded:
8717         {
8718             return ma_decoder_get_cursor_in_pcm_frames(&pDataBuffer->connector.decoder, pCursor);
8719         };
8720 
8721         case ma_resource_manager_data_supply_type_decoded:
8722         {
8723             return ma_audio_buffer_get_cursor_in_pcm_frames(&pDataBuffer->connector.buffer, pCursor);
8724         };
8725 
8726         case ma_resource_manager_data_supply_type_decoded_paged:
8727         {
8728             return ma_paged_audio_buffer_get_cursor_in_pcm_frames(&pDataBuffer->connector.pagedBuffer, pCursor);
8729         };
8730 
8731         case ma_resource_manager_data_supply_type_unknown:
8732         {
8733             return MA_BUSY;
8734         };
8735 
8736         default:
8737         {
8738             return MA_INVALID_ARGS;
8739         }
8740     }
8741 }
8742 
ma_resource_manager_data_buffer_get_length_in_pcm_frames(ma_resource_manager_data_buffer * pDataBuffer,ma_uint64 * pLength)8743 MA_API ma_result ma_resource_manager_data_buffer_get_length_in_pcm_frames(ma_resource_manager_data_buffer* pDataBuffer, ma_uint64* pLength)
8744 {
8745     /* We cannot be using the data source after it's been uninitialized. */
8746     MA_ASSERT(ma_resource_manager_data_buffer_node_result(pDataBuffer->pNode) != MA_UNAVAILABLE);
8747 
8748     if (pDataBuffer == NULL || pLength == NULL) {
8749         return MA_INVALID_ARGS;
8750     }
8751 
8752     if (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBuffer->pNode) == ma_resource_manager_data_supply_type_unknown) {
8753         return MA_BUSY; /* Still loading. */
8754     }
8755 
8756     return ma_data_source_get_length_in_pcm_frames(ma_resource_manager_data_buffer_get_connector(pDataBuffer), pLength);
8757 }
8758 
ma_resource_manager_data_buffer_result(const ma_resource_manager_data_buffer * pDataBuffer)8759 MA_API ma_result ma_resource_manager_data_buffer_result(const ma_resource_manager_data_buffer* pDataBuffer)
8760 {
8761     if (pDataBuffer == NULL) {
8762         return MA_INVALID_ARGS;
8763     }
8764 
8765     return c89atomic_load_i32((ma_result*)&pDataBuffer->result);    /* Need a naughty const-cast here. */
8766 }
8767 
ma_resource_manager_data_buffer_set_looping(ma_resource_manager_data_buffer * pDataBuffer,ma_bool32 isLooping)8768 MA_API ma_result ma_resource_manager_data_buffer_set_looping(ma_resource_manager_data_buffer* pDataBuffer, ma_bool32 isLooping)
8769 {
8770     if (pDataBuffer == NULL) {
8771         return MA_INVALID_ARGS;
8772     }
8773 
8774     c89atomic_exchange_32(&pDataBuffer->isLooping, isLooping);
8775 
8776     return MA_SUCCESS;
8777 }
8778 
ma_resource_manager_data_buffer_get_looping(const ma_resource_manager_data_buffer * pDataBuffer,ma_bool32 * pIsLooping)8779 MA_API ma_result ma_resource_manager_data_buffer_get_looping(const ma_resource_manager_data_buffer* pDataBuffer, ma_bool32* pIsLooping)
8780 {
8781     if (pIsLooping == NULL) {
8782         return MA_INVALID_ARGS;
8783     }
8784 
8785     *pIsLooping = MA_FALSE;
8786 
8787     if (pDataBuffer == NULL) {
8788         return MA_INVALID_ARGS;
8789     }
8790 
8791     *pIsLooping = c89atomic_load_32((ma_bool32*)&pDataBuffer->isLooping);
8792 
8793     return MA_SUCCESS;
8794 }
8795 
ma_resource_manager_data_buffer_get_available_frames(ma_resource_manager_data_buffer * pDataBuffer,ma_uint64 * pAvailableFrames)8796 MA_API ma_result ma_resource_manager_data_buffer_get_available_frames(ma_resource_manager_data_buffer* pDataBuffer, ma_uint64* pAvailableFrames)
8797 {
8798     if (pAvailableFrames == NULL) {
8799         return MA_INVALID_ARGS;
8800     }
8801 
8802     *pAvailableFrames = 0;
8803 
8804     if (pDataBuffer == NULL) {
8805         return MA_INVALID_ARGS;
8806     }
8807 
8808     if (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBuffer->pNode) == ma_resource_manager_data_supply_type_unknown) {
8809         if (ma_resource_manager_data_buffer_node_result(pDataBuffer->pNode) == MA_BUSY) {
8810             return MA_BUSY;
8811         } else {
8812             return MA_INVALID_OPERATION;    /* No connector. */
8813         }
8814     }
8815 
8816     switch (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBuffer->pNode))
8817     {
8818         case ma_resource_manager_data_supply_type_encoded:
8819         {
8820             return ma_decoder_get_available_frames(&pDataBuffer->connector.decoder, pAvailableFrames);
8821         };
8822 
8823         case ma_resource_manager_data_supply_type_decoded:
8824         {
8825             return ma_audio_buffer_get_available_frames(&pDataBuffer->connector.buffer, pAvailableFrames);
8826         };
8827 
8828         case ma_resource_manager_data_supply_type_decoded_paged:
8829         {
8830             ma_uint64 cursor;
8831             ma_paged_audio_buffer_get_cursor_in_pcm_frames(&pDataBuffer->connector.pagedBuffer, &cursor);
8832 
8833             if (pDataBuffer->pNode->data.decodedPaged.decodedFrameCount > cursor) {
8834                 *pAvailableFrames = pDataBuffer->pNode->data.decodedPaged.decodedFrameCount - cursor;
8835             } else {
8836                 *pAvailableFrames = 0;
8837             }
8838 
8839             return MA_SUCCESS;
8840         };
8841 
8842         case ma_resource_manager_data_supply_type_unknown:
8843         default:
8844         {
8845             /* Unknown supply type. Should never hit this. */
8846             return MA_INVALID_ARGS;
8847         }
8848     }
8849 }
8850 
ma_resource_manager_register_file(ma_resource_manager * pResourceManager,const char * pFilePath,ma_uint32 flags)8851 MA_API ma_result ma_resource_manager_register_file(ma_resource_manager* pResourceManager, const char* pFilePath, ma_uint32 flags)
8852 {
8853     return ma_resource_manager_data_buffer_node_acquire(pResourceManager, pFilePath, NULL, 0, flags, NULL, NULL, NULL, NULL);
8854 }
8855 
ma_resource_manager_register_file_w(ma_resource_manager * pResourceManager,const wchar_t * pFilePath,ma_uint32 flags)8856 MA_API ma_result ma_resource_manager_register_file_w(ma_resource_manager* pResourceManager, const wchar_t* pFilePath, ma_uint32 flags)
8857 {
8858     return ma_resource_manager_data_buffer_node_acquire(pResourceManager, NULL, pFilePath, 0, flags, NULL, NULL, NULL, NULL);
8859 }
8860 
8861 
ma_resource_manager_register_data(ma_resource_manager * pResourceManager,const char * pName,const wchar_t * pNameW,ma_resource_manager_data_supply * pExistingData)8862 static ma_result ma_resource_manager_register_data(ma_resource_manager* pResourceManager, const char* pName, const wchar_t* pNameW, ma_resource_manager_data_supply* pExistingData)
8863 {
8864     return ma_resource_manager_data_buffer_node_acquire(pResourceManager, pName, pNameW, 0, 0, pExistingData, NULL, NULL, NULL);
8865 }
8866 
ma_resource_manager_register_decoded_data_internal(ma_resource_manager * pResourceManager,const char * pName,const wchar_t * pNameW,const void * pData,ma_uint64 frameCount,ma_format format,ma_uint32 channels,ma_uint32 sampleRate)8867 static ma_result ma_resource_manager_register_decoded_data_internal(ma_resource_manager* pResourceManager, const char* pName, const wchar_t* pNameW, const void* pData, ma_uint64 frameCount, ma_format format, ma_uint32 channels, ma_uint32 sampleRate)
8868 {
8869     ma_resource_manager_data_supply data;
8870     data.type                    = ma_resource_manager_data_supply_type_decoded;
8871     data.decoded.pData           = pData;
8872     data.decoded.totalFrameCount = frameCount;
8873     data.decoded.format          = format;
8874     data.decoded.channels        = channels;
8875     data.decoded.sampleRate      = sampleRate;
8876 
8877     return ma_resource_manager_register_data(pResourceManager, pName, pNameW, &data);
8878 }
8879 
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)8880 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)
8881 {
8882     return ma_resource_manager_register_decoded_data_internal(pResourceManager, pName, NULL, pData, frameCount, format, channels, sampleRate);
8883 }
8884 
ma_resource_manager_register_decoded_data_w(ma_resource_manager * pResourceManager,const wchar_t * pName,const void * pData,ma_uint64 frameCount,ma_format format,ma_uint32 channels,ma_uint32 sampleRate)8885 MA_API ma_result ma_resource_manager_register_decoded_data_w(ma_resource_manager* pResourceManager, const wchar_t* pName, const void* pData, ma_uint64 frameCount, ma_format format, ma_uint32 channels, ma_uint32 sampleRate)
8886 {
8887     return ma_resource_manager_register_decoded_data_internal(pResourceManager, NULL, pName, pData, frameCount, format, channels, sampleRate);
8888 }
8889 
8890 
ma_resource_manager_register_encoded_data_internal(ma_resource_manager * pResourceManager,const char * pName,const wchar_t * pNameW,const void * pData,size_t sizeInBytes)8891 static ma_result ma_resource_manager_register_encoded_data_internal(ma_resource_manager* pResourceManager, const char* pName, const wchar_t* pNameW, const void* pData, size_t sizeInBytes)
8892 {
8893     ma_resource_manager_data_supply data;
8894     data.type                = ma_resource_manager_data_supply_type_encoded;
8895     data.encoded.pData       = pData;
8896     data.encoded.sizeInBytes = sizeInBytes;
8897 
8898     return ma_resource_manager_register_data(pResourceManager, pName, pNameW, &data);
8899 }
8900 
ma_resource_manager_register_encoded_data(ma_resource_manager * pResourceManager,const char * pName,const void * pData,size_t sizeInBytes)8901 MA_API ma_result ma_resource_manager_register_encoded_data(ma_resource_manager* pResourceManager, const char* pName, const void* pData, size_t sizeInBytes)
8902 {
8903     return ma_resource_manager_register_encoded_data_internal(pResourceManager, pName, NULL, pData, sizeInBytes);
8904 }
8905 
ma_resource_manager_register_encoded_data_w(ma_resource_manager * pResourceManager,const wchar_t * pName,const void * pData,size_t sizeInBytes)8906 MA_API ma_result ma_resource_manager_register_encoded_data_w(ma_resource_manager* pResourceManager, const wchar_t* pName, const void* pData, size_t sizeInBytes)
8907 {
8908     return ma_resource_manager_register_encoded_data_internal(pResourceManager, NULL, pName, pData, sizeInBytes);
8909 }
8910 
8911 
ma_resource_manager_unregister_file(ma_resource_manager * pResourceManager,const char * pFilePath)8912 MA_API ma_result ma_resource_manager_unregister_file(ma_resource_manager* pResourceManager, const char* pFilePath)
8913 {
8914     return ma_resource_manager_unregister_data(pResourceManager, pFilePath);
8915 }
8916 
ma_resource_manager_unregister_file_w(ma_resource_manager * pResourceManager,const wchar_t * pFilePath)8917 MA_API ma_result ma_resource_manager_unregister_file_w(ma_resource_manager* pResourceManager, const wchar_t* pFilePath)
8918 {
8919     return ma_resource_manager_unregister_data_w(pResourceManager, pFilePath);
8920 }
8921 
ma_resource_manager_unregister_data(ma_resource_manager * pResourceManager,const char * pName)8922 MA_API ma_result ma_resource_manager_unregister_data(ma_resource_manager* pResourceManager, const char* pName)
8923 {
8924     return ma_resource_manager_data_buffer_node_unacquire(pResourceManager, NULL, pName, NULL);
8925 }
8926 
ma_resource_manager_unregister_data_w(ma_resource_manager * pResourceManager,const wchar_t * pName)8927 MA_API ma_result ma_resource_manager_unregister_data_w(ma_resource_manager* pResourceManager, const wchar_t* pName)
8928 {
8929     return ma_resource_manager_data_buffer_node_unacquire(pResourceManager, NULL, NULL, pName);
8930 }
8931 
8932 
ma_resource_manager_data_stream_next_execution_order(ma_resource_manager_data_stream * pDataStream)8933 static ma_uint32 ma_resource_manager_data_stream_next_execution_order(ma_resource_manager_data_stream* pDataStream)
8934 {
8935     MA_ASSERT(pDataStream != NULL);
8936     return c89atomic_fetch_add_32(&pDataStream->executionCounter, 1);
8937 }
8938 
ma_resource_manager_data_stream_is_decoder_at_end(const ma_resource_manager_data_stream * pDataStream)8939 static ma_bool32 ma_resource_manager_data_stream_is_decoder_at_end(const ma_resource_manager_data_stream* pDataStream)
8940 {
8941     MA_ASSERT(pDataStream != NULL);
8942     return c89atomic_load_32((ma_bool32*)&pDataStream->isDecoderAtEnd);
8943 }
8944 
ma_resource_manager_data_stream_seek_counter(const ma_resource_manager_data_stream * pDataStream)8945 static ma_uint32 ma_resource_manager_data_stream_seek_counter(const ma_resource_manager_data_stream* pDataStream)
8946 {
8947     MA_ASSERT(pDataStream != NULL);
8948     return c89atomic_load_32((ma_uint32*)&pDataStream->seekCounter);
8949 }
8950 
8951 
ma_resource_manager_data_stream_cb__read_pcm_frames(ma_data_source * pDataSource,void * pFramesOut,ma_uint64 frameCount,ma_uint64 * pFramesRead)8952 static ma_result ma_resource_manager_data_stream_cb__read_pcm_frames(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead)
8953 {
8954     return ma_resource_manager_data_stream_read_pcm_frames((ma_resource_manager_data_stream*)pDataSource, pFramesOut, frameCount, pFramesRead);
8955 }
8956 
ma_resource_manager_data_stream_cb__seek_to_pcm_frame(ma_data_source * pDataSource,ma_uint64 frameIndex)8957 static ma_result ma_resource_manager_data_stream_cb__seek_to_pcm_frame(ma_data_source* pDataSource, ma_uint64 frameIndex)
8958 {
8959     return ma_resource_manager_data_stream_seek_to_pcm_frame((ma_resource_manager_data_stream*)pDataSource, frameIndex);
8960 }
8961 
ma_resource_manager_data_stream_cb__map(ma_data_source * pDataSource,void ** ppFramesOut,ma_uint64 * pFrameCount)8962 static ma_result ma_resource_manager_data_stream_cb__map(ma_data_source* pDataSource, void** ppFramesOut, ma_uint64* pFrameCount)
8963 {
8964     return ma_resource_manager_data_stream_map((ma_resource_manager_data_stream*)pDataSource, ppFramesOut, pFrameCount);
8965 }
8966 
ma_resource_manager_data_stream_cb__unmap(ma_data_source * pDataSource,ma_uint64 frameCount)8967 static ma_result ma_resource_manager_data_stream_cb__unmap(ma_data_source* pDataSource, ma_uint64 frameCount)
8968 {
8969     return ma_resource_manager_data_stream_unmap((ma_resource_manager_data_stream*)pDataSource, frameCount);
8970 }
8971 
ma_resource_manager_data_stream_cb__get_data_format(ma_data_source * pDataSource,ma_format * pFormat,ma_uint32 * pChannels,ma_uint32 * pSampleRate)8972 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)
8973 {
8974     return ma_resource_manager_data_stream_get_data_format((ma_resource_manager_data_stream*)pDataSource, pFormat, pChannels, pSampleRate);
8975 }
8976 
ma_resource_manager_data_stream_cb__get_cursor_in_pcm_frames(ma_data_source * pDataSource,ma_uint64 * pCursor)8977 static ma_result ma_resource_manager_data_stream_cb__get_cursor_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pCursor)
8978 {
8979     return ma_resource_manager_data_stream_get_cursor_in_pcm_frames((ma_resource_manager_data_stream*)pDataSource, pCursor);
8980 }
8981 
ma_resource_manager_data_stream_cb__get_length_in_pcm_frames(ma_data_source * pDataSource,ma_uint64 * pLength)8982 static ma_result ma_resource_manager_data_stream_cb__get_length_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pLength)
8983 {
8984     return ma_resource_manager_data_stream_get_length_in_pcm_frames((ma_resource_manager_data_stream*)pDataSource, pLength);
8985 }
8986 
8987 static ma_data_source_vtable g_ma_resource_manager_data_stream_vtable =
8988 {
8989     ma_resource_manager_data_stream_cb__read_pcm_frames,
8990     ma_resource_manager_data_stream_cb__seek_to_pcm_frame,
8991     ma_resource_manager_data_stream_cb__map,
8992     ma_resource_manager_data_stream_cb__unmap,
8993     ma_resource_manager_data_stream_cb__get_data_format,
8994     ma_resource_manager_data_stream_cb__get_cursor_in_pcm_frames,
8995     ma_resource_manager_data_stream_cb__get_length_in_pcm_frames
8996 };
8997 
ma_resource_manager_data_stream_init_internal(ma_resource_manager * pResourceManager,const char * pFilePath,const wchar_t * pFilePathW,ma_uint32 flags,const ma_pipeline_notifications * pNotifications,ma_resource_manager_data_stream * pDataStream)8998 static ma_result ma_resource_manager_data_stream_init_internal(ma_resource_manager* pResourceManager, const char* pFilePath, const wchar_t* pFilePathW, ma_uint32 flags, const ma_pipeline_notifications* pNotifications, ma_resource_manager_data_stream* pDataStream)
8999 {
9000     ma_result result;
9001     ma_data_source_config dataSourceConfig;
9002     char* pFilePathCopy = NULL;
9003     wchar_t* pFilePathWCopy = NULL;
9004     ma_job job;
9005     ma_bool32 waitBeforeReturning = MA_FALSE;
9006     ma_resource_manager_inline_notification waitNotification;
9007     ma_pipeline_notifications notifications;
9008 
9009     if (pNotifications != NULL) {
9010         notifications = *pNotifications;
9011         pNotifications = NULL;  /* From here on out, `notifications` should be used instead of `pNotifications`. Setting this to NULL to catch any errors at testing time. */
9012     } else {
9013         MA_ZERO_OBJECT(&notifications);
9014     }
9015 
9016     if (pDataStream == NULL) {
9017         ma_pipeline_notifications_signal_all_notifications(&notifications);
9018         return MA_INVALID_ARGS;
9019     }
9020 
9021     MA_ZERO_OBJECT(pDataStream);
9022 
9023     dataSourceConfig = ma_data_source_config_init();
9024     dataSourceConfig.vtable = &g_ma_resource_manager_data_stream_vtable;
9025 
9026     result = ma_data_source_init(&dataSourceConfig, &pDataStream->ds);
9027     if (result != MA_SUCCESS) {
9028         ma_pipeline_notifications_signal_all_notifications(&notifications);
9029         return result;
9030     }
9031 
9032     pDataStream->pResourceManager = pResourceManager;
9033     pDataStream->flags            = flags;
9034     pDataStream->result           = MA_BUSY;
9035 
9036     if (pResourceManager == NULL || (pFilePath == NULL && pFilePathW == NULL)) {
9037         ma_pipeline_notifications_signal_all_notifications(&notifications);
9038         return MA_INVALID_ARGS;
9039     }
9040 
9041     /* 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.  */
9042 
9043     /* 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. */
9044     if (pFilePath != NULL) {
9045         pFilePathCopy = ma_copy_string(pFilePath, &pResourceManager->config.allocationCallbacks/*, MA_ALLOCATION_TYPE_TRANSIENT_STRING*/);
9046     } else {
9047         pFilePathWCopy = ma_copy_string_w(pFilePathW, &pResourceManager->config.allocationCallbacks/*, MA_ALLOCATION_TYPE_TRANSIENT_STRING*/);
9048     }
9049 
9050     if (pFilePathCopy == NULL && pFilePathWCopy == NULL) {
9051         ma_pipeline_notifications_signal_all_notifications(&notifications);
9052         return MA_OUT_OF_MEMORY;
9053     }
9054 
9055     /*
9056     We need to check for the presence of MA_DATA_SOURCE_FLAG_ASYNC. If it's not set, we need to wait before returning. Otherwise we
9057     can return immediately. Likewise, we'll also check for MA_DATA_SOURCE_FLAG_WAIT_INIT and do the same.
9058     */
9059     if ((flags & MA_DATA_SOURCE_FLAG_ASYNC) == 0 || (flags & MA_DATA_SOURCE_FLAG_WAIT_INIT) != 0) {
9060         waitBeforeReturning = MA_TRUE;
9061         ma_resource_manager_inline_notification_init(pResourceManager, &waitNotification);
9062     }
9063 
9064     ma_pipeline_notifications_acquire_all_fences(&notifications);
9065 
9066     /* 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. */
9067     job = ma_job_init(MA_JOB_LOAD_DATA_STREAM);
9068     job.order = ma_resource_manager_data_stream_next_execution_order(pDataStream);
9069     job.loadDataStream.pDataStream       = pDataStream;
9070     job.loadDataStream.pFilePath         = pFilePathCopy;
9071     job.loadDataStream.pFilePathW        = pFilePathWCopy;
9072     job.loadDataStream.pInitNotification = (waitBeforeReturning == MA_TRUE) ? &waitNotification : notifications.init.pNotification;
9073     job.loadDataStream.pInitFence        = notifications.init.pFence;
9074     result = ma_resource_manager_post_job(pResourceManager, &job);
9075     if (result != MA_SUCCESS) {
9076         ma_pipeline_notifications_signal_all_notifications(&notifications);
9077         ma_pipeline_notifications_release_all_fences(&notifications);
9078 
9079         if (waitBeforeReturning) {
9080             ma_resource_manager_inline_notification_uninit(&waitNotification);
9081         }
9082 
9083         ma_free(pFilePathCopy,  &pResourceManager->config.allocationCallbacks/*, MA_ALLOCATION_TYPE_TRANSIENT_STRING*/);
9084         ma_free(pFilePathWCopy, &pResourceManager->config.allocationCallbacks/*, MA_ALLOCATION_TYPE_TRANSIENT_STRING*/);
9085         return result;
9086     }
9087 
9088     /* Wait if needed. */
9089     if (waitBeforeReturning) {
9090         ma_resource_manager_inline_notification_wait_and_uninit(&waitNotification);
9091 
9092         if (notifications.init.pNotification != NULL) {
9093             ma_async_notification_signal(notifications.init.pNotification);
9094         }
9095 
9096         /* NOTE: Do not release pInitFence here. That will be done by the job. */
9097     }
9098 
9099     return MA_SUCCESS;
9100 }
9101 
ma_resource_manager_data_stream_init(ma_resource_manager * pResourceManager,const char * pFilePath,ma_uint32 flags,const ma_pipeline_notifications * pNotifications,ma_resource_manager_data_stream * pDataStream)9102 MA_API ma_result ma_resource_manager_data_stream_init(ma_resource_manager* pResourceManager, const char* pFilePath, ma_uint32 flags, const ma_pipeline_notifications* pNotifications, ma_resource_manager_data_stream* pDataStream)
9103 {
9104     return ma_resource_manager_data_stream_init_internal(pResourceManager, pFilePath, NULL, flags, pNotifications, pDataStream);
9105 }
9106 
ma_resource_manager_data_stream_init_w(ma_resource_manager * pResourceManager,const wchar_t * pFilePath,ma_uint32 flags,const ma_pipeline_notifications * pNotifications,ma_resource_manager_data_stream * pDataStream)9107 MA_API ma_result ma_resource_manager_data_stream_init_w(ma_resource_manager* pResourceManager, const wchar_t* pFilePath, ma_uint32 flags, const ma_pipeline_notifications* pNotifications, ma_resource_manager_data_stream* pDataStream)
9108 {
9109     return ma_resource_manager_data_stream_init_internal(pResourceManager, NULL, pFilePath, flags, pNotifications, pDataStream);
9110 }
9111 
ma_resource_manager_data_stream_uninit(ma_resource_manager_data_stream * pDataStream)9112 MA_API ma_result ma_resource_manager_data_stream_uninit(ma_resource_manager_data_stream* pDataStream)
9113 {
9114     ma_resource_manager_inline_notification freeEvent;
9115     ma_job job;
9116 
9117     if (pDataStream == NULL) {
9118         return MA_INVALID_ARGS;
9119     }
9120 
9121     /* The first thing to do is set the result to unavailable. This will prevent future page decoding. */
9122     c89atomic_exchange_i32(&pDataStream->result, MA_UNAVAILABLE);
9123 
9124     /*
9125     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
9126     to wait for it to complete before returning which means we need an event.
9127     */
9128     ma_resource_manager_inline_notification_init(pDataStream->pResourceManager, &freeEvent);
9129 
9130     job = ma_job_init(MA_JOB_FREE_DATA_STREAM);
9131     job.order = ma_resource_manager_data_stream_next_execution_order(pDataStream);
9132     job.freeDataStream.pDataStream       = pDataStream;
9133     job.freeDataStream.pDoneNotification = &freeEvent;
9134     job.freeDataStream.pDoneFence        = NULL;
9135     ma_resource_manager_post_job(pDataStream->pResourceManager, &job);
9136 
9137     /* We need to wait for the job to finish processing before we return. */
9138     ma_resource_manager_inline_notification_wait_and_uninit(&freeEvent);
9139 
9140     return MA_SUCCESS;
9141 }
9142 
9143 
ma_resource_manager_data_stream_get_page_size_in_frames(ma_resource_manager_data_stream * pDataStream)9144 static ma_uint32 ma_resource_manager_data_stream_get_page_size_in_frames(ma_resource_manager_data_stream* pDataStream)
9145 {
9146     MA_ASSERT(pDataStream != NULL);
9147     MA_ASSERT(pDataStream->isDecoderInitialized == MA_TRUE);
9148 
9149     return MA_RESOURCE_MANAGER_PAGE_SIZE_IN_MILLISECONDS * (pDataStream->decoder.outputSampleRate/1000);
9150 }
9151 
ma_resource_manager_data_stream_get_page_data_pointer(ma_resource_manager_data_stream * pDataStream,ma_uint32 pageIndex,ma_uint32 relativeCursor)9152 static void* ma_resource_manager_data_stream_get_page_data_pointer(ma_resource_manager_data_stream* pDataStream, ma_uint32 pageIndex, ma_uint32 relativeCursor)
9153 {
9154     MA_ASSERT(pDataStream != NULL);
9155     MA_ASSERT(pDataStream->isDecoderInitialized == MA_TRUE);
9156     MA_ASSERT(pageIndex == 0 || pageIndex == 1);
9157 
9158     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));
9159 }
9160 
ma_resource_manager_data_stream_fill_page(ma_resource_manager_data_stream * pDataStream,ma_uint32 pageIndex)9161 static void ma_resource_manager_data_stream_fill_page(ma_resource_manager_data_stream* pDataStream, ma_uint32 pageIndex)
9162 {
9163     ma_bool32 isLooping;
9164     ma_uint64 pageSizeInFrames;
9165     ma_uint64 totalFramesReadForThisPage = 0;
9166     void* pPageData = ma_resource_manager_data_stream_get_page_data_pointer(pDataStream, pageIndex, 0);
9167 
9168     pageSizeInFrames = ma_resource_manager_data_stream_get_page_size_in_frames(pDataStream);
9169 
9170     ma_resource_manager_data_stream_get_looping(pDataStream, &isLooping);   /* Won't fail. */
9171 
9172     if (isLooping) {
9173         while (totalFramesReadForThisPage < pageSizeInFrames) {
9174             ma_uint64 framesRemaining;
9175             ma_uint64 framesRead;
9176 
9177             framesRemaining = pageSizeInFrames - totalFramesReadForThisPage;
9178             framesRead = ma_decoder_read_pcm_frames(&pDataStream->decoder, ma_offset_pcm_frames_ptr(pPageData, totalFramesReadForThisPage, pDataStream->decoder.outputFormat, pDataStream->decoder.outputChannels), framesRemaining);
9179             totalFramesReadForThisPage += framesRead;
9180 
9181             /* Loop back to the start if we reached the end. We'll also have a known length at this point as well. */
9182             if (framesRead < framesRemaining) {
9183                 if (pDataStream->totalLengthInPCMFrames == 0) {
9184                     ma_decoder_get_cursor_in_pcm_frames(&pDataStream->decoder, &pDataStream->totalLengthInPCMFrames);
9185                 }
9186 
9187                 ma_decoder_seek_to_pcm_frame(&pDataStream->decoder, 0);
9188             }
9189         }
9190     } else {
9191         totalFramesReadForThisPage = ma_decoder_read_pcm_frames(&pDataStream->decoder, pPageData, pageSizeInFrames);
9192     }
9193 
9194     if (totalFramesReadForThisPage < pageSizeInFrames) {
9195         c89atomic_exchange_32(&pDataStream->isDecoderAtEnd, MA_TRUE);
9196     }
9197 
9198     c89atomic_exchange_32(&pDataStream->pageFrameCount[pageIndex], (ma_uint32)totalFramesReadForThisPage);
9199     c89atomic_exchange_32(&pDataStream->isPageValid[pageIndex], MA_TRUE);
9200 }
9201 
ma_resource_manager_data_stream_fill_pages(ma_resource_manager_data_stream * pDataStream)9202 static void ma_resource_manager_data_stream_fill_pages(ma_resource_manager_data_stream* pDataStream)
9203 {
9204     ma_uint32 iPage;
9205 
9206     MA_ASSERT(pDataStream != NULL);
9207 
9208     for (iPage = 0; iPage < 2; iPage += 1) {
9209         ma_resource_manager_data_stream_fill_page(pDataStream, iPage);
9210     }
9211 }
9212 
ma_resource_manager_data_stream_read_pcm_frames(ma_resource_manager_data_stream * pDataStream,void * pFramesOut,ma_uint64 frameCount,ma_uint64 * pFramesRead)9213 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)
9214 {
9215     ma_result result = MA_SUCCESS;
9216     ma_uint64 totalFramesProcessed;
9217     ma_format format;
9218     ma_uint32 channels;
9219 
9220     /* Safety. */
9221     if (pFramesRead != NULL) {
9222         *pFramesRead = 0;
9223     }
9224 
9225     /* We cannot be using the data source after it's been uninitialized. */
9226     MA_ASSERT(ma_resource_manager_data_stream_result(pDataStream) != MA_UNAVAILABLE);
9227 
9228     if (pDataStream == NULL) {
9229         return MA_INVALID_ARGS;
9230     }
9231 
9232     if (ma_resource_manager_data_stream_result(pDataStream) != MA_SUCCESS) {
9233         return MA_INVALID_OPERATION;
9234     }
9235 
9236     /* Don't attempt to read while we're in the middle of seeking. Tell the caller that we're busy. */
9237     if (ma_resource_manager_data_stream_seek_counter(pDataStream) > 0) {
9238         return MA_BUSY;
9239     }
9240 
9241     ma_resource_manager_data_stream_get_data_format(pDataStream, &format, &channels, NULL);
9242 
9243     /* Reading is implemented in terms of map/unmap. We need to run this in a loop because mapping is clamped against page boundaries. */
9244     totalFramesProcessed = 0;
9245     while (totalFramesProcessed < frameCount) {
9246         void* pMappedFrames;
9247         ma_uint64 mappedFrameCount;
9248 
9249         mappedFrameCount = frameCount - totalFramesProcessed;
9250         result = ma_resource_manager_data_stream_map(pDataStream, &pMappedFrames, &mappedFrameCount);
9251         if (result != MA_SUCCESS) {
9252             break;
9253         }
9254 
9255         /* 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. */
9256         if (pFramesOut != NULL) {
9257             ma_copy_pcm_frames(ma_offset_pcm_frames_ptr(pFramesOut, totalFramesProcessed, format, channels), pMappedFrames, mappedFrameCount, format, channels);
9258         }
9259 
9260         totalFramesProcessed += mappedFrameCount;
9261 
9262         result = ma_resource_manager_data_stream_unmap(pDataStream, mappedFrameCount);
9263         if (result != MA_SUCCESS) {
9264             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. */
9265         }
9266     }
9267 
9268     if (pFramesRead != NULL) {
9269         *pFramesRead = totalFramesProcessed;
9270     }
9271 
9272     return result;
9273 }
9274 
ma_resource_manager_data_stream_map(ma_resource_manager_data_stream * pDataStream,void ** ppFramesOut,ma_uint64 * pFrameCount)9275 MA_API ma_result ma_resource_manager_data_stream_map(ma_resource_manager_data_stream* pDataStream, void** ppFramesOut, ma_uint64* pFrameCount)
9276 {
9277     ma_uint64 framesAvailable;
9278     ma_uint64 frameCount = 0;
9279 
9280     /* We cannot be using the data source after it's been uninitialized. */
9281     MA_ASSERT(ma_resource_manager_data_stream_result(pDataStream) != MA_UNAVAILABLE);
9282 
9283     if (pFrameCount != NULL) {
9284         frameCount = *pFrameCount;
9285         *pFrameCount = 0;
9286     }
9287     if (ppFramesOut != NULL) {
9288         *ppFramesOut = NULL;
9289     }
9290 
9291     if (pDataStream == NULL || ppFramesOut == NULL || pFrameCount == NULL) {
9292         return MA_INVALID_ARGS;
9293     }
9294 
9295     if (ma_resource_manager_data_stream_result(pDataStream) != MA_SUCCESS) {
9296         return MA_INVALID_OPERATION;
9297     }
9298 
9299     /* Don't attempt to read while we're in the middle of seeking. Tell the caller that we're busy. */
9300     if (ma_resource_manager_data_stream_seek_counter(pDataStream) > 0) {
9301         return MA_BUSY;
9302     }
9303 
9304     /* If the page we're on is invalid it means we've caught up to the job thread. */
9305     if (c89atomic_load_32(&pDataStream->isPageValid[pDataStream->currentPageIndex]) == MA_FALSE) {
9306         framesAvailable = 0;
9307     } else {
9308         /*
9309         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
9310         that the unmap process will only post an update for one page at a time. Keeping mapping tied to page boundaries makes this simpler.
9311         */
9312         ma_uint32 currentPageFrameCount = c89atomic_load_32(&pDataStream->pageFrameCount[pDataStream->currentPageIndex]);
9313         MA_ASSERT(currentPageFrameCount >= pDataStream->relativeCursor);
9314 
9315         framesAvailable = currentPageFrameCount - pDataStream->relativeCursor;
9316     }
9317 
9318     /* If there's no frames available and the result is set to MA_AT_END we need to return MA_AT_END. */
9319     if (framesAvailable == 0) {
9320         if (ma_resource_manager_data_stream_is_decoder_at_end(pDataStream)) {
9321             return MA_AT_END;
9322         } else {
9323             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. */
9324         }
9325     }
9326 
9327     MA_ASSERT(framesAvailable > 0);
9328 
9329     if (frameCount > framesAvailable) {
9330         frameCount = framesAvailable;
9331     }
9332 
9333     *ppFramesOut = ma_resource_manager_data_stream_get_page_data_pointer(pDataStream, pDataStream->currentPageIndex, pDataStream->relativeCursor);
9334     *pFrameCount = frameCount;
9335 
9336     return MA_SUCCESS;
9337 }
9338 
ma_resource_manager_data_stream_set_absolute_cursor(ma_resource_manager_data_stream * pDataStream,ma_uint64 absoluteCursor)9339 static void ma_resource_manager_data_stream_set_absolute_cursor(ma_resource_manager_data_stream* pDataStream, ma_uint64 absoluteCursor)
9340 {
9341     /* Loop if possible. */
9342     if (absoluteCursor > pDataStream->totalLengthInPCMFrames && pDataStream->totalLengthInPCMFrames > 0) {
9343         absoluteCursor = absoluteCursor % pDataStream->totalLengthInPCMFrames;
9344     }
9345 
9346     c89atomic_exchange_64(&pDataStream->absoluteCursor, absoluteCursor);
9347 }
9348 
ma_resource_manager_data_stream_unmap(ma_resource_manager_data_stream * pDataStream,ma_uint64 frameCount)9349 MA_API ma_result ma_resource_manager_data_stream_unmap(ma_resource_manager_data_stream* pDataStream, ma_uint64 frameCount)
9350 {
9351     ma_uint32 newRelativeCursor;
9352     ma_uint32 pageSizeInFrames;
9353     ma_job job;
9354 
9355     /* We cannot be using the data source after it's been uninitialized. */
9356     MA_ASSERT(ma_resource_manager_data_stream_result(pDataStream) != MA_UNAVAILABLE);
9357 
9358     if (pDataStream == NULL) {
9359         return MA_INVALID_ARGS;
9360     }
9361 
9362     if (ma_resource_manager_data_stream_result(pDataStream) != MA_SUCCESS) {
9363         return MA_INVALID_OPERATION;
9364     }
9365 
9366     /* The frame count should always fit inside a 32-bit integer. */
9367     if (frameCount > 0xFFFFFFFF) {
9368         return MA_INVALID_ARGS;
9369     }
9370 
9371     pageSizeInFrames = ma_resource_manager_data_stream_get_page_size_in_frames(pDataStream);
9372 
9373     /* The absolute cursor needs to be updated for ma_resource_manager_data_stream_get_cursor_in_pcm_frames(). */
9374     ma_resource_manager_data_stream_set_absolute_cursor(pDataStream, c89atomic_load_64(&pDataStream->absoluteCursor) + frameCount);
9375 
9376     /* Here is where we need to check if we need to load a new page, and if so, post a job to load it. */
9377     newRelativeCursor = pDataStream->relativeCursor + (ma_uint32)frameCount;
9378 
9379     /* 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. */
9380     if (newRelativeCursor >= pageSizeInFrames) {
9381         newRelativeCursor -= pageSizeInFrames;
9382 
9383         /* Here is where we post the job start decoding. */
9384         job = ma_job_init(MA_JOB_PAGE_DATA_STREAM);
9385         job.order = ma_resource_manager_data_stream_next_execution_order(pDataStream);
9386         job.pageDataStream.pDataStream = pDataStream;
9387         job.pageDataStream.pageIndex   = pDataStream->currentPageIndex;
9388 
9389         /* The page needs to be marked as invalid so that the public API doesn't try reading from it. */
9390         c89atomic_exchange_32(&pDataStream->isPageValid[pDataStream->currentPageIndex], MA_FALSE);
9391 
9392         /* Before posting the job we need to make sure we set some state. */
9393         pDataStream->relativeCursor   = newRelativeCursor;
9394         pDataStream->currentPageIndex = (pDataStream->currentPageIndex + 1) & 0x01;
9395         return ma_resource_manager_post_job(pDataStream->pResourceManager, &job);
9396     } else {
9397         /* We haven't moved into a new page so we can just move the cursor forward. */
9398         pDataStream->relativeCursor = newRelativeCursor;
9399         return MA_SUCCESS;
9400     }
9401 }
9402 
ma_resource_manager_data_stream_seek_to_pcm_frame(ma_resource_manager_data_stream * pDataStream,ma_uint64 frameIndex)9403 MA_API ma_result ma_resource_manager_data_stream_seek_to_pcm_frame(ma_resource_manager_data_stream* pDataStream, ma_uint64 frameIndex)
9404 {
9405     ma_job job;
9406     ma_result streamResult;
9407 
9408     streamResult = ma_resource_manager_data_stream_result(pDataStream);
9409 
9410     /* We cannot be using the data source after it's been uninitialized. */
9411     MA_ASSERT(streamResult != MA_UNAVAILABLE);
9412 
9413     if (pDataStream == NULL) {
9414         return MA_INVALID_ARGS;
9415     }
9416 
9417     if (streamResult != MA_SUCCESS && streamResult != MA_BUSY) {
9418         return MA_INVALID_OPERATION;
9419     }
9420 
9421     /* 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. */
9422     c89atomic_fetch_add_32(&pDataStream->seekCounter, 1);
9423 
9424     /* Update the absolute cursor so that ma_resource_manager_data_stream_get_cursor_in_pcm_frames() returns the new position. */
9425     ma_resource_manager_data_stream_set_absolute_cursor(pDataStream, frameIndex);
9426 
9427     /*
9428     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
9429     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
9430     the first page.
9431     */
9432     pDataStream->relativeCursor   = 0;
9433     pDataStream->currentPageIndex = 0;
9434     c89atomic_exchange_32(&pDataStream->isPageValid[0], MA_FALSE);
9435     c89atomic_exchange_32(&pDataStream->isPageValid[1], MA_FALSE);
9436 
9437     /* Make sure the data stream is not marked as at the end or else if we seek in response to hitting the end, we won't be able to read any more data. */
9438     c89atomic_exchange_32(&pDataStream->isDecoderAtEnd, MA_FALSE);
9439 
9440     /*
9441     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
9442     are invalid and any content contained within them will be discarded and replaced with newly decoded data.
9443     */
9444     job = ma_job_init(MA_JOB_SEEK_DATA_STREAM);
9445     job.order = ma_resource_manager_data_stream_next_execution_order(pDataStream);
9446     job.seekDataStream.pDataStream = pDataStream;
9447     job.seekDataStream.frameIndex  = frameIndex;
9448     return ma_resource_manager_post_job(pDataStream->pResourceManager, &job);
9449 }
9450 
ma_resource_manager_data_stream_get_data_format(ma_resource_manager_data_stream * pDataStream,ma_format * pFormat,ma_uint32 * pChannels,ma_uint32 * pSampleRate)9451 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)
9452 {
9453     /* We cannot be using the data source after it's been uninitialized. */
9454     MA_ASSERT(ma_resource_manager_data_stream_result(pDataStream) != MA_UNAVAILABLE);
9455 
9456     if (pFormat != NULL) {
9457         *pFormat = ma_format_unknown;
9458     }
9459 
9460     if (pChannels != NULL) {
9461         *pChannels = 0;
9462     }
9463 
9464     if (pSampleRate != NULL) {
9465         *pSampleRate = 0;
9466     }
9467 
9468     if (pDataStream == NULL) {
9469         return MA_INVALID_ARGS;
9470     }
9471 
9472     if (ma_resource_manager_data_stream_result(pDataStream) != MA_SUCCESS) {
9473         return MA_INVALID_OPERATION;
9474     }
9475 
9476     /*
9477     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
9478     such that the application is responsible for ensuring it's not called while uninitializing so it should be safe.
9479     */
9480     return ma_data_source_get_data_format(&pDataStream->decoder, pFormat, pChannels, pSampleRate);
9481 }
9482 
ma_resource_manager_data_stream_get_cursor_in_pcm_frames(ma_resource_manager_data_stream * pDataStream,ma_uint64 * pCursor)9483 MA_API ma_result ma_resource_manager_data_stream_get_cursor_in_pcm_frames(ma_resource_manager_data_stream* pDataStream, ma_uint64* pCursor)
9484 {
9485     if (pCursor == NULL) {
9486         return MA_INVALID_ARGS;
9487     }
9488 
9489     *pCursor = 0;
9490 
9491     /* We cannot be using the data source after it's been uninitialized. */
9492     MA_ASSERT(ma_resource_manager_data_stream_result(pDataStream) != MA_UNAVAILABLE);
9493 
9494     if (pDataStream == NULL) {
9495         return MA_INVALID_ARGS;
9496     }
9497 
9498     if (ma_resource_manager_data_stream_result(pDataStream) != MA_SUCCESS) {
9499         return MA_INVALID_OPERATION;
9500     }
9501 
9502     *pCursor = pDataStream->absoluteCursor;
9503 
9504     return MA_SUCCESS;
9505 }
9506 
ma_resource_manager_data_stream_get_length_in_pcm_frames(ma_resource_manager_data_stream * pDataStream,ma_uint64 * pLength)9507 MA_API ma_result ma_resource_manager_data_stream_get_length_in_pcm_frames(ma_resource_manager_data_stream* pDataStream, ma_uint64* pLength)
9508 {
9509     ma_result streamResult;
9510 
9511     if (pLength == NULL) {
9512         return MA_INVALID_ARGS;
9513     }
9514 
9515     *pLength = 0;
9516 
9517     streamResult = ma_resource_manager_data_stream_result(pDataStream);
9518 
9519     /* We cannot be using the data source after it's been uninitialized. */
9520     MA_ASSERT(streamResult != MA_UNAVAILABLE);
9521 
9522     if (pDataStream == NULL) {
9523         return MA_INVALID_ARGS;
9524     }
9525 
9526     if (streamResult != MA_SUCCESS) {
9527         return streamResult;
9528     }
9529 
9530     /*
9531     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
9532     calculated when we initialized it on the job thread.
9533     */
9534     *pLength = pDataStream->totalLengthInPCMFrames;
9535     if (*pLength == 0) {
9536         return MA_NOT_IMPLEMENTED;  /* Some decoders may not have a known length. */
9537     }
9538 
9539     return MA_SUCCESS;
9540 }
9541 
ma_resource_manager_data_stream_result(const ma_resource_manager_data_stream * pDataStream)9542 MA_API ma_result ma_resource_manager_data_stream_result(const ma_resource_manager_data_stream* pDataStream)
9543 {
9544     if (pDataStream == NULL) {
9545         return MA_INVALID_ARGS;
9546     }
9547 
9548     return c89atomic_load_i32(&pDataStream->result);
9549 }
9550 
ma_resource_manager_data_stream_set_looping(ma_resource_manager_data_stream * pDataStream,ma_bool32 isLooping)9551 MA_API ma_result ma_resource_manager_data_stream_set_looping(ma_resource_manager_data_stream* pDataStream, ma_bool32 isLooping)
9552 {
9553     if (pDataStream == NULL) {
9554         return MA_INVALID_ARGS;
9555     }
9556 
9557     c89atomic_exchange_32(&pDataStream->isLooping, isLooping);
9558 
9559     return MA_SUCCESS;
9560 }
9561 
ma_resource_manager_data_stream_get_looping(const ma_resource_manager_data_stream * pDataStream,ma_bool32 * pIsLooping)9562 MA_API ma_result ma_resource_manager_data_stream_get_looping(const ma_resource_manager_data_stream* pDataStream, ma_bool32* pIsLooping)
9563 {
9564     if (pIsLooping == NULL) {
9565         return MA_INVALID_ARGS;
9566     }
9567 
9568     *pIsLooping = MA_FALSE;
9569 
9570     if (pDataStream == NULL) {
9571         return MA_INVALID_ARGS;
9572     }
9573 
9574     *pIsLooping = c89atomic_load_32((ma_bool32*)&pDataStream->isLooping);   /* Naughty const-cast. Value won't change from here in practice (maybe from another thread). */
9575 
9576     return MA_SUCCESS;
9577 }
9578 
ma_resource_manager_data_stream_get_available_frames(ma_resource_manager_data_stream * pDataStream,ma_uint64 * pAvailableFrames)9579 MA_API ma_result ma_resource_manager_data_stream_get_available_frames(ma_resource_manager_data_stream* pDataStream, ma_uint64* pAvailableFrames)
9580 {
9581     ma_uint32 pageIndex0;
9582     ma_uint32 pageIndex1;
9583     ma_uint32 relativeCursor;
9584     ma_uint64 availableFrames;
9585 
9586     if (pAvailableFrames == NULL) {
9587         return MA_INVALID_ARGS;
9588     }
9589 
9590     *pAvailableFrames = 0;
9591 
9592     if (pDataStream == NULL) {
9593         return MA_INVALID_ARGS;
9594     }
9595 
9596     pageIndex0     =  pDataStream->currentPageIndex;
9597     pageIndex1     = (pDataStream->currentPageIndex + 1) & 0x01;
9598     relativeCursor =  pDataStream->relativeCursor;
9599 
9600     availableFrames = 0;
9601     if (c89atomic_load_32(&pDataStream->isPageValid[pageIndex0])) {
9602         availableFrames += c89atomic_load_32(&pDataStream->pageFrameCount[pageIndex0]) - relativeCursor;
9603         if (c89atomic_load_32(&pDataStream->isPageValid[pageIndex1])) {
9604             availableFrames += c89atomic_load_32(&pDataStream->pageFrameCount[pageIndex1]);
9605         }
9606     }
9607 
9608     *pAvailableFrames = availableFrames;
9609     return MA_SUCCESS;
9610 }
9611 
9612 
ma_resource_manager_data_source_preinit(ma_resource_manager * pResourceManager,ma_uint32 flags,ma_resource_manager_data_source * pDataSource)9613 static ma_result ma_resource_manager_data_source_preinit(ma_resource_manager* pResourceManager, ma_uint32 flags, ma_resource_manager_data_source* pDataSource)
9614 {
9615     if (pDataSource == NULL) {
9616         return MA_INVALID_ARGS;
9617     }
9618 
9619     MA_ZERO_OBJECT(pDataSource);
9620 
9621     if (pResourceManager == NULL) {
9622         return MA_INVALID_ARGS;
9623     }
9624 
9625     pDataSource->flags = flags;
9626 
9627     return MA_SUCCESS;
9628 }
9629 
ma_resource_manager_data_source_init(ma_resource_manager * pResourceManager,const char * pName,ma_uint32 flags,const ma_pipeline_notifications * pNotifications,ma_resource_manager_data_source * pDataSource)9630 MA_API ma_result ma_resource_manager_data_source_init(ma_resource_manager* pResourceManager, const char* pName, ma_uint32 flags, const ma_pipeline_notifications* pNotifications, ma_resource_manager_data_source* pDataSource)
9631 {
9632     ma_result result;
9633 
9634     result = ma_resource_manager_data_source_preinit(pResourceManager, flags, pDataSource);
9635     if (result != MA_SUCCESS) {
9636         return result;
9637     }
9638 
9639     /* The data source itself is just a data stream or a data buffer. */
9640     if ((flags & MA_DATA_SOURCE_FLAG_STREAM) != 0) {
9641         return ma_resource_manager_data_stream_init(pResourceManager, pName, flags, pNotifications, &pDataSource->stream);
9642     } else {
9643         return ma_resource_manager_data_buffer_init(pResourceManager, pName, flags, pNotifications, &pDataSource->buffer);
9644     }
9645 }
9646 
ma_resource_manager_data_source_init_w(ma_resource_manager * pResourceManager,const wchar_t * pName,ma_uint32 flags,const ma_pipeline_notifications * pNotifications,ma_resource_manager_data_source * pDataSource)9647 MA_API ma_result ma_resource_manager_data_source_init_w(ma_resource_manager* pResourceManager, const wchar_t* pName, ma_uint32 flags, const ma_pipeline_notifications* pNotifications, ma_resource_manager_data_source* pDataSource)
9648 {
9649     ma_result result;
9650 
9651     result = ma_resource_manager_data_source_preinit(pResourceManager, flags, pDataSource);
9652     if (result != MA_SUCCESS) {
9653         return result;
9654     }
9655 
9656     /* The data source itself is just a data stream or a data buffer. */
9657     if ((flags & MA_DATA_SOURCE_FLAG_STREAM) != 0) {
9658         return ma_resource_manager_data_stream_init_w(pResourceManager, pName, flags, pNotifications, &pDataSource->stream);
9659     } else {
9660         return ma_resource_manager_data_buffer_init_w(pResourceManager, pName, flags, pNotifications, &pDataSource->buffer);
9661     }
9662 }
9663 
ma_resource_manager_data_source_init_copy(ma_resource_manager * pResourceManager,const ma_resource_manager_data_source * pExistingDataSource,ma_resource_manager_data_source * pDataSource)9664 MA_API ma_result ma_resource_manager_data_source_init_copy(ma_resource_manager* pResourceManager, const ma_resource_manager_data_source* pExistingDataSource, ma_resource_manager_data_source* pDataSource)
9665 {
9666     ma_result result;
9667 
9668     if (pExistingDataSource == NULL) {
9669         return MA_INVALID_ARGS;
9670     }
9671 
9672     result = ma_resource_manager_data_source_preinit(pResourceManager, pExistingDataSource->flags, pDataSource);
9673     if (result != MA_SUCCESS) {
9674         return result;
9675     }
9676 
9677     /* Copying can only be done from data buffers. Streams cannot be copied. */
9678     if ((pExistingDataSource->flags & MA_DATA_SOURCE_FLAG_STREAM) != 0) {
9679         return MA_INVALID_OPERATION;
9680     }
9681 
9682     return ma_resource_manager_data_buffer_init_copy(pResourceManager, &pExistingDataSource->buffer, &pDataSource->buffer);
9683 }
9684 
ma_resource_manager_data_source_uninit(ma_resource_manager_data_source * pDataSource)9685 MA_API ma_result ma_resource_manager_data_source_uninit(ma_resource_manager_data_source* pDataSource)
9686 {
9687     if (pDataSource == NULL) {
9688         return MA_INVALID_ARGS;
9689     }
9690 
9691     /* All we need to is uninitialize the underlying data buffer or data stream. */
9692     if ((pDataSource->flags & MA_DATA_SOURCE_FLAG_STREAM) != 0) {
9693         return ma_resource_manager_data_stream_uninit(&pDataSource->stream);
9694     } else {
9695         return ma_resource_manager_data_buffer_uninit(&pDataSource->buffer);
9696     }
9697 }
9698 
ma_resource_manager_data_source_read_pcm_frames(ma_resource_manager_data_source * pDataSource,void * pFramesOut,ma_uint64 frameCount,ma_uint64 * pFramesRead)9699 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)
9700 {
9701     /* Safety. */
9702     if (pFramesRead != NULL) {
9703         *pFramesRead = 0;
9704     }
9705 
9706     if (pDataSource == NULL) {
9707         return MA_INVALID_ARGS;
9708     }
9709 
9710     if ((pDataSource->flags & MA_DATA_SOURCE_FLAG_STREAM) != 0) {
9711         return ma_resource_manager_data_stream_read_pcm_frames(&pDataSource->stream, pFramesOut, frameCount, pFramesRead);
9712     } else {
9713         return ma_resource_manager_data_buffer_read_pcm_frames(&pDataSource->buffer, pFramesOut, frameCount, pFramesRead);
9714     }
9715 }
9716 
ma_resource_manager_data_source_seek_to_pcm_frame(ma_resource_manager_data_source * pDataSource,ma_uint64 frameIndex)9717 MA_API ma_result ma_resource_manager_data_source_seek_to_pcm_frame(ma_resource_manager_data_source* pDataSource, ma_uint64 frameIndex)
9718 {
9719     if (pDataSource == NULL) {
9720         return MA_INVALID_ARGS;
9721     }
9722 
9723     if ((pDataSource->flags & MA_DATA_SOURCE_FLAG_STREAM) != 0) {
9724         return ma_resource_manager_data_stream_seek_to_pcm_frame(&pDataSource->stream, frameIndex);
9725     } else {
9726         return ma_resource_manager_data_buffer_seek_to_pcm_frame(&pDataSource->buffer, frameIndex);
9727     }
9728 }
9729 
ma_resource_manager_data_source_map(ma_resource_manager_data_source * pDataSource,void ** ppFramesOut,ma_uint64 * pFrameCount)9730 MA_API ma_result ma_resource_manager_data_source_map(ma_resource_manager_data_source* pDataSource, void** ppFramesOut, ma_uint64* pFrameCount)
9731 {
9732     if (pDataSource == NULL) {
9733         return MA_INVALID_ARGS;
9734     }
9735 
9736     if ((pDataSource->flags & MA_DATA_SOURCE_FLAG_STREAM) != 0) {
9737         return ma_resource_manager_data_stream_map(&pDataSource->stream, ppFramesOut, pFrameCount);
9738     } else {
9739         return MA_NOT_IMPLEMENTED;  /* Mapping not supported with data buffers. */
9740     }
9741 }
9742 
ma_resource_manager_data_source_unmap(ma_resource_manager_data_source * pDataSource,ma_uint64 frameCount)9743 MA_API ma_result ma_resource_manager_data_source_unmap(ma_resource_manager_data_source* pDataSource, ma_uint64 frameCount)
9744 {
9745     if (pDataSource == NULL) {
9746         return MA_INVALID_ARGS;
9747     }
9748 
9749     if ((pDataSource->flags & MA_DATA_SOURCE_FLAG_STREAM) != 0) {
9750         return ma_resource_manager_data_stream_unmap(&pDataSource->stream, frameCount);
9751     } else {
9752         return MA_NOT_IMPLEMENTED;  /* Mapping not supported with data buffers. */
9753     }
9754 }
9755 
ma_resource_manager_data_source_get_data_format(ma_resource_manager_data_source * pDataSource,ma_format * pFormat,ma_uint32 * pChannels,ma_uint32 * pSampleRate)9756 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)
9757 {
9758     if (pDataSource == NULL) {
9759         return MA_INVALID_ARGS;
9760     }
9761 
9762     if ((pDataSource->flags & MA_DATA_SOURCE_FLAG_STREAM) != 0) {
9763         return ma_resource_manager_data_stream_get_data_format(&pDataSource->stream, pFormat, pChannels, pSampleRate);
9764     } else {
9765         return ma_resource_manager_data_buffer_get_data_format(&pDataSource->buffer, pFormat, pChannels, pSampleRate);
9766     }
9767 }
9768 
ma_resource_manager_data_source_get_cursor_in_pcm_frames(ma_resource_manager_data_source * pDataSource,ma_uint64 * pCursor)9769 MA_API ma_result ma_resource_manager_data_source_get_cursor_in_pcm_frames(ma_resource_manager_data_source* pDataSource, ma_uint64* pCursor)
9770 {
9771     if (pDataSource == NULL) {
9772         return MA_INVALID_ARGS;
9773     }
9774 
9775     if ((pDataSource->flags & MA_DATA_SOURCE_FLAG_STREAM) != 0) {
9776         return ma_resource_manager_data_stream_get_cursor_in_pcm_frames(&pDataSource->stream, pCursor);
9777     } else {
9778         return ma_resource_manager_data_buffer_get_cursor_in_pcm_frames(&pDataSource->buffer, pCursor);
9779     }
9780 }
9781 
ma_resource_manager_data_source_get_length_in_pcm_frames(ma_resource_manager_data_source * pDataSource,ma_uint64 * pLength)9782 MA_API ma_result ma_resource_manager_data_source_get_length_in_pcm_frames(ma_resource_manager_data_source* pDataSource, ma_uint64* pLength)
9783 {
9784     if (pDataSource == NULL) {
9785         return MA_INVALID_ARGS;
9786     }
9787 
9788     if ((pDataSource->flags & MA_DATA_SOURCE_FLAG_STREAM) != 0) {
9789         return ma_resource_manager_data_stream_get_length_in_pcm_frames(&pDataSource->stream, pLength);
9790     } else {
9791         return ma_resource_manager_data_buffer_get_length_in_pcm_frames(&pDataSource->buffer, pLength);
9792     }
9793 }
9794 
ma_resource_manager_data_source_result(const ma_resource_manager_data_source * pDataSource)9795 MA_API ma_result ma_resource_manager_data_source_result(const ma_resource_manager_data_source* pDataSource)
9796 {
9797     if (pDataSource == NULL) {
9798         return MA_INVALID_ARGS;
9799     }
9800 
9801     if ((pDataSource->flags & MA_DATA_SOURCE_FLAG_STREAM) != 0) {
9802         return ma_resource_manager_data_stream_result(&pDataSource->stream);
9803     } else {
9804         return ma_resource_manager_data_buffer_result(&pDataSource->buffer);
9805     }
9806 }
9807 
ma_resource_manager_data_source_set_looping(ma_resource_manager_data_source * pDataSource,ma_bool32 isLooping)9808 MA_API ma_result ma_resource_manager_data_source_set_looping(ma_resource_manager_data_source* pDataSource, ma_bool32 isLooping)
9809 {
9810     if (pDataSource == NULL) {
9811         return MA_INVALID_ARGS;
9812     }
9813 
9814     if ((pDataSource->flags & MA_DATA_SOURCE_FLAG_STREAM) != 0) {
9815         return ma_resource_manager_data_stream_set_looping(&pDataSource->stream, isLooping);
9816     } else {
9817         return ma_resource_manager_data_buffer_set_looping(&pDataSource->buffer, isLooping);
9818     }
9819 }
9820 
ma_resource_manager_data_source_get_looping(const ma_resource_manager_data_source * pDataSource,ma_bool32 * pIsLooping)9821 MA_API ma_result ma_resource_manager_data_source_get_looping(const ma_resource_manager_data_source* pDataSource, ma_bool32* pIsLooping)
9822 {
9823     if (pDataSource == NULL || pIsLooping == NULL) {
9824         return MA_INVALID_ARGS;
9825     }
9826 
9827     if ((pDataSource->flags & MA_DATA_SOURCE_FLAG_STREAM) != 0) {
9828         return ma_resource_manager_data_stream_get_looping(&pDataSource->stream, pIsLooping);
9829     } else {
9830         return ma_resource_manager_data_buffer_get_looping(&pDataSource->buffer, pIsLooping);
9831     }
9832 }
9833 
ma_resource_manager_data_source_get_available_frames(ma_resource_manager_data_source * pDataSource,ma_uint64 * pAvailableFrames)9834 MA_API ma_result ma_resource_manager_data_source_get_available_frames(ma_resource_manager_data_source* pDataSource, ma_uint64* pAvailableFrames)
9835 {
9836     if (pAvailableFrames == NULL) {
9837         return MA_INVALID_ARGS;
9838     }
9839 
9840     *pAvailableFrames = 0;
9841 
9842     if (pDataSource == NULL) {
9843         return MA_INVALID_ARGS;
9844     }
9845 
9846     if ((pDataSource->flags & MA_DATA_SOURCE_FLAG_STREAM) != 0) {
9847         return ma_resource_manager_data_stream_get_available_frames(&pDataSource->stream, pAvailableFrames);
9848     } else {
9849         return ma_resource_manager_data_buffer_get_available_frames(&pDataSource->buffer, pAvailableFrames);
9850     }
9851 }
9852 
9853 
ma_resource_manager_post_job(ma_resource_manager * pResourceManager,const ma_job * pJob)9854 MA_API ma_result ma_resource_manager_post_job(ma_resource_manager* pResourceManager, const ma_job* pJob)
9855 {
9856     if (pResourceManager == NULL) {
9857         return MA_INVALID_ARGS;
9858     }
9859 
9860     return ma_job_queue_post(&pResourceManager->jobQueue, pJob);
9861 }
9862 
ma_resource_manager_post_job_quit(ma_resource_manager * pResourceManager)9863 MA_API ma_result ma_resource_manager_post_job_quit(ma_resource_manager* pResourceManager)
9864 {
9865     ma_job job = ma_job_init(MA_JOB_QUIT);
9866     return ma_resource_manager_post_job(pResourceManager, &job);
9867 }
9868 
ma_resource_manager_next_job(ma_resource_manager * pResourceManager,ma_job * pJob)9869 MA_API ma_result ma_resource_manager_next_job(ma_resource_manager* pResourceManager, ma_job* pJob)
9870 {
9871     if (pResourceManager == NULL) {
9872         return MA_INVALID_ARGS;
9873     }
9874 
9875     return ma_job_queue_next(&pResourceManager->jobQueue, pJob);
9876 }
9877 
9878 
ma_resource_manager_process_job__load_data_buffer_node(ma_resource_manager * pResourceManager,ma_job * pJob)9879 static ma_result ma_resource_manager_process_job__load_data_buffer_node(ma_resource_manager* pResourceManager, ma_job* pJob)
9880 {
9881     ma_result result = MA_SUCCESS;
9882 
9883     MA_ASSERT(pResourceManager != NULL);
9884     MA_ASSERT(pJob != NULL);
9885     MA_ASSERT(pJob->loadDataBufferNode.pDataBufferNode != NULL);
9886     MA_ASSERT(pJob->loadDataBufferNode.pDataBufferNode->isDataOwnedByResourceManager == MA_TRUE);  /* The data should always be owned by the resource manager. */
9887 
9888     /* First thing we need to do is check whether or not the data buffer is getting deleted. If so we just abort. */
9889     if (ma_resource_manager_data_buffer_node_result(pJob->loadDataBufferNode.pDataBufferNode) != MA_BUSY) {
9890         result = ma_resource_manager_data_buffer_node_result(pJob->loadDataBufferNode.pDataBufferNode);    /* The data buffer may be getting deleted before it's even been loaded. */
9891         goto done;
9892     }
9893 
9894     /* 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. */
9895     if (pJob->order != pJob->loadDataBufferNode.pDataBufferNode->executionPointer) {
9896         return ma_resource_manager_post_job(pResourceManager, pJob);    /* Attempting to execute out of order. Probably interleaved with a MA_JOB_FREE_DATA_BUFFER job. */
9897     }
9898 
9899     /*
9900     We're ready to start loading. Essentially what we're doing here is initializing the data supply
9901     of the node. Once this is complete, data buffers can have their connectors initialized which
9902     will allow then to have audio data read from them.
9903 
9904     Note that when the data supply type has been moved away from "unknown", that is when other threads
9905     will determine that the node is available for data delivery and the data buffer connectors can be
9906     initialized. Therefore, it's important that it is set after the data supply has been initialized.
9907     */
9908     if (pJob->loadDataBufferNode.decode) {
9909         /*
9910         Decoding. This is the complex case because we're not going to be doing the entire decoding
9911         process here. Instead it's going to be split of multiple jobs and loaded in pages. The
9912         reason for this is to evenly distribute decoding time across multiple sounds, rather than
9913         having one huge sound hog all the available processing resources.
9914 
9915         The first thing we do is initialize a decoder. This is allocated on the heap and is passed
9916         around to the paging jobs. When the last paging job has completed it's processing, it'll
9917         free the decoder for us.
9918 
9919         This job does not do any actual decoding. It instead just posts a PAGE_DATA_BUFFER_NODE job
9920         which is where the actual decoding work will be done. However, once this job is complete,
9921         the node will be in a state where data buffer connectors can be initialized.
9922         */
9923         ma_decoder* pDecoder;   /* <-- Free'd on the last page decode. */
9924         ma_job pageDataBufferNodeJob;
9925 
9926         /* Allocate the decoder by initializing a decoded data supply. */
9927         result = ma_resource_manager_data_buffer_node_init_supply_decoded(pResourceManager, pJob->loadDataBufferNode.pDataBufferNode, pJob->loadDataBufferNode.pFilePath, pJob->loadDataBufferNode.pFilePathW, &pDecoder);
9928 
9929         /*
9930         Don't ever propagate an MA_BUSY result code or else the resource manager will think the
9931         node is just busy decoding rather than in an error state. This should never happen, but
9932         including this logic for safety just in case.
9933         */
9934         if (result == MA_BUSY) {
9935             result  = MA_ERROR;
9936         }
9937 
9938         if (result != MA_SUCCESS) {
9939             if (pJob->loadDataBufferNode.pFilePath != NULL) {
9940                 ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_WARNING, "Failed to initialize data supply for \"%s\". %s.\n", pJob->loadDataBufferNode.pFilePath, ma_result_description(result));
9941             } else {
9942                 ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_WARNING, "Failed to initialize data supply for \"%ls\", %s.\n", pJob->loadDataBufferNode.pFilePathW, ma_result_description(result));
9943             }
9944 
9945             goto done;
9946         }
9947 
9948         /*
9949         At this point the node's data supply is initialized and other threads can start initializing
9950         their data buffer connectors. However, no data will actually be available until we start to
9951         actually decode it. To do this, we need to post a paging job which is where the decoding
9952         work is done.
9953 
9954         Note that if an error occurred at an earlier point, this section will have been skipped.
9955         */
9956         pageDataBufferNodeJob = ma_job_init(MA_JOB_PAGE_DATA_BUFFER_NODE);
9957         pageDataBufferNodeJob.order                                = ma_resource_manager_data_buffer_node_next_execution_order(pJob->loadDataBufferNode.pDataBufferNode);
9958         pageDataBufferNodeJob.pageDataBufferNode.pDataBufferNode   = pJob->loadDataBufferNode.pDataBufferNode;
9959         pageDataBufferNodeJob.pageDataBufferNode.pDecoder          = pDecoder;
9960         pageDataBufferNodeJob.pageDataBufferNode.pDoneNotification = pJob->loadDataBufferNode.pDoneNotification;
9961         pageDataBufferNodeJob.pageDataBufferNode.pDoneFence        = pJob->loadDataBufferNode.pDoneFence;
9962 
9963         /* The job has been set up so it can now be posted. */
9964         result = ma_resource_manager_post_job(pResourceManager, &pageDataBufferNodeJob);
9965 
9966         /*
9967         When we get here, we want to make sure the result code is set to MA_BUSY. The reason for
9968         this is that the result will be copied over to the node's internal result variable. In
9969         this case, since the decoding is still in-progress, we need to make sure the result code
9970         is set to MA_BUSY.
9971         */
9972         if (result != MA_SUCCESS) {
9973             ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_ERROR, "Failed to post MA_JOB_PAGE_DATA_BUFFER_NODE job. %d\n", ma_result_description(result));
9974             ma_decoder_uninit(pDecoder);
9975             ma_free(pDecoder, &pResourceManager->config.allocationCallbacks);
9976         } else {
9977             result = MA_BUSY;
9978         }
9979     } else {
9980         /* No decoding. This is the simple case. We need only read the file content into memory and we're done. */
9981         result = ma_resource_manager_data_buffer_node_init_supply_encoded(pResourceManager, pJob->loadDataBufferNode.pDataBufferNode, pJob->loadDataBufferNode.pFilePath, pJob->loadDataBufferNode.pFilePathW);
9982     }
9983 
9984 
9985 done:
9986     /* File paths are no longer needed. */
9987     ma_free(pJob->loadDataBufferNode.pFilePath,  &pResourceManager->config.allocationCallbacks/*, MA_ALLOCATION_TYPE_TRANSIENT_STRING*/);
9988     ma_free(pJob->loadDataBufferNode.pFilePathW, &pResourceManager->config.allocationCallbacks/*, MA_ALLOCATION_TYPE_TRANSIENT_STRING*/);
9989 
9990     /*
9991     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
9992     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
9993     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
9994     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
9995     other error code would cause the buffer to look like it's in a state that it's not.
9996     */
9997     c89atomic_compare_and_swap_32(&pJob->loadDataBufferNode.pDataBufferNode->result, MA_BUSY, result);
9998 
9999     /* At this point initialization is complete and we can signal the notification if any. */
10000     if (pJob->loadDataBufferNode.pInitNotification != NULL) {
10001         ma_async_notification_signal(pJob->loadDataBufferNode.pInitNotification);
10002     }
10003     if (pJob->loadDataBufferNode.pInitFence != NULL) {
10004         ma_fence_release(pJob->loadDataBufferNode.pInitFence);
10005     }
10006 
10007     /* If we have a success result it means we've fully loaded the buffer. This will happen in the non-decoding case. */
10008     if (result != MA_BUSY) {
10009         if (pJob->loadDataBufferNode.pDoneNotification != NULL) {
10010             ma_async_notification_signal(pJob->loadDataBufferNode.pDoneNotification);
10011         }
10012         if (pJob->loadDataBufferNode.pDoneFence != NULL) {
10013             ma_fence_release(pJob->loadDataBufferNode.pDoneFence);
10014         }
10015     }
10016 
10017     /* Increment the node's execution pointer so that the next jobs can be processed. This is how we keep decoding of pages in-order. */
10018     c89atomic_fetch_add_32(&pJob->loadDataBufferNode.pDataBufferNode->executionPointer, 1);
10019     return result;
10020 }
10021 
ma_resource_manager_process_job__free_data_buffer_node(ma_resource_manager * pResourceManager,ma_job * pJob)10022 static ma_result ma_resource_manager_process_job__free_data_buffer_node(ma_resource_manager* pResourceManager, ma_job* pJob)
10023 {
10024     MA_ASSERT(pResourceManager != NULL);
10025     MA_ASSERT(pJob             != NULL);
10026     MA_ASSERT(pJob->freeDataBufferNode.pDataBufferNode != NULL);
10027 
10028     if (pJob->order != pJob->freeDataBufferNode.pDataBufferNode->executionPointer) {
10029         return ma_resource_manager_post_job(pResourceManager, pJob);    /* Out of order. */
10030     }
10031 
10032     ma_resource_manager_data_buffer_node_free(pResourceManager, pJob->freeDataBufferNode.pDataBufferNode);
10033 
10034     /* The event needs to be signalled last. */
10035     if (pJob->freeDataBufferNode.pDoneNotification != NULL) {
10036         ma_async_notification_signal(pJob->freeDataBufferNode.pDoneNotification);
10037     }
10038 
10039     if (pJob->freeDataBufferNode.pDoneFence != NULL) {
10040         ma_fence_release(pJob->freeDataBufferNode.pDoneFence);
10041     }
10042 
10043     c89atomic_fetch_add_32(&pJob->freeDataBufferNode.pDataBufferNode->executionPointer, 1);
10044     return MA_SUCCESS;
10045 }
10046 
ma_resource_manager_process_job__page_data_buffer_node(ma_resource_manager * pResourceManager,ma_job * pJob)10047 static ma_result ma_resource_manager_process_job__page_data_buffer_node(ma_resource_manager* pResourceManager, ma_job* pJob)
10048 {
10049     ma_result result = MA_SUCCESS;
10050 
10051     MA_ASSERT(pResourceManager != NULL);
10052     MA_ASSERT(pJob             != NULL);
10053 
10054     /* Don't do any more decoding if the data buffer has started the uninitialization process. */
10055     if (ma_resource_manager_data_buffer_node_result(pJob->pageDataBufferNode.pDataBufferNode) != MA_BUSY) {
10056         result = ma_resource_manager_data_buffer_node_result(pJob->pageDataBufferNode.pDataBufferNode);
10057 		goto done;
10058     }
10059 
10060     if (pJob->order != pJob->pageDataBufferNode.pDataBufferNode->executionPointer) {
10061         return ma_resource_manager_post_job(pResourceManager, pJob);    /* Out of order. */
10062     }
10063 
10064     /* We're ready to decode the next page. */
10065     result = ma_resource_manager_data_buffer_node_decode_next_page(pResourceManager, pJob->pageDataBufferNode.pDataBufferNode, pJob->pageDataBufferNode.pDecoder);
10066 
10067     /*
10068     If we have a success code by this point, we want to post another job. We're going to set the
10069     result back to MA_BUSY to make it clear that there's still more to load.
10070     */
10071     if (result == MA_SUCCESS) {
10072         ma_job newJob;
10073         newJob = *pJob; /* Everything is the same as the input job, except the execution order. */
10074         newJob.order = ma_resource_manager_data_buffer_node_next_execution_order(pJob->pageDataBufferNode.pDataBufferNode);   /* We need a fresh execution order. */
10075 
10076         result = ma_resource_manager_post_job(pResourceManager, &newJob);
10077 
10078         /* Since the sound isn't yet fully decoded we want the status to be set to busy. */
10079         if (result == MA_SUCCESS) {
10080             result  = MA_BUSY;
10081         }
10082     }
10083 
10084 done:
10085     /* If there's still more to decode the result will be set to MA_BUSY. Otherwise we can free the decoder. */
10086     if (result != MA_BUSY) {
10087         ma_decoder_uninit(pJob->pageDataBufferNode.pDecoder);
10088         ma_free(pJob->pageDataBufferNode.pDecoder, &pResourceManager->config.allocationCallbacks/*, MA_ALLOCATION_TYPE_DECODER*/);
10089     }
10090 
10091     /* If we reached the end we need to treat it as successful. */
10092     if (result == MA_AT_END) {
10093         result  = MA_SUCCESS;
10094     }
10095 
10096     /* Make sure we set the result of node in case some error occurred. */
10097     c89atomic_compare_and_swap_32(&pJob->pageDataBufferNode.pDataBufferNode->result, MA_BUSY, result);
10098 
10099     /* Signal the notification after setting the result in case the notification callback wants to inspect the result code. */
10100     if (result != MA_BUSY) {
10101         if (pJob->pageDataBufferNode.pDoneNotification != NULL) {
10102             ma_async_notification_signal(pJob->pageDataBufferNode.pDoneNotification);
10103         }
10104 
10105         if (pJob->pageDataBufferNode.pDoneFence != NULL) {
10106             ma_fence_release(pJob->pageDataBufferNode.pDoneFence);
10107         }
10108     }
10109 
10110     c89atomic_fetch_add_32(&pJob->pageDataBufferNode.pDataBufferNode->executionPointer, 1);
10111     return result;
10112 }
10113 
10114 
ma_resource_manager_process_job__load_data_buffer(ma_resource_manager * pResourceManager,ma_job * pJob)10115 static ma_result ma_resource_manager_process_job__load_data_buffer(ma_resource_manager* pResourceManager, ma_job* pJob)
10116 {
10117     ma_result result = MA_SUCCESS;
10118 
10119     /*
10120     All we're doing here is checking if the node has finished loading. If not, we just re-post the job
10121     and keep waiting. Otherwise we increment the execution counter and set the buffer's result code.
10122     */
10123     MA_ASSERT(pResourceManager != NULL);
10124     MA_ASSERT(pJob             != NULL);
10125     MA_ASSERT(pJob->loadDataBuffer.pDataBuffer != NULL);
10126 
10127     if (pJob->order != pJob->loadDataBuffer.pDataBuffer->executionPointer) {
10128         return ma_resource_manager_post_job(pResourceManager, pJob);    /* Attempting to execute out of order. Probably interleaved with a MA_JOB_FREE_DATA_BUFFER job. */
10129     }
10130 
10131     /*
10132     First thing we need to do is check whether or not the data buffer is getting deleted. If so we
10133     just abort, but making sure we increment the execution pointer.
10134     */
10135     if (ma_resource_manager_data_buffer_result(pJob->loadDataBuffer.pDataBuffer) != MA_BUSY) {
10136         result = ma_resource_manager_data_buffer_result(pJob->loadDataBuffer.pDataBuffer);    /* The data buffer may be getting deleted before it's even been loaded. */
10137         goto done;
10138     }
10139 
10140     /* Try initializing the connector if we haven't already. */
10141     if (pJob->loadDataBuffer.pDataBuffer->isConnectorInitialized == MA_FALSE) {
10142         if (ma_resource_manager_data_buffer_node_get_data_supply_type(pJob->loadDataBuffer.pDataBuffer->pNode) != ma_resource_manager_data_supply_type_unknown) {
10143             /* We can now initialize the connector. If this fails, we need to abort. It's very rare for this to fail. */
10144             result = ma_resource_manager_data_buffer_init_connector(pJob->loadDataBuffer.pDataBuffer, pJob->loadDataBuffer.pInitNotification, pJob->loadDataBuffer.pInitFence);
10145             if (result != MA_SUCCESS) {
10146                 ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_ERROR, "Failed to initialize connector for data buffer. %s.\n", ma_result_description(result));
10147                 goto done;
10148             }
10149         }
10150     } else {
10151         /* The connector is already initialized. Nothing to do here. */
10152     }
10153 
10154     /*
10155     If the data node is still loading, we need to repost the job and *not* increment the execution
10156     pointer (i.e. we need to not fall through to the "done" label).
10157     */
10158     result = ma_resource_manager_data_buffer_node_result(pJob->loadDataBuffer.pDataBuffer->pNode);
10159     if (result == MA_BUSY) {
10160         return ma_resource_manager_post_job(pResourceManager, pJob);
10161     }
10162 
10163 done:
10164     /* Only move away from a busy code so that we don't trash any existing error codes. */
10165     c89atomic_compare_and_swap_32(&pJob->loadDataBuffer.pDataBuffer->result, MA_BUSY, result);
10166 
10167     /* Only signal the other threads after the result has been set just for cleanliness sake. */
10168     if (pJob->loadDataBuffer.pDoneNotification != NULL) {
10169         ma_async_notification_signal(pJob->loadDataBuffer.pDoneNotification);
10170     }
10171     if (pJob->loadDataBuffer.pDoneFence != NULL) {
10172         ma_fence_release(pJob->loadDataBuffer.pDoneFence);
10173     }
10174 
10175 	/*
10176 	If at this point the data buffer has not had it's connector initialized, it means the
10177 	notification event was never signalled which means we need to signal it here.
10178 	*/
10179 	if (pJob->loadDataBuffer.pDataBuffer->isConnectorInitialized == MA_FALSE && result != MA_SUCCESS) {
10180 		if (pJob->loadDataBuffer.pInitNotification != NULL) {
10181 		    ma_async_notification_signal(pJob->loadDataBuffer.pInitNotification);
10182 		}
10183         if (pJob->loadDataBuffer.pInitFence != NULL) {
10184             ma_fence_release(pJob->loadDataBuffer.pInitFence);
10185         }
10186 	}
10187 
10188     c89atomic_fetch_add_32(&pJob->loadDataBuffer.pDataBuffer->executionPointer, 1);
10189     return result;
10190 }
10191 
ma_resource_manager_process_job__free_data_buffer(ma_resource_manager * pResourceManager,ma_job * pJob)10192 static ma_result ma_resource_manager_process_job__free_data_buffer(ma_resource_manager* pResourceManager, ma_job* pJob)
10193 {
10194     MA_ASSERT(pResourceManager != NULL);
10195     MA_ASSERT(pJob             != NULL);
10196     MA_ASSERT(pJob->freeDataBuffer.pDataBuffer != NULL);
10197 
10198     if (pJob->order != pJob->freeDataBuffer.pDataBuffer->executionPointer) {
10199         return ma_resource_manager_post_job(pResourceManager, pJob);    /* Out of order. */
10200     }
10201 
10202     ma_resource_manager_data_buffer_uninit_internal(pJob->freeDataBuffer.pDataBuffer);
10203 
10204     /* The event needs to be signalled last. */
10205     if (pJob->freeDataBuffer.pDoneNotification != NULL) {
10206         ma_async_notification_signal(pJob->freeDataBuffer.pDoneNotification);
10207     }
10208 
10209     if (pJob->freeDataBuffer.pDoneFence != NULL) {
10210         ma_fence_release(pJob->freeDataBuffer.pDoneFence);
10211     }
10212 
10213     c89atomic_fetch_add_32(&pJob->freeDataBuffer.pDataBuffer->executionPointer, 1);
10214     return MA_SUCCESS;
10215 }
10216 
ma_resource_manager_process_job__load_data_stream(ma_resource_manager * pResourceManager,ma_job * pJob)10217 static ma_result ma_resource_manager_process_job__load_data_stream(ma_resource_manager* pResourceManager, ma_job* pJob)
10218 {
10219     ma_result result = MA_SUCCESS;
10220     ma_decoder_config decoderConfig;
10221     ma_uint32 pageBufferSizeInBytes;
10222     ma_resource_manager_data_stream* pDataStream;
10223 
10224     MA_ASSERT(pResourceManager != NULL);
10225     MA_ASSERT(pJob             != NULL);
10226 
10227     pDataStream = pJob->loadDataStream.pDataStream;
10228 
10229     if (ma_resource_manager_data_stream_result(pDataStream) != MA_BUSY) {
10230         result = MA_INVALID_OPERATION;  /* Most likely the data stream is being uninitialized. */
10231         goto done;
10232     }
10233 
10234     if (pJob->order != pDataStream->executionPointer) {
10235         return ma_resource_manager_post_job(pResourceManager, pJob);    /* Out of order. */
10236     }
10237 
10238     /* We need to initialize the decoder first so we can determine the size of the pages. */
10239     decoderConfig = ma_decoder_config_init(pResourceManager->config.decodedFormat, pResourceManager->config.decodedChannels, pResourceManager->config.decodedSampleRate);
10240     decoderConfig.allocationCallbacks = pResourceManager->config.allocationCallbacks;
10241 
10242     if (pJob->loadDataStream.pFilePath != NULL) {
10243         result = ma_decoder_init_vfs(pResourceManager->config.pVFS, pJob->loadDataStream.pFilePath, &decoderConfig, &pDataStream->decoder);
10244     } else {
10245         result = ma_decoder_init_vfs_w(pResourceManager->config.pVFS, pJob->loadDataStream.pFilePathW, &decoderConfig, &pDataStream->decoder);
10246     }
10247     if (result != MA_SUCCESS) {
10248         goto done;
10249     }
10250 
10251     /* Retrieve the total length of the file before marking the decoder are loaded. */
10252     pDataStream->totalLengthInPCMFrames = ma_decoder_get_length_in_pcm_frames(&pDataStream->decoder);
10253 
10254     /*
10255     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
10256     and we don't want to have another thread trying to access the decoder while it's scanning.
10257     */
10258     pDataStream->isDecoderInitialized = MA_TRUE;
10259 
10260     /* We have the decoder so we can now initialize our page buffer. */
10261     pageBufferSizeInBytes = ma_resource_manager_data_stream_get_page_size_in_frames(pDataStream) * 2 * ma_get_bytes_per_frame(pDataStream->decoder.outputFormat, pDataStream->decoder.outputChannels);
10262 
10263     pDataStream->pPageData = ma_malloc(pageBufferSizeInBytes, &pResourceManager->config.allocationCallbacks/*, MA_ALLOCATION_TYPE_DECODED_BUFFER*/);
10264     if (pDataStream->pPageData == NULL) {
10265         ma_decoder_uninit(&pDataStream->decoder);
10266         result = MA_OUT_OF_MEMORY;
10267         goto done;
10268     }
10269 
10270     /* We have our decoder and our page buffer, so now we need to fill our pages. */
10271     ma_resource_manager_data_stream_fill_pages(pDataStream);
10272 
10273     /* And now we're done. We want to make sure the result is MA_SUCCESS. */
10274     result = MA_SUCCESS;
10275 
10276 done:
10277     ma_free(pJob->loadDataStream.pFilePath,  &pResourceManager->config.allocationCallbacks/*, MA_ALLOCATION_TYPE_TRANSIENT_STRING*/);
10278     ma_free(pJob->loadDataStream.pFilePathW, &pResourceManager->config.allocationCallbacks/*, MA_ALLOCATION_TYPE_TRANSIENT_STRING*/);
10279 
10280     /* 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). */
10281     c89atomic_compare_and_swap_32(&pDataStream->result, MA_BUSY, result);
10282 
10283     /* Only signal the other threads after the result has been set just for cleanliness sake. */
10284     if (pJob->loadDataStream.pInitNotification != NULL) {
10285         ma_async_notification_signal(pJob->loadDataStream.pInitNotification);
10286     }
10287     if (pJob->loadDataStream.pInitFence != NULL) {
10288         ma_fence_release(pJob->loadDataStream.pInitFence);
10289     }
10290 
10291     c89atomic_fetch_add_32(&pDataStream->executionPointer, 1);
10292     return result;
10293 }
10294 
ma_resource_manager_process_job__free_data_stream(ma_resource_manager * pResourceManager,ma_job * pJob)10295 static ma_result ma_resource_manager_process_job__free_data_stream(ma_resource_manager* pResourceManager, ma_job* pJob)
10296 {
10297     ma_resource_manager_data_stream* pDataStream;
10298 
10299     MA_ASSERT(pResourceManager != NULL);
10300     MA_ASSERT(pJob             != NULL);
10301 
10302     pDataStream = pJob->freeDataStream.pDataStream;
10303     MA_ASSERT(pDataStream != NULL);
10304 
10305     /* If our status is not MA_UNAVAILABLE we have a bug somewhere. */
10306     MA_ASSERT(ma_resource_manager_data_stream_result(pDataStream) == MA_UNAVAILABLE);
10307 
10308     if (pJob->order != pDataStream->executionPointer) {
10309         return ma_resource_manager_post_job(pResourceManager, pJob);    /* Out of order. */
10310     }
10311 
10312     if (pDataStream->isDecoderInitialized) {
10313         ma_decoder_uninit(&pDataStream->decoder);
10314     }
10315 
10316     if (pDataStream->pPageData != NULL) {
10317         ma_free(pDataStream->pPageData, &pResourceManager->config.allocationCallbacks/*, MA_ALLOCATION_TYPE_DECODED_BUFFER*/);
10318         pDataStream->pPageData = NULL;  /* Just in case... */
10319     }
10320 
10321     ma_data_source_uninit(&pDataStream->ds);
10322 
10323     /* The event needs to be signalled last. */
10324     if (pJob->freeDataStream.pDoneNotification != NULL) {
10325         ma_async_notification_signal(pJob->freeDataStream.pDoneNotification);
10326     }
10327     if (pJob->freeDataStream.pDoneFence != NULL) {
10328         ma_fence_release(pJob->freeDataStream.pDoneFence);
10329     }
10330 
10331     /*c89atomic_fetch_add_32(&pDataStream->executionPointer, 1);*/
10332     return MA_SUCCESS;
10333 }
10334 
ma_resource_manager_process_job__page_data_stream(ma_resource_manager * pResourceManager,ma_job * pJob)10335 static ma_result ma_resource_manager_process_job__page_data_stream(ma_resource_manager* pResourceManager, ma_job* pJob)
10336 {
10337     ma_result result = MA_SUCCESS;
10338     ma_resource_manager_data_stream* pDataStream;
10339 
10340     MA_ASSERT(pResourceManager != NULL);
10341     MA_ASSERT(pJob             != NULL);
10342 
10343     pDataStream = pJob->pageDataStream.pDataStream;
10344     MA_ASSERT(pDataStream != NULL);
10345 
10346     /* For streams, the status should be MA_SUCCESS. */
10347     if (ma_resource_manager_data_stream_result(pDataStream) != MA_SUCCESS) {
10348         result = MA_INVALID_OPERATION;
10349         goto done;
10350     }
10351 
10352     if (pJob->order != pDataStream->executionPointer) {
10353         return ma_resource_manager_post_job(pResourceManager, pJob);    /* Out of order. */
10354     }
10355 
10356     ma_resource_manager_data_stream_fill_page(pDataStream, pJob->pageDataStream.pageIndex);
10357 
10358 done:
10359     c89atomic_fetch_add_32(&pDataStream->executionPointer, 1);
10360     return result;
10361 }
10362 
ma_resource_manager_process_job__seek_data_stream(ma_resource_manager * pResourceManager,ma_job * pJob)10363 static ma_result ma_resource_manager_process_job__seek_data_stream(ma_resource_manager* pResourceManager, ma_job* pJob)
10364 {
10365     ma_result result = MA_SUCCESS;
10366     ma_resource_manager_data_stream* pDataStream;
10367 
10368     MA_ASSERT(pResourceManager != NULL);
10369     MA_ASSERT(pJob             != NULL);
10370 
10371     pDataStream = pJob->seekDataStream.pDataStream;
10372     MA_ASSERT(pDataStream != NULL);
10373 
10374     /* For streams the status should be MA_SUCCESS for this to do anything. */
10375     if (ma_resource_manager_data_stream_result(pDataStream) != MA_SUCCESS || pDataStream->isDecoderInitialized == MA_FALSE) {
10376         result = MA_INVALID_OPERATION;
10377         goto done;
10378     }
10379 
10380     if (pJob->order != pDataStream->executionPointer) {
10381         return ma_resource_manager_post_job(pResourceManager, pJob);    /* Out of order. */
10382     }
10383 
10384     /*
10385     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
10386     instead of initializing the decoder, we seek to a frame.
10387     */
10388     ma_decoder_seek_to_pcm_frame(&pDataStream->decoder, pJob->seekDataStream.frameIndex);
10389 
10390     /* After seeking we'll need to reload the pages. */
10391     ma_resource_manager_data_stream_fill_pages(pDataStream);
10392 
10393     /* We need to let the public API know that we're done seeking. */
10394     c89atomic_fetch_sub_32(&pDataStream->seekCounter, 1);
10395 
10396 done:
10397     c89atomic_fetch_add_32(&pDataStream->executionPointer, 1);
10398     return result;
10399 }
10400 
ma_resource_manager_process_job(ma_resource_manager * pResourceManager,ma_job * pJob)10401 MA_API ma_result ma_resource_manager_process_job(ma_resource_manager* pResourceManager, ma_job* pJob)
10402 {
10403     if (pResourceManager == NULL || pJob == NULL) {
10404         return MA_INVALID_ARGS;
10405     }
10406 
10407     switch (pJob->toc.breakup.code)
10408     {
10409         /* Data Buffer Node */
10410         case MA_JOB_LOAD_DATA_BUFFER_NODE: return ma_resource_manager_process_job__load_data_buffer_node(pResourceManager, pJob);
10411         case MA_JOB_FREE_DATA_BUFFER_NODE: return ma_resource_manager_process_job__free_data_buffer_node(pResourceManager, pJob);
10412         case MA_JOB_PAGE_DATA_BUFFER_NODE: return ma_resource_manager_process_job__page_data_buffer_node(pResourceManager, pJob);
10413 
10414         /* Data Buffer */
10415         case MA_JOB_LOAD_DATA_BUFFER: return ma_resource_manager_process_job__load_data_buffer(pResourceManager, pJob);
10416         case MA_JOB_FREE_DATA_BUFFER: return ma_resource_manager_process_job__free_data_buffer(pResourceManager, pJob);
10417 
10418         /* Data Stream */
10419         case MA_JOB_LOAD_DATA_STREAM: return ma_resource_manager_process_job__load_data_stream(pResourceManager, pJob);
10420         case MA_JOB_FREE_DATA_STREAM: return ma_resource_manager_process_job__free_data_stream(pResourceManager, pJob);
10421         case MA_JOB_PAGE_DATA_STREAM: return ma_resource_manager_process_job__page_data_stream(pResourceManager, pJob);
10422         case MA_JOB_SEEK_DATA_STREAM: return ma_resource_manager_process_job__seek_data_stream(pResourceManager, pJob);
10423 
10424         default: break;
10425     }
10426 
10427     /* Getting here means we don't know what the job code is and cannot do anything with it. */
10428     return MA_INVALID_OPERATION;
10429 }
10430 
ma_resource_manager_process_next_job(ma_resource_manager * pResourceManager)10431 MA_API ma_result ma_resource_manager_process_next_job(ma_resource_manager* pResourceManager)
10432 {
10433     ma_result result;
10434     ma_job job;
10435 
10436     if (pResourceManager == NULL) {
10437         return MA_INVALID_ARGS;
10438     }
10439 
10440     /* This will return MA_CANCELLED if the next job is a quit job. */
10441     result = ma_resource_manager_next_job(pResourceManager, &job);
10442     if (result != MA_SUCCESS) {
10443         return result;
10444     }
10445 
10446     return ma_resource_manager_process_job(pResourceManager, &job);
10447 }
10448 
10449 
10450 
10451 
10452 
ma_panner_config_init(ma_format format,ma_uint32 channels)10453 MA_API ma_panner_config ma_panner_config_init(ma_format format, ma_uint32 channels)
10454 {
10455     ma_panner_config config;
10456 
10457     MA_ZERO_OBJECT(&config);
10458     config.format   = format;
10459     config.channels = channels;
10460     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. */
10461     config.pan      = 0;
10462 
10463     return config;
10464 }
10465 
10466 
ma_panner_init(const ma_panner_config * pConfig,ma_panner * pPanner)10467 MA_API ma_result ma_panner_init(const ma_panner_config* pConfig, ma_panner* pPanner)
10468 {
10469     if (pPanner == NULL) {
10470         return MA_INVALID_ARGS;
10471     }
10472 
10473     MA_ZERO_OBJECT(pPanner);
10474 
10475     if (pConfig == NULL) {
10476         return MA_INVALID_ARGS;
10477     }
10478 
10479     pPanner->format   = pConfig->format;
10480     pPanner->channels = pConfig->channels;
10481     pPanner->mode     = pConfig->mode;
10482     pPanner->pan      = pConfig->pan;
10483 
10484     return MA_SUCCESS;
10485 }
10486 
ma_stereo_balance_pcm_frames_f32(float * pFramesOut,const float * pFramesIn,ma_uint64 frameCount,float pan)10487 static void ma_stereo_balance_pcm_frames_f32(float* pFramesOut, const float* pFramesIn, ma_uint64 frameCount, float pan)
10488 {
10489     ma_uint64 iFrame;
10490 
10491     if (pan > 0) {
10492         float factor = 1.0f - pan;
10493         if (pFramesOut == pFramesIn) {
10494             for (iFrame = 0; iFrame < frameCount; iFrame += 1) {
10495                 pFramesOut[iFrame*2 + 0] = pFramesIn[iFrame*2 + 0] * factor;
10496             }
10497         } else {
10498             for (iFrame = 0; iFrame < frameCount; iFrame += 1) {
10499                 pFramesOut[iFrame*2 + 0] = pFramesIn[iFrame*2 + 0] * factor;
10500                 pFramesOut[iFrame*2 + 1] = pFramesIn[iFrame*2 + 1];
10501             }
10502         }
10503     } else {
10504         float factor = 1.0f + pan;
10505         if (pFramesOut == pFramesIn) {
10506             for (iFrame = 0; iFrame < frameCount; iFrame += 1) {
10507                 pFramesOut[iFrame*2 + 1] = pFramesIn[iFrame*2 + 1] * factor;
10508             }
10509         } else {
10510             for (iFrame = 0; iFrame < frameCount; iFrame += 1) {
10511                 pFramesOut[iFrame*2 + 0] = pFramesIn[iFrame*2 + 0];
10512                 pFramesOut[iFrame*2 + 1] = pFramesIn[iFrame*2 + 1] * factor;
10513             }
10514         }
10515     }
10516 }
10517 
ma_stereo_balance_pcm_frames(void * pFramesOut,const void * pFramesIn,ma_uint64 frameCount,ma_format format,float pan)10518 static void ma_stereo_balance_pcm_frames(void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount, ma_format format, float pan)
10519 {
10520     if (pan == 0) {
10521         /* Fast path. No panning required. */
10522         if (pFramesOut == pFramesIn) {
10523             /* No-op */
10524         } else {
10525             ma_copy_pcm_frames(pFramesOut, pFramesIn, frameCount, format, 2);
10526         }
10527 
10528         return;
10529     }
10530 
10531     switch (format) {
10532         case ma_format_f32: ma_stereo_balance_pcm_frames_f32((float*)pFramesOut, (float*)pFramesIn, frameCount, pan); break;
10533 
10534         /* Unknown format. Just copy. */
10535         default:
10536         {
10537             ma_copy_pcm_frames(pFramesOut, pFramesIn, frameCount, format, 2);
10538         } break;
10539     }
10540 }
10541 
10542 
ma_stereo_pan_pcm_frames_f32(float * pFramesOut,const float * pFramesIn,ma_uint64 frameCount,float pan)10543 static void ma_stereo_pan_pcm_frames_f32(float* pFramesOut, const float* pFramesIn, ma_uint64 frameCount, float pan)
10544 {
10545     ma_uint64 iFrame;
10546 
10547     if (pan > 0) {
10548         float factorL0 = 1.0f - pan;
10549         float factorL1 = 0.0f + pan;
10550 
10551         for (iFrame = 0; iFrame < frameCount; iFrame += 1) {
10552             float sample0 = (pFramesIn[iFrame*2 + 0] * factorL0);
10553             float sample1 = (pFramesIn[iFrame*2 + 0] * factorL1) + pFramesIn[iFrame*2 + 1];
10554 
10555             pFramesOut[iFrame*2 + 0] = sample0;
10556             pFramesOut[iFrame*2 + 1] = sample1;
10557         }
10558     } else {
10559         float factorR0 = 0.0f - pan;
10560         float factorR1 = 1.0f + pan;
10561 
10562         for (iFrame = 0; iFrame < frameCount; iFrame += 1) {
10563             float sample0 = pFramesIn[iFrame*2 + 0] + (pFramesIn[iFrame*2 + 1] * factorR0);
10564             float sample1 =                           (pFramesIn[iFrame*2 + 1] * factorR1);
10565 
10566             pFramesOut[iFrame*2 + 0] = sample0;
10567             pFramesOut[iFrame*2 + 1] = sample1;
10568         }
10569     }
10570 }
10571 
ma_stereo_pan_pcm_frames(void * pFramesOut,const void * pFramesIn,ma_uint64 frameCount,ma_format format,float pan)10572 static void ma_stereo_pan_pcm_frames(void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount, ma_format format, float pan)
10573 {
10574     if (pan == 0) {
10575         /* Fast path. No panning required. */
10576         if (pFramesOut == pFramesIn) {
10577             /* No-op */
10578         } else {
10579             ma_copy_pcm_frames(pFramesOut, pFramesIn, frameCount, format, 2);
10580         }
10581 
10582         return;
10583     }
10584 
10585     switch (format) {
10586         case ma_format_f32: ma_stereo_pan_pcm_frames_f32((float*)pFramesOut, (float*)pFramesIn, frameCount, pan); break;
10587 
10588         /* Unknown format. Just copy. */
10589         default:
10590         {
10591             ma_copy_pcm_frames(pFramesOut, pFramesIn, frameCount, format, 2);
10592         } break;
10593     }
10594 }
10595 
ma_panner_process_pcm_frames(ma_panner * pPanner,void * pFramesOut,const void * pFramesIn,ma_uint64 frameCount)10596 MA_API ma_result ma_panner_process_pcm_frames(ma_panner* pPanner, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount)
10597 {
10598     if (pPanner == NULL || pFramesOut == NULL || pFramesIn == NULL) {
10599         return MA_INVALID_ARGS;
10600     }
10601 
10602     if (pPanner->channels == 2) {
10603         /* Stereo case. For now assume channel 0 is left and channel right is 1, but should probably add support for a channel map. */
10604         if (pPanner->mode == ma_pan_mode_balance) {
10605             ma_stereo_balance_pcm_frames(pFramesOut, pFramesIn, frameCount, pPanner->format, pPanner->pan);
10606         } else {
10607             ma_stereo_pan_pcm_frames(pFramesOut, pFramesIn, frameCount, pPanner->format, pPanner->pan);
10608         }
10609     } else {
10610         if (pPanner->channels == 1) {
10611             /* Panning has no effect on mono streams. */
10612             ma_copy_pcm_frames(pFramesOut, pFramesIn, frameCount, pPanner->format, pPanner->channels);
10613         } else {
10614             /* For now we're not going to support non-stereo set ups. Not sure how I want to handle this case just yet. */
10615             ma_copy_pcm_frames(pFramesOut, pFramesIn, frameCount, pPanner->format, pPanner->channels);
10616         }
10617     }
10618 
10619     return MA_SUCCESS;
10620 }
10621 
ma_panner_set_mode(ma_panner * pPanner,ma_pan_mode mode)10622 MA_API void ma_panner_set_mode(ma_panner* pPanner, ma_pan_mode mode)
10623 {
10624     if (pPanner == NULL) {
10625         return;
10626     }
10627 
10628     pPanner->mode = mode;
10629 }
10630 
ma_panner_set_pan(ma_panner * pPanner,float pan)10631 MA_API void ma_panner_set_pan(ma_panner* pPanner, float pan)
10632 {
10633     if (pPanner == NULL) {
10634         return;
10635     }
10636 
10637     pPanner->pan = ma_clamp(pan, -1.0f, 1.0f);
10638 }
10639 
10640 
10641 
10642 
ma_fader_config_init(ma_format format,ma_uint32 channels,ma_uint32 sampleRate)10643 MA_API ma_fader_config ma_fader_config_init(ma_format format, ma_uint32 channels, ma_uint32 sampleRate)
10644 {
10645     ma_fader_config config;
10646 
10647     MA_ZERO_OBJECT(&config);
10648     config.format     = format;
10649     config.channels   = channels;
10650     config.sampleRate = sampleRate;
10651 
10652     return config;
10653 }
10654 
10655 
ma_fader_init(const ma_fader_config * pConfig,ma_fader * pFader)10656 MA_API ma_result ma_fader_init(const ma_fader_config* pConfig, ma_fader* pFader)
10657 {
10658     if (pFader == NULL) {
10659         return MA_INVALID_ARGS;
10660     }
10661 
10662     MA_ZERO_OBJECT(pFader);
10663 
10664     if (pConfig == NULL) {
10665         return MA_INVALID_ARGS;
10666     }
10667 
10668     /* Only f32 is supported for now. */
10669     if (pConfig->format != ma_format_f32) {
10670         return MA_INVALID_ARGS;
10671     }
10672 
10673     pFader->config         = *pConfig;
10674     pFader->volumeBeg      = 1;
10675     pFader->volumeEnd      = 1;
10676     pFader->lengthInFrames = 0;
10677     pFader->cursorInFrames = 0;
10678 
10679     return MA_SUCCESS;
10680 }
10681 
ma_fader_process_pcm_frames(ma_fader * pFader,void * pFramesOut,const void * pFramesIn,ma_uint64 frameCount)10682 MA_API ma_result ma_fader_process_pcm_frames(ma_fader* pFader, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount)
10683 {
10684     if (pFader == NULL) {
10685         return MA_INVALID_ARGS;
10686     }
10687 
10688     /* Optimized path if volumeBeg and volumeEnd are equal. */
10689     if (pFader->volumeBeg == pFader->volumeEnd) {
10690         if (pFader->volumeBeg == 1) {
10691             /* Straight copy. */
10692             ma_copy_pcm_frames(pFramesOut, pFramesIn, frameCount, pFader->config.format, pFader->config.channels);
10693         } else {
10694             /* Copy with volume. */
10695             ma_volume_and_clip_pcm_frames(pFramesOut, pFramesIn, frameCount, pFader->config.format, pFader->config.channels, pFader->volumeEnd);
10696         }
10697     } else {
10698         /* Slower path. Volumes are different, so may need to do an interpolation. */
10699         if (pFader->cursorInFrames >= pFader->lengthInFrames) {
10700             /* Fast path. We've gone past the end of the fade period so just apply the end volume to all samples. */
10701             ma_volume_and_clip_pcm_frames(pFramesOut, pFramesIn, frameCount, pFader->config.format, pFader->config.channels, pFader->volumeEnd);
10702         } else {
10703             /* Slow path. This is where we do the actual fading. */
10704             ma_uint64 iFrame;
10705             ma_uint32 iChannel;
10706 
10707             /* For now we only support f32. Support for other formats will be added later. */
10708             if (pFader->config.format == ma_format_f32) {
10709                 const float* pFramesInF32  = (const float*)pFramesIn;
10710                 /* */ float* pFramesOutF32 = (      float*)pFramesOut;
10711 
10712                 for (iFrame = 0; iFrame < frameCount; iFrame += 1) {
10713                     float a = ma_min(pFader->cursorInFrames + iFrame, pFader->lengthInFrames) / (float)pFader->lengthInFrames;
10714                     float volume = ma_mix_f32_fast(pFader->volumeBeg, pFader->volumeEnd, a);
10715 
10716                     for (iChannel = 0; iChannel < pFader->config.channels; iChannel += 1) {
10717                         pFramesOutF32[iFrame*pFader->config.channels + iChannel] = pFramesInF32[iFrame*pFader->config.channels + iChannel] * volume;
10718                     }
10719                 }
10720             } else {
10721                 return MA_NOT_IMPLEMENTED;
10722             }
10723         }
10724     }
10725 
10726     pFader->cursorInFrames += frameCount;
10727 
10728     return MA_SUCCESS;
10729 }
10730 
ma_fader_get_data_format(const ma_fader * pFader,ma_format * pFormat,ma_uint32 * pChannels,ma_uint32 * pSampleRate)10731 MA_API void ma_fader_get_data_format(const ma_fader* pFader, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate)
10732 {
10733     if (pFader == NULL) {
10734         return;
10735     }
10736 
10737     if (pFormat != NULL) {
10738         *pFormat = pFader->config.format;
10739     }
10740 
10741     if (pChannels != NULL) {
10742         *pChannels = pFader->config.channels;
10743     }
10744 
10745     if (pSampleRate != NULL) {
10746         *pSampleRate = pFader->config.sampleRate;
10747     }
10748 }
10749 
ma_fader_set_fade(ma_fader * pFader,float volumeBeg,float volumeEnd,ma_uint64 lengthInFrames)10750 MA_API void ma_fader_set_fade(ma_fader* pFader, float volumeBeg, float volumeEnd, ma_uint64 lengthInFrames)
10751 {
10752     if (pFader == NULL) {
10753         return;
10754     }
10755 
10756     /* If the volume is negative, use current volume. */
10757     if (volumeBeg < 0) {
10758         volumeBeg = ma_fader_get_current_volume(pFader);
10759     }
10760 
10761     pFader->volumeBeg      = volumeBeg;
10762     pFader->volumeEnd      = volumeEnd;
10763     pFader->lengthInFrames = lengthInFrames;
10764     pFader->cursorInFrames = 0; /* Reset cursor. */
10765 }
10766 
ma_fader_get_current_volume(ma_fader * pFader)10767 MA_API float ma_fader_get_current_volume(ma_fader* pFader)
10768 {
10769     if (pFader == NULL) {
10770         return 0.0f;
10771     }
10772 
10773     /* The current volume depends on the position of the cursor. */
10774     if (pFader->cursorInFrames <= 0) {
10775         return pFader->volumeBeg;
10776     } else if (pFader->cursorInFrames >= pFader->lengthInFrames) {
10777         return pFader->volumeEnd;
10778     } else {
10779         /* The cursor is somewhere inside the fading period. We can figure this out with a simple linear interpoluation between volumeBeg and volumeEnd based on our cursor position. */
10780         return ma_mix_f32_fast(pFader->volumeBeg, pFader->volumeEnd, pFader->cursorInFrames / (float)pFader->lengthInFrames);
10781     }
10782 }
10783 
10784 
10785 
10786 
10787 
ma_vec3f_init_3f(float x,float y,float z)10788 MA_API ma_vec3f ma_vec3f_init_3f(float x, float y, float z)
10789 {
10790     ma_vec3f v;
10791 
10792     v.x = x;
10793     v.y = y;
10794     v.z = z;
10795 
10796     return v;
10797 }
10798 
ma_vec3f_sub(ma_vec3f a,ma_vec3f b)10799 MA_API ma_vec3f ma_vec3f_sub(ma_vec3f a, ma_vec3f b)
10800 {
10801     return ma_vec3f_init_3f(
10802         a.x - b.x,
10803         a.y - b.y,
10804         a.z - b.z
10805     );
10806 }
10807 
ma_vec3f_neg(ma_vec3f a)10808 MA_API ma_vec3f ma_vec3f_neg(ma_vec3f a)
10809 {
10810     return ma_vec3f_init_3f(
10811         -a.x,
10812         -a.y,
10813         -a.z
10814     );
10815 }
10816 
ma_vec3f_dot(ma_vec3f a,ma_vec3f b)10817 MA_API float ma_vec3f_dot(ma_vec3f a, ma_vec3f b)
10818 {
10819     return a.x*b.x + a.y*b.y + a.z*b.z;
10820 }
10821 
ma_vec3f_len2(ma_vec3f v)10822 MA_API float ma_vec3f_len2(ma_vec3f v)
10823 {
10824     return ma_vec3f_dot(v, v);
10825 }
10826 
ma_vec3f_len(ma_vec3f v)10827 MA_API float ma_vec3f_len(ma_vec3f v)
10828 {
10829     return (float)ma_sqrtd(ma_vec3f_len2(v));
10830 }
10831 
ma_vec3f_dist(ma_vec3f a,ma_vec3f b)10832 MA_API float ma_vec3f_dist(ma_vec3f a, ma_vec3f b)
10833 {
10834     return ma_vec3f_len(ma_vec3f_sub(a, b));
10835 }
10836 
ma_vec3f_normalize(ma_vec3f v)10837 MA_API ma_vec3f ma_vec3f_normalize(ma_vec3f v)
10838 {
10839     float f;
10840     float l = ma_vec3f_len(v);
10841     if (l == 0) {
10842         return ma_vec3f_init_3f(0, 0, 0);
10843     }
10844 
10845     f = 1 / l;
10846     v.x *= f;
10847     v.y *= f;
10848     v.z *= f;
10849 
10850     return v;
10851 }
10852 
ma_vec3f_cross(ma_vec3f a,ma_vec3f b)10853 MA_API ma_vec3f ma_vec3f_cross(ma_vec3f a, ma_vec3f b)
10854 {
10855     return ma_vec3f_init_3f(
10856         a.y*b.z - a.z*b.y,
10857         a.z*b.x - a.x*b.z,
10858         a.x*b.y - a.y*b.x
10859     );
10860 }
10861 
10862 
10863 
10864 
10865 #ifndef MA_DEFAULT_SPEED_OF_SOUND
10866 #define MA_DEFAULT_SPEED_OF_SOUND   343.3f
10867 #endif
10868 
10869 /*
10870 These vectors represent the direction that speakers are facing from the center point. They're used
10871 for panning in the spatializer. Must be normalized.
10872 */
10873 static ma_vec3f g_maChannelDirections[MA_CHANNEL_POSITION_COUNT] = {
10874     { 0.0f,     0.0f,    -1.0f    },  /* MA_CHANNEL_NONE */
10875     { 0.0f,     0.0f,    -1.0f    },  /* MA_CHANNEL_MONO */
10876     {-0.7071f,  0.0f,    -0.7071f },  /* MA_CHANNEL_FRONT_LEFT */
10877     {+0.7071f,  0.0f,    -0.7071f },  /* MA_CHANNEL_FRONT_RIGHT */
10878     { 0.0f,     0.0f,    -1.0f    },  /* MA_CHANNEL_FRONT_CENTER */
10879     { 0.0f,     0.0f,    -1.0f    },  /* MA_CHANNEL_LFE */
10880     {-0.7071f,  0.0f,    +0.7071f },  /* MA_CHANNEL_BACK_LEFT */
10881     {+0.7071f,  0.0f,    +0.7071f },  /* MA_CHANNEL_BACK_RIGHT */
10882     {-0.3162f,  0.0f,    -0.9487f },  /* MA_CHANNEL_FRONT_LEFT_CENTER */
10883     {+0.3162f,  0.0f,    -0.9487f },  /* MA_CHANNEL_FRONT_RIGHT_CENTER */
10884     { 0.0f,     0.0f,    +1.0f    },  /* MA_CHANNEL_BACK_CENTER */
10885     {-1.0f,     0.0f,     0.0f    },  /* MA_CHANNEL_SIDE_LEFT */
10886     {+1.0f,     0.0f,     0.0f    },  /* MA_CHANNEL_SIDE_RIGHT */
10887     { 0.0f,    +1.0f,     0.0f    },  /* MA_CHANNEL_TOP_CENTER */
10888     {-0.5774f, +0.5774f, -0.5774f },  /* MA_CHANNEL_TOP_FRONT_LEFT */
10889     { 0.0f,    +0.7071f, -0.7071f },  /* MA_CHANNEL_TOP_FRONT_CENTER */
10890     {+0.5774f, +0.5774f, -0.5774f },  /* MA_CHANNEL_TOP_FRONT_RIGHT */
10891     {-0.5774f, +0.5774f, +0.5774f },  /* MA_CHANNEL_TOP_BACK_LEFT */
10892     { 0.0f,    +0.7071f, +0.7071f },  /* MA_CHANNEL_TOP_BACK_CENTER */
10893     {+0.5774f, +0.5774f, +0.5774f },  /* MA_CHANNEL_TOP_BACK_RIGHT */
10894     { 0.0f,     0.0f,    -1.0f    },  /* MA_CHANNEL_AUX_0 */
10895     { 0.0f,     0.0f,    -1.0f    },  /* MA_CHANNEL_AUX_1 */
10896     { 0.0f,     0.0f,    -1.0f    },  /* MA_CHANNEL_AUX_2 */
10897     { 0.0f,     0.0f,    -1.0f    },  /* MA_CHANNEL_AUX_3 */
10898     { 0.0f,     0.0f,    -1.0f    },  /* MA_CHANNEL_AUX_4 */
10899     { 0.0f,     0.0f,    -1.0f    },  /* MA_CHANNEL_AUX_5 */
10900     { 0.0f,     0.0f,    -1.0f    },  /* MA_CHANNEL_AUX_6 */
10901     { 0.0f,     0.0f,    -1.0f    },  /* MA_CHANNEL_AUX_7 */
10902     { 0.0f,     0.0f,    -1.0f    },  /* MA_CHANNEL_AUX_8 */
10903     { 0.0f,     0.0f,    -1.0f    },  /* MA_CHANNEL_AUX_9 */
10904     { 0.0f,     0.0f,    -1.0f    },  /* MA_CHANNEL_AUX_10 */
10905     { 0.0f,     0.0f,    -1.0f    },  /* MA_CHANNEL_AUX_11 */
10906     { 0.0f,     0.0f,    -1.0f    },  /* MA_CHANNEL_AUX_12 */
10907     { 0.0f,     0.0f,    -1.0f    },  /* MA_CHANNEL_AUX_13 */
10908     { 0.0f,     0.0f,    -1.0f    },  /* MA_CHANNEL_AUX_14 */
10909     { 0.0f,     0.0f,    -1.0f    },  /* MA_CHANNEL_AUX_15 */
10910     { 0.0f,     0.0f,    -1.0f    },  /* MA_CHANNEL_AUX_16 */
10911     { 0.0f,     0.0f,    -1.0f    },  /* MA_CHANNEL_AUX_17 */
10912     { 0.0f,     0.0f,    -1.0f    },  /* MA_CHANNEL_AUX_18 */
10913     { 0.0f,     0.0f,    -1.0f    },  /* MA_CHANNEL_AUX_19 */
10914     { 0.0f,     0.0f,    -1.0f    },  /* MA_CHANNEL_AUX_20 */
10915     { 0.0f,     0.0f,    -1.0f    },  /* MA_CHANNEL_AUX_21 */
10916     { 0.0f,     0.0f,    -1.0f    },  /* MA_CHANNEL_AUX_22 */
10917     { 0.0f,     0.0f,    -1.0f    },  /* MA_CHANNEL_AUX_23 */
10918     { 0.0f,     0.0f,    -1.0f    },  /* MA_CHANNEL_AUX_24 */
10919     { 0.0f,     0.0f,    -1.0f    },  /* MA_CHANNEL_AUX_25 */
10920     { 0.0f,     0.0f,    -1.0f    },  /* MA_CHANNEL_AUX_26 */
10921     { 0.0f,     0.0f,    -1.0f    },  /* MA_CHANNEL_AUX_27 */
10922     { 0.0f,     0.0f,    -1.0f    },  /* MA_CHANNEL_AUX_28 */
10923     { 0.0f,     0.0f,    -1.0f    },  /* MA_CHANNEL_AUX_29 */
10924     { 0.0f,     0.0f,    -1.0f    },  /* MA_CHANNEL_AUX_30 */
10925     { 0.0f,     0.0f,    -1.0f    }   /* MA_CHANNEL_AUX_31 */
10926 };
10927 
ma_get_channel_direction(ma_channel channel)10928 static ma_vec3f ma_get_channel_direction(ma_channel channel)
10929 {
10930     if (channel >= MA_CHANNEL_POSITION_COUNT) {
10931         return ma_vec3f_init_3f(0, 0, -1);
10932     } else {
10933         return g_maChannelDirections[channel];
10934     }
10935 }
10936 
10937 
10938 
ma_attenuation_inverse(float distance,float minDistance,float maxDistance,float rolloff)10939 static float ma_attenuation_inverse(float distance, float minDistance, float maxDistance, float rolloff)
10940 {
10941     if (minDistance >= maxDistance) {
10942         return 1;   /* To avoid division by zero. Do not attenuate. */
10943     }
10944 
10945     return minDistance / (minDistance + rolloff * (ma_clamp(distance, minDistance, maxDistance) - minDistance));
10946 }
10947 
ma_attenuation_linear(float distance,float minDistance,float maxDistance,float rolloff)10948 static float ma_attenuation_linear(float distance, float minDistance, float maxDistance, float rolloff)
10949 {
10950     if (minDistance >= maxDistance) {
10951         return 1;   /* To avoid division by zero. Do not attenuate. */
10952     }
10953 
10954     return 1 - rolloff * (ma_clamp(distance, minDistance, maxDistance) - minDistance) / (maxDistance - minDistance);
10955 }
10956 
ma_attenuation_exponential(float distance,float minDistance,float maxDistance,float rolloff)10957 static float ma_attenuation_exponential(float distance, float minDistance, float maxDistance, float rolloff)
10958 {
10959     if (minDistance >= maxDistance) {
10960         return 1;   /* To avoid division by zero. Do not attenuate. */
10961     }
10962 
10963     return (float)ma_powd(ma_clamp(distance, minDistance, maxDistance) / minDistance, -rolloff);
10964 }
10965 
10966 
10967 /*
10968 Dopper Effect calculation taken from the OpenAL spec, with two main differences:
10969 
10970   1) The source to listener vector will have already been calcualted at an earlier step so we can
10971      just use that directly. We need only the position of the source relative to the origin.
10972 
10973   2) We don't scale by a frequency because we actually just want the ratio which we'll plug straight
10974      into the resampler directly.
10975 */
ma_doppler_pitch(ma_vec3f relativePosition,ma_vec3f sourceVelocity,ma_vec3f listenVelocity,float speedOfSound,float dopplerFactor)10976 static float ma_doppler_pitch(ma_vec3f relativePosition, ma_vec3f sourceVelocity, ma_vec3f listenVelocity, float speedOfSound, float dopplerFactor)
10977 {
10978     float len;
10979     float vls;
10980     float vss;
10981 
10982     len = ma_vec3f_len(relativePosition);
10983 
10984     /*
10985     There's a case where the position of the source will be right on top of the listener in which
10986     case the length will be 0 and we'll end up with a division by zero. We can just return a ratio
10987     of 1.0 in this case. This is not considered in the OpenAL spec, but is necessary.
10988     */
10989     if (len == 0) {
10990         return 1.0;
10991     }
10992 
10993     vls = ma_vec3f_dot(relativePosition, listenVelocity) / len;
10994     vss = ma_vec3f_dot(relativePosition, sourceVelocity) / len;
10995 
10996     vls = ma_min(vls, speedOfSound / dopplerFactor);
10997     vss = ma_min(vss, speedOfSound / dopplerFactor);
10998 
10999     return (speedOfSound - dopplerFactor*vls) / (speedOfSound - dopplerFactor*vss);
11000 }
11001 
11002 
ma_get_default_channel_map_for_spatializer(ma_uint32 channelCount,ma_channel * pChannelMap)11003 static void ma_get_default_channel_map_for_spatializer(ma_uint32 channelCount, ma_channel* pChannelMap)
11004 {
11005     /*
11006     Special case for stereo. Want to default the left and right speakers to side left and side
11007     right so that they're facing directly down the X axis rather than slightly forward. Not
11008     doing this will result in sounds being quieter when behind the listener. This might
11009     actually be good for some scenerios, but I don't think it's an appropriate default because
11010     it can be a bit unexpected.
11011     */
11012     if (channelCount == 2) {
11013         pChannelMap[0] = MA_CHANNEL_SIDE_LEFT;
11014         pChannelMap[1] = MA_CHANNEL_SIDE_RIGHT;
11015     } else {
11016         ma_get_standard_channel_map(ma_standard_channel_map_default, channelCount, pChannelMap);
11017     }
11018 }
11019 
11020 
ma_spatializer_listener_config_init(ma_uint32 channelsOut)11021 MA_API ma_spatializer_listener_config ma_spatializer_listener_config_init(ma_uint32 channelsOut)
11022 {
11023     ma_spatializer_listener_config config;
11024 
11025     MA_ZERO_OBJECT(&config);
11026     config.channelsOut             = channelsOut;
11027     config.pChannelMapOut          = NULL;
11028     config.handedness              = ma_handedness_right;
11029     config.worldUp                 = ma_vec3f_init_3f(0, 1,  0);
11030     config.coneInnerAngleInRadians = 6.283185f; /* 360 degrees. */
11031     config.coneOuterAngleInRadians = 6.283185f; /* 360 degrees. */
11032     config.coneOuterGain           = 0;
11033     config.speedOfSound            = 343.3f;    /* Same as OpenAL. Used for doppler effect. */
11034 
11035     return config;
11036 }
11037 
11038 
11039 typedef struct
11040 {
11041     size_t sizeInBytes;
11042     size_t channelMapOutOffset;
11043 } ma_spatializer_listener_heap_layout;
11044 
ma_spatializer_listener_get_heap_layout(const ma_spatializer_listener_config * pConfig,ma_spatializer_listener_heap_layout * pHeapLayout)11045 static ma_result ma_spatializer_listener_get_heap_layout(const ma_spatializer_listener_config* pConfig, ma_spatializer_listener_heap_layout* pHeapLayout)
11046 {
11047     MA_ASSERT(pHeapLayout != NULL);
11048 
11049     MA_ZERO_OBJECT(pHeapLayout);
11050 
11051     if (pConfig == NULL) {
11052         return MA_INVALID_ARGS;
11053     }
11054 
11055     if (pConfig->channelsOut == 0) {
11056         return MA_INVALID_ARGS;
11057     }
11058 
11059     pHeapLayout->sizeInBytes = 0;
11060 
11061     /* Channel map. We always need this, even for passthroughs. */
11062     pHeapLayout->channelMapOutOffset = pHeapLayout->sizeInBytes;
11063     pHeapLayout->sizeInBytes += ma_align_64(sizeof(*pConfig->pChannelMapOut) * pConfig->channelsOut);
11064 
11065     return MA_SUCCESS;
11066 }
11067 
11068 
ma_spatializer_listener_get_heap_size(const ma_spatializer_listener_config * pConfig,size_t * pHeapSizeInBytes)11069 MA_API ma_result ma_spatializer_listener_get_heap_size(const ma_spatializer_listener_config* pConfig, size_t* pHeapSizeInBytes)
11070 {
11071     ma_result result;
11072     ma_spatializer_listener_heap_layout heapLayout;
11073 
11074     if (pHeapSizeInBytes == NULL) {
11075         return MA_INVALID_ARGS;
11076     }
11077 
11078     *pHeapSizeInBytes = 0;
11079 
11080     result = ma_spatializer_listener_get_heap_layout(pConfig, &heapLayout);
11081     if (result != MA_SUCCESS) {
11082         return result;
11083     }
11084 
11085     *pHeapSizeInBytes = heapLayout.sizeInBytes;
11086 
11087     return MA_SUCCESS;
11088 }
11089 
ma_spatializer_listener_init_preallocated(const ma_spatializer_listener_config * pConfig,void * pHeap,ma_spatializer_listener * pListener)11090 MA_API ma_result ma_spatializer_listener_init_preallocated(const ma_spatializer_listener_config* pConfig, void* pHeap, ma_spatializer_listener* pListener)
11091 {
11092     ma_result result;
11093     ma_spatializer_listener_heap_layout heapLayout;
11094 
11095     if (pListener == NULL) {
11096         return MA_INVALID_ARGS;
11097     }
11098 
11099     MA_ZERO_OBJECT(pListener);
11100 
11101     result = ma_spatializer_listener_get_heap_layout(pConfig, &heapLayout);
11102     if (result != MA_SUCCESS) {
11103         return result;
11104     }
11105 
11106     pListener->_pHeap    = pHeap;
11107     pListener->config    = *pConfig;
11108     pListener->position  = ma_vec3f_init_3f(0, 0,  0);
11109     pListener->direction = ma_vec3f_init_3f(0, 0, -1);
11110     pListener->velocity  = ma_vec3f_init_3f(0, 0,  0);
11111 
11112     /* Swap the forward direction if we're left handed (it was initialized based on right handed). */
11113     if (pListener->config.handedness == ma_handedness_left) {
11114         pListener->direction = ma_vec3f_neg(pListener->direction);
11115     }
11116 
11117 
11118     /* We must always have a valid channel map. */
11119     pListener->config.pChannelMapOut = (ma_channel*)ma_offset_ptr(pHeap, heapLayout.channelMapOutOffset);
11120 
11121     /* Use a slightly different default channel map for stereo. */
11122     if (pConfig->pChannelMapOut == NULL) {
11123         ma_get_default_channel_map_for_spatializer(pConfig->channelsOut, pListener->config.pChannelMapOut);
11124     } else {
11125         ma_channel_map_copy_or_default(pListener->config.pChannelMapOut, pConfig->pChannelMapOut, pConfig->channelsOut);
11126     }
11127 
11128     return MA_SUCCESS;
11129 }
11130 
ma_spatializer_listener_init(const ma_spatializer_listener_config * pConfig,const ma_allocation_callbacks * pAllocationCallbacks,ma_spatializer_listener * pListener)11131 MA_API ma_result ma_spatializer_listener_init(const ma_spatializer_listener_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_spatializer_listener* pListener)
11132 {
11133     ma_result result;
11134     size_t heapSizeInBytes;
11135     void* pHeap;
11136 
11137     result = ma_spatializer_listener_get_heap_size(pConfig, &heapSizeInBytes);
11138     if (result != MA_SUCCESS) {
11139         return result;
11140     }
11141 
11142     if (heapSizeInBytes > 0) {
11143         pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks);
11144         if (pHeap == NULL) {
11145             return MA_OUT_OF_MEMORY;
11146         }
11147     } else {
11148         pHeap = NULL;
11149     }
11150 
11151     result = ma_spatializer_listener_init_preallocated(pConfig, pHeap, pListener);
11152     if (result != MA_SUCCESS) {
11153         ma_free(pHeap, pAllocationCallbacks);
11154         return result;
11155     }
11156 
11157     pListener->_ownsHeap = MA_TRUE;
11158     return MA_SUCCESS;
11159 }
11160 
ma_spatializer_listener_uninit(ma_spatializer_listener * pListener,const ma_allocation_callbacks * pAllocationCallbacks)11161 MA_API void ma_spatializer_listener_uninit(ma_spatializer_listener* pListener, const ma_allocation_callbacks* pAllocationCallbacks)
11162 {
11163     if (pListener == NULL) {
11164         return;
11165     }
11166 
11167     if (pListener->_pHeap != NULL && pListener->_ownsHeap) {
11168         ma_free(pListener->_pHeap, pAllocationCallbacks);
11169     }
11170 }
11171 
ma_spatializer_listener_get_channel_map(ma_spatializer_listener * pListener)11172 MA_API ma_channel* ma_spatializer_listener_get_channel_map(ma_spatializer_listener* pListener)
11173 {
11174     if (pListener == NULL) {
11175         return NULL;
11176     }
11177 
11178     return pListener->config.pChannelMapOut;
11179 }
11180 
ma_spatializer_listener_set_cone(ma_spatializer_listener * pListener,float innerAngleInRadians,float outerAngleInRadians,float outerGain)11181 MA_API void ma_spatializer_listener_set_cone(ma_spatializer_listener* pListener, float innerAngleInRadians, float outerAngleInRadians, float outerGain)
11182 {
11183     if (pListener == NULL) {
11184         return;
11185     }
11186 
11187     pListener->config.coneInnerAngleInRadians = innerAngleInRadians;
11188     pListener->config.coneOuterAngleInRadians = outerAngleInRadians;
11189     pListener->config.coneOuterGain           = outerGain;
11190 }
11191 
ma_spatializer_listener_get_cone(const ma_spatializer_listener * pListener,float * pInnerAngleInRadians,float * pOuterAngleInRadians,float * pOuterGain)11192 MA_API void ma_spatializer_listener_get_cone(const ma_spatializer_listener* pListener, float* pInnerAngleInRadians, float* pOuterAngleInRadians, float* pOuterGain)
11193 {
11194     if (pListener == NULL) {
11195         return;
11196     }
11197 
11198     if (pInnerAngleInRadians != NULL) {
11199         *pInnerAngleInRadians = pListener->config.coneInnerAngleInRadians;
11200     }
11201 
11202     if (pOuterAngleInRadians != NULL) {
11203         *pOuterAngleInRadians = pListener->config.coneOuterAngleInRadians;
11204     }
11205 
11206     if (pOuterGain != NULL) {
11207         *pOuterGain = pListener->config.coneOuterGain;
11208     }
11209 }
11210 
ma_spatializer_listener_set_position(ma_spatializer_listener * pListener,float x,float y,float z)11211 MA_API void ma_spatializer_listener_set_position(ma_spatializer_listener* pListener, float x, float y, float z)
11212 {
11213     if (pListener == NULL) {
11214         return;
11215     }
11216 
11217     pListener->position = ma_vec3f_init_3f(x, y, z);
11218 }
11219 
ma_spatializer_listener_get_position(const ma_spatializer_listener * pListener)11220 MA_API ma_vec3f ma_spatializer_listener_get_position(const ma_spatializer_listener* pListener)
11221 {
11222     if (pListener == NULL) {
11223         return ma_vec3f_init_3f(0, 0, 0);
11224     }
11225 
11226     return pListener->position;
11227 }
11228 
ma_spatializer_listener_set_direction(ma_spatializer_listener * pListener,float x,float y,float z)11229 MA_API void ma_spatializer_listener_set_direction(ma_spatializer_listener* pListener, float x, float y, float z)
11230 {
11231     if (pListener == NULL) {
11232         return;
11233     }
11234 
11235     pListener->direction = ma_vec3f_init_3f(x, y, z);
11236 }
11237 
ma_spatializer_listener_get_direction(const ma_spatializer_listener * pListener)11238 MA_API ma_vec3f ma_spatializer_listener_get_direction(const ma_spatializer_listener* pListener)
11239 {
11240     if (pListener == NULL) {
11241         return ma_vec3f_init_3f(0, 0, -1);
11242     }
11243 
11244     return pListener->direction;
11245 }
11246 
ma_spatializer_listener_set_velocity(ma_spatializer_listener * pListener,float x,float y,float z)11247 MA_API void ma_spatializer_listener_set_velocity(ma_spatializer_listener* pListener, float x, float y, float z)
11248 {
11249     if (pListener == NULL) {
11250         return;
11251     }
11252 
11253     pListener->velocity = ma_vec3f_init_3f(x, y, z);
11254 }
11255 
ma_spatializer_listener_get_velocity(const ma_spatializer_listener * pListener)11256 MA_API ma_vec3f ma_spatializer_listener_get_velocity(const ma_spatializer_listener* pListener)
11257 {
11258     if (pListener == NULL) {
11259         return ma_vec3f_init_3f(0, 0, 0);
11260     }
11261 
11262     return pListener->velocity;
11263 }
11264 
ma_spatializer_listener_set_speed_of_sound(ma_spatializer_listener * pListener,float speedOfSound)11265 MA_API void ma_spatializer_listener_set_speed_of_sound(ma_spatializer_listener* pListener, float speedOfSound)
11266 {
11267     if (pListener == NULL) {
11268         return;
11269     }
11270 
11271     pListener->config.speedOfSound = speedOfSound;
11272 }
11273 
ma_spatializer_listener_get_speed_of_sound(const ma_spatializer_listener * pListener)11274 MA_API float ma_spatializer_listener_get_speed_of_sound(const ma_spatializer_listener* pListener)
11275 {
11276     if (pListener == NULL) {
11277         return 0;
11278     }
11279 
11280     return pListener->config.speedOfSound;
11281 }
11282 
ma_spatializer_listener_set_world_up(ma_spatializer_listener * pListener,float x,float y,float z)11283 MA_API void ma_spatializer_listener_set_world_up(ma_spatializer_listener* pListener, float x, float y, float z)
11284 {
11285     if (pListener == NULL) {
11286         return;
11287     }
11288 
11289     pListener->config.worldUp = ma_vec3f_init_3f(x, y, z);
11290 }
11291 
ma_spatializer_listener_get_world_up(const ma_spatializer_listener * pListener)11292 MA_API ma_vec3f ma_spatializer_listener_get_world_up(const ma_spatializer_listener* pListener)
11293 {
11294     if (pListener == NULL) {
11295         return ma_vec3f_init_3f(0, 1, 0);
11296     }
11297 
11298     return pListener->config.worldUp;
11299 }
11300 
11301 
11302 
11303 
ma_spatializer_config_init(ma_uint32 channelsIn,ma_uint32 channelsOut)11304 MA_API ma_spatializer_config ma_spatializer_config_init(ma_uint32 channelsIn, ma_uint32 channelsOut)
11305 {
11306     ma_spatializer_config config;
11307 
11308     MA_ZERO_OBJECT(&config);
11309     config.channelsIn              = channelsIn;
11310     config.channelsOut             = channelsOut;
11311     config.pChannelMapIn           = NULL;
11312     config.attenuationModel        = ma_attenuation_model_inverse;
11313     config.positioning             = ma_positioning_absolute;
11314     config.handedness              = ma_handedness_right;
11315     config.minGain                 = 0;
11316     config.maxGain                 = 1;
11317     config.minDistance             = 1;
11318     config.maxDistance             = MA_FLT_MAX;
11319     config.rolloff                 = 1;
11320     config.coneInnerAngleInRadians = 6.283185f; /* 360 degrees. */
11321     config.coneOuterAngleInRadians = 6.283185f; /* 360 degress. */
11322     config.coneOuterGain           = 0.0f;
11323     config.dopplerFactor           = 1;
11324     config.gainSmoothTimeInFrames  = 360;       /* 7.5ms @ 48K. */
11325 
11326     return config;
11327 }
11328 
11329 
ma_spatializer_gainer_config_init(const ma_spatializer_config * pConfig)11330 static ma_gainer_config ma_spatializer_gainer_config_init(const ma_spatializer_config* pConfig)
11331 {
11332     MA_ASSERT(pConfig != NULL);
11333     return ma_gainer_config_init(pConfig->channelsOut, pConfig->gainSmoothTimeInFrames);
11334 }
11335 
ma_spatializer_validate_config(const ma_spatializer_config * pConfig)11336 static ma_result ma_spatializer_validate_config(const ma_spatializer_config* pConfig)
11337 {
11338     MA_ASSERT(pConfig != NULL);
11339 
11340     if (pConfig->channelsIn == 0 || pConfig->channelsOut == 0) {
11341         return MA_INVALID_ARGS;
11342     }
11343 
11344     return MA_SUCCESS;
11345 }
11346 
11347 typedef struct
11348 {
11349     size_t sizeInBytes;
11350     size_t channelMapInOffset;
11351     size_t newChannelGainsOffset;
11352     size_t gainerOffset;
11353 } ma_spatializer_heap_layout;
11354 
ma_spatializer_get_heap_layout(const ma_spatializer_config * pConfig,ma_spatializer_heap_layout * pHeapLayout)11355 static ma_result ma_spatializer_get_heap_layout(const ma_spatializer_config* pConfig, ma_spatializer_heap_layout* pHeapLayout)
11356 {
11357     ma_result result;
11358 
11359     MA_ASSERT(pHeapLayout != NULL);
11360 
11361     MA_ZERO_OBJECT(pHeapLayout);
11362 
11363     if (pConfig == NULL) {
11364         return MA_INVALID_ARGS;
11365     }
11366 
11367     result = ma_spatializer_validate_config(pConfig);
11368     if (result != MA_SUCCESS) {
11369         return result;
11370     }
11371 
11372     pHeapLayout->sizeInBytes = 0;
11373 
11374     /* Channel map. */
11375     pHeapLayout->channelMapInOffset = MA_SIZE_MAX;  /* <-- MA_SIZE_MAX indicates no allocation necessary. */
11376     if (pConfig->pChannelMapIn != NULL) {
11377         pHeapLayout->channelMapInOffset = pHeapLayout->sizeInBytes;
11378         pHeapLayout->sizeInBytes += ma_align_64(sizeof(*pConfig->pChannelMapIn) * pConfig->channelsIn);
11379     }
11380 
11381     /* New channel gains for output. */
11382     pHeapLayout->newChannelGainsOffset = pHeapLayout->sizeInBytes;
11383     pHeapLayout->sizeInBytes += ma_align_64(sizeof(float) * pConfig->channelsOut);
11384 
11385     /* Gainer. */
11386     {
11387         size_t gainerHeapSizeInBytes;
11388         ma_gainer_config gainerConfig;
11389 
11390         gainerConfig = ma_spatializer_gainer_config_init(pConfig);
11391 
11392         result = ma_gainer_get_heap_size(&gainerConfig, &gainerHeapSizeInBytes);
11393         if (result != MA_SUCCESS) {
11394             return result;
11395         }
11396 
11397         pHeapLayout->gainerOffset = pHeapLayout->sizeInBytes;
11398         pHeapLayout->sizeInBytes += ma_align_64(gainerHeapSizeInBytes);
11399     }
11400 
11401     return MA_SUCCESS;
11402 }
11403 
ma_spatializer_get_heap_size(const ma_spatializer_config * pConfig,size_t * pHeapSizeInBytes)11404 MA_API ma_result ma_spatializer_get_heap_size(const ma_spatializer_config* pConfig, size_t* pHeapSizeInBytes)
11405 {
11406     ma_result result;
11407     ma_spatializer_heap_layout heapLayout;
11408 
11409     if (pHeapSizeInBytes == NULL) {
11410         return MA_INVALID_ARGS;
11411     }
11412 
11413     *pHeapSizeInBytes = 0;  /* Safety. */
11414 
11415     result = ma_spatializer_get_heap_layout(pConfig, &heapLayout);
11416     if (result != MA_SUCCESS) {
11417         return result;
11418     }
11419 
11420     *pHeapSizeInBytes = heapLayout.sizeInBytes;
11421 
11422     return MA_SUCCESS;
11423 }
11424 
11425 
ma_spatializer_init_preallocated(const ma_spatializer_config * pConfig,void * pHeap,ma_spatializer * pSpatializer)11426 MA_API ma_result ma_spatializer_init_preallocated(const ma_spatializer_config* pConfig, void* pHeap, ma_spatializer* pSpatializer)
11427 {
11428     ma_result result;
11429     ma_spatializer_heap_layout heapLayout;
11430     ma_gainer_config gainerConfig;
11431 
11432     if (pSpatializer == NULL) {
11433         return MA_INVALID_ARGS;
11434     }
11435 
11436     MA_ZERO_OBJECT(pSpatializer);
11437 
11438     if (pConfig == NULL || pHeap == NULL) {
11439         return MA_INVALID_ARGS;
11440     }
11441 
11442     result = ma_spatializer_get_heap_layout(pConfig, &heapLayout);
11443     if (result != MA_SUCCESS) {
11444         return result;
11445     }
11446 
11447     pSpatializer->_pHeap       = pHeap;
11448     pSpatializer->config       = *pConfig;
11449     pSpatializer->position     = ma_vec3f_init_3f(0, 0,  0);
11450     pSpatializer->direction    = ma_vec3f_init_3f(0, 0, -1);
11451     pSpatializer->velocity     = ma_vec3f_init_3f(0, 0,  0);
11452     pSpatializer->dopplerPitch = 1;
11453 
11454     /* Swap the forward direction if we're left handed (it was initialized based on right handed). */
11455     if (pSpatializer->config.handedness == ma_handedness_left) {
11456         pSpatializer->direction = ma_vec3f_neg(pSpatializer->direction);
11457     }
11458 
11459     /* Channel map. This will be on the heap. */
11460     if (pConfig->pChannelMapIn != NULL) {
11461         pSpatializer->config.pChannelMapIn = (ma_channel*)ma_offset_ptr(pHeap, heapLayout.channelMapInOffset);
11462         ma_channel_map_copy_or_default(pSpatializer->config.pChannelMapIn, pConfig->pChannelMapIn, pSpatializer->config.channelsIn);
11463     }
11464 
11465     /* New channel gains for output channels. */
11466     pSpatializer->pNewChannelGainsOut = (float*)ma_offset_ptr(pHeap, heapLayout.newChannelGainsOffset);
11467 
11468     /* Gainer. */
11469     gainerConfig = ma_spatializer_gainer_config_init(pConfig);
11470 
11471     result = ma_gainer_init_preallocated(&gainerConfig, ma_offset_ptr(pHeap, heapLayout.gainerOffset), &pSpatializer->gainer);
11472     if (result != MA_SUCCESS) {
11473         return result;  /* Failed to initialize the gainer. */
11474     }
11475 
11476     return MA_SUCCESS;
11477 }
11478 
ma_spatializer_init(const ma_spatializer_config * pConfig,const ma_allocation_callbacks * pAllocationCallbacks,ma_spatializer * pSpatializer)11479 MA_API ma_result ma_spatializer_init(const ma_spatializer_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_spatializer* pSpatializer)
11480 {
11481     ma_result result;
11482     size_t heapSizeInBytes;
11483     void* pHeap;
11484 
11485     /* We'll need a heap allocation to retrieve the size. */
11486     result = ma_spatializer_get_heap_size(pConfig, &heapSizeInBytes);
11487     if (result != MA_SUCCESS) {
11488         return result;
11489     }
11490 
11491     if (heapSizeInBytes > 0) {
11492         pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks);
11493         if (pHeap == NULL) {
11494             return MA_OUT_OF_MEMORY;
11495         }
11496     } else {
11497         pHeap = NULL;
11498     }
11499 
11500     result = ma_spatializer_init_preallocated(pConfig, pHeap, pSpatializer);
11501     if (result != MA_SUCCESS) {
11502         ma_free(pHeap, pAllocationCallbacks);
11503         return result;
11504     }
11505 
11506     pSpatializer->_ownsHeap = MA_TRUE;
11507     return MA_SUCCESS;
11508 }
11509 
ma_spatializer_uninit(ma_spatializer * pSpatializer,const ma_allocation_callbacks * pAllocationCallbacks)11510 MA_API void ma_spatializer_uninit(ma_spatializer* pSpatializer, const ma_allocation_callbacks* pAllocationCallbacks)
11511 {
11512     if (pSpatializer == NULL) {
11513         return;
11514     }
11515 
11516     ma_gainer_uninit(&pSpatializer->gainer, pAllocationCallbacks);
11517 
11518     if (pSpatializer->_pHeap != NULL && pSpatializer->_ownsHeap) {
11519         ma_free(pSpatializer->_pHeap, pAllocationCallbacks);
11520     }
11521 }
11522 
ma_calculate_angular_gain(ma_vec3f dirA,ma_vec3f dirB,float coneInnerAngleInRadians,float coneOuterAngleInRadians,float coneOuterGain)11523 static float ma_calculate_angular_gain(ma_vec3f dirA, ma_vec3f dirB, float coneInnerAngleInRadians, float coneOuterAngleInRadians, float coneOuterGain)
11524 {
11525     /*
11526     Angular attenuation.
11527 
11528     Unlike distance gain, the math for this is not specified by the OpenAL spec so we'll just go ahead and figure
11529     this out for ourselves at the expense of possibly being inconsistent with other implementations.
11530 
11531     To do cone attenuation, I'm just using the same math that we'd use to implement a basic spotlight in OpenGL. We
11532     just need to get the direction from the source to the listener and then do a dot product against that and the
11533     direction of the spotlight. Then we just compare that dot product against the cosine of the inner and outer
11534     angles. If the dot product is greater than the the outer angle, we just use coneOuterGain. If it's less than
11535     the inner angle, we just use a gain of 1. Otherwise we linearly interpolate between 1 and coneOuterGain.
11536     */
11537     if (coneInnerAngleInRadians < 6.283185f) {
11538         float angularGain = 1;
11539         float cutoffInner = (float)ma_cosd(coneInnerAngleInRadians*0.5f);
11540         float cutoffOuter = (float)ma_cosd(coneOuterAngleInRadians*0.5f);
11541         float d;
11542 
11543         d = ma_vec3f_dot(dirA, dirB);
11544 
11545         if (d > cutoffInner) {
11546             /* It's inside the inner angle. */
11547             angularGain = 1;
11548         } else {
11549             /* It's outside the inner angle. */
11550             if (d > cutoffOuter) {
11551                 /* It's between the inner and outer angle. We need to linearly interpolate between 1 and coneOuterGain. */
11552                 angularGain = ma_mix_f32(coneOuterGain, 1, (d - cutoffOuter) / (cutoffInner - cutoffOuter));
11553             } else {
11554                 /* It's outside the outer angle. */
11555                 angularGain = coneOuterGain;
11556             }
11557         }
11558 
11559         /*printf("d = %f; cutoffInner = %f; cutoffOuter = %f; angularGain = %f\n", d, cutoffInner, cutoffOuter, angularGain);*/
11560         return angularGain;
11561     } else {
11562         /* Inner angle is 360 degrees so no need to do any attenuation. */
11563         return 1;
11564     }
11565 }
11566 
ma_spatializer_process_pcm_frames(ma_spatializer * pSpatializer,ma_spatializer_listener * pListener,void * pFramesOut,const void * pFramesIn,ma_uint64 frameCount)11567 MA_API ma_result ma_spatializer_process_pcm_frames(ma_spatializer* pSpatializer, ma_spatializer_listener* pListener, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount)
11568 {
11569     ma_channel* pChannelMapIn  = pSpatializer->config.pChannelMapIn;
11570     ma_channel* pChannelMapOut = pListener->config.pChannelMapOut;
11571 
11572     if (pSpatializer == NULL) {
11573         return MA_INVALID_ARGS;
11574     }
11575 
11576     /* If we're not spatializing we need to run an optimized path. */
11577     if (pSpatializer->config.attenuationModel == ma_attenuation_model_none) {
11578         /* No attenuation is required, but we'll need to do some channel conversion. */
11579         if (pSpatializer->config.channelsIn == pSpatializer->config.channelsOut) {
11580             ma_copy_pcm_frames(pFramesOut, pFramesIn, frameCount, ma_format_f32, pSpatializer->config.channelsIn);
11581         } else {
11582             ma_channel_map_apply_f32((float*)pFramesOut, pChannelMapOut, pSpatializer->config.channelsOut, (const float*)pFramesIn, pChannelMapIn, pSpatializer->config.channelsIn, frameCount, ma_channel_mix_mode_rectangular);   /* Safe casts to float* because f32 is the only supported format. */
11583         }
11584 
11585         /*
11586         We're not doing attenuation so don't bother with doppler for now. I'm not sure if this is
11587         the correct thinking so might need to review this later.
11588         */
11589         pSpatializer->dopplerPitch = 1;
11590     } else {
11591         /*
11592         Let's first determine which listener the sound is closest to. Need to keep in mind that we
11593         might not have a world or any listeners, in which case we just spatializer based on the
11594         listener being positioned at the origin (0, 0, 0).
11595         */
11596         ma_vec3f relativePosNormalized;
11597         ma_vec3f relativePos;   /* The position relative to the listener. */
11598         ma_vec3f relativeDir;   /* The direction of the sound, relative to the listener. */
11599         ma_vec3f listenerVel;   /* The volocity of the listener. For doppler pitch calculation. */
11600         float speedOfSound;
11601         float distance = 0;
11602         float gain = 1;
11603         ma_uint32 iChannel;
11604         const ma_uint32 channelsOut = pSpatializer->config.channelsOut;
11605         const ma_uint32 channelsIn  = pSpatializer->config.channelsIn;
11606 
11607         /*
11608         We'll need the listener velocity for doppler pitch calculations. The speed of sound is
11609         defined by the listener, so we'll grab that here too.
11610         */
11611         if (pListener != NULL) {
11612             listenerVel  = pListener->velocity;
11613             speedOfSound = pListener->config.speedOfSound;
11614         } else {
11615             listenerVel  = ma_vec3f_init_3f(0, 0, 0);
11616             speedOfSound = MA_DEFAULT_SPEED_OF_SOUND;
11617         }
11618 
11619         if (pListener == NULL || pSpatializer->config.positioning == ma_positioning_relative) {
11620             /* There's no listener or we're using relative positioning. */
11621             relativePos = pSpatializer->position;
11622             relativeDir = pSpatializer->direction;
11623         } else {
11624             /*
11625             We've found a listener and we're using absolute positioning. We need to transform the
11626             sound's position and direction so that it's relative to listener. Later on we'll use
11627             this for determining the factors to apply to each channel to apply the panning effect.
11628             */
11629             ma_vec3f v;
11630             ma_vec3f axisX;
11631             ma_vec3f axisY;
11632             ma_vec3f axisZ;
11633             float m[4][4];
11634 
11635             /*
11636             We need to calcualte the right vector from our forward and up vectors. This is done with
11637             a cross product.
11638             */
11639             axisZ = ma_vec3f_normalize(pListener->direction);                               /* Normalization required here because we can't trust the caller. */
11640             axisX = ma_vec3f_normalize(ma_vec3f_cross(axisZ, pListener->config.worldUp));   /* Normalization required here because the world up vector may not be perpendicular with the forward vector. */
11641 
11642             /*
11643             The calculation of axisX above can result in a zero-length vector if the listener is
11644             looking straight up on the Y axis. We'll need to fall back to a +X in this case so that
11645             the calculations below don't fall apart. This is where a quaternion based listener and
11646             sound orientation would come in handy.
11647             */
11648             if (ma_vec3f_len2(axisX) == 0) {
11649                 axisX = ma_vec3f_init_3f(1, 0, 0);
11650             }
11651 
11652             axisY = ma_vec3f_cross(axisX, axisZ);                                           /* No normalization is required here because axisX and axisZ are unit length and perpendicular. */
11653 
11654             /*
11655             We need to swap the X axis if we're left handed because otherwise the cross product above
11656             will have resulted in it pointing in the wrong direction (right handed was assumed in the
11657             cross products above).
11658             */
11659             if (pListener->config.handedness == ma_handedness_left) {
11660                 axisX = ma_vec3f_neg(axisX);
11661             }
11662 
11663             /* Lookat. */
11664             m[0][0] =  axisX.x; m[1][0] =  axisX.y; m[2][0] =  axisX.z; m[3][0] = -ma_vec3f_dot(axisX,               pListener->position);
11665             m[0][1] =  axisY.x; m[1][1] =  axisY.y; m[2][1] =  axisY.z; m[3][1] = -ma_vec3f_dot(axisY,               pListener->position);
11666             m[0][2] = -axisZ.x; m[1][2] = -axisZ.y; m[2][2] = -axisZ.z; m[3][2] = -ma_vec3f_dot(ma_vec3f_neg(axisZ), pListener->position);
11667             m[0][3] = 0;        m[1][3] = 0;        m[2][3] = 0;        m[3][3] = 1;
11668 
11669             /*
11670             Multiply the lookat matrix by the spatializer position to transform it to listener
11671             space. This allows calculations to work based on the sound being relative to the
11672             origin which makes things simpler.
11673             */
11674             v = pSpatializer->position;
11675             relativePos.x = m[0][0] * v.x + m[1][0] * v.y + m[2][0] * v.z + m[3][0] * 1;
11676             relativePos.y = m[0][1] * v.x + m[1][1] * v.y + m[2][1] * v.z + m[3][1] * 1;
11677             relativePos.z = m[0][2] * v.x + m[1][2] * v.y + m[2][2] * v.z + m[3][2] * 1;
11678 
11679             /*
11680             The direction of the sound needs to also be transformed so that it's relative to the
11681             rotation of the listener.
11682             */
11683             v = pSpatializer->direction;
11684             relativeDir.x = m[0][0] * v.x + m[1][0] * v.y + m[2][0] * v.z;
11685             relativeDir.y = m[0][1] * v.x + m[1][1] * v.y + m[2][1] * v.z;
11686             relativeDir.z = m[0][2] * v.x + m[1][2] * v.y + m[2][2] * v.z;
11687         }
11688 
11689         distance = ma_vec3f_len(relativePos);
11690 
11691         /* We've gathered the data, so now we can apply some spatialization. */
11692         switch (pSpatializer->config.attenuationModel) {
11693             case ma_attenuation_model_inverse:
11694             {
11695                 gain = ma_attenuation_inverse(distance, pSpatializer->config.minDistance, pSpatializer->config.maxDistance, pSpatializer->config.rolloff);
11696             } break;
11697             case ma_attenuation_model_linear:
11698             {
11699                 gain = ma_attenuation_linear(distance, pSpatializer->config.minDistance, pSpatializer->config.maxDistance, pSpatializer->config.rolloff);
11700             } break;
11701             case ma_attenuation_model_exponential:
11702             {
11703                 gain = ma_attenuation_exponential(distance, pSpatializer->config.minDistance, pSpatializer->config.maxDistance, pSpatializer->config.rolloff);
11704             } break;
11705             case ma_attenuation_model_none:
11706             default:
11707             {
11708                 gain = 1;
11709             } break;
11710         }
11711 
11712         /* Normalize the position. */
11713         if (distance > 0.001f) {
11714             float distanceInv = 1/distance;
11715             relativePosNormalized    = relativePos;
11716             relativePosNormalized.x *= distanceInv;
11717             relativePosNormalized.y *= distanceInv;
11718             relativePosNormalized.z *= distanceInv;
11719         } else {
11720             distance = 0;
11721             relativePosNormalized = ma_vec3f_init_3f(0, 0, 0);
11722         }
11723 
11724         /*
11725         Angular attenuation.
11726 
11727         Unlike distance gain, the math for this is not specified by the OpenAL spec so we'll just go ahead and figure
11728         this out for ourselves at the expense of possibly being inconsistent with other implementations.
11729 
11730         To do cone attenuation, I'm just using the same math that we'd use to implement a basic spotlight in OpenGL. We
11731         just need to get the direction from the source to the listener and then do a dot product against that and the
11732         direction of the spotlight. Then we just compare that dot product against the cosine of the inner and outer
11733         angles. If the dot product is greater than the the outer angle, we just use coneOuterGain. If it's less than
11734         the inner angle, we just use a gain of 1. Otherwise we linearly interpolate between 1 and coneOuterGain.
11735         */
11736         if (distance > 0) {
11737             /* Source anglular gain. */
11738             gain *= ma_calculate_angular_gain(relativeDir, ma_vec3f_neg(relativePosNormalized), pSpatializer->config.coneInnerAngleInRadians, pSpatializer->config.coneOuterAngleInRadians, pSpatializer->config.coneOuterGain);
11739 
11740             /*
11741             We're supporting angular gain on the listener as well for those who want to reduce the volume of sounds that
11742             are positioned behind the listener. On default settings, this will have no effect.
11743             */
11744             if (pListener != NULL && pListener->config.coneInnerAngleInRadians < 6.283185f) {
11745                 ma_vec3f listenerDirection;
11746                 float listenerInnerAngle;
11747                 float listenerOuterAngle;
11748                 float listenerOuterGain;
11749 
11750                 if (pListener->config.handedness == ma_handedness_right) {
11751                     listenerDirection = ma_vec3f_init_3f(0, 0, -1);
11752                 } else {
11753                     listenerDirection = ma_vec3f_init_3f(0, 0, +1);
11754                 }
11755 
11756                 listenerInnerAngle = pListener->config.coneInnerAngleInRadians;
11757                 listenerOuterAngle = pListener->config.coneOuterAngleInRadians;
11758                 listenerOuterGain  = pListener->config.coneOuterGain;
11759 
11760                 gain *= ma_calculate_angular_gain(listenerDirection, relativePosNormalized, listenerInnerAngle, listenerOuterAngle, listenerOuterGain);
11761             }
11762         } else {
11763             /* The sound is right on top of the listener. Don't do any angular attenuation. */
11764         }
11765 
11766 
11767         /* Clamp the gain. */
11768         gain = ma_clamp(gain, pSpatializer->config.minGain, pSpatializer->config.maxGain);
11769 
11770         /*
11771         Panning. This is where we'll apply the gain and convert to the output channel count. We have an optimized path for
11772         when we're converting to a mono stream. In that case we don't really need to do any panning - we just apply the
11773         gain to the final output.
11774         */
11775         /*printf("distance=%f; gain=%f\n", distance, gain);*/
11776 
11777         /* We must have a valid channel map here to ensure we spatialize properly. */
11778         MA_ASSERT(pChannelMapOut != NULL);
11779 
11780         /*
11781         We're not converting to mono so we'll want to apply some panning. This is where the feeling of something being
11782         to the left, right, infront or behind the listener is calculated. I'm just using a basic model here. Note that
11783         the code below is not based on any specific algorithm. I'm just implementing this off the top of my head and
11784         seeing how it goes. There might be better ways to do this.
11785 
11786         To determine the direction of the sound relative to a speaker I'm using dot products. Each speaker is given a
11787         direction. For example, the left channel in a stereo system will be -1 on the X axis and the right channel will
11788         be +1 on the X axis. A dot product is performed against the direction vector of the channel and the normalized
11789         position of the sound.
11790         */
11791         for (iChannel = 0; iChannel < channelsOut; iChannel += 1) {
11792             pSpatializer->pNewChannelGainsOut[iChannel] = gain;
11793         }
11794 
11795         /* Convert to our output channel count. */
11796         ma_channel_map_apply_f32((float*)pFramesOut, pChannelMapOut, channelsOut, (const float*)pFramesIn, pChannelMapIn, channelsIn, frameCount, ma_channel_mix_mode_rectangular);
11797 
11798         /*
11799         Calculate our per-channel gains. We do this based on the normalized relative position of the sound and it's
11800         relation to the direction of the channel.
11801         */
11802         if (distance > 0) {
11803             ma_vec3f unitPos = relativePos;
11804             float distanceInv = 1/distance;
11805             unitPos.x *= distanceInv;
11806             unitPos.y *= distanceInv;
11807             unitPos.z *= distanceInv;
11808 
11809             for (iChannel = 0; iChannel < channelsOut; iChannel += 1) {
11810                 ma_channel channelOut;
11811                 float d;
11812 
11813                 channelOut = ma_channel_map_get_channel(pChannelMapOut, channelsOut, iChannel);
11814                 if (ma_is_spatial_channel_position(channelOut)) {
11815                     d = ma_vec3f_dot(unitPos, ma_get_channel_direction(channelOut));
11816                 } else {
11817                     d = 1;  /* It's not a spatial channel so there's no real notion of direction. */
11818                 }
11819 
11820                 /*
11821                 In my testing, if the panning effect is too aggressive it makes spatialization feel uncomfortable.
11822                 The "dMin" variable below is used to control the aggressiveness of the panning effect. When set to
11823                 0, panning will be most extreme and any sounds that are positioned on the opposite side of the
11824                 speaker will be completely silent from that speaker. Not only does this feel uncomfortable, it
11825                 doesn't even remotely represent the real world at all because sounds that come from your right side
11826                 are still clearly audible from your left side. Setting "dMin" to 1 will  result in no panning at
11827                 all, which is also not ideal. By setting it to something greater than 0, the spatialization effect
11828                 becomes much less dramatic and a lot more bearable.
11829 
11830                 Summary: 0 = more extreme panning; 1 = no panning.
11831                 */
11832                 float dMin = 0.2f;  /* TODO: Consider making this configurable. */
11833 
11834                 /*
11835                 At this point, "d" will be positive if the sound is on the same side as the channel and negative if
11836                 it's on the opposite side. It will be in the range of -1..1. There's two ways I can think of to
11837                 calculate a panning value. The first is to simply convert it to 0..1, however this has a problem
11838                 which I'm not entirely happy with. Considering a stereo system, when a sound is positioned right
11839                 in front of the listener it'll result in each speaker getting a gain of 0.5. I don't know if I like
11840                 the idea of having a scaling factor of 0.5 being applied to a sound when it's sitting right in front
11841                 of the listener. I would intuitively expect that to be played at full volume, or close to it.
11842 
11843                 The second idea I think of is to only apply a reduction in gain when the sound is on the opposite
11844                 side of the speaker. That is, reduce the gain only when the dot product is negative. The problem
11845                 with this is that there will not be any attenuation as the sound sweeps around the 180 degrees
11846                 where the dot product is positive. The idea with this option is that you leave the gain at 1 when
11847                 the sound is being played on the same side as the speaker and then you just reduce the volume when
11848                 the sound is on the other side.
11849 
11850                 The summarize, I think the first option should give a better sense of spatialization, but the second
11851                 option is better for preserving the sound's power.
11852 
11853                 UPDATE: In my testing, I find the first option to sound better. You can feel the sense of space a
11854                 bit better, but you can also hear the reduction in volume when it's right in front.
11855                 */
11856                 #if 1
11857                 {
11858                     /*
11859                     Scale the dot product from -1..1 to 0..1. Will result in a sound directly in front losing power
11860                     by being played at 0.5 gain.
11861                     */
11862                     d = (d + 1) * 0.5f;  /* -1..1 to 0..1 */
11863                     d = ma_max(d, dMin);
11864                     pSpatializer->pNewChannelGainsOut[iChannel] *= d;
11865                 }
11866                 #else
11867                 {
11868                     /*
11869                     Only reduce the volume of the sound if it's on the opposite side. This path keeps the volume more
11870                     consistent, but comes at the expense of a worse sense of space and positioning.
11871                     */
11872                     if (d < 0) {
11873                         d += 1; /* Move into the positive range. */
11874                         d = ma_max(d, dMin);
11875                         channelGainsOut[iChannel] *= d;
11876                     }
11877                 }
11878                 #endif
11879             }
11880         } else {
11881             /* Assume the sound is right on top of us. Don't do any panning. */
11882         }
11883 
11884         /* Now we need to apply the volume to each channel. This needs to run through the gainer to ensure we get a smooth volume transition. */
11885         ma_gainer_set_gains(&pSpatializer->gainer, pSpatializer->pNewChannelGainsOut);
11886         ma_gainer_process_pcm_frames(&pSpatializer->gainer, pFramesOut, pFramesOut, frameCount);
11887 
11888         /*
11889         Before leaving we'll want to update our doppler pitch so that the caller can apply some
11890         pitch shifting if they desire. Note that we need to negate the relative position here
11891         because the doppler calculation needs to be source-to-listener, but ours is listener-to-
11892         source.
11893         */
11894         if (pSpatializer->config.dopplerFactor > 0) {
11895             pSpatializer->dopplerPitch = ma_doppler_pitch(ma_vec3f_sub(pListener->position, pSpatializer->position), pSpatializer->velocity, listenerVel, speedOfSound, pSpatializer->config.dopplerFactor);
11896         } else {
11897             pSpatializer->dopplerPitch = 1;
11898         }
11899     }
11900 
11901     return MA_SUCCESS;
11902 }
11903 
ma_spatializer_get_input_channels(const ma_spatializer * pSpatializer)11904 MA_API ma_uint32 ma_spatializer_get_input_channels(const ma_spatializer* pSpatializer)
11905 {
11906     if (pSpatializer == NULL) {
11907         return 0;
11908     }
11909 
11910     return pSpatializer->config.channelsIn;
11911 }
11912 
ma_spatializer_get_output_channels(const ma_spatializer * pSpatializer)11913 MA_API ma_uint32 ma_spatializer_get_output_channels(const ma_spatializer* pSpatializer)
11914 {
11915     if (pSpatializer == NULL) {
11916         return 0;
11917     }
11918 
11919     return pSpatializer->config.channelsOut;
11920 }
11921 
ma_spatializer_set_attenuation_model(ma_spatializer * pSpatializer,ma_attenuation_model attenuationModel)11922 MA_API void ma_spatializer_set_attenuation_model(ma_spatializer* pSpatializer, ma_attenuation_model attenuationModel)
11923 {
11924     if (pSpatializer == NULL) {
11925         return;
11926     }
11927 
11928     pSpatializer->config.attenuationModel = attenuationModel;
11929 }
11930 
ma_spatializer_get_attenuation_model(const ma_spatializer * pSpatializer)11931 MA_API ma_attenuation_model ma_spatializer_get_attenuation_model(const ma_spatializer* pSpatializer)
11932 {
11933     if (pSpatializer == NULL) {
11934         return ma_attenuation_model_none;
11935     }
11936 
11937     return pSpatializer->config.attenuationModel;
11938 }
11939 
ma_spatializer_set_positioning(ma_spatializer * pSpatializer,ma_positioning positioning)11940 MA_API void ma_spatializer_set_positioning(ma_spatializer* pSpatializer, ma_positioning positioning)
11941 {
11942     if (pSpatializer == NULL) {
11943         return;
11944     }
11945 
11946     pSpatializer->config.positioning = positioning;
11947 }
11948 
ma_spatializer_get_positioning(const ma_spatializer * pSpatializer)11949 MA_API ma_positioning ma_spatializer_get_positioning(const ma_spatializer* pSpatializer)
11950 {
11951     if (pSpatializer == NULL) {
11952         return ma_positioning_absolute;
11953     }
11954 
11955     return pSpatializer->config.positioning;
11956 }
11957 
ma_spatializer_set_rolloff(ma_spatializer * pSpatializer,float rolloff)11958 MA_API void ma_spatializer_set_rolloff(ma_spatializer* pSpatializer, float rolloff)
11959 {
11960     if (pSpatializer == NULL) {
11961         return;
11962     }
11963 
11964     pSpatializer->config.rolloff = rolloff;
11965 }
11966 
ma_spatializer_get_rolloff(const ma_spatializer * pSpatializer)11967 MA_API float ma_spatializer_get_rolloff(const ma_spatializer* pSpatializer)
11968 {
11969     if (pSpatializer == NULL) {
11970         return 0;
11971     }
11972 
11973     return pSpatializer->config.rolloff;
11974 }
11975 
ma_spatializer_set_min_gain(ma_spatializer * pSpatializer,float minGain)11976 MA_API void ma_spatializer_set_min_gain(ma_spatializer* pSpatializer, float minGain)
11977 {
11978     if (pSpatializer == NULL) {
11979         return;
11980     }
11981 
11982     pSpatializer->config.minGain = minGain;
11983 }
11984 
ma_spatializer_get_min_gain(const ma_spatializer * pSpatializer)11985 MA_API float ma_spatializer_get_min_gain(const ma_spatializer* pSpatializer)
11986 {
11987     if (pSpatializer == NULL) {
11988         return 0;
11989     }
11990 
11991     return pSpatializer->config.minGain;
11992 }
11993 
ma_spatializer_set_max_gain(ma_spatializer * pSpatializer,float maxGain)11994 MA_API void ma_spatializer_set_max_gain(ma_spatializer* pSpatializer, float maxGain)
11995 {
11996     if (pSpatializer == NULL) {
11997         return;
11998     }
11999 
12000     pSpatializer->config.maxGain = maxGain;
12001 }
12002 
ma_spatializer_get_max_gain(const ma_spatializer * pSpatializer)12003 MA_API float ma_spatializer_get_max_gain(const ma_spatializer* pSpatializer)
12004 {
12005     if (pSpatializer == NULL) {
12006         return 0;
12007     }
12008 
12009     return pSpatializer->config.maxGain;
12010 }
12011 
ma_spatializer_set_min_distance(ma_spatializer * pSpatializer,float minDistance)12012 MA_API void ma_spatializer_set_min_distance(ma_spatializer* pSpatializer, float minDistance)
12013 {
12014     if (pSpatializer == NULL) {
12015         return;
12016     }
12017 
12018     pSpatializer->config.minDistance = minDistance;
12019 }
12020 
ma_spatializer_get_min_distance(const ma_spatializer * pSpatializer)12021 MA_API float ma_spatializer_get_min_distance(const ma_spatializer* pSpatializer)
12022 {
12023     if (pSpatializer == NULL) {
12024         return 0;
12025     }
12026 
12027     return pSpatializer->config.minDistance;
12028 }
12029 
ma_spatializer_set_max_distance(ma_spatializer * pSpatializer,float maxDistance)12030 MA_API void ma_spatializer_set_max_distance(ma_spatializer* pSpatializer, float maxDistance)
12031 {
12032     if (pSpatializer == NULL) {
12033         return;
12034     }
12035 
12036     pSpatializer->config.maxDistance = maxDistance;
12037 }
12038 
ma_spatializer_get_max_distance(const ma_spatializer * pSpatializer)12039 MA_API float ma_spatializer_get_max_distance(const ma_spatializer* pSpatializer)
12040 {
12041     if (pSpatializer == NULL) {
12042         return 0;
12043     }
12044 
12045     return pSpatializer->config.maxDistance;
12046 }
12047 
ma_spatializer_set_cone(ma_spatializer * pSpatializer,float innerAngleInRadians,float outerAngleInRadians,float outerGain)12048 MA_API void ma_spatializer_set_cone(ma_spatializer* pSpatializer, float innerAngleInRadians, float outerAngleInRadians, float outerGain)
12049 {
12050     if (pSpatializer == NULL) {
12051         return;
12052     }
12053 
12054     pSpatializer->config.coneInnerAngleInRadians = innerAngleInRadians;
12055     pSpatializer->config.coneOuterAngleInRadians = outerAngleInRadians;
12056     pSpatializer->config.coneOuterGain           = outerGain;
12057 }
12058 
ma_spatializer_get_cone(const ma_spatializer * pSpatializer,float * pInnerAngleInRadians,float * pOuterAngleInRadians,float * pOuterGain)12059 MA_API void ma_spatializer_get_cone(const ma_spatializer* pSpatializer, float* pInnerAngleInRadians, float* pOuterAngleInRadians, float* pOuterGain)
12060 {
12061     if (pSpatializer == NULL) {
12062         return;
12063     }
12064 
12065     if (pInnerAngleInRadians != NULL) {
12066         *pInnerAngleInRadians = pSpatializer->config.coneInnerAngleInRadians;
12067     }
12068 
12069     if (pOuterAngleInRadians != NULL) {
12070         *pOuterAngleInRadians = pSpatializer->config.coneOuterAngleInRadians;
12071     }
12072 
12073     if (pOuterGain != NULL) {
12074         *pOuterGain = pSpatializer->config.coneOuterGain;
12075     }
12076 }
12077 
ma_spatializer_set_doppler_factor(ma_spatializer * pSpatializer,float dopplerFactor)12078 MA_API void ma_spatializer_set_doppler_factor(ma_spatializer* pSpatializer, float dopplerFactor)
12079 {
12080     if (pSpatializer == NULL) {
12081         return;
12082     }
12083 
12084     pSpatializer->config.dopplerFactor = dopplerFactor;
12085 }
12086 
ma_spatializer_get_doppler_factor(const ma_spatializer * pSpatializer)12087 MA_API float ma_spatializer_get_doppler_factor(const ma_spatializer* pSpatializer)
12088 {
12089     if (pSpatializer == NULL) {
12090         return 1;
12091     }
12092 
12093     return pSpatializer->config.dopplerFactor;
12094 }
12095 
ma_spatializer_set_position(ma_spatializer * pSpatializer,float x,float y,float z)12096 MA_API void ma_spatializer_set_position(ma_spatializer* pSpatializer, float x, float y, float z)
12097 {
12098     if (pSpatializer == NULL) {
12099         return;
12100     }
12101 
12102     pSpatializer->position = ma_vec3f_init_3f(x, y, z);
12103 }
12104 
ma_spatializer_get_position(const ma_spatializer * pSpatializer)12105 MA_API ma_vec3f ma_spatializer_get_position(const ma_spatializer* pSpatializer)
12106 {
12107     if (pSpatializer == NULL) {
12108         return ma_vec3f_init_3f(0, 0, 0);
12109     }
12110 
12111     return pSpatializer->position;
12112 }
12113 
ma_spatializer_set_direction(ma_spatializer * pSpatializer,float x,float y,float z)12114 MA_API void ma_spatializer_set_direction(ma_spatializer* pSpatializer, float x, float y, float z)
12115 {
12116     if (pSpatializer == NULL) {
12117         return;
12118     }
12119 
12120     pSpatializer->direction = ma_vec3f_init_3f(x, y, z);
12121 }
12122 
ma_spatializer_get_direction(const ma_spatializer * pSpatializer)12123 MA_API ma_vec3f ma_spatializer_get_direction(const ma_spatializer* pSpatializer)
12124 {
12125     if (pSpatializer == NULL) {
12126         return ma_vec3f_init_3f(0, 0, -1);
12127     }
12128 
12129     return pSpatializer->direction;
12130 }
12131 
ma_spatializer_set_velocity(ma_spatializer * pSpatializer,float x,float y,float z)12132 MA_API void ma_spatializer_set_velocity(ma_spatializer* pSpatializer, float x, float y, float z)
12133 {
12134     if (pSpatializer == NULL) {
12135         return;
12136     }
12137 
12138     pSpatializer->velocity = ma_vec3f_init_3f(x, y, z);
12139 }
12140 
ma_spatializer_get_velocity(const ma_spatializer * pSpatializer)12141 MA_API ma_vec3f ma_spatializer_get_velocity(const ma_spatializer* pSpatializer)
12142 {
12143     if (pSpatializer == NULL) {
12144         return ma_vec3f_init_3f(0, 0, 0);
12145     }
12146 
12147     return pSpatializer->velocity;
12148 }
12149 
12150 
12151 
12152 
12153 /**************************************************************************************************************************************************************
12154 
12155 Engine
12156 
12157 **************************************************************************************************************************************************************/
12158 #define MA_SEEK_TARGET_NONE         (~(ma_uint64)0)
12159 
ma_engine_node_config_init(ma_engine * pEngine,ma_engine_node_type type,ma_uint32 flags)12160 MA_API ma_engine_node_config ma_engine_node_config_init(ma_engine* pEngine, ma_engine_node_type type, ma_uint32 flags)
12161 {
12162     ma_engine_node_config config;
12163 
12164     MA_ZERO_OBJECT(&config);
12165     config.pEngine                  = pEngine;
12166     config.type                     = type;
12167     config.isPitchDisabled          = (flags & MA_SOUND_FLAG_NO_PITCH) != 0;
12168     config.isSpatializationDisabled = (flags & MA_SOUND_FLAG_NO_SPATIALIZATION) != 0;
12169 
12170     return config;
12171 }
12172 
12173 
ma_engine_node_update_pitch_if_required(ma_engine_node * pEngineNode)12174 static void ma_engine_node_update_pitch_if_required(ma_engine_node* pEngineNode)
12175 {
12176     ma_bool32 isUpdateRequired = MA_FALSE;
12177     float newPitch;
12178 
12179     MA_ASSERT(pEngineNode != NULL);
12180 
12181     newPitch = c89atomic_load_explicit_f32(&pEngineNode->pitch, c89atomic_memory_order_acquire);
12182 
12183     if (pEngineNode->oldPitch != newPitch) {
12184         pEngineNode->oldPitch  = newPitch;
12185         isUpdateRequired = MA_TRUE;
12186     }
12187 
12188     if (pEngineNode->oldDopplerPitch != pEngineNode->spatializer.dopplerPitch) {
12189         pEngineNode->oldDopplerPitch  = pEngineNode->spatializer.dopplerPitch;
12190         isUpdateRequired = MA_TRUE;
12191     }
12192 
12193     if (isUpdateRequired) {
12194         float basePitch = (float)pEngineNode->sampleRate / ma_engine_get_sample_rate(pEngineNode->pEngine);
12195         ma_resampler_set_rate_ratio(&pEngineNode->resampler, basePitch * pEngineNode->oldPitch * pEngineNode->oldDopplerPitch);
12196     }
12197 }
12198 
ma_engine_node_is_pitching_enabled(const ma_engine_node * pEngineNode)12199 static ma_bool32 ma_engine_node_is_pitching_enabled(const ma_engine_node* pEngineNode)
12200 {
12201     MA_ASSERT(pEngineNode != NULL);
12202 
12203     /* Don't try to be clever by skiping resampling in the pitch=1 case or else you'll glitch when moving away from 1. */
12204     return !c89atomic_load_explicit_8(&pEngineNode->isPitchDisabled, c89atomic_memory_order_acquire);
12205 }
12206 
ma_engine_node_is_spatialization_enabled(const ma_engine_node * pEngineNode)12207 static ma_bool32 ma_engine_node_is_spatialization_enabled(const ma_engine_node* pEngineNode)
12208 {
12209     MA_ASSERT(pEngineNode != NULL);
12210 
12211     return !c89atomic_load_explicit_8(&pEngineNode->isSpatializationDisabled, c89atomic_memory_order_acquire);
12212 }
12213 
ma_engine_node_get_required_input_frame_count(const ma_engine_node * pEngineNode,ma_uint64 outputFrameCount)12214 static ma_uint64 ma_engine_node_get_required_input_frame_count(const ma_engine_node* pEngineNode, ma_uint64 outputFrameCount)
12215 {
12216     if (ma_engine_node_is_pitching_enabled(pEngineNode)) {
12217         return ma_resampler_get_required_input_frame_count(&pEngineNode->resampler, outputFrameCount);
12218     } else {
12219         return outputFrameCount;    /* No resampling, so 1:1. */
12220     }
12221 }
12222 
ma_engine_node_process_pcm_frames__general(ma_engine_node * pEngineNode,const float ** ppFramesIn,ma_uint32 * pFrameCountIn,float ** ppFramesOut,ma_uint32 * pFrameCountOut)12223 static void ma_engine_node_process_pcm_frames__general(ma_engine_node* pEngineNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut)
12224 {
12225     ma_uint32 frameCountIn;
12226     ma_uint32 frameCountOut;
12227     ma_uint32 totalFramesProcessedIn;
12228     ma_uint32 totalFramesProcessedOut;
12229     ma_uint32 channelsIn;
12230     ma_uint32 channelsOut;
12231     ma_bool32 isPitchingEnabled;
12232     ma_bool32 isFadingEnabled;
12233     ma_bool32 isSpatializationEnabled;
12234     ma_bool32 isPanningEnabled;
12235 
12236     frameCountIn  = *pFrameCountIn;
12237     frameCountOut = *pFrameCountOut;
12238 
12239     channelsIn  = ma_spatializer_get_input_channels(&pEngineNode->spatializer);
12240     channelsOut = ma_spatializer_get_output_channels(&pEngineNode->spatializer);
12241 
12242     totalFramesProcessedIn  = 0;
12243     totalFramesProcessedOut = 0;
12244 
12245     isPitchingEnabled       = ma_engine_node_is_pitching_enabled(pEngineNode);
12246     isFadingEnabled         = pEngineNode->fader.volumeBeg != 1 || pEngineNode->fader.volumeEnd != 1;
12247     isSpatializationEnabled = ma_engine_node_is_spatialization_enabled(pEngineNode);
12248     isPanningEnabled        = pEngineNode->panner.pan != 0 && channelsOut != 1;
12249 
12250     /* Keep going while we've still got data available for processing. */
12251     while (totalFramesProcessedOut < frameCountOut) {
12252         /*
12253         We need to process in a specific order. We always do resampling first because it's likely
12254         we're going to be increasing the channel count after spatialization. Also, I want to do
12255         fading based on the output sample rate.
12256 
12257         We'll first read into a buffer from the resampler. Then we'll do all processing that
12258         operates on the on the input channel count. We'll then get the spatializer to output to
12259         the output buffer and then do all effects from that point directly in the output buffer
12260         in-place.
12261 
12262         Note that we're always running the resampler. If we try to be clever and skip resampling
12263         when the pitch is 1, we'll get a glitch when we move away from 1, back to 1, and then
12264         away from 1 again. We'll want to implement any pitch=1 optimizations in the resampler
12265         itself.
12266 
12267         There's a small optimization here that we'll utilize since it might be a fairly common
12268         case. When the input and output channel counts are the same, we'll read straight into the
12269         output buffer from the resampler and do everything in-place.
12270         */
12271         const float* pRunningFramesIn;
12272         float* pRunningFramesOut;
12273         float* pWorkingBuffer;   /* This is the buffer that we'll be processing frames in. This is in input channels. */
12274         float temp[MA_DATA_CONVERTER_STACK_BUFFER_SIZE / sizeof(float)];
12275         ma_uint32 tempCapInFrames = ma_countof(temp) / channelsIn;
12276         ma_uint32 framesAvailableIn;
12277         ma_uint32 framesAvailableOut;
12278         ma_uint32 framesJustProcessedIn;
12279         ma_uint32 framesJustProcessedOut;
12280         ma_bool32 isWorkingBufferValid = MA_FALSE;
12281 
12282         framesAvailableIn  = frameCountIn  - totalFramesProcessedIn;
12283         framesAvailableOut = frameCountOut - totalFramesProcessedOut;
12284 
12285         pRunningFramesIn  = ma_offset_pcm_frames_const_ptr_f32(ppFramesIn[0], totalFramesProcessedIn, channelsIn);
12286         pRunningFramesOut = ma_offset_pcm_frames_ptr_f32(ppFramesOut[0], totalFramesProcessedOut, channelsOut);
12287 
12288         if (channelsIn == channelsOut) {
12289             /* Fast path. Channel counts are the same. No need for an intermediary input buffer. */
12290             pWorkingBuffer = pRunningFramesOut;
12291         } else {
12292             /* Slow path. Channel counts are different. Need to use an intermediary input buffer. */
12293             pWorkingBuffer = temp;
12294             if (framesAvailableOut > tempCapInFrames) {
12295                 framesAvailableOut = tempCapInFrames;
12296             }
12297         }
12298 
12299         /* First is resampler. */
12300         if (isPitchingEnabled) {
12301             ma_uint64 resampleFrameCountIn  = framesAvailableIn;
12302             ma_uint64 resampleFrameCountOut = framesAvailableOut;
12303 
12304             ma_resampler_process_pcm_frames(&pEngineNode->resampler, pRunningFramesIn, &resampleFrameCountIn, pWorkingBuffer, &resampleFrameCountOut);
12305             isWorkingBufferValid = MA_TRUE;
12306 
12307             framesJustProcessedIn  = (ma_uint32)resampleFrameCountIn;
12308             framesJustProcessedOut = (ma_uint32)resampleFrameCountOut;
12309         } else {
12310             framesJustProcessedIn  = framesAvailableIn;
12311             framesJustProcessedOut = framesAvailableOut;
12312         }
12313 
12314         /* Fading. */
12315         if (isFadingEnabled) {
12316             if (isWorkingBufferValid) {
12317                 ma_fader_process_pcm_frames(&pEngineNode->fader, pWorkingBuffer, pWorkingBuffer, framesJustProcessedOut);   /* In-place processing. */
12318             } else {
12319                 ma_fader_process_pcm_frames(&pEngineNode->fader, pWorkingBuffer, pRunningFramesIn, framesJustProcessedOut);
12320                 isWorkingBufferValid = MA_TRUE;
12321             }
12322         }
12323 
12324         /*
12325         If at this point we still haven't actually done anything with the working buffer we need
12326         to just read straight from the input buffer.
12327         */
12328         if (isWorkingBufferValid == MA_FALSE) {
12329             pWorkingBuffer = (float*)pRunningFramesIn;  /* Naughty const cast, but it's safe at this point because we won't ever be writing to it from this point out. */
12330         }
12331 
12332         /* Spatialization. */
12333         if (isSpatializationEnabled) {
12334             ma_uint32 iListener;
12335 
12336             /*
12337             When determining the listener to use, we first check to see if the sound is pinned to a
12338             specific listener. If so, we use that. Otherwise we just use the closest listener.
12339             */
12340             if (pEngineNode->pinnedListenerIndex != MA_LISTENER_INDEX_CLOSEST && pEngineNode->pinnedListenerIndex < ma_engine_get_listener_count(pEngineNode->pEngine)) {
12341                 iListener = pEngineNode->pinnedListenerIndex;
12342             } else {
12343                 iListener = ma_engine_find_closest_listener(pEngineNode->pEngine, pEngineNode->spatializer.position.x, pEngineNode->spatializer.position.y, pEngineNode->spatializer.position.z);
12344             }
12345 
12346             ma_spatializer_process_pcm_frames(&pEngineNode->spatializer, &pEngineNode->pEngine->listeners[iListener], pRunningFramesOut, pWorkingBuffer, framesJustProcessedOut);
12347         } else {
12348             /* No spatialization, but we still need to do channel conversion. */
12349             if (channelsIn == channelsOut) {
12350                 /* No channel conversion required. Just copy straight to the output buffer. */
12351                 ma_copy_pcm_frames(pRunningFramesOut, pWorkingBuffer, framesJustProcessedOut, ma_format_f32, channelsOut);
12352             } else {
12353                 /* Channel conversion required. TODO: Add support for channel maps here. */
12354                 ma_channel_map_apply_f32(pRunningFramesOut, NULL, channelsOut, pWorkingBuffer, NULL, channelsIn, framesJustProcessedOut, ma_channel_mix_mode_simple);
12355             }
12356         }
12357 
12358         /* At this point we can guarantee that the output buffer contains valid data. We can process everything in place now. */
12359 
12360         /* Panning. */
12361         if (isPanningEnabled) {
12362             ma_panner_process_pcm_frames(&pEngineNode->panner, pRunningFramesOut, pRunningFramesOut, framesJustProcessedOut);   /* In-place processing. */
12363         }
12364 
12365         /* We're done for this chunk. */
12366         totalFramesProcessedIn  += framesJustProcessedIn;
12367         totalFramesProcessedOut += framesJustProcessedOut;
12368 
12369         /* If we didn't process any output frames this iteration it means we've either run out of input data, or run out of room in the output buffer. */
12370         if (framesJustProcessedOut == 0) {
12371             break;
12372         }
12373     }
12374 
12375     /* At this point we're done processing. */
12376     *pFrameCountIn  = totalFramesProcessedIn;
12377     *pFrameCountOut = totalFramesProcessedOut;
12378 }
12379 
ma_engine_node_process_pcm_frames__sound(ma_node * pNode,const float ** ppFramesIn,ma_uint32 * pFrameCountIn,float ** ppFramesOut,ma_uint32 * pFrameCountOut)12380 static void ma_engine_node_process_pcm_frames__sound(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut)
12381 {
12382     /* For sounds, we need to first read from the data source. Then we need to apply the engine effects (pan, pitch, fades, etc.). */
12383     ma_result result = MA_SUCCESS;
12384     ma_sound* pSound = (ma_sound*)pNode;
12385     ma_uint32 frameCount = *pFrameCountOut;
12386     ma_uint32 totalFramesRead = 0;
12387     ma_format dataSourceFormat;
12388     ma_uint32 dataSourceChannels;
12389     ma_uint8 temp[MA_DATA_CONVERTER_STACK_BUFFER_SIZE];
12390     ma_uint32 tempCapInFrames;
12391 
12392     /* This is a data source node which means no input buses. */
12393     (void)ppFramesIn;
12394     (void)pFrameCountIn;
12395 
12396     /* If we're marked at the end we need to stop the sound and do nothing. */
12397     if (ma_sound_at_end(pSound)) {
12398         ma_sound_stop(pSound);
12399         *pFrameCountOut = 0;
12400         return;
12401     }
12402 
12403     /* If we're seeking, do so now before reading. */
12404     if (pSound->seekTarget != MA_SEEK_TARGET_NONE) {
12405         ma_data_source_seek_to_pcm_frame(pSound->pDataSource, pSound->seekTarget);
12406 
12407         /* Any time-dependant effects need to have their times updated. */
12408         ma_node_set_time(pSound, pSound->seekTarget);
12409 
12410         pSound->seekTarget  = MA_SEEK_TARGET_NONE;
12411     }
12412 
12413     /*
12414     We want to update the pitch once. For sounds, this can be either at the start or at the end. If
12415     we don't force this to only ever be updating once, we could end up in a situation where
12416     retrieving the required input frame count ends up being different to what we actually retrieve.
12417     What could happen is that the required input frame count is calculated, the pitch is update,
12418     and then this processing function is called resulting in a different number of input frames
12419     being processed. Do not call this in ma_engine_node_process_pcm_frames__general() or else
12420     you'll hit the aforementioned bug.
12421     */
12422     ma_engine_node_update_pitch_if_required(&pSound->engineNode);
12423 
12424     /*
12425     For the convenience of the caller, we're doing to allow data sources to use non-floating-point formats and channel counts that differ
12426     from the main engine.
12427     */
12428     result = ma_data_source_get_data_format(pSound->pDataSource, &dataSourceFormat, &dataSourceChannels, NULL);
12429     if (result == MA_SUCCESS) {
12430         tempCapInFrames = sizeof(temp) / ma_get_bytes_per_frame(dataSourceFormat, dataSourceChannels);
12431 
12432         /* Keep reading until we've read as much as was requested or we reach the end of the data source. */
12433         while (totalFramesRead < frameCount) {
12434             ma_uint32 framesRemaining = frameCount - totalFramesRead;
12435             ma_uint32 framesToRead;
12436             ma_uint64 framesJustRead;
12437             ma_uint32 frameCountIn;
12438             ma_uint32 frameCountOut;
12439             const float* pRunningFramesIn;
12440             float* pRunningFramesOut;
12441 
12442             /*
12443             The first thing we need to do is read into the temporary buffer. We can calculate exactly
12444             how many input frames we'll need after resampling.
12445             */
12446             framesToRead = (ma_uint32)ma_engine_node_get_required_input_frame_count(&pSound->engineNode, framesRemaining);
12447             if (framesToRead > tempCapInFrames) {
12448                 framesToRead = tempCapInFrames;
12449             }
12450 
12451             result = ma_data_source_read_pcm_frames(pSound->pDataSource, temp, framesToRead, &framesJustRead, ma_sound_is_looping(pSound));
12452 
12453             /* 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. */
12454             if (result == MA_AT_END) {
12455                 c89atomic_exchange_8(&pSound->atEnd, MA_TRUE); /* This will be set to false in ma_sound_start(). */
12456             }
12457 
12458             pRunningFramesOut = ma_offset_pcm_frames_ptr_f32(ppFramesOut[0], totalFramesRead, ma_engine_get_channels(ma_sound_get_engine(pSound)));
12459 
12460             frameCountIn = (ma_uint32)framesJustRead;
12461             frameCountOut = framesRemaining;
12462 
12463             /* Convert if necessary. */
12464             if (dataSourceFormat == ma_format_f32) {
12465                 /* Fast path. No data conversion necessary. */
12466                 pRunningFramesIn = (float*)temp;
12467                 ma_engine_node_process_pcm_frames__general(&pSound->engineNode, &pRunningFramesIn, &frameCountIn, &pRunningFramesOut, &frameCountOut);
12468             } else {
12469                 /* Slow path. Need to do sample format conversion to f32. If we give the f32 buffer the same count as the first temp buffer, we're guaranteed it'll be large enough. */
12470                 float tempf32[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; /* Do not do `MA_DATA_CONVERTER_STACK_BUFFER_SIZE/sizeof(float)` here like we've done in other places. */
12471                 ma_convert_pcm_frames_format(tempf32, ma_format_f32, temp, dataSourceFormat, framesJustRead, dataSourceChannels, ma_dither_mode_none);
12472 
12473                 /* Now that we have our samples in f32 format we can process like normal. */
12474                 pRunningFramesIn = tempf32;
12475                 ma_engine_node_process_pcm_frames__general(&pSound->engineNode, &pRunningFramesIn, &frameCountIn, &pRunningFramesOut, &frameCountOut);
12476             }
12477 
12478             /* We should have processed all of our input frames since we calculated the required number of input frames at the top. */
12479             MA_ASSERT(frameCountIn == framesJustRead);
12480             totalFramesRead += (ma_uint32)frameCountOut;   /* Safe cast. */
12481 
12482             if (result != MA_SUCCESS || ma_sound_at_end(pSound)) {
12483                 break;  /* Might have reached the end. */
12484             }
12485         }
12486     }
12487 
12488     *pFrameCountOut = totalFramesRead;
12489 }
12490 
ma_engine_node_process_pcm_frames__group(ma_node * pNode,const float ** ppFramesIn,ma_uint32 * pFrameCountIn,float ** ppFramesOut,ma_uint32 * pFrameCountOut)12491 static void ma_engine_node_process_pcm_frames__group(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut)
12492 {
12493     /*
12494     Make sure the pitch is updated before trying to read anything. It's important that this is done
12495     only once and not in ma_engine_node_process_pcm_frames__general(). The reason for this is that
12496     ma_engine_node_process_pcm_frames__general() will call ma_engine_node_get_required_input_frame_count(),
12497     and if another thread modifies the pitch just after that call it can result in a glitch due to
12498     the input rate changing.
12499     */
12500     ma_engine_node_update_pitch_if_required((ma_engine_node*)pNode);
12501 
12502     /* For groups, the input data has already been read and we just need to apply the effect. */
12503     ma_engine_node_process_pcm_frames__general((ma_engine_node*)pNode, ppFramesIn, pFrameCountIn, ppFramesOut, pFrameCountOut);
12504 }
12505 
ma_engine_node_get_required_input_frame_count__group(ma_node * pNode,ma_uint32 outputFrameCount)12506 static ma_uint32 ma_engine_node_get_required_input_frame_count__group(ma_node* pNode, ma_uint32 outputFrameCount)
12507 {
12508     ma_uint64 result;
12509 
12510     /* Our pitch will affect this calculation. We need to update it. */
12511     ma_engine_node_update_pitch_if_required((ma_engine_node*)pNode);
12512 
12513     result = ma_engine_node_get_required_input_frame_count((ma_engine_node*)pNode, outputFrameCount);
12514     if (result > 0xFFFFFFFF) {
12515         result = 0xFFFFFFFF;    /* Will never happen because miniaudio will only ever process in relatively small chunks. */
12516     }
12517 
12518     return (ma_uint32)result;
12519 }
12520 
12521 
12522 static ma_node_vtable g_ma_engine_node_vtable__sound =
12523 {
12524     ma_engine_node_process_pcm_frames__sound,
12525     NULL,   /* onGetRequiredInputFrameCount */
12526     0,      /* Sounds are data source nodes which means they have zero inputs (their input is drawn from the data source itself). */
12527     1,      /* Sounds have one output bus. */
12528     0       /* Default flags. */
12529 };
12530 
12531 static ma_node_vtable g_ma_engine_node_vtable__group =
12532 {
12533     ma_engine_node_process_pcm_frames__group,
12534     ma_engine_node_get_required_input_frame_count__group,
12535     1,      /* Groups have one input bus. */
12536     1,      /* Groups have one output bus. */
12537     MA_NODE_FLAG_DIFFERENT_PROCESSING_RATES /* The engine node does resampling so should let miniaudio know about it. */
12538 };
12539 
12540 
12541 
ma_engine_node_base_node_config_init(const ma_engine_node_config * pConfig)12542 static ma_node_config ma_engine_node_base_node_config_init(const ma_engine_node_config* pConfig)
12543 {
12544     ma_node_config baseNodeConfig;
12545 
12546     if (pConfig->type == ma_engine_node_type_sound) {
12547         /* Sound. */
12548         baseNodeConfig = ma_node_config_init();
12549         baseNodeConfig.vtable       = &g_ma_engine_node_vtable__sound;
12550         baseNodeConfig.initialState = ma_node_state_stopped;    /* Sounds are stopped by default. */
12551     } else {
12552         /* Group. */
12553         baseNodeConfig = ma_node_config_init();
12554         baseNodeConfig.vtable       = &g_ma_engine_node_vtable__group;
12555         baseNodeConfig.initialState = ma_node_state_started;    /* Groups are started by default. */
12556     }
12557 
12558     return baseNodeConfig;
12559 }
12560 
ma_engine_node_spatializer_config_init(const ma_node_config * pBaseNodeConfig)12561 static ma_spatializer_config ma_engine_node_spatializer_config_init(const ma_node_config* pBaseNodeConfig)
12562 {
12563     return ma_spatializer_config_init(pBaseNodeConfig->pInputChannels[0], pBaseNodeConfig->pOutputChannels[0]);
12564 }
12565 
12566 typedef struct
12567 {
12568     size_t sizeInBytes;
12569     size_t baseNodeOffset;
12570     size_t spatializerOffset;
12571 } ma_engine_node_heap_layout;
12572 
ma_engine_node_get_heap_layout(const ma_engine_node_config * pConfig,ma_engine_node_heap_layout * pHeapLayout)12573 static ma_result ma_engine_node_get_heap_layout(const ma_engine_node_config* pConfig, ma_engine_node_heap_layout* pHeapLayout)
12574 {
12575     ma_result result;
12576     size_t tempHeapSize;
12577     ma_node_config baseNodeConfig;
12578     ma_spatializer_config spatializerConfig;
12579     ma_uint32 channelsIn;
12580     ma_uint32 channelsOut;
12581 
12582     MA_ASSERT(pHeapLayout);
12583 
12584     MA_ZERO_OBJECT(pHeapLayout);
12585 
12586     if (pConfig == NULL) {
12587         return MA_INVALID_ARGS;
12588     }
12589 
12590     if (pConfig->pEngine == NULL) {
12591         return MA_INVALID_ARGS; /* An engine must be specified. */
12592     }
12593 
12594     pHeapLayout->sizeInBytes = 0;
12595 
12596     channelsIn  = (pConfig->channelsIn  != 0) ? pConfig->channelsIn  : ma_engine_get_channels(pConfig->pEngine);
12597     channelsOut = (pConfig->channelsOut != 0) ? pConfig->channelsOut : ma_engine_get_channels(pConfig->pEngine);
12598 
12599 
12600     /* Base node. */
12601     baseNodeConfig = ma_engine_node_base_node_config_init(pConfig);
12602     baseNodeConfig.pInputChannels  = &channelsIn;
12603     baseNodeConfig.pOutputChannels = &channelsOut;
12604 
12605     result = ma_node_get_heap_size(&baseNodeConfig, &tempHeapSize);
12606     if (result != MA_SUCCESS) {
12607         return result;  /* Failed to retrieve the size of the heap for the base node. */
12608     }
12609 
12610     pHeapLayout->baseNodeOffset = pHeapLayout->sizeInBytes;
12611     pHeapLayout->sizeInBytes += ma_align_64(tempHeapSize);
12612 
12613 
12614     /* Spatializer. */
12615     spatializerConfig = ma_engine_node_spatializer_config_init(&baseNodeConfig);
12616 
12617     result = ma_spatializer_get_heap_size(&spatializerConfig, &tempHeapSize);
12618     if (result != MA_SUCCESS) {
12619         return result;  /* Failed to retrieve the size of the heap for the spatializer. */
12620     }
12621 
12622     pHeapLayout->spatializerOffset = pHeapLayout->sizeInBytes;
12623     pHeapLayout->sizeInBytes += ma_align_64(tempHeapSize);
12624 
12625 
12626     return MA_SUCCESS;
12627 }
12628 
ma_engine_node_get_heap_size(const ma_engine_node_config * pConfig,size_t * pHeapSizeInBytes)12629 MA_API ma_result ma_engine_node_get_heap_size(const ma_engine_node_config* pConfig, size_t* pHeapSizeInBytes)
12630 {
12631     ma_result result;
12632     ma_engine_node_heap_layout heapLayout;
12633 
12634     if (pHeapSizeInBytes == NULL) {
12635         return MA_INVALID_ARGS;
12636     }
12637 
12638     *pHeapSizeInBytes = 0;
12639 
12640     result = ma_engine_node_get_heap_layout(pConfig, &heapLayout);
12641     if (result != MA_SUCCESS) {
12642         return result;
12643     }
12644 
12645     *pHeapSizeInBytes = heapLayout.sizeInBytes;
12646 
12647     return MA_SUCCESS;
12648 }
12649 
ma_engine_node_init_preallocated(const ma_engine_node_config * pConfig,void * pHeap,ma_engine_node * pEngineNode)12650 MA_API ma_result ma_engine_node_init_preallocated(const ma_engine_node_config* pConfig, void* pHeap, ma_engine_node* pEngineNode)
12651 {
12652     ma_result result;
12653     ma_engine_node_heap_layout heapLayout;
12654     ma_node_config baseNodeConfig;
12655     ma_resampler_config resamplerConfig;
12656     ma_fader_config faderConfig;
12657     ma_spatializer_config spatializerConfig;
12658     ma_panner_config pannerConfig;
12659     ma_uint32 channelsIn;
12660     ma_uint32 channelsOut;
12661 
12662     if (pEngineNode == NULL) {
12663         return MA_INVALID_ARGS;
12664     }
12665 
12666     MA_ZERO_OBJECT(pEngineNode);
12667 
12668     result = ma_engine_node_get_heap_layout(pConfig, &heapLayout);
12669     if (result != MA_SUCCESS) {
12670         return result;
12671     }
12672 
12673     if (pConfig->pinnedListenerIndex != MA_LISTENER_INDEX_CLOSEST && pConfig->pinnedListenerIndex >= ma_engine_get_listener_count(pConfig->pEngine)) {
12674         return MA_INVALID_ARGS; /* Invalid listener. */
12675     }
12676 
12677     pEngineNode->_pHeap                   = pHeap;
12678     pEngineNode->pEngine                  = pConfig->pEngine;
12679     pEngineNode->sampleRate               = (pConfig->sampleRate > 0) ? pConfig->sampleRate : ma_engine_get_sample_rate(pEngineNode->pEngine);
12680     pEngineNode->pitch                    = 1;
12681     pEngineNode->oldPitch                 = 1;
12682     pEngineNode->oldDopplerPitch          = 1;
12683     pEngineNode->isPitchDisabled          = pConfig->isPitchDisabled;
12684     pEngineNode->isSpatializationDisabled = pConfig->isSpatializationDisabled;
12685     pEngineNode->pinnedListenerIndex      = pConfig->pinnedListenerIndex;
12686 
12687 
12688     channelsIn  = (pConfig->channelsIn  != 0) ? pConfig->channelsIn  : ma_engine_get_channels(pConfig->pEngine);
12689     channelsOut = (pConfig->channelsOut != 0) ? pConfig->channelsOut : ma_engine_get_channels(pConfig->pEngine);
12690 
12691 
12692     /* Base node. */
12693     baseNodeConfig = ma_engine_node_base_node_config_init(pConfig);
12694     baseNodeConfig.pInputChannels  = &channelsIn;
12695     baseNodeConfig.pOutputChannels = &channelsOut;
12696 
12697     result = ma_node_init_preallocated(&pConfig->pEngine->nodeGraph, &baseNodeConfig, ma_offset_ptr(pHeap, heapLayout.baseNodeOffset), &pEngineNode->baseNode);
12698     if (result != MA_SUCCESS) {
12699         goto error0;
12700     }
12701 
12702 
12703     /*
12704     We can now initialize the effects we need in order to implement the engine node. There's a
12705     defined order of operations here, mainly centered around when we convert our channels from the
12706     data source's native channel count to the engine's channel count. As a rule, we want to do as
12707     much computation as possible before spatialization because there's a chance that will increase
12708     the channel count, thereby increasing the amount of work needing to be done to process.
12709     */
12710 
12711     /* We'll always do resampling first. */
12712     resamplerConfig = ma_resampler_config_init(ma_format_f32, baseNodeConfig.pInputChannels[0], pEngineNode->sampleRate, ma_engine_get_sample_rate(pEngineNode->pEngine), ma_resample_algorithm_linear);
12713     resamplerConfig.linear.lpfOrder = 0;    /* <-- Need to disable low-pass filtering for pitch shifting for now because there's cases where the biquads are becoming unstable. Need to figure out a better fix for this. */
12714 
12715     result = ma_resampler_init(&resamplerConfig, &pEngineNode->resampler);
12716     if (result != MA_SUCCESS) {
12717         goto error1;
12718     }
12719 
12720 
12721     /* After resampling will come the fader. */
12722     faderConfig = ma_fader_config_init(ma_format_f32, baseNodeConfig.pInputChannels[0], ma_engine_get_sample_rate(pEngineNode->pEngine));
12723 
12724     result = ma_fader_init(&faderConfig, &pEngineNode->fader);
12725     if (result != MA_SUCCESS) {
12726         goto error2;
12727     }
12728 
12729 
12730     /*
12731     Spatialization comes next. We spatialize based ont he node's output channel count. It's up the caller to
12732     ensure channels counts link up correctly in the node graph.
12733     */
12734     spatializerConfig = ma_engine_node_spatializer_config_init(&baseNodeConfig);
12735     spatializerConfig.gainSmoothTimeInFrames = pEngineNode->pEngine->gainSmoothTimeInFrames;
12736 
12737     result = ma_spatializer_init_preallocated(&spatializerConfig, ma_offset_ptr(pHeap, heapLayout.spatializerOffset), &pEngineNode->spatializer);
12738     if (result != MA_SUCCESS) {
12739         goto error2;
12740     }
12741 
12742 
12743     /*
12744     After spatialization comes panning. We need to do this after spatialization because otherwise we wouldn't
12745     be able to pan mono sounds.
12746     */
12747     pannerConfig = ma_panner_config_init(ma_format_f32, baseNodeConfig.pOutputChannels[0]);
12748 
12749     result = ma_panner_init(&pannerConfig, &pEngineNode->panner);
12750     if (result != MA_SUCCESS) {
12751         goto error3;
12752     }
12753 
12754     return MA_SUCCESS;
12755 
12756 error3: ma_spatializer_uninit(&pEngineNode->spatializer, NULL); /* <-- No need for allocation callbacks here because we use a preallocated heap. */
12757 error2: ma_resampler_uninit(&pEngineNode->resampler);
12758 error1: ma_node_uninit(&pEngineNode->baseNode, NULL);           /* <-- No need for allocation callbacks here because we use a preallocated heap. */
12759 error0: return result;
12760 }
12761 
ma_engine_node_init(const ma_engine_node_config * pConfig,const ma_allocation_callbacks * pAllocationCallbacks,ma_engine_node * pEngineNode)12762 MA_API ma_result ma_engine_node_init(const ma_engine_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_engine_node* pEngineNode)
12763 {
12764     ma_result result;
12765     size_t heapSizeInBytes;
12766     void* pHeap;
12767 
12768     result = ma_engine_node_get_heap_size(pConfig, &heapSizeInBytes);
12769     if (result != MA_SUCCESS) {
12770         return result;
12771     }
12772 
12773     if (heapSizeInBytes > 0) {
12774         pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks);
12775         if (pHeap == NULL) {
12776             return MA_OUT_OF_MEMORY;
12777         }
12778     } else {
12779         pHeap = NULL;
12780     }
12781 
12782     result = ma_engine_node_init_preallocated(pConfig, pHeap, pEngineNode);
12783     if (result != MA_SUCCESS) {
12784         ma_free(pHeap, pAllocationCallbacks);
12785         return result;
12786     }
12787 
12788     pEngineNode->_ownsHeap = MA_TRUE;
12789     return MA_SUCCESS;
12790 }
12791 
ma_engine_node_uninit(ma_engine_node * pEngineNode,const ma_allocation_callbacks * pAllocationCallbacks)12792 MA_API void ma_engine_node_uninit(ma_engine_node* pEngineNode, const ma_allocation_callbacks* pAllocationCallbacks)
12793 {
12794     /*
12795     The base node always needs to be uninitialized first to ensure it's detached from the graph completely before we
12796     destroy anything that might be in the middle of being used by the processing function.
12797     */
12798     ma_node_uninit(&pEngineNode->baseNode, pAllocationCallbacks);
12799 
12800     /* Now that the node has been uninitialized we can safely uninitialize the rest. */
12801     ma_spatializer_uninit(&pEngineNode->spatializer, NULL);
12802     ma_resampler_uninit(&pEngineNode->resampler);
12803 
12804     /* Free the heap last. */
12805     if (pEngineNode->_pHeap != NULL && pEngineNode->_ownsHeap) {
12806         ma_free(pEngineNode->_pHeap, pAllocationCallbacks);
12807     }
12808 }
12809 
12810 
ma_sound_config_init(void)12811 MA_API ma_sound_config ma_sound_config_init(void)
12812 {
12813     ma_sound_config config;
12814 
12815     MA_ZERO_OBJECT(&config);
12816 
12817     return config;
12818 }
12819 
ma_sound_group_config_init(void)12820 MA_API ma_sound_group_config ma_sound_group_config_init(void)
12821 {
12822     ma_sound_group_config config;
12823 
12824     MA_ZERO_OBJECT(&config);
12825 
12826     return config;
12827 }
12828 
12829 
ma_engine_config_init(void)12830 MA_API ma_engine_config ma_engine_config_init(void)
12831 {
12832     ma_engine_config config;
12833 
12834     MA_ZERO_OBJECT(&config);
12835     config.listenerCount = 1;   /* Always want at least one listener. */
12836 
12837     return config;
12838 }
12839 
12840 
ma_engine_data_callback_internal(ma_device * pDevice,void * pFramesOut,const void * pFramesIn,ma_uint32 frameCount)12841 static void ma_engine_data_callback_internal(ma_device* pDevice, void* pFramesOut, const void* pFramesIn, ma_uint32 frameCount)
12842 {
12843     ma_engine* pEngine = (ma_engine*)pDevice->pUserData;
12844 
12845     /*
12846     Experiment: Try processing a resource manager job if we're on the Emscripten build.
12847 
12848     This serves two purposes:
12849 
12850         1) It ensures jobs are actually processed at some point since we cannot guarantee that the
12851            caller is doing the right thing and calling ma_resource_manager_process_next_job(); and
12852 
12853         2) It's an attempt at working around an issue where processing jobs on the Emscripten main
12854            loop doesn't work as well as it should. When trying to load sounds without the `DECODE`
12855            flag or with the `ASYNC` flag, the sound data is just not able to be loaded in time
12856            before the callback is processed. I think it's got something to do with the single-
12857            threaded nature of Web, but I'm not entirely sure.
12858     */
12859     #if !defined(MA_NO_RESOURCE_MANAGER) && defined(MA_EMSCRIPTEN)
12860     {
12861         if (pEngine->pResourceManager != NULL) {
12862             if ((pEngine->pResourceManager->config.flags & MA_RESOURCE_MANAGER_FLAG_NO_THREADING) != 0) {
12863                 ma_resource_manager_process_next_job(pEngine->pResourceManager);
12864             }
12865         }
12866     }
12867     #endif
12868 
12869     ma_engine_data_callback(pEngine, pFramesOut, pFramesIn, frameCount);
12870 }
12871 
ma_engine_init(const ma_engine_config * pConfig,ma_engine * pEngine)12872 MA_API ma_result ma_engine_init(const ma_engine_config* pConfig, ma_engine* pEngine)
12873 {
12874     ma_result result;
12875     ma_node_graph_config nodeGraphConfig;
12876     ma_engine_config engineConfig;
12877     ma_spatializer_listener_config listenerConfig;
12878     ma_uint32 iListener;
12879 
12880     if (pEngine != NULL) {
12881         MA_ZERO_OBJECT(pEngine);
12882     }
12883 
12884     /* The config is allowed to be NULL in which case we use defaults for everything. */
12885     if (pConfig != NULL) {
12886         engineConfig = *pConfig;
12887     } else {
12888         engineConfig = ma_engine_config_init();
12889     }
12890 
12891     pEngine->pResourceManager = engineConfig.pResourceManager;
12892     pEngine->pDevice          = engineConfig.pDevice;
12893     ma_allocation_callbacks_init_copy(&pEngine->allocationCallbacks, &engineConfig.allocationCallbacks);
12894 
12895     /* If we don't have a device, we need one. */
12896     if (pEngine->pDevice == NULL) {
12897         ma_device_config deviceConfig;
12898 
12899         pEngine->pDevice = (ma_device*)ma_malloc(sizeof(*pEngine->pDevice), &pEngine->allocationCallbacks/*, MA_ALLOCATION_TYPE_CONTEXT*/);
12900         if (pEngine->pDevice == NULL) {
12901             return MA_OUT_OF_MEMORY;
12902         }
12903 
12904         deviceConfig = ma_device_config_init(ma_device_type_playback);
12905         deviceConfig.playback.pDeviceID       = engineConfig.pPlaybackDeviceID;
12906         deviceConfig.playback.format          = ma_format_f32;
12907         deviceConfig.playback.channels        = engineConfig.channels;
12908         deviceConfig.sampleRate               = engineConfig.sampleRate;
12909         deviceConfig.dataCallback             = ma_engine_data_callback_internal;
12910         deviceConfig.pUserData                = pEngine;
12911         deviceConfig.periodSizeInFrames       = engineConfig.periodSizeInFrames;
12912         deviceConfig.periodSizeInMilliseconds = engineConfig.periodSizeInMilliseconds;
12913         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. */
12914         deviceConfig.noClip                   = MA_TRUE;    /* The mixing engine will do clipping itself. */
12915 
12916         if (engineConfig.pContext == NULL) {
12917             ma_context_config contextConfig = ma_context_config_init();
12918             contextConfig.allocationCallbacks = pEngine->allocationCallbacks;
12919             contextConfig.pLog = engineConfig.pLog;
12920 
12921             /* If the engine config does not specify a log, use the resource manager's if we have one. */
12922             #ifndef MA_NO_RESOURCE_MANAGER
12923             {
12924                 if (contextConfig.pLog == NULL && engineConfig.pResourceManager != NULL) {
12925                     contextConfig.pLog = ma_resource_manager_get_log(engineConfig.pResourceManager);
12926                 }
12927             }
12928             #endif
12929 
12930             result = ma_device_init_ex(NULL, 0, &contextConfig, &deviceConfig, pEngine->pDevice);
12931         } else {
12932             result = ma_device_init(engineConfig.pContext, &deviceConfig, pEngine->pDevice);
12933         }
12934 
12935         if (result != MA_SUCCESS) {
12936             ma_free(pEngine->pDevice, &pEngine->allocationCallbacks/*, MA_ALLOCATION_TYPE_CONTEXT*/);
12937             pEngine->pDevice = NULL;
12938             return result;
12939         }
12940 
12941         pEngine->ownsDevice = MA_TRUE;
12942     }
12943 
12944     /*
12945     The engine always uses either the log that was passed into the config, or the context's log. Either
12946     way, the engine never has ownership of the log.
12947     */
12948     if (engineConfig.pLog != NULL) {
12949         pEngine->pLog = engineConfig.pLog;
12950     } else {
12951         pEngine->pLog = ma_device_get_log(pEngine->pDevice);
12952     }
12953 
12954 
12955     /* The engine is a node graph. This needs to be initialized after we have the device so we can can determine the channel count. */
12956     nodeGraphConfig = ma_node_graph_config_init(pEngine->pDevice->playback.channels);
12957 
12958     result = ma_node_graph_init(&nodeGraphConfig, &pEngine->allocationCallbacks, &pEngine->nodeGraph);
12959     if (result != MA_SUCCESS) {
12960         goto on_error_1;
12961     }
12962 
12963 
12964     /* We need at least one listener. */
12965     if (engineConfig.listenerCount == 0) {
12966         engineConfig.listenerCount = 1;
12967     }
12968 
12969     if (engineConfig.listenerCount > MA_ENGINE_MAX_LISTENERS) {
12970         result = MA_INVALID_ARGS;   /* Too many listeners. */
12971         goto on_error_1;
12972     }
12973 
12974     for (iListener = 0; iListener < engineConfig.listenerCount; iListener += 1) {
12975         listenerConfig = ma_spatializer_listener_config_init(pEngine->pDevice->playback.channels);
12976 
12977         result = ma_spatializer_listener_init(&listenerConfig, &pEngine->allocationCallbacks, &pEngine->listeners[iListener]);  /* TODO: Change this to a pre-allocated heap. */
12978         if (result != MA_SUCCESS) {
12979             goto on_error_2;
12980         }
12981 
12982         pEngine->listenerCount += 1;
12983     }
12984 
12985 
12986     /* Gain smoothing for spatialized sounds. */
12987     pEngine->gainSmoothTimeInFrames = engineConfig.gainSmoothTimeInFrames;
12988     if (pEngine->gainSmoothTimeInFrames == 0) {
12989         ma_uint32 gainSmoothTimeInMilliseconds = engineConfig.gainSmoothTimeInMilliseconds;
12990         if (gainSmoothTimeInMilliseconds == 0) {
12991             gainSmoothTimeInMilliseconds = 8;
12992         }
12993 
12994         pEngine->gainSmoothTimeInFrames = (gainSmoothTimeInMilliseconds * ma_engine_get_sample_rate(pEngine)) / 1000;  /* 8ms by default. */
12995     }
12996 
12997 
12998     /* We need a resource manager. */
12999     #ifndef MA_NO_RESOURCE_MANAGER
13000     {
13001         if (pEngine->pResourceManager == NULL) {
13002             ma_resource_manager_config resourceManagerConfig;
13003 
13004             pEngine->pResourceManager = (ma_resource_manager*)ma_malloc(sizeof(*pEngine->pResourceManager), &pEngine->allocationCallbacks);
13005             if (pEngine->pResourceManager == NULL) {
13006                 result = MA_OUT_OF_MEMORY;
13007                 goto on_error_2;
13008             }
13009 
13010             resourceManagerConfig = ma_resource_manager_config_init();
13011             resourceManagerConfig.pLog              = pEngine->pLog;    /* Always use the engine's log for internally-managed resource managers. */
13012             resourceManagerConfig.decodedFormat     = ma_format_f32;
13013             resourceManagerConfig.decodedChannels   = 0;  /* Leave the decoded channel count as 0 so we can get good spatialization. */
13014             resourceManagerConfig.decodedSampleRate = ma_engine_get_sample_rate(pEngine);
13015             ma_allocation_callbacks_init_copy(&resourceManagerConfig.allocationCallbacks, &pEngine->allocationCallbacks);
13016             resourceManagerConfig.pVFS              = engineConfig.pResourceManagerVFS;
13017 
13018             /* The Emscripten build cannot use threads. */
13019             #if defined(MA_EMSCRIPTEN)
13020             {
13021                 resourceManagerConfig.jobThreadCount = 0;
13022                 resourceManagerConfig.flags |= MA_RESOURCE_MANAGER_FLAG_NO_THREADING;
13023             }
13024             #endif
13025 
13026             result = ma_resource_manager_init(&resourceManagerConfig, pEngine->pResourceManager);
13027             if (result != MA_SUCCESS) {
13028                 goto on_error_3;
13029             }
13030 
13031             pEngine->ownsResourceManager = MA_TRUE;
13032         }
13033     }
13034     #endif
13035 
13036     /* Setup some stuff for inlined sounds. That is sounds played with ma_engine_play_sound(). */
13037     ma_mutex_init(&pEngine->inlinedSoundLock);
13038     pEngine->pInlinedSoundHead = NULL;
13039 
13040     /* Start the engine if required. This should always be the last step. */
13041     if (engineConfig.noAutoStart == MA_FALSE) {
13042         result = ma_engine_start(pEngine);
13043         if (result != MA_SUCCESS) {
13044             goto on_error_4;    /* Failed to start the engine. */
13045         }
13046     }
13047 
13048     return MA_SUCCESS;
13049 
13050 on_error_4:
13051     ma_mutex_uninit(&pEngine->inlinedSoundLock);
13052 #ifndef MA_NO_RESOURCE_MANAGER
13053 on_error_3:
13054     if (pEngine->ownsResourceManager) {
13055         ma_free(pEngine->pResourceManager, &pEngine->allocationCallbacks);
13056     }
13057 #endif  /* MA_NO_RESOURCE_MANAGER */
13058 on_error_2:
13059     for (iListener = 0; iListener < pEngine->listenerCount; iListener += 1) {
13060         ma_spatializer_listener_uninit(&pEngine->listeners[iListener], &pEngine->allocationCallbacks);
13061     }
13062 
13063     ma_node_graph_uninit(&pEngine->nodeGraph, &pEngine->allocationCallbacks);
13064 on_error_1:
13065     if (pEngine->ownsDevice) {
13066         ma_device_uninit(pEngine->pDevice);
13067         ma_free(pEngine->pDevice, &pEngine->allocationCallbacks/*, MA_ALLOCATION_TYPE_CONTEXT*/);
13068     }
13069 
13070     return result;
13071 }
13072 
ma_engine_uninit(ma_engine * pEngine)13073 MA_API void ma_engine_uninit(ma_engine* pEngine)
13074 {
13075     if (pEngine == NULL) {
13076         return;
13077     }
13078 
13079     /* The device must be uninitialized before the node graph to ensure the audio thread doesn't try accessing it. */
13080     if (pEngine->ownsDevice) {
13081         ma_device_uninit(pEngine->pDevice);
13082         ma_free(pEngine->pDevice, &pEngine->allocationCallbacks/*, MA_ALLOCATION_TYPE_CONTEXT*/);
13083     } else {
13084         ma_device_stop(pEngine->pDevice);
13085     }
13086 
13087     /*
13088     All inlined sounds need to be deleted. I'm going to use a lock here just to future proof in case
13089     I want to do some kind of garbage collection later on.
13090     */
13091     ma_mutex_lock(&pEngine->inlinedSoundLock);
13092     {
13093         for (;;) {
13094             ma_sound_inlined* pSoundToDelete = pEngine->pInlinedSoundHead;
13095             if (pSoundToDelete == NULL) {
13096                 break;  /* Done. */
13097             }
13098 
13099             pEngine->pInlinedSoundHead = pSoundToDelete->pNext;
13100 
13101             ma_sound_uninit(&pSoundToDelete->sound);
13102             ma_free(pSoundToDelete, &pEngine->allocationCallbacks);
13103         }
13104     }
13105     ma_mutex_unlock(&pEngine->inlinedSoundLock);
13106     ma_mutex_uninit(&pEngine->inlinedSoundLock);
13107 
13108 
13109     /* Make sure the node graph is uninitialized after the audio thread has been shutdown to prevent accessing of the node graph after being uninitialized. */
13110     ma_node_graph_uninit(&pEngine->nodeGraph, &pEngine->allocationCallbacks);
13111 
13112     /* Uninitialize the resource manager last to ensure we don't have a thread still trying to access it. */
13113 #ifndef MA_NO_RESOURCE_MANAGER
13114     if (pEngine->ownsResourceManager) {
13115         ma_resource_manager_uninit(pEngine->pResourceManager);
13116         ma_free(pEngine->pResourceManager, &pEngine->allocationCallbacks);
13117     }
13118 #endif
13119 }
13120 
ma_engine_data_callback(ma_engine * pEngine,void * pFramesOut,const void * pFramesIn,ma_uint32 frameCount)13121 MA_API void ma_engine_data_callback(ma_engine* pEngine, void* pFramesOut, const void* pFramesIn, ma_uint32 frameCount)
13122 {
13123     (void)pFramesIn;    /* Unused. */
13124 
13125     ma_node_graph_read_pcm_frames(&pEngine->nodeGraph, pFramesOut, frameCount, NULL);
13126 }
13127 
ma_engine_get_device(ma_engine * pEngine)13128 MA_API ma_device* ma_engine_get_device(ma_engine* pEngine)
13129 {
13130     if (pEngine == NULL) {
13131         return NULL;
13132     }
13133 
13134     return pEngine->pDevice;
13135 }
13136 
ma_engine_get_log(ma_engine * pEngine)13137 MA_API ma_log* ma_engine_get_log(ma_engine* pEngine)
13138 {
13139     if (pEngine == NULL) {
13140         return NULL;
13141     }
13142 
13143     if (pEngine->pLog != NULL) {
13144         return pEngine->pLog;
13145     } else {
13146         return ma_device_get_log(ma_engine_get_device(pEngine));
13147     }
13148 }
13149 
ma_engine_get_endpoint(ma_engine * pEngine)13150 MA_API ma_node* ma_engine_get_endpoint(ma_engine* pEngine)
13151 {
13152     return ma_node_graph_get_endpoint(&pEngine->nodeGraph);
13153 }
13154 
ma_engine_get_time(const ma_engine * pEngine)13155 MA_API ma_uint64 ma_engine_get_time(const ma_engine* pEngine)
13156 {
13157     return ma_node_graph_get_time(&pEngine->nodeGraph);
13158 }
13159 
ma_engine_set_time(ma_engine * pEngine,ma_uint64 globalTime)13160 MA_API ma_uint64 ma_engine_set_time(ma_engine* pEngine, ma_uint64 globalTime)
13161 {
13162     return ma_node_graph_set_time(&pEngine->nodeGraph, globalTime);
13163 }
13164 
ma_engine_get_channels(const ma_engine * pEngine)13165 MA_API ma_uint32 ma_engine_get_channels(const ma_engine* pEngine)
13166 {
13167     return ma_node_graph_get_channels(&pEngine->nodeGraph);
13168 }
13169 
ma_engine_get_sample_rate(const ma_engine * pEngine)13170 MA_API ma_uint32 ma_engine_get_sample_rate(const ma_engine* pEngine)
13171 {
13172     if (pEngine == NULL) {
13173         return 0;
13174     }
13175 
13176     if (pEngine->pDevice != NULL) {
13177         return pEngine->pDevice->sampleRate;
13178     } else {
13179         return 0;   /* No device. */
13180     }
13181 }
13182 
13183 
ma_engine_start(ma_engine * pEngine)13184 MA_API ma_result ma_engine_start(ma_engine* pEngine)
13185 {
13186     ma_result result;
13187 
13188     if (pEngine == NULL) {
13189         return MA_INVALID_ARGS;
13190     }
13191 
13192     result = ma_device_start(pEngine->pDevice);
13193     if (result != MA_SUCCESS) {
13194         return result;
13195     }
13196 
13197     return MA_SUCCESS;
13198 }
13199 
ma_engine_stop(ma_engine * pEngine)13200 MA_API ma_result ma_engine_stop(ma_engine* pEngine)
13201 {
13202     ma_result result;
13203 
13204     if (pEngine == NULL) {
13205         return MA_INVALID_ARGS;
13206     }
13207 
13208     result = ma_device_stop(pEngine->pDevice);
13209     if (result != MA_SUCCESS) {
13210         return result;
13211     }
13212 
13213     return MA_SUCCESS;
13214 }
13215 
ma_engine_set_volume(ma_engine * pEngine,float volume)13216 MA_API ma_result ma_engine_set_volume(ma_engine* pEngine, float volume)
13217 {
13218     if (pEngine == NULL) {
13219         return MA_INVALID_ARGS;
13220     }
13221 
13222     return ma_device_set_master_volume(pEngine->pDevice, volume);
13223 }
13224 
ma_engine_set_gain_db(ma_engine * pEngine,float gainDB)13225 MA_API ma_result ma_engine_set_gain_db(ma_engine* pEngine, float gainDB)
13226 {
13227     if (pEngine == NULL) {
13228         return MA_INVALID_ARGS;
13229     }
13230 
13231     return ma_device_set_master_gain_db(pEngine->pDevice, gainDB);
13232 }
13233 
13234 
ma_engine_get_listener_count(const ma_engine * pEngine)13235 MA_API ma_uint32 ma_engine_get_listener_count(const ma_engine* pEngine)
13236 {
13237     if (pEngine == NULL) {
13238         return 0;
13239     }
13240 
13241     return pEngine->listenerCount;
13242 }
13243 
ma_engine_find_closest_listener(const ma_engine * pEngine,float absolutePosX,float absolutePosY,float absolutePosZ)13244 MA_API ma_uint32 ma_engine_find_closest_listener(const ma_engine* pEngine, float absolutePosX, float absolutePosY, float absolutePosZ)
13245 {
13246     ma_uint32 iListener;
13247     ma_uint32 iListenerClosest;
13248     float closestLen2 = MA_FLT_MAX;
13249 
13250     if (pEngine == NULL || pEngine->listenerCount == 1) {
13251         return 0;
13252     }
13253 
13254     iListenerClosest = 0;
13255     for (iListener = 0; iListener < pEngine->listenerCount; iListener += 1) {
13256         float len2 = ma_vec3f_len2(ma_vec3f_sub(pEngine->listeners[iListener].position, ma_vec3f_init_3f(absolutePosX, absolutePosY, absolutePosZ)));
13257         if (closestLen2 > len2) {
13258             closestLen2 = len2;
13259             iListenerClosest = iListener;
13260         }
13261     }
13262 
13263     MA_ASSERT(iListenerClosest < 255);
13264     return iListenerClosest;
13265 }
13266 
ma_engine_listener_set_position(ma_engine * pEngine,ma_uint32 listenerIndex,float x,float y,float z)13267 MA_API void ma_engine_listener_set_position(ma_engine* pEngine, ma_uint32 listenerIndex, float x, float y, float z)
13268 {
13269     if (pEngine == NULL || listenerIndex >= pEngine->listenerCount) {
13270         return;
13271     }
13272 
13273     ma_spatializer_listener_set_position(&pEngine->listeners[listenerIndex], x, y, z);
13274 }
13275 
ma_engine_listener_get_position(const ma_engine * pEngine,ma_uint32 listenerIndex)13276 MA_API ma_vec3f ma_engine_listener_get_position(const ma_engine* pEngine, ma_uint32 listenerIndex)
13277 {
13278     if (pEngine == NULL || listenerIndex >= pEngine->listenerCount) {
13279         return ma_vec3f_init_3f(0, 0, 0);
13280     }
13281 
13282     return ma_spatializer_listener_get_position(&pEngine->listeners[listenerIndex]);
13283 }
13284 
ma_engine_listener_set_direction(ma_engine * pEngine,ma_uint32 listenerIndex,float x,float y,float z)13285 MA_API void ma_engine_listener_set_direction(ma_engine* pEngine, ma_uint32 listenerIndex, float x, float y, float z)
13286 {
13287     if (pEngine == NULL || listenerIndex >= pEngine->listenerCount) {
13288         return;
13289     }
13290 
13291     ma_spatializer_listener_set_direction(&pEngine->listeners[listenerIndex], x, y, z);
13292 }
13293 
ma_engine_listener_get_direction(const ma_engine * pEngine,ma_uint32 listenerIndex)13294 MA_API ma_vec3f ma_engine_listener_get_direction(const ma_engine* pEngine, ma_uint32 listenerIndex)
13295 {
13296     if (pEngine == NULL || listenerIndex >= pEngine->listenerCount) {
13297         return ma_vec3f_init_3f(0, 0, -1);
13298     }
13299 
13300     return ma_spatializer_listener_get_direction(&pEngine->listeners[listenerIndex]);
13301 }
13302 
ma_engine_listener_set_velocity(ma_engine * pEngine,ma_uint32 listenerIndex,float x,float y,float z)13303 MA_API void ma_engine_listener_set_velocity(ma_engine* pEngine, ma_uint32 listenerIndex, float x, float y, float z)
13304 {
13305     if (pEngine == NULL || listenerIndex >= pEngine->listenerCount) {
13306         return;
13307     }
13308 
13309     ma_spatializer_listener_set_velocity(&pEngine->listeners[listenerIndex], x, y, z);
13310 }
13311 
ma_engine_listener_get_velocity(const ma_engine * pEngine,ma_uint32 listenerIndex)13312 MA_API ma_vec3f ma_engine_listener_get_velocity(const ma_engine* pEngine, ma_uint32 listenerIndex)
13313 {
13314     if (pEngine == NULL || listenerIndex >= pEngine->listenerCount) {
13315         return ma_vec3f_init_3f(0, 0, 0);
13316     }
13317 
13318     return ma_spatializer_listener_get_velocity(&pEngine->listeners[listenerIndex]);
13319 }
13320 
ma_engine_listener_set_cone(ma_engine * pEngine,ma_uint32 listenerIndex,float innerAngleInRadians,float outerAngleInRadians,float outerGain)13321 MA_API void ma_engine_listener_set_cone(ma_engine* pEngine, ma_uint32 listenerIndex, float innerAngleInRadians, float outerAngleInRadians, float outerGain)
13322 {
13323     if (pEngine == NULL || listenerIndex >= pEngine->listenerCount) {
13324         return;
13325     }
13326 
13327     ma_spatializer_listener_set_cone(&pEngine->listeners[listenerIndex], innerAngleInRadians, outerAngleInRadians, outerGain);
13328 }
13329 
ma_engine_listener_get_cone(const ma_engine * pEngine,ma_uint32 listenerIndex,float * pInnerAngleInRadians,float * pOuterAngleInRadians,float * pOuterGain)13330 MA_API void ma_engine_listener_get_cone(const ma_engine* pEngine, ma_uint32 listenerIndex, float* pInnerAngleInRadians, float* pOuterAngleInRadians, float* pOuterGain)
13331 {
13332     if (pInnerAngleInRadians != NULL) {
13333         *pInnerAngleInRadians = 0;
13334     }
13335 
13336     if (pOuterAngleInRadians != NULL) {
13337         *pOuterAngleInRadians = 0;
13338     }
13339 
13340     if (pOuterGain != NULL) {
13341         *pOuterGain = 0;
13342     }
13343 
13344     ma_spatializer_listener_get_cone(&pEngine->listeners[listenerIndex], pInnerAngleInRadians, pOuterAngleInRadians, pOuterGain);
13345 }
13346 
ma_engine_listener_set_world_up(ma_engine * pEngine,ma_uint32 listenerIndex,float x,float y,float z)13347 MA_API void ma_engine_listener_set_world_up(ma_engine* pEngine, ma_uint32 listenerIndex, float x, float y, float z)
13348 {
13349     if (pEngine == NULL || listenerIndex >= pEngine->listenerCount) {
13350         return;
13351     }
13352 
13353     ma_spatializer_listener_set_world_up(&pEngine->listeners[listenerIndex], x, y, z);
13354 }
13355 
ma_engine_listener_get_world_up(const ma_engine * pEngine,ma_uint32 listenerIndex)13356 MA_API ma_vec3f ma_engine_listener_get_world_up(const ma_engine* pEngine, ma_uint32 listenerIndex)
13357 {
13358     if (pEngine == NULL || listenerIndex >= pEngine->listenerCount) {
13359         return ma_vec3f_init_3f(0, 1, 0);
13360     }
13361 
13362     return ma_spatializer_listener_get_world_up(&pEngine->listeners[listenerIndex]);
13363 }
13364 
13365 
13366 #ifndef MA_NO_RESOURCE_MANAGER
ma_engine_play_sound_ex(ma_engine * pEngine,const char * pFilePath,ma_node * pNode,ma_uint32 nodeInputBusIndex)13367 MA_API ma_result ma_engine_play_sound_ex(ma_engine* pEngine, const char* pFilePath, ma_node* pNode, ma_uint32 nodeInputBusIndex)
13368 {
13369     ma_result result = MA_SUCCESS;
13370     ma_sound_inlined* pSound = NULL;
13371     ma_sound_inlined* pNextSound = NULL;
13372 
13373     if (pEngine == NULL || pFilePath == NULL) {
13374         return MA_INVALID_ARGS;
13375     }
13376 
13377     /* Attach to the endpoint node if nothing is specicied. */
13378     if (pNode == NULL) {
13379         pNode = ma_node_graph_get_endpoint(&pEngine->nodeGraph);
13380         nodeInputBusIndex = 0;
13381     }
13382 
13383     /*
13384     We want to check if we can recycle an already-allocated inlined sound. Since this is just a
13385     helper I'm not *too* concerned about performance here and I'm happy to use a lock to keep
13386     the implementation simple. Maybe this can be optimized later if there's enough demand, but
13387     if this function is being used it probably means the caller doesn't really care too much.
13388 
13389     What we do is check the atEnd flag. When this is true, we can recycle the sound. Otherwise
13390     we just keep iterating. If we reach the end without finding a sound to recycle we just
13391     allocate a new one. This doesn't scale well for a massive number of sounds being played
13392     simultaneously as we don't ever actually free the sound objects. Some kind of garbage
13393     collection routine might be valuable for this which I'll think about.
13394     */
13395     ma_mutex_lock(&pEngine->inlinedSoundLock);
13396     {
13397         ma_uint32 soundFlags = 0;
13398 
13399         for (pNextSound = pEngine->pInlinedSoundHead; pNextSound != NULL; pNextSound = pNextSound->pNext) {
13400             if (ma_sound_at_end(&pNextSound->sound)) {
13401                 /*
13402                 The sound is at the end which means it's available for recycling. All we need to do
13403                 is uninitialize it and reinitialize it. All we're doing is recycling memory.
13404                 */
13405                 pSound = pNextSound;
13406                 c89atomic_fetch_sub_32(&pEngine->inlinedSoundCount, 1);
13407                 break;
13408             }
13409         }
13410 
13411         if (pSound != NULL) {
13412             /*
13413             We actually want to detach the sound from the list here. The reason is because we want the sound
13414             to be in a consistent state at the non-recycled case to simplify the logic below.
13415             */
13416             if (pEngine->pInlinedSoundHead == pSound) {
13417                 pEngine->pInlinedSoundHead =  pSound->pNext;
13418             }
13419 
13420             if (pSound->pPrev != NULL) {
13421                 pSound->pPrev->pNext = pSound->pNext;
13422             }
13423             if (pSound->pNext != NULL) {
13424                 pSound->pNext->pPrev = pSound->pPrev;
13425             }
13426 
13427             /* Now the previous sound needs to be uninitialized. */
13428             ma_sound_uninit(&pNextSound->sound);
13429         } else {
13430             /* No sound available for recycling. Allocate one now. */
13431             pSound = (ma_sound_inlined*)ma_malloc(sizeof(*pSound), &pEngine->allocationCallbacks);
13432         }
13433 
13434         if (pSound != NULL) {   /* Safety check for the allocation above. */
13435             /*
13436             At this point we should have memory allocated for the inlined sound. We just need
13437             to initialize it like a normal sound now.
13438             */
13439             soundFlags |= MA_SOUND_FLAG_ASYNC;                 /* For inlined sounds we don't want to be sitting around waiting for stuff to load so force an async load. */
13440             soundFlags |= MA_SOUND_FLAG_NO_DEFAULT_ATTACHMENT; /* We want specific control over where the sound is attached in the graph. We'll attach it manually just before playing the sound. */
13441             soundFlags |= MA_SOUND_FLAG_NO_PITCH;              /* Pitching isn't usable with inlined sounds, so disable it to save on speed. */
13442             soundFlags |= MA_SOUND_FLAG_NO_SPATIALIZATION;     /* Not currently doing spatialization with inlined sounds, but this might actually change later. For now disable spatialization. Will be removed if we ever add support for spatialization here. */
13443 
13444             result = ma_sound_init_from_file(pEngine, pFilePath, soundFlags, NULL, NULL, &pSound->sound);
13445             if (result == MA_SUCCESS) {
13446                 /* Now attach the sound to the graph. */
13447                 result = ma_node_attach_output_bus(pSound, 0, pNode, nodeInputBusIndex);
13448                 if (result == MA_SUCCESS) {
13449                     /* At this point the sound should be loaded and we can go ahead and add it to the list. The new item becomes the new head. */
13450                     pSound->pNext = pEngine->pInlinedSoundHead;
13451                     pSound->pPrev = NULL;
13452 
13453                     pEngine->pInlinedSoundHead = pSound;    /* <-- This is what attaches the sound to the list. */
13454                     if (pSound->pNext != NULL) {
13455                         pSound->pNext->pPrev = pSound;
13456                     }
13457                 } else {
13458                     ma_free(pSound, &pEngine->allocationCallbacks);
13459                 }
13460             } else {
13461                 ma_free(pSound, &pEngine->allocationCallbacks);
13462             }
13463         } else {
13464             result = MA_OUT_OF_MEMORY;
13465         }
13466     }
13467     ma_mutex_unlock(&pEngine->inlinedSoundLock);
13468 
13469     if (result != MA_SUCCESS) {
13470         return result;
13471     }
13472 
13473     /* Finally we can start playing the sound. */
13474     result = ma_sound_start(&pSound->sound);
13475     if (result != MA_SUCCESS) {
13476         /* Failed to start the sound. We need to mark it for recycling and return an error. */
13477         c89atomic_exchange_8(&pSound->sound.atEnd, MA_TRUE);
13478         return result;
13479     }
13480 
13481     c89atomic_fetch_add_32(&pEngine->inlinedSoundCount, 1);
13482     return result;
13483 }
13484 
ma_engine_play_sound(ma_engine * pEngine,const char * pFilePath,ma_sound_group * pGroup)13485 MA_API ma_result ma_engine_play_sound(ma_engine* pEngine, const char* pFilePath, ma_sound_group* pGroup)
13486 {
13487     return ma_engine_play_sound_ex(pEngine, pFilePath, pGroup, 0);
13488 }
13489 #endif
13490 
13491 
ma_sound_preinit(ma_engine * pEngine,ma_sound * pSound)13492 static ma_result ma_sound_preinit(ma_engine* pEngine, ma_sound* pSound)
13493 {
13494     if (pSound == NULL) {
13495         return MA_INVALID_ARGS;
13496     }
13497 
13498     MA_ZERO_OBJECT(pSound);
13499     pSound->seekTarget = MA_SEEK_TARGET_NONE;
13500 
13501     if (pEngine == NULL) {
13502         return MA_INVALID_ARGS;
13503     }
13504 
13505     return MA_SUCCESS;
13506 }
13507 
ma_sound_init_from_data_source_internal(ma_engine * pEngine,const ma_sound_config * pConfig,ma_sound * pSound)13508 static ma_result ma_sound_init_from_data_source_internal(ma_engine* pEngine, const ma_sound_config* pConfig, ma_sound* pSound)
13509 {
13510     ma_result result;
13511     ma_engine_node_config engineNodeConfig;
13512     ma_engine_node_type type;   /* Will be set to ma_engine_node_type_group if no data source is specified. */
13513 
13514     /* Do not clear pSound to zero here - that's done at a higher level with ma_sound_preinit(). */
13515     MA_ASSERT(pEngine != NULL);
13516     MA_ASSERT(pSound  != NULL);
13517 
13518     if (pConfig == NULL) {
13519         return MA_INVALID_ARGS;
13520     }
13521 
13522     pSound->pDataSource = pConfig->pDataSource;
13523 
13524     if (pConfig->pDataSource != NULL) {
13525         type = ma_engine_node_type_sound;
13526     } else {
13527         type = ma_engine_node_type_group;
13528     }
13529 
13530     /*
13531     Sounds are engine nodes. Before we can initialize this we need to determine the channel count.
13532     If we can't do this we need to abort. It's up to the caller to ensure they're using a data
13533     source that provides this information upfront.
13534     */
13535     engineNodeConfig = ma_engine_node_config_init(pEngine, type, pConfig->flags);
13536     engineNodeConfig.channelsIn  = pConfig->channelsIn;
13537     engineNodeConfig.channelsOut = pConfig->channelsOut;
13538 
13539     /* If we're loading from a data source the input channel count needs to be the data source's native channel count. */
13540     if (pConfig->pDataSource != NULL) {
13541         result = ma_data_source_get_data_format(pConfig->pDataSource, NULL, &engineNodeConfig.channelsIn, &engineNodeConfig.sampleRate);
13542         if (result != MA_SUCCESS) {
13543             return result;  /* Failed to retrieve the channel count. */
13544         }
13545 
13546         if (engineNodeConfig.channelsIn == 0) {
13547             return MA_INVALID_OPERATION;    /* Invalid channel count. */
13548         }
13549     }
13550 
13551 
13552     /* Getting here means we should have a valid channel count and we can initialize the engine node. */
13553     result = ma_engine_node_init(&engineNodeConfig, &pEngine->allocationCallbacks, &pSound->engineNode);
13554     if (result != MA_SUCCESS) {
13555         return result;
13556     }
13557 
13558     /* If no attachment is specified, attach the sound straight to the endpoint. */
13559     if (pConfig->pInitialAttachment == NULL) {
13560         /* No group. Attach straight to the endpoint by default, unless the caller has requested that do not. */
13561         if ((pConfig->flags & MA_SOUND_FLAG_NO_DEFAULT_ATTACHMENT) == 0) {
13562             result = ma_node_attach_output_bus(pSound, 0, ma_node_graph_get_endpoint(&pEngine->nodeGraph), 0);
13563         }
13564     } else {
13565         /* An attachment is specified. Attach to it by default. The sound has only a single output bus, and the config will specify which input bus to attach to. */
13566         result = ma_node_attach_output_bus(pSound, 0, pConfig->pInitialAttachment, pConfig->initialAttachmentInputBusIndex);
13567     }
13568 
13569     if (result != MA_SUCCESS) {
13570         ma_engine_node_uninit(&pSound->engineNode, &pEngine->allocationCallbacks);
13571         return result;
13572     }
13573 
13574     return MA_SUCCESS;
13575 }
13576 
13577 #ifndef MA_NO_RESOURCE_MANAGER
ma_sound_init_from_file_internal(ma_engine * pEngine,const ma_sound_config * pConfig,ma_sound * pSound)13578 MA_API ma_result ma_sound_init_from_file_internal(ma_engine* pEngine, const ma_sound_config* pConfig, ma_sound* pSound)
13579 {
13580     ma_result result = MA_SUCCESS;
13581     ma_uint32 flags;
13582     ma_sound_config config;
13583     ma_pipeline_notifications notifications;
13584 
13585     /*
13586     The engine requires knowledge of the channel count of the underlying data source before it can
13587     initialize the sound. Therefore, we need to make the resource manager wait until initialization
13588     of the underlying data source to be initialized so we can get access to the channel count. To
13589     do this, the MA_DATA_SOURCE_FLAG_WAIT_INIT is forced.
13590 
13591     Because we're initializing the data source before the sound, there's a chance the notification
13592     will get triggered before this function returns. This is OK, so long as the caller is aware of
13593     it and can avoid accessing the sound from within the notification.
13594     */
13595     flags = pConfig->flags | MA_DATA_SOURCE_FLAG_WAIT_INIT;
13596 
13597     pSound->pResourceManagerDataSource = (ma_resource_manager_data_source*)ma_malloc(sizeof(*pSound->pResourceManagerDataSource), &pEngine->allocationCallbacks);
13598     if (pSound->pResourceManagerDataSource == NULL) {
13599         return MA_OUT_OF_MEMORY;
13600     }
13601 
13602     notifications = ma_pipeline_notifications_init();
13603     notifications.done.pFence = pConfig->pDoneFence;
13604 
13605     /*
13606     We must wrap everything around the fence if one was specified. This ensures ma_fence_wait() does
13607     not return prematurely before the sound has finished initializing.
13608     */
13609     if (notifications.done.pFence) { ma_fence_acquire(notifications.done.pFence); }
13610     {
13611         if (pConfig->pFilePath != NULL) {
13612             result = ma_resource_manager_data_source_init(pEngine->pResourceManager, pConfig->pFilePath, flags, &notifications, pSound->pResourceManagerDataSource);
13613         } else {
13614             result = ma_resource_manager_data_source_init_w(pEngine->pResourceManager, pConfig->pFilePathW, flags, &notifications, pSound->pResourceManagerDataSource);
13615         }
13616 
13617         if (result != MA_SUCCESS) {
13618             goto done;
13619         }
13620 
13621         pSound->ownsDataSource = MA_TRUE;   /* <-- Important. Not setting this will result in the resource manager data source never getting uninitialized. */
13622 
13623         /* We need to use a slightly customized version of the config so we'll need to make a copy. */
13624         config = *pConfig;
13625         config.pFilePath   = NULL;
13626         config.pFilePathW  = NULL;
13627         config.pDataSource = pSound->pResourceManagerDataSource;
13628 
13629         result = ma_sound_init_from_data_source_internal(pEngine, &config, pSound);
13630         if (result != MA_SUCCESS) {
13631             ma_resource_manager_data_source_uninit(pSound->pResourceManagerDataSource);
13632             ma_free(pSound->pResourceManagerDataSource, &pEngine->allocationCallbacks);
13633             MA_ZERO_OBJECT(pSound);
13634             goto done;
13635         }
13636     }
13637 done:
13638     if (notifications.done.pFence) { ma_fence_release(notifications.done.pFence); }
13639     return result;
13640 }
13641 
ma_sound_init_from_file(ma_engine * pEngine,const char * pFilePath,ma_uint32 flags,ma_sound_group * pGroup,ma_fence * pDoneFence,ma_sound * pSound)13642 MA_API ma_result ma_sound_init_from_file(ma_engine* pEngine, const char* pFilePath, ma_uint32 flags, ma_sound_group* pGroup, ma_fence* pDoneFence, ma_sound* pSound)
13643 {
13644     ma_sound_config config = ma_sound_config_init();
13645     config.pFilePath          = pFilePath;
13646     config.flags              = flags;
13647     config.pInitialAttachment = pGroup;
13648     config.pDoneFence         = pDoneFence;
13649     return ma_sound_init_ex(pEngine, &config, pSound);
13650 }
13651 
ma_sound_init_from_file_w(ma_engine * pEngine,const wchar_t * pFilePath,ma_uint32 flags,ma_sound_group * pGroup,ma_fence * pDoneFence,ma_sound * pSound)13652 MA_API ma_result ma_sound_init_from_file_w(ma_engine* pEngine, const wchar_t* pFilePath, ma_uint32 flags, ma_sound_group* pGroup, ma_fence* pDoneFence, ma_sound* pSound)
13653 {
13654     ma_sound_config config = ma_sound_config_init();
13655     config.pFilePathW         = pFilePath;
13656     config.flags              = flags;
13657     config.pInitialAttachment = pGroup;
13658     config.pDoneFence         = pDoneFence;
13659     return ma_sound_init_ex(pEngine, &config, pSound);
13660 }
13661 
ma_sound_init_copy(ma_engine * pEngine,const ma_sound * pExistingSound,ma_uint32 flags,ma_sound_group * pGroup,ma_sound * pSound)13662 MA_API ma_result ma_sound_init_copy(ma_engine* pEngine, const ma_sound* pExistingSound, ma_uint32 flags, ma_sound_group* pGroup, ma_sound* pSound)
13663 {
13664     ma_result result;
13665     ma_sound_config config;
13666 
13667     result = ma_sound_preinit(pEngine, pSound);
13668     if (result != MA_SUCCESS) {
13669         return result;
13670     }
13671 
13672     if (pExistingSound == NULL) {
13673         return MA_INVALID_ARGS;
13674     }
13675 
13676     /* Cloning only works for data buffers (not streams) that are loaded from the resource manager. */
13677     if (pExistingSound->pResourceManagerDataSource == NULL) {
13678         return MA_INVALID_OPERATION;
13679     }
13680 
13681     /*
13682     We need to make a clone of the data source. If the data source is not a data buffer (i.e. a stream)
13683     the this will fail.
13684     */
13685     pSound->pResourceManagerDataSource = (ma_resource_manager_data_source*)ma_malloc(sizeof(*pSound->pResourceManagerDataSource), &pEngine->allocationCallbacks);
13686     if (pSound->pResourceManagerDataSource == NULL) {
13687         return MA_OUT_OF_MEMORY;
13688     }
13689 
13690     result = ma_resource_manager_data_source_init_copy(pEngine->pResourceManager, pExistingSound->pResourceManagerDataSource, pSound->pResourceManagerDataSource);
13691     if (result != MA_SUCCESS) {
13692         ma_free(pSound->pResourceManagerDataSource, &pEngine->allocationCallbacks);
13693         return result;
13694     }
13695 
13696     config = ma_sound_config_init();
13697     config.pDataSource        = pSound->pResourceManagerDataSource;
13698     config.flags              = flags;
13699     config.pInitialAttachment = pGroup;
13700 
13701     result = ma_sound_init_from_data_source_internal(pEngine, &config, pSound);
13702     if (result != MA_SUCCESS) {
13703         ma_resource_manager_data_source_uninit(pSound->pResourceManagerDataSource);
13704         ma_free(pSound->pResourceManagerDataSource, &pEngine->allocationCallbacks);
13705         MA_ZERO_OBJECT(pSound);
13706         return result;
13707     }
13708 
13709     return MA_SUCCESS;
13710 }
13711 #endif
13712 
ma_sound_init_from_data_source(ma_engine * pEngine,ma_data_source * pDataSource,ma_uint32 flags,ma_sound_group * pGroup,ma_sound * pSound)13713 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)
13714 {
13715     ma_sound_config config = ma_sound_config_init();
13716     config.pDataSource        = pDataSource;
13717     config.flags              = flags;
13718     config.pInitialAttachment = pGroup;
13719     return ma_sound_init_ex(pEngine, &config, pSound);
13720 }
13721 
ma_sound_init_ex(ma_engine * pEngine,const ma_sound_config * pConfig,ma_sound * pSound)13722 MA_API ma_result ma_sound_init_ex(ma_engine* pEngine, const ma_sound_config* pConfig, ma_sound* pSound)
13723 {
13724     ma_result result;
13725 
13726     result = ma_sound_preinit(pEngine, pSound);
13727     if (result != MA_SUCCESS) {
13728         return result;
13729     }
13730 
13731     if (pConfig == NULL) {
13732         return MA_INVALID_ARGS;
13733     }
13734 
13735     /* We need to load the sound differently depending on whether or not we're loading from a file. */
13736 #ifndef MA_NO_RESOURCE_MANAGER
13737     if (pConfig->pFilePath != NULL || pConfig->pFilePathW != NULL) {
13738         return ma_sound_init_from_file_internal(pEngine, pConfig, pSound);
13739     } else
13740 #endif
13741     {
13742         /*
13743         Getting here means we're not loading from a file. We may be loading from an already-initialized
13744         data source, or none at all. If we aren't specifying any data source, we'll be initializing the
13745         the equivalent to a group. ma_data_source_init_from_data_source_internal() will deal with this
13746         for us, so no special treatment required here.
13747         */
13748         return ma_sound_init_from_data_source_internal(pEngine, pConfig, pSound);
13749     }
13750 }
13751 
ma_sound_uninit(ma_sound * pSound)13752 MA_API void ma_sound_uninit(ma_sound* pSound)
13753 {
13754     if (pSound == NULL) {
13755         return;
13756     }
13757 
13758     /*
13759     Always uninitialize the node first. This ensures it's detached from the graph and does not return until it has done
13760     so which makes thread safety beyond this point trivial.
13761     */
13762     ma_node_uninit(pSound, &pSound->engineNode.pEngine->allocationCallbacks);
13763 
13764     /* 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. */
13765 #ifndef MA_NO_RESOURCE_MANAGER
13766     if (pSound->ownsDataSource) {
13767         ma_resource_manager_data_source_uninit(pSound->pResourceManagerDataSource);
13768         ma_free(pSound->pResourceManagerDataSource, &pSound->engineNode.pEngine->allocationCallbacks);
13769         pSound->pDataSource = NULL;
13770     }
13771 #else
13772     MA_ASSERT(pSound->ownsDataSource == MA_FALSE);
13773 #endif
13774 }
13775 
ma_sound_get_engine(const ma_sound * pSound)13776 MA_API ma_engine* ma_sound_get_engine(const ma_sound* pSound)
13777 {
13778     if (pSound == NULL) {
13779         return NULL;
13780     }
13781 
13782     return pSound->engineNode.pEngine;
13783 }
13784 
ma_sound_get_data_source(const ma_sound * pSound)13785 MA_API ma_data_source* ma_sound_get_data_source(const ma_sound* pSound)
13786 {
13787     if (pSound == NULL) {
13788         return NULL;
13789     }
13790 
13791     return pSound->pDataSource;
13792 }
13793 
ma_sound_start(ma_sound * pSound)13794 MA_API ma_result ma_sound_start(ma_sound* pSound)
13795 {
13796     if (pSound == NULL) {
13797         return MA_INVALID_ARGS;
13798     }
13799 
13800     /* If the sound is already playing, do nothing. */
13801     if (ma_sound_is_playing(pSound)) {
13802         return MA_SUCCESS;
13803     }
13804 
13805     /* If the sound is at the end it means we want to start from the start again. */
13806     if (ma_sound_at_end(pSound)) {
13807         ma_result result = ma_data_source_seek_to_pcm_frame(pSound->pDataSource, 0);
13808         if (result != MA_SUCCESS && result != MA_NOT_IMPLEMENTED) {
13809             return result;  /* Failed to seek back to the start. */
13810         }
13811 
13812         /* Make sure we clear the end indicator. */
13813         c89atomic_exchange_8(&pSound->atEnd, MA_FALSE);
13814     }
13815 
13816     /* Make sure the sound is started. If there's a start delay, the sound won't actually start until the start time is reached. */
13817     ma_node_set_state(pSound, ma_node_state_started);
13818 
13819     return MA_SUCCESS;
13820 }
13821 
ma_sound_stop(ma_sound * pSound)13822 MA_API ma_result ma_sound_stop(ma_sound* pSound)
13823 {
13824     if (pSound == NULL) {
13825         return MA_INVALID_ARGS;
13826     }
13827 
13828     /* This will stop the sound immediately. Use ma_sound_set_stop_time() to stop the sound at a specific time. */
13829     ma_node_set_state(pSound, ma_node_state_stopped);
13830 
13831     return MA_SUCCESS;
13832 }
13833 
ma_sound_set_volume(ma_sound * pSound,float volume)13834 MA_API ma_result ma_sound_set_volume(ma_sound* pSound, float volume)
13835 {
13836     if (pSound == NULL) {
13837         return MA_INVALID_ARGS;
13838     }
13839 
13840     /* The volume is controlled via the output bus. */
13841     ma_node_set_output_bus_volume(pSound, 0, volume);
13842 
13843     return MA_SUCCESS;
13844 }
13845 
ma_sound_set_gain_db(ma_sound * pSound,float gainDB)13846 MA_API ma_result ma_sound_set_gain_db(ma_sound* pSound, float gainDB)
13847 {
13848     return ma_sound_set_volume(pSound, ma_gain_db_to_factor(gainDB));
13849 }
13850 
ma_sound_set_pan(ma_sound * pSound,float pan)13851 MA_API void ma_sound_set_pan(ma_sound* pSound, float pan)
13852 {
13853     if (pSound == NULL) {
13854         return;
13855     }
13856 
13857     ma_panner_set_pan(&pSound->engineNode.panner, pan);
13858 }
13859 
ma_sound_set_pan_mode(ma_sound * pSound,ma_pan_mode panMode)13860 MA_API void ma_sound_set_pan_mode(ma_sound* pSound, ma_pan_mode panMode)
13861 {
13862     if (pSound == NULL) {
13863         return;
13864     }
13865 
13866     ma_panner_set_mode(&pSound->engineNode.panner, panMode);
13867 }
13868 
ma_sound_set_pitch(ma_sound * pSound,float pitch)13869 MA_API void ma_sound_set_pitch(ma_sound* pSound, float pitch)
13870 {
13871     if (pSound == NULL) {
13872         return;
13873     }
13874 
13875     c89atomic_exchange_explicit_f32(&pSound->engineNode.pitch, pitch, c89atomic_memory_order_release);
13876 }
13877 
ma_sound_set_spatialization_enabled(ma_sound * pSound,ma_bool32 enabled)13878 MA_API void ma_sound_set_spatialization_enabled(ma_sound* pSound, ma_bool32 enabled)
13879 {
13880     if (pSound == NULL) {
13881         return;
13882     }
13883 
13884     c89atomic_exchange_explicit_8(&pSound->engineNode.isSpatializationDisabled, !enabled, c89atomic_memory_order_release);
13885 }
13886 
ma_sound_set_pinned_listener_index(ma_sound * pSound,ma_uint8 listenerIndex)13887 MA_API void ma_sound_set_pinned_listener_index(ma_sound* pSound, ma_uint8 listenerIndex)
13888 {
13889     if (pSound == NULL || listenerIndex >= ma_engine_get_listener_count(ma_sound_get_engine(pSound))) {
13890         return;
13891     }
13892 
13893     c89atomic_exchange_explicit_8(&pSound->engineNode.pinnedListenerIndex, listenerIndex, c89atomic_memory_order_release);
13894 }
13895 
ma_sound_get_pinned_listener_index(const ma_sound * pSound)13896 MA_API ma_uint8 ma_sound_get_pinned_listener_index(const ma_sound* pSound)
13897 {
13898     if (pSound == NULL) {
13899         return MA_LISTENER_INDEX_CLOSEST;
13900     }
13901 
13902     return c89atomic_load_explicit_8(&pSound->engineNode.pinnedListenerIndex, c89atomic_memory_order_acquire);
13903 }
13904 
ma_sound_set_position(ma_sound * pSound,float x,float y,float z)13905 MA_API void ma_sound_set_position(ma_sound* pSound, float x, float y, float z)
13906 {
13907     if (pSound == NULL) {
13908         return;
13909     }
13910 
13911     ma_spatializer_set_position(&pSound->engineNode.spatializer, x, y, z);
13912 }
13913 
ma_sound_get_position(const ma_sound * pSound)13914 MA_API ma_vec3f ma_sound_get_position(const ma_sound* pSound)
13915 {
13916     if (pSound == NULL) {
13917         return ma_vec3f_init_3f(0, 0, 0);
13918     }
13919 
13920     return ma_spatializer_get_position(&pSound->engineNode.spatializer);
13921 }
13922 
ma_sound_set_direction(ma_sound * pSound,float x,float y,float z)13923 MA_API void ma_sound_set_direction(ma_sound* pSound, float x, float y, float z)
13924 {
13925     if (pSound == NULL) {
13926         return;
13927     }
13928 
13929     ma_spatializer_set_direction(&pSound->engineNode.spatializer, x, y, z);
13930 }
13931 
ma_sound_get_direction(const ma_sound * pSound)13932 MA_API ma_vec3f ma_sound_get_direction(const ma_sound* pSound)
13933 {
13934     if (pSound == NULL) {
13935         return ma_vec3f_init_3f(0, 0, 0);
13936     }
13937 
13938     return ma_spatializer_get_direction(&pSound->engineNode.spatializer);
13939 }
13940 
ma_sound_set_velocity(ma_sound * pSound,float x,float y,float z)13941 MA_API void ma_sound_set_velocity(ma_sound* pSound, float x, float y, float z)
13942 {
13943     if (pSound == NULL) {
13944         return;
13945     }
13946 
13947     ma_spatializer_set_velocity(&pSound->engineNode.spatializer, x, y, z);
13948 }
13949 
ma_sound_get_velocity(const ma_sound * pSound)13950 MA_API ma_vec3f ma_sound_get_velocity(const ma_sound* pSound)
13951 {
13952     if (pSound == NULL) {
13953         return ma_vec3f_init_3f(0, 0, 0);
13954     }
13955 
13956     return ma_spatializer_get_velocity(&pSound->engineNode.spatializer);
13957 }
13958 
ma_sound_set_attenuation_model(ma_sound * pSound,ma_attenuation_model attenuationModel)13959 MA_API void ma_sound_set_attenuation_model(ma_sound* pSound, ma_attenuation_model attenuationModel)
13960 {
13961     if (pSound == NULL) {
13962         return;
13963     }
13964 
13965     ma_spatializer_set_attenuation_model(&pSound->engineNode.spatializer, attenuationModel);
13966 }
13967 
ma_sound_get_attenuation_model(const ma_sound * pSound)13968 MA_API ma_attenuation_model ma_sound_get_attenuation_model(const ma_sound* pSound)
13969 {
13970     if (pSound == NULL) {
13971         return ma_attenuation_model_none;
13972     }
13973 
13974     return ma_spatializer_get_attenuation_model(&pSound->engineNode.spatializer);
13975 }
13976 
ma_sound_set_positioning(ma_sound * pSound,ma_positioning positioning)13977 MA_API void ma_sound_set_positioning(ma_sound* pSound, ma_positioning positioning)
13978 {
13979     if (pSound == NULL) {
13980         return;
13981     }
13982 
13983     ma_spatializer_set_positioning(&pSound->engineNode.spatializer, positioning);
13984 }
13985 
ma_sound_get_positioning(const ma_sound * pSound)13986 MA_API ma_positioning ma_sound_get_positioning(const ma_sound* pSound)
13987 {
13988     if (pSound == NULL) {
13989         return ma_positioning_absolute;
13990     }
13991 
13992     return ma_spatializer_get_positioning(&pSound->engineNode.spatializer);
13993 }
13994 
ma_sound_set_rolloff(ma_sound * pSound,float rolloff)13995 MA_API void ma_sound_set_rolloff(ma_sound* pSound, float rolloff)
13996 {
13997     if (pSound == NULL) {
13998         return;
13999     }
14000 
14001     ma_spatializer_set_rolloff(&pSound->engineNode.spatializer, rolloff);
14002 }
14003 
ma_sound_get_rolloff(const ma_sound * pSound)14004 MA_API float ma_sound_get_rolloff(const ma_sound* pSound)
14005 {
14006     if (pSound == NULL) {
14007         return 0;
14008     }
14009 
14010     return ma_spatializer_get_rolloff(&pSound->engineNode.spatializer);
14011 }
14012 
ma_sound_set_min_gain(ma_sound * pSound,float minGain)14013 MA_API void ma_sound_set_min_gain(ma_sound* pSound, float minGain)
14014 {
14015     if (pSound == NULL) {
14016         return;
14017     }
14018 
14019     ma_spatializer_set_min_gain(&pSound->engineNode.spatializer, minGain);
14020 }
14021 
ma_sound_get_min_gain(const ma_sound * pSound)14022 MA_API float ma_sound_get_min_gain(const ma_sound* pSound)
14023 {
14024     if (pSound == NULL) {
14025         return 0;
14026     }
14027 
14028     return ma_spatializer_get_min_gain(&pSound->engineNode.spatializer);
14029 }
14030 
ma_sound_set_max_gain(ma_sound * pSound,float maxGain)14031 MA_API void ma_sound_set_max_gain(ma_sound* pSound, float maxGain)
14032 {
14033     if (pSound == NULL) {
14034         return;
14035     }
14036 
14037     ma_spatializer_set_max_gain(&pSound->engineNode.spatializer, maxGain);
14038 }
14039 
ma_sound_get_max_gain(const ma_sound * pSound)14040 MA_API float ma_sound_get_max_gain(const ma_sound* pSound)
14041 {
14042     if (pSound == NULL) {
14043         return 0;
14044     }
14045 
14046     return ma_spatializer_get_max_gain(&pSound->engineNode.spatializer);
14047 }
14048 
ma_sound_set_min_distance(ma_sound * pSound,float minDistance)14049 MA_API void ma_sound_set_min_distance(ma_sound* pSound, float minDistance)
14050 {
14051     if (pSound == NULL) {
14052         return;
14053     }
14054 
14055     ma_spatializer_set_min_distance(&pSound->engineNode.spatializer, minDistance);
14056 }
14057 
ma_sound_get_min_distance(const ma_sound * pSound)14058 MA_API float ma_sound_get_min_distance(const ma_sound* pSound)
14059 {
14060     if (pSound == NULL) {
14061         return 0;
14062     }
14063 
14064     return ma_spatializer_get_min_distance(&pSound->engineNode.spatializer);
14065 }
14066 
ma_sound_set_max_distance(ma_sound * pSound,float maxDistance)14067 MA_API void ma_sound_set_max_distance(ma_sound* pSound, float maxDistance)
14068 {
14069     if (pSound == NULL) {
14070         return;
14071     }
14072 
14073     ma_spatializer_set_max_distance(&pSound->engineNode.spatializer, maxDistance);
14074 }
14075 
ma_sound_get_max_distance(const ma_sound * pSound)14076 MA_API float ma_sound_get_max_distance(const ma_sound* pSound)
14077 {
14078     if (pSound == NULL) {
14079         return 0;
14080     }
14081 
14082     return ma_spatializer_get_max_distance(&pSound->engineNode.spatializer);
14083 }
14084 
ma_sound_set_cone(ma_sound * pSound,float innerAngleInRadians,float outerAngleInRadians,float outerGain)14085 MA_API void ma_sound_set_cone(ma_sound* pSound, float innerAngleInRadians, float outerAngleInRadians, float outerGain)
14086 {
14087     if (pSound == NULL) {
14088         return;
14089     }
14090 
14091     ma_spatializer_set_cone(&pSound->engineNode.spatializer, innerAngleInRadians, outerAngleInRadians, outerGain);
14092 }
14093 
ma_sound_get_cone(const ma_sound * pSound,float * pInnerAngleInRadians,float * pOuterAngleInRadians,float * pOuterGain)14094 MA_API void ma_sound_get_cone(const ma_sound* pSound, float* pInnerAngleInRadians, float* pOuterAngleInRadians, float* pOuterGain)
14095 {
14096     if (pInnerAngleInRadians != NULL) {
14097         *pInnerAngleInRadians = 0;
14098     }
14099 
14100     if (pOuterAngleInRadians != NULL) {
14101         *pOuterAngleInRadians = 0;
14102     }
14103 
14104     if (pOuterGain != NULL) {
14105         *pOuterGain = 0;
14106     }
14107 
14108     ma_spatializer_get_cone(&pSound->engineNode.spatializer, pInnerAngleInRadians, pOuterAngleInRadians, pOuterGain);
14109 }
14110 
ma_sound_set_doppler_factor(ma_sound * pSound,float dopplerFactor)14111 MA_API void ma_sound_set_doppler_factor(ma_sound* pSound, float dopplerFactor)
14112 {
14113     if (pSound == NULL) {
14114         return;
14115     }
14116 
14117     ma_spatializer_set_doppler_factor(&pSound->engineNode.spatializer, dopplerFactor);
14118 }
14119 
ma_sound_get_doppler_factor(const ma_sound * pSound)14120 MA_API float ma_sound_get_doppler_factor(const ma_sound* pSound)
14121 {
14122     if (pSound == NULL) {
14123         return 0;
14124     }
14125 
14126     return ma_spatializer_get_doppler_factor(&pSound->engineNode.spatializer);
14127 }
14128 
14129 
ma_sound_set_fade_in_pcm_frames(ma_sound * pSound,float volumeBeg,float volumeEnd,ma_uint64 fadeLengthInFrames)14130 MA_API void ma_sound_set_fade_in_pcm_frames(ma_sound* pSound, float volumeBeg, float volumeEnd, ma_uint64 fadeLengthInFrames)
14131 {
14132     if (pSound == NULL) {
14133         return;
14134     }
14135 
14136     ma_fader_set_fade(&pSound->engineNode.fader, volumeBeg, volumeEnd, fadeLengthInFrames);
14137 }
14138 
ma_sound_set_fade_in_milliseconds(ma_sound * pSound,float volumeBeg,float volumeEnd,ma_uint64 fadeLengthInMilliseconds)14139 MA_API void ma_sound_set_fade_in_milliseconds(ma_sound* pSound, float volumeBeg, float volumeEnd, ma_uint64 fadeLengthInMilliseconds)
14140 {
14141     if (pSound == NULL) {
14142         return;
14143     }
14144 
14145     ma_sound_set_fade_in_pcm_frames(pSound, volumeBeg, volumeEnd, (fadeLengthInMilliseconds * pSound->engineNode.fader.config.sampleRate) / 1000);
14146 }
14147 
ma_sound_get_current_fade_volume(ma_sound * pSound)14148 MA_API float ma_sound_get_current_fade_volume(ma_sound* pSound)
14149 {
14150     if (pSound == NULL) {
14151         return MA_INVALID_ARGS;
14152     }
14153 
14154     return ma_fader_get_current_volume(&pSound->engineNode.fader);
14155 }
14156 
ma_sound_set_start_time_in_pcm_frames(ma_sound * pSound,ma_uint64 absoluteGlobalTimeInFrames)14157 MA_API void ma_sound_set_start_time_in_pcm_frames(ma_sound* pSound, ma_uint64 absoluteGlobalTimeInFrames)
14158 {
14159     if (pSound == NULL) {
14160         return;
14161     }
14162 
14163     ma_node_set_state_time(pSound, ma_node_state_started, absoluteGlobalTimeInFrames);
14164 }
14165 
ma_sound_set_start_time_in_milliseconds(ma_sound * pSound,ma_uint64 absoluteGlobalTimeInMilliseconds)14166 MA_API void ma_sound_set_start_time_in_milliseconds(ma_sound* pSound, ma_uint64 absoluteGlobalTimeInMilliseconds)
14167 {
14168     if (pSound == NULL) {
14169         return;
14170     }
14171 
14172     ma_sound_set_start_time_in_pcm_frames(pSound, absoluteGlobalTimeInMilliseconds * ma_engine_get_sample_rate(ma_sound_get_engine(pSound)) / 1000);
14173 }
14174 
ma_sound_set_stop_time_in_pcm_frames(ma_sound * pSound,ma_uint64 absoluteGlobalTimeInFrames)14175 MA_API void ma_sound_set_stop_time_in_pcm_frames(ma_sound* pSound, ma_uint64 absoluteGlobalTimeInFrames)
14176 {
14177     if (pSound == NULL) {
14178         return;
14179     }
14180 
14181     ma_node_set_state_time(pSound, ma_node_state_stopped, absoluteGlobalTimeInFrames);
14182 }
14183 
ma_sound_set_stop_time_in_milliseconds(ma_sound * pSound,ma_uint64 absoluteGlobalTimeInMilliseconds)14184 MA_API void ma_sound_set_stop_time_in_milliseconds(ma_sound* pSound, ma_uint64 absoluteGlobalTimeInMilliseconds)
14185 {
14186     if (pSound == NULL) {
14187         return;
14188     }
14189 
14190     ma_sound_set_stop_time_in_pcm_frames(pSound, absoluteGlobalTimeInMilliseconds * ma_engine_get_sample_rate(ma_sound_get_engine(pSound)) / 1000);
14191 }
14192 
ma_sound_is_playing(const ma_sound * pSound)14193 MA_API ma_bool32 ma_sound_is_playing(const ma_sound* pSound)
14194 {
14195     if (pSound == NULL) {
14196         return MA_FALSE;
14197     }
14198 
14199     return ma_node_get_state_by_time(pSound, ma_engine_get_time(ma_sound_get_engine(pSound))) == ma_node_state_started;
14200 }
14201 
ma_sound_get_time_in_pcm_frames(const ma_sound * pSound)14202 MA_API ma_uint64 ma_sound_get_time_in_pcm_frames(const ma_sound* pSound)
14203 {
14204     if (pSound == NULL) {
14205         return 0;
14206     }
14207 
14208     return ma_node_get_time(pSound);
14209 }
14210 
ma_sound_set_looping(ma_sound * pSound,ma_bool8 isLooping)14211 MA_API void ma_sound_set_looping(ma_sound* pSound, ma_bool8 isLooping)
14212 {
14213     if (pSound == NULL) {
14214         return;
14215     }
14216 
14217     /* Looping is only a valid concept if the sound is backed by a data source. */
14218     if (pSound->pDataSource == NULL) {
14219         return;
14220     }
14221 
14222     c89atomic_exchange_8(&pSound->isLooping, isLooping);
14223 
14224     /*
14225     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
14226     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
14227     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
14228     generically.
14229     */
14230 #ifndef MA_NO_RESOURCE_MANAGER
14231     if (pSound->pDataSource == pSound->pResourceManagerDataSource) {
14232         ma_resource_manager_data_source_set_looping(pSound->pResourceManagerDataSource, isLooping);
14233     }
14234 #endif
14235 }
14236 
ma_sound_is_looping(const ma_sound * pSound)14237 MA_API ma_bool32 ma_sound_is_looping(const ma_sound* pSound)
14238 {
14239     if (pSound == NULL) {
14240         return MA_FALSE;
14241     }
14242 
14243     /* There is no notion of looping for sounds that are not backed by a data source. */
14244     if (pSound->pDataSource == NULL) {
14245         return MA_FALSE;
14246     }
14247 
14248     return c89atomic_load_8(&pSound->isLooping);
14249 }
14250 
ma_sound_at_end(const ma_sound * pSound)14251 MA_API ma_bool32 ma_sound_at_end(const ma_sound* pSound)
14252 {
14253     if (pSound == NULL) {
14254         return MA_FALSE;
14255     }
14256 
14257     /* There is no notion of an end of a sound if it's not backed by a data source. */
14258     if (pSound->pDataSource == NULL) {
14259         return MA_FALSE;
14260     }
14261 
14262     return c89atomic_load_8(&pSound->atEnd);
14263 }
14264 
ma_sound_seek_to_pcm_frame(ma_sound * pSound,ma_uint64 frameIndex)14265 MA_API ma_result ma_sound_seek_to_pcm_frame(ma_sound* pSound, ma_uint64 frameIndex)
14266 {
14267     if (pSound == NULL) {
14268         return MA_INVALID_ARGS;
14269     }
14270 
14271     /* Seeking is only valid for sounds that are backed by a data source. */
14272     if (pSound->pDataSource == NULL) {
14273         return MA_INVALID_OPERATION;
14274     }
14275 
14276     /*
14277     Resource manager data sources are thread safe which means we can just seek immediately. However, we cannot guarantee that other data sources are
14278     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.
14279     */
14280 #ifndef MA_NO_RESOURCE_MANAGER
14281     if (pSound->pDataSource == pSound->pResourceManagerDataSource) {
14282         ma_result result = ma_resource_manager_data_source_seek_to_pcm_frame(pSound->pResourceManagerDataSource, frameIndex);
14283         if (result != MA_SUCCESS) {
14284             return result;
14285         }
14286 
14287         /* Time dependant effects need to have their timers updated. */
14288         return ma_node_set_time(&pSound->engineNode, frameIndex);
14289     }
14290 #endif
14291 
14292     /* 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. */
14293     pSound->seekTarget = frameIndex;
14294 
14295     return MA_SUCCESS;
14296 }
14297 
ma_sound_get_data_format(ma_sound * pSound,ma_format * pFormat,ma_uint32 * pChannels,ma_uint32 * pSampleRate)14298 MA_API ma_result ma_sound_get_data_format(ma_sound* pSound, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate)
14299 {
14300     if (pSound == NULL) {
14301         return MA_INVALID_ARGS;
14302     }
14303 
14304     /* The data format is retrieved directly from the data source if the sound is backed by one. Otherwise we pull it from the node. */
14305     if (pSound->pDataSource == NULL) {
14306         if (pFormat != NULL) {
14307             *pFormat = ma_format_f32;
14308         }
14309 
14310         if (pChannels != NULL) {
14311             *pChannels = ma_node_get_input_channels(&pSound->engineNode, 0);
14312         }
14313 
14314         if (pSampleRate != NULL) {
14315             *pSampleRate = pSound->engineNode.resampler.config.sampleRateIn;
14316         }
14317 
14318         return MA_SUCCESS;
14319     } else {
14320         return ma_data_source_get_data_format(pSound->pDataSource, pFormat, pChannels, pSampleRate);
14321     }
14322 }
14323 
ma_sound_get_cursor_in_pcm_frames(ma_sound * pSound,ma_uint64 * pCursor)14324 MA_API ma_result ma_sound_get_cursor_in_pcm_frames(ma_sound* pSound, ma_uint64* pCursor)
14325 {
14326     if (pSound == NULL) {
14327         return MA_INVALID_ARGS;
14328     }
14329 
14330     /* The notion of a cursor is only valid for sounds that are backed by a data source. */
14331     if (pSound->pDataSource == NULL) {
14332         return MA_INVALID_OPERATION;
14333     }
14334 
14335     return ma_data_source_get_cursor_in_pcm_frames(pSound->pDataSource, pCursor);
14336 }
14337 
ma_sound_get_length_in_pcm_frames(ma_sound * pSound,ma_uint64 * pLength)14338 MA_API ma_result ma_sound_get_length_in_pcm_frames(ma_sound* pSound, ma_uint64* pLength)
14339 {
14340     if (pSound == NULL) {
14341         return MA_INVALID_ARGS;
14342     }
14343 
14344     /* The notion of a sound length is only valid for sounds that are backed by a data source. */
14345     if (pSound->pDataSource == NULL) {
14346         return MA_INVALID_OPERATION;
14347     }
14348 
14349     return ma_data_source_get_length_in_pcm_frames(pSound->pDataSource, pLength);
14350 }
14351 
14352 
ma_sound_group_init(ma_engine * pEngine,ma_uint32 flags,ma_sound_group * pParentGroup,ma_sound_group * pGroup)14353 MA_API ma_result ma_sound_group_init(ma_engine* pEngine, ma_uint32 flags, ma_sound_group* pParentGroup, ma_sound_group* pGroup)
14354 {
14355     ma_sound_group_config config = ma_sound_group_config_init();
14356     config.flags              = flags;
14357     config.pInitialAttachment = pParentGroup;
14358     return ma_sound_group_init_ex(pEngine, &config, pGroup);
14359 }
14360 
ma_sound_group_init_ex(ma_engine * pEngine,const ma_sound_group_config * pConfig,ma_sound_group * pGroup)14361 MA_API ma_result ma_sound_group_init_ex(ma_engine* pEngine, const ma_sound_group_config* pConfig, ma_sound_group* pGroup)
14362 {
14363     ma_sound_config soundConfig;
14364 
14365     if (pGroup == NULL) {
14366         return MA_INVALID_ARGS;
14367     }
14368 
14369     MA_ZERO_OBJECT(pGroup);
14370 
14371     if (pConfig == NULL) {
14372         return MA_INVALID_ARGS;
14373     }
14374 
14375     /* A sound group is just a sound without a data source. */
14376     soundConfig = *pConfig;
14377     soundConfig.pFilePath   = NULL;
14378     soundConfig.pFilePathW  = NULL;
14379     soundConfig.pDataSource = NULL;
14380 
14381     /*
14382     Groups need to have spatialization disabled by default because I think it'll be pretty rare
14383     that programs will want to spatialize groups (but not unheard of). Certainly it feels like
14384     disabling this by default feels like the right option. Spatialization can be enabled with a
14385     call to ma_sound_group_set_spatialization_enabled().
14386     */
14387     soundConfig.flags |= MA_SOUND_FLAG_NO_SPATIALIZATION;
14388 
14389     return ma_sound_init_ex(pEngine, &soundConfig, pGroup);
14390 }
14391 
ma_sound_group_uninit(ma_sound_group * pGroup)14392 MA_API void ma_sound_group_uninit(ma_sound_group* pGroup)
14393 {
14394     ma_sound_uninit(pGroup);
14395 }
14396 
ma_sound_group_get_engine(const ma_sound_group * pGroup)14397 MA_API ma_engine* ma_sound_group_get_engine(const ma_sound_group* pGroup)
14398 {
14399     return ma_sound_get_engine(pGroup);
14400 }
14401 
ma_sound_group_start(ma_sound_group * pGroup)14402 MA_API ma_result ma_sound_group_start(ma_sound_group* pGroup)
14403 {
14404     return ma_sound_start(pGroup);
14405 }
14406 
ma_sound_group_stop(ma_sound_group * pGroup)14407 MA_API ma_result ma_sound_group_stop(ma_sound_group* pGroup)
14408 {
14409     return ma_sound_stop(pGroup);
14410 }
14411 
ma_sound_group_set_volume(ma_sound_group * pGroup,float volume)14412 MA_API ma_result ma_sound_group_set_volume(ma_sound_group* pGroup, float volume)
14413 {
14414     return ma_sound_set_volume(pGroup, volume);
14415 }
14416 
ma_sound_group_set_gain_db(ma_sound_group * pGroup,float gainDB)14417 MA_API ma_result ma_sound_group_set_gain_db(ma_sound_group* pGroup, float gainDB)
14418 {
14419     return ma_sound_set_gain_db(pGroup, gainDB);
14420 }
14421 
ma_sound_group_set_pan(ma_sound_group * pGroup,float pan)14422 MA_API void ma_sound_group_set_pan(ma_sound_group* pGroup, float pan)
14423 {
14424     ma_sound_set_pan(pGroup, pan);
14425 }
14426 
ma_sound_group_set_pan_mode(ma_sound_group * pGroup,ma_pan_mode panMode)14427 MA_API void ma_sound_group_set_pan_mode(ma_sound_group* pGroup, ma_pan_mode panMode)
14428 {
14429     ma_sound_set_pan_mode(pGroup, panMode);
14430 }
14431 
ma_sound_group_set_pitch(ma_sound_group * pGroup,float pitch)14432 MA_API void ma_sound_group_set_pitch(ma_sound_group* pGroup, float pitch)
14433 {
14434     ma_sound_set_pitch(pGroup, pitch);
14435 }
14436 
ma_sound_group_set_spatialization_enabled(ma_sound_group * pGroup,ma_bool32 enabled)14437 MA_API void ma_sound_group_set_spatialization_enabled(ma_sound_group* pGroup, ma_bool32 enabled)
14438 {
14439     ma_sound_set_spatialization_enabled(pGroup, enabled);
14440 }
14441 
ma_sound_group_set_pinned_listener_index(ma_sound_group * pGroup,ma_uint8 listenerIndex)14442 MA_API void ma_sound_group_set_pinned_listener_index(ma_sound_group* pGroup, ma_uint8 listenerIndex)
14443 {
14444     ma_sound_set_pinned_listener_index(pGroup, listenerIndex);
14445 }
14446 
ma_sound_group_get_pinned_listener_index(const ma_sound_group * pGroup)14447 MA_API ma_uint8 ma_sound_group_get_pinned_listener_index(const ma_sound_group* pGroup)
14448 {
14449     return ma_sound_get_pinned_listener_index(pGroup);
14450 }
14451 
ma_sound_group_set_position(ma_sound_group * pGroup,float x,float y,float z)14452 MA_API void ma_sound_group_set_position(ma_sound_group* pGroup, float x, float y, float z)
14453 {
14454     ma_sound_set_position(pGroup, x, y, z);
14455 }
14456 
ma_sound_group_get_position(const ma_sound_group * pGroup)14457 MA_API ma_vec3f ma_sound_group_get_position(const ma_sound_group* pGroup)
14458 {
14459     return ma_sound_get_position(pGroup);
14460 }
14461 
ma_sound_group_set_direction(ma_sound_group * pGroup,float x,float y,float z)14462 MA_API void ma_sound_group_set_direction(ma_sound_group* pGroup, float x, float y, float z)
14463 {
14464     ma_sound_set_direction(pGroup, x, y, z);
14465 }
14466 
ma_sound_group_get_direction(const ma_sound_group * pGroup)14467 MA_API ma_vec3f ma_sound_group_get_direction(const ma_sound_group* pGroup)
14468 {
14469     return ma_sound_get_direction(pGroup);
14470 }
14471 
ma_sound_group_set_velocity(ma_sound_group * pGroup,float x,float y,float z)14472 MA_API void ma_sound_group_set_velocity(ma_sound_group* pGroup, float x, float y, float z)
14473 {
14474     ma_sound_set_velocity(pGroup, x, y, z);
14475 }
14476 
ma_sound_group_get_velocity(const ma_sound_group * pGroup)14477 MA_API ma_vec3f ma_sound_group_get_velocity(const ma_sound_group* pGroup)
14478 {
14479     return ma_sound_get_velocity(pGroup);
14480 }
14481 
ma_sound_group_set_attenuation_model(ma_sound_group * pGroup,ma_attenuation_model attenuationModel)14482 MA_API void ma_sound_group_set_attenuation_model(ma_sound_group* pGroup, ma_attenuation_model attenuationModel)
14483 {
14484     ma_sound_set_attenuation_model(pGroup, attenuationModel);
14485 }
14486 
ma_sound_group_get_attenuation_model(const ma_sound_group * pGroup)14487 MA_API ma_attenuation_model ma_sound_group_get_attenuation_model(const ma_sound_group* pGroup)
14488 {
14489     return ma_sound_get_attenuation_model(pGroup);
14490 }
14491 
ma_sound_group_set_positioning(ma_sound_group * pGroup,ma_positioning positioning)14492 MA_API void ma_sound_group_set_positioning(ma_sound_group* pGroup, ma_positioning positioning)
14493 {
14494     ma_sound_set_positioning(pGroup, positioning);
14495 }
14496 
ma_sound_group_get_positioning(const ma_sound_group * pGroup)14497 MA_API ma_positioning ma_sound_group_get_positioning(const ma_sound_group* pGroup)
14498 {
14499     return ma_sound_get_positioning(pGroup);
14500 }
14501 
ma_sound_group_set_rolloff(ma_sound_group * pGroup,float rolloff)14502 MA_API void ma_sound_group_set_rolloff(ma_sound_group* pGroup, float rolloff)
14503 {
14504     ma_sound_set_rolloff(pGroup, rolloff);
14505 }
14506 
ma_sound_group_get_rolloff(const ma_sound_group * pGroup)14507 MA_API float ma_sound_group_get_rolloff(const ma_sound_group* pGroup)
14508 {
14509     return ma_sound_get_rolloff(pGroup);
14510 }
14511 
ma_sound_group_set_min_gain(ma_sound_group * pGroup,float minGain)14512 MA_API void ma_sound_group_set_min_gain(ma_sound_group* pGroup, float minGain)
14513 {
14514     ma_sound_set_min_gain(pGroup, minGain);
14515 }
14516 
ma_sound_group_get_min_gain(const ma_sound_group * pGroup)14517 MA_API float ma_sound_group_get_min_gain(const ma_sound_group* pGroup)
14518 {
14519     return ma_sound_get_min_gain(pGroup);
14520 }
14521 
ma_sound_group_set_max_gain(ma_sound_group * pGroup,float maxGain)14522 MA_API void ma_sound_group_set_max_gain(ma_sound_group* pGroup, float maxGain)
14523 {
14524     ma_sound_set_max_gain(pGroup, maxGain);
14525 }
14526 
ma_sound_group_get_max_gain(const ma_sound_group * pGroup)14527 MA_API float ma_sound_group_get_max_gain(const ma_sound_group* pGroup)
14528 {
14529     return ma_sound_get_max_gain(pGroup);
14530 }
14531 
ma_sound_group_set_min_distance(ma_sound_group * pGroup,float minDistance)14532 MA_API void ma_sound_group_set_min_distance(ma_sound_group* pGroup, float minDistance)
14533 {
14534     ma_sound_set_min_distance(pGroup, minDistance);
14535 }
14536 
ma_sound_group_get_min_distance(const ma_sound_group * pGroup)14537 MA_API float ma_sound_group_get_min_distance(const ma_sound_group* pGroup)
14538 {
14539     return ma_sound_get_min_distance(pGroup);
14540 }
14541 
ma_sound_group_set_max_distance(ma_sound_group * pGroup,float maxDistance)14542 MA_API void ma_sound_group_set_max_distance(ma_sound_group* pGroup, float maxDistance)
14543 {
14544     ma_sound_set_max_distance(pGroup, maxDistance);
14545 }
14546 
ma_sound_group_get_max_distance(const ma_sound_group * pGroup)14547 MA_API float ma_sound_group_get_max_distance(const ma_sound_group* pGroup)
14548 {
14549     return ma_sound_get_max_distance(pGroup);
14550 }
14551 
ma_sound_group_set_cone(ma_sound_group * pGroup,float innerAngleInRadians,float outerAngleInRadians,float outerGain)14552 MA_API void ma_sound_group_set_cone(ma_sound_group* pGroup, float innerAngleInRadians, float outerAngleInRadians, float outerGain)
14553 {
14554     ma_sound_set_cone(pGroup, innerAngleInRadians, outerAngleInRadians, outerGain);
14555 }
14556 
ma_sound_group_get_cone(const ma_sound_group * pGroup,float * pInnerAngleInRadians,float * pOuterAngleInRadians,float * pOuterGain)14557 MA_API void ma_sound_group_get_cone(const ma_sound_group* pGroup, float* pInnerAngleInRadians, float* pOuterAngleInRadians, float* pOuterGain)
14558 {
14559     ma_sound_get_cone(pGroup, pInnerAngleInRadians, pOuterAngleInRadians, pOuterGain);
14560 }
14561 
ma_sound_group_set_doppler_factor(ma_sound_group * pGroup,float dopplerFactor)14562 MA_API void ma_sound_group_set_doppler_factor(ma_sound_group* pGroup, float dopplerFactor)
14563 {
14564     ma_sound_set_doppler_factor(pGroup, dopplerFactor);
14565 }
14566 
ma_sound_group_get_doppler_factor(const ma_sound_group * pGroup)14567 MA_API float ma_sound_group_get_doppler_factor(const ma_sound_group* pGroup)
14568 {
14569     return ma_sound_get_doppler_factor(pGroup);
14570 }
14571 
ma_sound_group_set_fade_in_pcm_frames(ma_sound_group * pGroup,float volumeBeg,float volumeEnd,ma_uint64 fadeLengthInFrames)14572 MA_API void ma_sound_group_set_fade_in_pcm_frames(ma_sound_group* pGroup, float volumeBeg, float volumeEnd, ma_uint64 fadeLengthInFrames)
14573 {
14574     ma_sound_set_fade_in_pcm_frames(pGroup, volumeBeg, volumeEnd, fadeLengthInFrames);
14575 }
14576 
ma_sound_group_set_fade_in_milliseconds(ma_sound_group * pGroup,float volumeBeg,float volumeEnd,ma_uint64 fadeLengthInMilliseconds)14577 MA_API void ma_sound_group_set_fade_in_milliseconds(ma_sound_group* pGroup, float volumeBeg, float volumeEnd, ma_uint64 fadeLengthInMilliseconds)
14578 {
14579     ma_sound_set_fade_in_milliseconds(pGroup, volumeBeg, volumeEnd, fadeLengthInMilliseconds);
14580 }
14581 
ma_sound_group_get_current_fade_volume(ma_sound_group * pGroup)14582 MA_API float ma_sound_group_get_current_fade_volume(ma_sound_group* pGroup)
14583 {
14584     return ma_sound_get_current_fade_volume(pGroup);
14585 }
14586 
ma_sound_group_set_start_time_in_pcm_frames(ma_sound_group * pGroup,ma_uint64 absoluteGlobalTimeInFrames)14587 MA_API void ma_sound_group_set_start_time_in_pcm_frames(ma_sound_group* pGroup, ma_uint64 absoluteGlobalTimeInFrames)
14588 {
14589     ma_sound_set_start_time_in_pcm_frames(pGroup, absoluteGlobalTimeInFrames);
14590 }
14591 
ma_sound_group_set_start_time_in_milliseconds(ma_sound_group * pGroup,ma_uint64 absoluteGlobalTimeInMilliseconds)14592 MA_API void ma_sound_group_set_start_time_in_milliseconds(ma_sound_group* pGroup, ma_uint64 absoluteGlobalTimeInMilliseconds)
14593 {
14594     ma_sound_set_start_time_in_milliseconds(pGroup, absoluteGlobalTimeInMilliseconds);
14595 }
14596 
ma_sound_group_set_stop_time_in_pcm_frames(ma_sound_group * pGroup,ma_uint64 absoluteGlobalTimeInFrames)14597 MA_API void ma_sound_group_set_stop_time_in_pcm_frames(ma_sound_group* pGroup, ma_uint64 absoluteGlobalTimeInFrames)
14598 {
14599     ma_sound_set_stop_time_in_pcm_frames(pGroup, absoluteGlobalTimeInFrames);
14600 }
14601 
ma_sound_group_set_stop_time_in_milliseconds(ma_sound_group * pGroup,ma_uint64 absoluteGlobalTimeInMilliseconds)14602 MA_API void ma_sound_group_set_stop_time_in_milliseconds(ma_sound_group* pGroup, ma_uint64 absoluteGlobalTimeInMilliseconds)
14603 {
14604     ma_sound_set_stop_time_in_milliseconds(pGroup, absoluteGlobalTimeInMilliseconds);
14605 }
14606 
ma_sound_group_is_playing(const ma_sound_group * pGroup)14607 MA_API ma_bool32 ma_sound_group_is_playing(const ma_sound_group* pGroup)
14608 {
14609     return ma_sound_is_playing(pGroup);
14610 }
14611 
ma_sound_group_get_time_in_pcm_frames(const ma_sound_group * pGroup)14612 MA_API ma_uint64 ma_sound_group_get_time_in_pcm_frames(const ma_sound_group* pGroup)
14613 {
14614     return ma_sound_get_time_in_pcm_frames(pGroup);
14615 }
14616 
14617 
14618 
14619 
14620 /*
14621 Biquad Node
14622 */
ma_biquad_node_config_init(ma_uint32 channels,float b0,float b1,float b2,float a0,float a1,float a2)14623 MA_API ma_biquad_node_config ma_biquad_node_config_init(ma_uint32 channels, float b0, float b1, float b2, float a0, float a1, float a2)
14624 {
14625     ma_biquad_node_config config;
14626 
14627     config.nodeConfig = ma_node_config_init();
14628     config.biquad = ma_biquad_config_init(ma_format_f32, channels, b0, b1, b2, a0, a1, a2);
14629 
14630     return config;
14631 }
14632 
ma_biquad_node_process_pcm_frames(ma_node * pNode,const float ** ppFramesIn,ma_uint32 * pFrameCountIn,float ** ppFramesOut,ma_uint32 * pFrameCountOut)14633 static void ma_biquad_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut)
14634 {
14635     ma_biquad_node* pLPFNode = (ma_biquad_node*)pNode;
14636 
14637     MA_ASSERT(pNode != NULL);
14638     (void)pFrameCountIn;
14639 
14640     ma_biquad_process_pcm_frames(&pLPFNode->biquad, ppFramesOut[0], ppFramesIn[0], *pFrameCountOut);
14641 }
14642 
14643 static ma_node_vtable g_ma_biquad_node_vtable =
14644 {
14645     ma_biquad_node_process_pcm_frames,
14646     NULL,   /* onGetRequiredInputFrameCount */
14647     1,      /* One input. */
14648     1,      /* One output. */
14649     0       /* Default flags. */
14650 };
14651 
ma_biquad_node_init(ma_node_graph * pNodeGraph,const ma_biquad_node_config * pConfig,const ma_allocation_callbacks * pAllocationCallbacks,ma_biquad_node * pNode)14652 MA_API ma_result ma_biquad_node_init(ma_node_graph* pNodeGraph, const ma_biquad_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_biquad_node* pNode)
14653 {
14654     ma_result result;
14655     ma_node_config baseNodeConfig;
14656 
14657     if (pNode == NULL) {
14658         return MA_INVALID_ARGS;
14659     }
14660 
14661     MA_ZERO_OBJECT(pNode);
14662 
14663     if (pConfig == NULL) {
14664         return MA_INVALID_ARGS;
14665     }
14666 
14667     if (pConfig->biquad.format != ma_format_f32) {
14668         return MA_INVALID_ARGS; /* The format must be f32. */
14669     }
14670 
14671     result = ma_biquad_init(&pConfig->biquad, &pNode->biquad);
14672     if (result != MA_SUCCESS) {
14673         return result;
14674     }
14675 
14676     baseNodeConfig = ma_node_config_init();
14677     baseNodeConfig.vtable          = &g_ma_biquad_node_vtable;
14678     baseNodeConfig.pInputChannels  = &pConfig->biquad.channels;
14679     baseNodeConfig.pOutputChannels = &pConfig->biquad.channels;
14680 
14681     result = ma_node_init(pNodeGraph, &baseNodeConfig, pAllocationCallbacks, pNode);
14682     if (result != MA_SUCCESS) {
14683         return result;
14684     }
14685 
14686     return result;
14687 }
14688 
ma_biquad_node_reinit(const ma_biquad_config * pConfig,ma_biquad_node * pNode)14689 MA_API ma_result ma_biquad_node_reinit(const ma_biquad_config* pConfig, ma_biquad_node* pNode)
14690 {
14691     ma_biquad_node* pLPFNode = (ma_biquad_node*)pNode;
14692 
14693     MA_ASSERT(pNode != NULL);
14694 
14695     return ma_biquad_reinit(pConfig, &pLPFNode->biquad);
14696 }
14697 
ma_biquad_node_uninit(ma_biquad_node * pNode,const ma_allocation_callbacks * pAllocationCallbacks)14698 MA_API void ma_biquad_node_uninit(ma_biquad_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks)
14699 {
14700     ma_node_uninit(pNode, pAllocationCallbacks);
14701 }
14702 
14703 
14704 
14705 /*
14706 Low Pass Filter Node
14707 */
ma_lpf_node_config_init(ma_uint32 channels,ma_uint32 sampleRate,double cutoffFrequency,ma_uint32 order)14708 MA_API ma_lpf_node_config ma_lpf_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double cutoffFrequency, ma_uint32 order)
14709 {
14710     ma_lpf_node_config config;
14711 
14712     config.nodeConfig = ma_node_config_init();
14713     config.lpf = ma_lpf_config_init(ma_format_f32, channels, sampleRate, cutoffFrequency, order);
14714 
14715     return config;
14716 }
14717 
ma_lpf_node_process_pcm_frames(ma_node * pNode,const float ** ppFramesIn,ma_uint32 * pFrameCountIn,float ** ppFramesOut,ma_uint32 * pFrameCountOut)14718 static void ma_lpf_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut)
14719 {
14720     ma_lpf_node* pLPFNode = (ma_lpf_node*)pNode;
14721 
14722     MA_ASSERT(pNode != NULL);
14723     (void)pFrameCountIn;
14724 
14725     ma_lpf_process_pcm_frames(&pLPFNode->lpf, ppFramesOut[0], ppFramesIn[0], *pFrameCountOut);
14726 }
14727 
14728 static ma_node_vtable g_ma_lpf_node_vtable =
14729 {
14730     ma_lpf_node_process_pcm_frames,
14731     NULL,   /* onGetRequiredInputFrameCount */
14732     1,      /* One input. */
14733     1,      /* One output. */
14734     0       /* Default flags. */
14735 };
14736 
ma_lpf_node_init(ma_node_graph * pNodeGraph,const ma_lpf_node_config * pConfig,const ma_allocation_callbacks * pAllocationCallbacks,ma_lpf_node * pNode)14737 MA_API ma_result ma_lpf_node_init(ma_node_graph* pNodeGraph, const ma_lpf_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_lpf_node* pNode)
14738 {
14739     ma_result result;
14740     ma_node_config baseNodeConfig;
14741 
14742     if (pNode == NULL) {
14743         return MA_INVALID_ARGS;
14744     }
14745 
14746     MA_ZERO_OBJECT(pNode);
14747 
14748     if (pConfig == NULL) {
14749         return MA_INVALID_ARGS;
14750     }
14751 
14752     if (pConfig->lpf.format != ma_format_f32) {
14753         return MA_INVALID_ARGS; /* The format must be f32. */
14754     }
14755 
14756     result = ma_lpf_init(&pConfig->lpf, &pNode->lpf);
14757     if (result != MA_SUCCESS) {
14758         return result;
14759     }
14760 
14761     baseNodeConfig = ma_node_config_init();
14762     baseNodeConfig.vtable          = &g_ma_lpf_node_vtable;
14763     baseNodeConfig.pInputChannels  = &pConfig->lpf.channels;
14764     baseNodeConfig.pOutputChannels = &pConfig->lpf.channels;
14765 
14766     result = ma_node_init(pNodeGraph, &baseNodeConfig, pAllocationCallbacks, pNode);
14767     if (result != MA_SUCCESS) {
14768         return result;
14769     }
14770 
14771     return result;
14772 }
14773 
ma_lpf_node_reinit(const ma_lpf_config * pConfig,ma_lpf_node * pNode)14774 MA_API ma_result ma_lpf_node_reinit(const ma_lpf_config* pConfig, ma_lpf_node* pNode)
14775 {
14776     ma_lpf_node* pLPFNode = (ma_lpf_node*)pNode;
14777 
14778     MA_ASSERT(pNode != NULL);
14779 
14780     return ma_lpf_reinit(pConfig, &pLPFNode->lpf);
14781 }
14782 
ma_lpf_node_uninit(ma_lpf_node * pNode,const ma_allocation_callbacks * pAllocationCallbacks)14783 MA_API void ma_lpf_node_uninit(ma_lpf_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks)
14784 {
14785     ma_node_uninit(pNode, pAllocationCallbacks);
14786 }
14787 
14788 
14789 
14790 /*
14791 High Pass Filter Node
14792 */
ma_hpf_node_config_init(ma_uint32 channels,ma_uint32 sampleRate,double cutoffFrequency,ma_uint32 order)14793 MA_API ma_hpf_node_config ma_hpf_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double cutoffFrequency, ma_uint32 order)
14794 {
14795     ma_hpf_node_config config;
14796 
14797     config.nodeConfig = ma_node_config_init();
14798     config.hpf = ma_hpf_config_init(ma_format_f32, channels, sampleRate, cutoffFrequency, order);
14799 
14800     return config;
14801 }
14802 
ma_hpf_node_process_pcm_frames(ma_node * pNode,const float ** ppFramesIn,ma_uint32 * pFrameCountIn,float ** ppFramesOut,ma_uint32 * pFrameCountOut)14803 static void ma_hpf_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut)
14804 {
14805     ma_hpf_node* pHPFNode = (ma_hpf_node*)pNode;
14806 
14807     MA_ASSERT(pNode != NULL);
14808     (void)pFrameCountIn;
14809 
14810     ma_hpf_process_pcm_frames(&pHPFNode->hpf, ppFramesOut[0], ppFramesIn[0], *pFrameCountOut);
14811 }
14812 
14813 static ma_node_vtable g_ma_hpf_node_vtable =
14814 {
14815     ma_hpf_node_process_pcm_frames,
14816     NULL,   /* onGetRequiredInputFrameCount */
14817     1,      /* One input. */
14818     1,      /* One output. */
14819     0       /* Default flags. */
14820 };
14821 
ma_hpf_node_init(ma_node_graph * pNodeGraph,const ma_hpf_node_config * pConfig,const ma_allocation_callbacks * pAllocationCallbacks,ma_hpf_node * pNode)14822 MA_API ma_result ma_hpf_node_init(ma_node_graph* pNodeGraph, const ma_hpf_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_hpf_node* pNode)
14823 {
14824     ma_result result;
14825     ma_node_config baseNodeConfig;
14826 
14827     if (pNode == NULL) {
14828         return MA_INVALID_ARGS;
14829     }
14830 
14831     MA_ZERO_OBJECT(pNode);
14832 
14833     if (pConfig == NULL) {
14834         return MA_INVALID_ARGS;
14835     }
14836 
14837     if (pConfig->hpf.format != ma_format_f32) {
14838         return MA_INVALID_ARGS; /* The format must be f32. */
14839     }
14840 
14841     result = ma_hpf_init(&pConfig->hpf, &pNode->hpf);
14842     if (result != MA_SUCCESS) {
14843         return result;
14844     }
14845 
14846     baseNodeConfig = ma_node_config_init();
14847     baseNodeConfig.vtable          = &g_ma_hpf_node_vtable;
14848     baseNodeConfig.pInputChannels  = &pConfig->hpf.channels;
14849     baseNodeConfig.pOutputChannels = &pConfig->hpf.channels;
14850 
14851     result = ma_node_init(pNodeGraph, &baseNodeConfig, pAllocationCallbacks, pNode);
14852     if (result != MA_SUCCESS) {
14853         return result;
14854     }
14855 
14856     return result;
14857 }
14858 
ma_hpf_node_reinit(const ma_hpf_config * pConfig,ma_hpf_node * pNode)14859 MA_API ma_result ma_hpf_node_reinit(const ma_hpf_config* pConfig, ma_hpf_node* pNode)
14860 {
14861     ma_hpf_node* pHPFNode = (ma_hpf_node*)pNode;
14862 
14863     MA_ASSERT(pNode != NULL);
14864 
14865     return ma_hpf_reinit(pConfig, &pHPFNode->hpf);
14866 }
14867 
ma_hpf_node_uninit(ma_hpf_node * pNode,const ma_allocation_callbacks * pAllocationCallbacks)14868 MA_API void ma_hpf_node_uninit(ma_hpf_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks)
14869 {
14870     ma_node_uninit(pNode, pAllocationCallbacks);
14871 }
14872 
14873 
14874 
14875 
14876 /*
14877 Band Pass Filter Node
14878 */
ma_bpf_node_config_init(ma_uint32 channels,ma_uint32 sampleRate,double cutoffFrequency,ma_uint32 order)14879 MA_API ma_bpf_node_config ma_bpf_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double cutoffFrequency, ma_uint32 order)
14880 {
14881     ma_bpf_node_config config;
14882 
14883     config.nodeConfig = ma_node_config_init();
14884     config.bpf = ma_bpf_config_init(ma_format_f32, channels, sampleRate, cutoffFrequency, order);
14885 
14886     return config;
14887 }
14888 
ma_bpf_node_process_pcm_frames(ma_node * pNode,const float ** ppFramesIn,ma_uint32 * pFrameCountIn,float ** ppFramesOut,ma_uint32 * pFrameCountOut)14889 static void ma_bpf_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut)
14890 {
14891     ma_bpf_node* pBPFNode = (ma_bpf_node*)pNode;
14892 
14893     MA_ASSERT(pNode != NULL);
14894     (void)pFrameCountIn;
14895 
14896     ma_bpf_process_pcm_frames(&pBPFNode->bpf, ppFramesOut[0], ppFramesIn[0], *pFrameCountOut);
14897 }
14898 
14899 static ma_node_vtable g_ma_bpf_node_vtable =
14900 {
14901     ma_bpf_node_process_pcm_frames,
14902     NULL,   /* onGetRequiredInputFrameCount */
14903     1,      /* One input. */
14904     1,      /* One output. */
14905     0       /* Default flags. */
14906 };
14907 
ma_bpf_node_init(ma_node_graph * pNodeGraph,const ma_bpf_node_config * pConfig,const ma_allocation_callbacks * pAllocationCallbacks,ma_bpf_node * pNode)14908 MA_API ma_result ma_bpf_node_init(ma_node_graph* pNodeGraph, const ma_bpf_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_bpf_node* pNode)
14909 {
14910     ma_result result;
14911     ma_node_config baseNodeConfig;
14912 
14913     if (pNode == NULL) {
14914         return MA_INVALID_ARGS;
14915     }
14916 
14917     MA_ZERO_OBJECT(pNode);
14918 
14919     if (pConfig == NULL) {
14920         return MA_INVALID_ARGS;
14921     }
14922 
14923     if (pConfig->bpf.format != ma_format_f32) {
14924         return MA_INVALID_ARGS; /* The format must be f32. */
14925     }
14926 
14927     result = ma_bpf_init(&pConfig->bpf, &pNode->bpf);
14928     if (result != MA_SUCCESS) {
14929         return result;
14930     }
14931 
14932     baseNodeConfig = ma_node_config_init();
14933     baseNodeConfig.vtable          = &g_ma_bpf_node_vtable;
14934     baseNodeConfig.pInputChannels  = &pConfig->bpf.channels;
14935     baseNodeConfig.pOutputChannels = &pConfig->bpf.channels;
14936 
14937     result = ma_node_init(pNodeGraph, &baseNodeConfig, pAllocationCallbacks, pNode);
14938     if (result != MA_SUCCESS) {
14939         return result;
14940     }
14941 
14942     return result;
14943 }
14944 
ma_bpf_node_reinit(const ma_bpf_config * pConfig,ma_bpf_node * pNode)14945 MA_API ma_result ma_bpf_node_reinit(const ma_bpf_config* pConfig, ma_bpf_node* pNode)
14946 {
14947     ma_bpf_node* pBPFNode = (ma_bpf_node*)pNode;
14948 
14949     MA_ASSERT(pNode != NULL);
14950 
14951     return ma_bpf_reinit(pConfig, &pBPFNode->bpf);
14952 }
14953 
ma_bpf_node_uninit(ma_bpf_node * pNode,const ma_allocation_callbacks * pAllocationCallbacks)14954 MA_API void ma_bpf_node_uninit(ma_bpf_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks)
14955 {
14956     ma_node_uninit(pNode, pAllocationCallbacks);
14957 }
14958 
14959 
14960 
14961 /*
14962 Notching Filter Node
14963 */
ma_notch_node_config_init(ma_uint32 channels,ma_uint32 sampleRate,double q,double frequency)14964 MA_API ma_notch_node_config ma_notch_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double q, double frequency)
14965 {
14966     ma_notch_node_config config;
14967 
14968     config.nodeConfig = ma_node_config_init();
14969     config.notch = ma_notch2_config_init(ma_format_f32, channels, sampleRate, q, frequency);
14970 
14971     return config;
14972 }
14973 
ma_notch_node_process_pcm_frames(ma_node * pNode,const float ** ppFramesIn,ma_uint32 * pFrameCountIn,float ** ppFramesOut,ma_uint32 * pFrameCountOut)14974 static void ma_notch_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut)
14975 {
14976     ma_notch_node* pBPFNode = (ma_notch_node*)pNode;
14977 
14978     MA_ASSERT(pNode != NULL);
14979     (void)pFrameCountIn;
14980 
14981     ma_notch2_process_pcm_frames(&pBPFNode->notch, ppFramesOut[0], ppFramesIn[0], *pFrameCountOut);
14982 }
14983 
14984 static ma_node_vtable g_ma_notch_node_vtable =
14985 {
14986     ma_notch_node_process_pcm_frames,
14987     NULL,   /* onGetRequiredInputFrameCount */
14988     1,      /* One input. */
14989     1,      /* One output. */
14990     0       /* Default flags. */
14991 };
14992 
ma_notch_node_init(ma_node_graph * pNodeGraph,const ma_notch_node_config * pConfig,const ma_allocation_callbacks * pAllocationCallbacks,ma_notch_node * pNode)14993 MA_API ma_result ma_notch_node_init(ma_node_graph* pNodeGraph, const ma_notch_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_notch_node* pNode)
14994 {
14995     ma_result result;
14996     ma_node_config baseNodeConfig;
14997 
14998     if (pNode == NULL) {
14999         return MA_INVALID_ARGS;
15000     }
15001 
15002     MA_ZERO_OBJECT(pNode);
15003 
15004     if (pConfig == NULL) {
15005         return MA_INVALID_ARGS;
15006     }
15007 
15008     if (pConfig->notch.format != ma_format_f32) {
15009         return MA_INVALID_ARGS; /* The format must be f32. */
15010     }
15011 
15012     result = ma_notch2_init(&pConfig->notch, &pNode->notch);
15013     if (result != MA_SUCCESS) {
15014         return result;
15015     }
15016 
15017     baseNodeConfig = ma_node_config_init();
15018     baseNodeConfig.vtable          = &g_ma_notch_node_vtable;
15019     baseNodeConfig.pInputChannels  = &pConfig->notch.channels;
15020     baseNodeConfig.pOutputChannels = &pConfig->notch.channels;
15021 
15022     result = ma_node_init(pNodeGraph, &baseNodeConfig, pAllocationCallbacks, pNode);
15023     if (result != MA_SUCCESS) {
15024         return result;
15025     }
15026 
15027     return result;
15028 }
15029 
ma_notch_node_reinit(const ma_notch_config * pConfig,ma_notch_node * pNode)15030 MA_API ma_result ma_notch_node_reinit(const ma_notch_config* pConfig, ma_notch_node* pNode)
15031 {
15032     ma_notch_node* pBPFNode = (ma_notch_node*)pNode;
15033 
15034     MA_ASSERT(pNode != NULL);
15035 
15036     return ma_notch2_reinit(pConfig, &pBPFNode->notch);
15037 }
15038 
ma_notch_node_uninit(ma_notch_node * pNode,const ma_allocation_callbacks * pAllocationCallbacks)15039 MA_API void ma_notch_node_uninit(ma_notch_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks)
15040 {
15041     ma_node_uninit(pNode, pAllocationCallbacks);
15042 }
15043 
15044 
15045 
15046 /*
15047 Peaking Filter Node
15048 */
ma_peak_node_config_init(ma_uint32 channels,ma_uint32 sampleRate,double gainDB,double q,double frequency)15049 MA_API ma_peak_node_config ma_peak_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double gainDB, double q, double frequency)
15050 {
15051     ma_peak_node_config config;
15052 
15053     config.nodeConfig = ma_node_config_init();
15054     config.peak = ma_peak2_config_init(ma_format_f32, channels, sampleRate, gainDB, q, frequency);
15055 
15056     return config;
15057 }
15058 
ma_peak_node_process_pcm_frames(ma_node * pNode,const float ** ppFramesIn,ma_uint32 * pFrameCountIn,float ** ppFramesOut,ma_uint32 * pFrameCountOut)15059 static void ma_peak_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut)
15060 {
15061     ma_peak_node* pBPFNode = (ma_peak_node*)pNode;
15062 
15063     MA_ASSERT(pNode != NULL);
15064     (void)pFrameCountIn;
15065 
15066     ma_peak2_process_pcm_frames(&pBPFNode->peak, ppFramesOut[0], ppFramesIn[0], *pFrameCountOut);
15067 }
15068 
15069 static ma_node_vtable g_ma_peak_node_vtable =
15070 {
15071     ma_peak_node_process_pcm_frames,
15072     NULL,   /* onGetRequiredInputFrameCount */
15073     1,      /* One input. */
15074     1,      /* One output. */
15075     0       /* Default flags. */
15076 };
15077 
ma_peak_node_init(ma_node_graph * pNodeGraph,const ma_peak_node_config * pConfig,const ma_allocation_callbacks * pAllocationCallbacks,ma_peak_node * pNode)15078 MA_API ma_result ma_peak_node_init(ma_node_graph* pNodeGraph, const ma_peak_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_peak_node* pNode)
15079 {
15080     ma_result result;
15081     ma_node_config baseNodeConfig;
15082 
15083     if (pNode == NULL) {
15084         return MA_INVALID_ARGS;
15085     }
15086 
15087     MA_ZERO_OBJECT(pNode);
15088 
15089     if (pConfig == NULL) {
15090         return MA_INVALID_ARGS;
15091     }
15092 
15093     if (pConfig->peak.format != ma_format_f32) {
15094         return MA_INVALID_ARGS; /* The format must be f32. */
15095     }
15096 
15097     result = ma_peak2_init(&pConfig->peak, &pNode->peak);
15098     if (result != MA_SUCCESS) {
15099         ma_node_uninit(pNode, pAllocationCallbacks);
15100         return result;
15101     }
15102 
15103     baseNodeConfig = ma_node_config_init();
15104     baseNodeConfig.vtable          = &g_ma_peak_node_vtable;
15105     baseNodeConfig.pInputChannels  = &pConfig->peak.channels;
15106     baseNodeConfig.pOutputChannels = &pConfig->peak.channels;
15107 
15108     result = ma_node_init(pNodeGraph, &baseNodeConfig, pAllocationCallbacks, pNode);
15109     if (result != MA_SUCCESS) {
15110         return result;
15111     }
15112 
15113     return result;
15114 }
15115 
ma_peak_node_reinit(const ma_peak_config * pConfig,ma_peak_node * pNode)15116 MA_API ma_result ma_peak_node_reinit(const ma_peak_config* pConfig, ma_peak_node* pNode)
15117 {
15118     ma_peak_node* pBPFNode = (ma_peak_node*)pNode;
15119 
15120     MA_ASSERT(pNode != NULL);
15121 
15122     return ma_peak2_reinit(pConfig, &pBPFNode->peak);
15123 }
15124 
ma_peak_node_uninit(ma_peak_node * pNode,const ma_allocation_callbacks * pAllocationCallbacks)15125 MA_API void ma_peak_node_uninit(ma_peak_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks)
15126 {
15127     ma_node_uninit(pNode, pAllocationCallbacks);
15128 }
15129 
15130 
15131 
15132 /*
15133 Low Shelf Filter Node
15134 */
ma_loshelf_node_config_init(ma_uint32 channels,ma_uint32 sampleRate,double gainDB,double q,double frequency)15135 MA_API ma_loshelf_node_config ma_loshelf_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double gainDB, double q, double frequency)
15136 {
15137     ma_loshelf_node_config config;
15138 
15139     config.nodeConfig = ma_node_config_init();
15140     config.loshelf = ma_loshelf2_config_init(ma_format_f32, channels, sampleRate, gainDB, q, frequency);
15141 
15142     return config;
15143 }
15144 
ma_loshelf_node_process_pcm_frames(ma_node * pNode,const float ** ppFramesIn,ma_uint32 * pFrameCountIn,float ** ppFramesOut,ma_uint32 * pFrameCountOut)15145 static void ma_loshelf_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut)
15146 {
15147     ma_loshelf_node* pBPFNode = (ma_loshelf_node*)pNode;
15148 
15149     MA_ASSERT(pNode != NULL);
15150     (void)pFrameCountIn;
15151 
15152     ma_loshelf2_process_pcm_frames(&pBPFNode->loshelf, ppFramesOut[0], ppFramesIn[0], *pFrameCountOut);
15153 }
15154 
15155 static ma_node_vtable g_ma_loshelf_node_vtable =
15156 {
15157     ma_loshelf_node_process_pcm_frames,
15158     NULL,   /* onGetRequiredInputFrameCount */
15159     1,      /* One input. */
15160     1,      /* One output. */
15161     0       /* Default flags. */
15162 };
15163 
ma_loshelf_node_init(ma_node_graph * pNodeGraph,const ma_loshelf_node_config * pConfig,const ma_allocation_callbacks * pAllocationCallbacks,ma_loshelf_node * pNode)15164 MA_API ma_result ma_loshelf_node_init(ma_node_graph* pNodeGraph, const ma_loshelf_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_loshelf_node* pNode)
15165 {
15166     ma_result result;
15167     ma_node_config baseNodeConfig;
15168 
15169     if (pNode == NULL) {
15170         return MA_INVALID_ARGS;
15171     }
15172 
15173     MA_ZERO_OBJECT(pNode);
15174 
15175     if (pConfig == NULL) {
15176         return MA_INVALID_ARGS;
15177     }
15178 
15179     if (pConfig->loshelf.format != ma_format_f32) {
15180         return MA_INVALID_ARGS; /* The format must be f32. */
15181     }
15182 
15183     result = ma_loshelf2_init(&pConfig->loshelf, &pNode->loshelf);
15184     if (result != MA_SUCCESS) {
15185         return result;
15186     }
15187 
15188     baseNodeConfig = ma_node_config_init();
15189     baseNodeConfig.vtable          = &g_ma_loshelf_node_vtable;
15190     baseNodeConfig.pInputChannels  = &pConfig->loshelf.channels;
15191     baseNodeConfig.pOutputChannels = &pConfig->loshelf.channels;
15192 
15193     result = ma_node_init(pNodeGraph, &baseNodeConfig, pAllocationCallbacks, pNode);
15194     if (result != MA_SUCCESS) {
15195         return result;
15196     }
15197 
15198     return result;
15199 }
15200 
ma_loshelf_node_reinit(const ma_loshelf_config * pConfig,ma_loshelf_node * pNode)15201 MA_API ma_result ma_loshelf_node_reinit(const ma_loshelf_config* pConfig, ma_loshelf_node* pNode)
15202 {
15203     ma_loshelf_node* pBPFNode = (ma_loshelf_node*)pNode;
15204 
15205     MA_ASSERT(pNode != NULL);
15206 
15207     return ma_loshelf2_reinit(pConfig, &pBPFNode->loshelf);
15208 }
15209 
ma_loshelf_node_uninit(ma_loshelf_node * pNode,const ma_allocation_callbacks * pAllocationCallbacks)15210 MA_API void ma_loshelf_node_uninit(ma_loshelf_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks)
15211 {
15212     ma_node_uninit(pNode, pAllocationCallbacks);
15213 }
15214 
15215 
15216 
15217 /*
15218 High Shelf Filter Node
15219 */
ma_hishelf_node_config_init(ma_uint32 channels,ma_uint32 sampleRate,double gainDB,double q,double frequency)15220 MA_API ma_hishelf_node_config ma_hishelf_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double gainDB, double q, double frequency)
15221 {
15222     ma_hishelf_node_config config;
15223 
15224     config.nodeConfig = ma_node_config_init();
15225     config.hishelf = ma_hishelf2_config_init(ma_format_f32, channels, sampleRate, gainDB, q, frequency);
15226 
15227     return config;
15228 }
15229 
ma_hishelf_node_process_pcm_frames(ma_node * pNode,const float ** ppFramesIn,ma_uint32 * pFrameCountIn,float ** ppFramesOut,ma_uint32 * pFrameCountOut)15230 static void ma_hishelf_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut)
15231 {
15232     ma_hishelf_node* pBPFNode = (ma_hishelf_node*)pNode;
15233 
15234     MA_ASSERT(pNode != NULL);
15235     (void)pFrameCountIn;
15236 
15237     ma_hishelf2_process_pcm_frames(&pBPFNode->hishelf, ppFramesOut[0], ppFramesIn[0], *pFrameCountOut);
15238 }
15239 
15240 static ma_node_vtable g_ma_hishelf_node_vtable =
15241 {
15242     ma_hishelf_node_process_pcm_frames,
15243     NULL,   /* onGetRequiredInputFrameCount */
15244     1,      /* One input. */
15245     1,      /* One output. */
15246     0       /* Default flags. */
15247 };
15248 
ma_hishelf_node_init(ma_node_graph * pNodeGraph,const ma_hishelf_node_config * pConfig,const ma_allocation_callbacks * pAllocationCallbacks,ma_hishelf_node * pNode)15249 MA_API ma_result ma_hishelf_node_init(ma_node_graph* pNodeGraph, const ma_hishelf_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_hishelf_node* pNode)
15250 {
15251     ma_result result;
15252     ma_node_config baseNodeConfig;
15253 
15254     if (pNode == NULL) {
15255         return MA_INVALID_ARGS;
15256     }
15257 
15258     MA_ZERO_OBJECT(pNode);
15259 
15260     if (pConfig == NULL) {
15261         return MA_INVALID_ARGS;
15262     }
15263 
15264     if (pConfig->hishelf.format != ma_format_f32) {
15265         return MA_INVALID_ARGS; /* The format must be f32. */
15266     }
15267 
15268     result = ma_hishelf2_init(&pConfig->hishelf, &pNode->hishelf);
15269     if (result != MA_SUCCESS) {
15270         return result;
15271     }
15272 
15273     baseNodeConfig = ma_node_config_init();
15274     baseNodeConfig.vtable          = &g_ma_hishelf_node_vtable;
15275     baseNodeConfig.pInputChannels  = &pConfig->hishelf.channels;
15276     baseNodeConfig.pOutputChannels = &pConfig->hishelf.channels;
15277 
15278     result = ma_node_init(pNodeGraph, &baseNodeConfig, pAllocationCallbacks, pNode);
15279     if (result != MA_SUCCESS) {
15280         return result;
15281     }
15282 
15283     return result;
15284 }
15285 
ma_hishelf_node_reinit(const ma_hishelf_config * pConfig,ma_hishelf_node * pNode)15286 MA_API ma_result ma_hishelf_node_reinit(const ma_hishelf_config* pConfig, ma_hishelf_node* pNode)
15287 {
15288     ma_hishelf_node* pBPFNode = (ma_hishelf_node*)pNode;
15289 
15290     MA_ASSERT(pNode != NULL);
15291 
15292     return ma_hishelf2_reinit(pConfig, &pBPFNode->hishelf);
15293 }
15294 
ma_hishelf_node_uninit(ma_hishelf_node * pNode,const ma_allocation_callbacks * pAllocationCallbacks)15295 MA_API void ma_hishelf_node_uninit(ma_hishelf_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks)
15296 {
15297     ma_node_uninit(pNode, pAllocationCallbacks);
15298 }
15299 
15300 
15301 
15302 /*
15303 Delay
15304 */
ma_delay_config_init(ma_uint32 channels,ma_uint32 sampleRate,ma_uint32 delayInFrames,float decay)15305 MA_API ma_delay_config ma_delay_config_init(ma_uint32 channels, ma_uint32 sampleRate, ma_uint32 delayInFrames, float decay)
15306 {
15307     ma_delay_config config;
15308 
15309     MA_ZERO_OBJECT(&config);
15310     config.channels      = channels;
15311     config.sampleRate    = sampleRate;
15312     config.delayInFrames = delayInFrames;
15313     config.delayStart    = (decay == 0) ? MA_TRUE : MA_FALSE;   /* Delay the start if it looks like we're not configuring an echo. */
15314     config.wet           = 1;
15315     config.dry           = 1;
15316     config.decay         = decay;
15317 
15318     return config;
15319 }
15320 
15321 
ma_delay_init(const ma_delay_config * pConfig,const ma_allocation_callbacks * pAllocationCallbacks,ma_delay * pDelay)15322 MA_API ma_result ma_delay_init(const ma_delay_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_delay* pDelay)
15323 {
15324     if (pDelay == NULL) {
15325         return MA_INVALID_ARGS;
15326     }
15327 
15328     MA_ZERO_OBJECT(pDelay);
15329 
15330     if (pConfig == NULL) {
15331         return MA_INVALID_ARGS;
15332     }
15333 
15334     if (pConfig->decay < 0 || pConfig->decay > 1) {
15335         return MA_INVALID_ARGS;
15336     }
15337 
15338     pDelay->config             = *pConfig;
15339     pDelay->bufferSizeInFrames = pConfig->delayInFrames;
15340     pDelay->cursor             = 0;
15341 
15342     pDelay->pBuffer = (float*)ma_malloc((size_t)(pDelay->bufferSizeInFrames * ma_get_bytes_per_frame(ma_format_f32, pConfig->channels)), pAllocationCallbacks);
15343     if (pDelay->pBuffer == NULL) {
15344         return MA_OUT_OF_MEMORY;
15345     }
15346 
15347     ma_silence_pcm_frames(pDelay->pBuffer, pDelay->bufferSizeInFrames, ma_format_f32, pConfig->channels);
15348 
15349     return MA_SUCCESS;
15350 }
15351 
ma_delay_uninit(ma_delay * pDelay,const ma_allocation_callbacks * pAllocationCallbacks)15352 MA_API void ma_delay_uninit(ma_delay* pDelay, const ma_allocation_callbacks* pAllocationCallbacks)
15353 {
15354     if (pDelay == NULL) {
15355         return;
15356     }
15357 
15358     ma_free(pDelay->pBuffer, pAllocationCallbacks);
15359 }
15360 
ma_delay_process_pcm_frames(ma_delay * pDelay,void * pFramesOut,const void * pFramesIn,ma_uint32 frameCount)15361 MA_API ma_result ma_delay_process_pcm_frames(ma_delay* pDelay, void* pFramesOut, const void* pFramesIn, ma_uint32 frameCount)
15362 {
15363     ma_uint32 iFrame;
15364     ma_uint32 iChannel;
15365     float* pFramesOutF32 = (float*)pFramesOut;
15366     const float* pFramesInF32 = (const float*)pFramesIn;
15367 
15368     if (pDelay == NULL || pFramesOut == NULL || pFramesIn == NULL) {
15369         return MA_INVALID_ARGS;
15370     }
15371 
15372     for (iFrame = 0; iFrame < frameCount; iFrame += 1) {
15373         for (iChannel = 0; iChannel < pDelay->config.channels; iChannel += 1) {
15374             ma_uint32 iBuffer = (pDelay->cursor * pDelay->config.channels) + iChannel;
15375 
15376             if (pDelay->config.delayStart) {
15377                 /* Delayed start. */
15378 
15379                 /* Read */
15380                 pFramesOutF32[iChannel] = pDelay->pBuffer[iBuffer] * pDelay->config.wet;
15381 
15382                 /* Feedback */
15383                 pDelay->pBuffer[iBuffer] = (pDelay->pBuffer[iBuffer] * pDelay->config.decay) + (pFramesInF32[iChannel] * pDelay->config.dry);
15384             } else {
15385                 /* Immediate start */
15386 
15387                 /* Feedback */
15388                 pDelay->pBuffer[iBuffer] = (pDelay->pBuffer[iBuffer] * pDelay->config.decay) + (pFramesInF32[iChannel] * pDelay->config.dry);
15389 
15390                 /* Read */
15391                 pFramesOutF32[iChannel] = pDelay->pBuffer[iBuffer] * pDelay->config.wet;
15392             }
15393         }
15394 
15395         pDelay->cursor = (pDelay->cursor + 1) % pDelay->bufferSizeInFrames;
15396 
15397         pFramesOutF32 += pDelay->config.channels;
15398         pFramesInF32  += pDelay->config.channels;
15399     }
15400 
15401     return MA_SUCCESS;
15402 }
15403 
ma_delay_set_wet(ma_delay * pDelay,float value)15404 MA_API void ma_delay_set_wet(ma_delay* pDelay, float value)
15405 {
15406     if (pDelay == NULL) {
15407         return;
15408     }
15409 
15410     pDelay->config.wet = value;
15411 }
15412 
ma_delay_get_wet(const ma_delay * pDelay)15413 MA_API float ma_delay_get_wet(const ma_delay* pDelay)
15414 {
15415     if (pDelay == NULL) {
15416         return 0;
15417     }
15418 
15419     return pDelay->config.wet;
15420 }
15421 
ma_delay_set_dry(ma_delay * pDelay,float value)15422 MA_API void ma_delay_set_dry(ma_delay* pDelay, float value)
15423 {
15424     if (pDelay == NULL) {
15425         return;
15426     }
15427 
15428     pDelay->config.dry = value;
15429 }
15430 
ma_delay_get_dry(const ma_delay * pDelay)15431 MA_API float ma_delay_get_dry(const ma_delay* pDelay)
15432 {
15433     if (pDelay == NULL) {
15434         return 0;
15435     }
15436 
15437     return pDelay->config.dry;
15438 }
15439 
ma_delay_set_decay(ma_delay * pDelay,float value)15440 MA_API void ma_delay_set_decay(ma_delay* pDelay, float value)
15441 {
15442     if (pDelay == NULL) {
15443         return;
15444     }
15445 
15446     pDelay->config.decay = value;
15447 }
15448 
ma_delay_get_decay(const ma_delay * pDelay)15449 MA_API float ma_delay_get_decay(const ma_delay* pDelay)
15450 {
15451     if (pDelay == NULL) {
15452         return 0;
15453     }
15454 
15455     return pDelay->config.decay;
15456 }
15457 
15458 
15459 
15460 
ma_delay_node_config_init(ma_uint32 channels,ma_uint32 sampleRate,ma_uint32 delayInFrames,float decay)15461 MA_API ma_delay_node_config ma_delay_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, ma_uint32 delayInFrames, float decay)
15462 {
15463     ma_delay_node_config config;
15464 
15465     config.nodeConfig = ma_node_config_init();
15466     config.delay = ma_delay_config_init(channels, sampleRate, delayInFrames, decay);
15467 
15468     return config;
15469 }
15470 
15471 
ma_delay_node_process_pcm_frames(ma_node * pNode,const float ** ppFramesIn,ma_uint32 * pFrameCountIn,float ** ppFramesOut,ma_uint32 * pFrameCountOut)15472 static void ma_delay_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut)
15473 {
15474     ma_delay_node* pDelayNode = (ma_delay_node*)pNode;
15475 
15476     (void)pFrameCountIn;
15477 
15478     ma_delay_process_pcm_frames(&pDelayNode->delay, ppFramesOut[0], ppFramesIn[0], *pFrameCountOut);
15479 }
15480 
15481 static ma_node_vtable g_ma_delay_node_vtable =
15482 {
15483     ma_delay_node_process_pcm_frames,
15484     NULL,
15485     1,  /* 1 input channels. */
15486     1,  /* 1 output channel. */
15487     MA_NODE_FLAG_CONTINUOUS_PROCESSING  /* Reverb requires continuous processing to ensure the tail get's processed. */
15488 };
15489 
ma_delay_node_init(ma_node_graph * pNodeGraph,const ma_delay_node_config * pConfig,const ma_allocation_callbacks * pAllocationCallbacks,ma_delay_node * pDelayNode)15490 MA_API ma_result ma_delay_node_init(ma_node_graph* pNodeGraph, const ma_delay_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_delay_node* pDelayNode)
15491 {
15492     ma_result result;
15493     ma_node_config baseConfig;
15494 
15495     if (pDelayNode == NULL) {
15496         return MA_INVALID_ARGS;
15497     }
15498 
15499     MA_ZERO_OBJECT(pDelayNode);
15500 
15501     result = ma_delay_init(&pConfig->delay, pAllocationCallbacks, &pDelayNode->delay);
15502     if (result != MA_SUCCESS) {
15503         return result;
15504     }
15505 
15506     baseConfig = pConfig->nodeConfig;
15507     baseConfig.vtable          = &g_ma_delay_node_vtable;
15508     baseConfig.pInputChannels  = &pConfig->delay.channels;
15509     baseConfig.pOutputChannels = &pConfig->delay.channels;
15510 
15511     result = ma_node_init(pNodeGraph, &baseConfig, pAllocationCallbacks, &pDelayNode->baseNode);
15512     if (result != MA_SUCCESS) {
15513         ma_delay_uninit(&pDelayNode->delay, pAllocationCallbacks);
15514         return result;
15515     }
15516 
15517     return result;
15518 }
15519 
ma_delay_node_uninit(ma_delay_node * pDelayNode,const ma_allocation_callbacks * pAllocationCallbacks)15520 MA_API void ma_delay_node_uninit(ma_delay_node* pDelayNode, const ma_allocation_callbacks* pAllocationCallbacks)
15521 {
15522     if (pDelayNode == NULL) {
15523         return;
15524     }
15525 
15526     /* The base node is always uninitialized first. */
15527     ma_node_uninit(pDelayNode, pAllocationCallbacks);
15528     ma_delay_uninit(&pDelayNode->delay, pAllocationCallbacks);
15529 }
15530 
ma_delay_node_set_wet(ma_delay_node * pDelayNode,float value)15531 MA_API void ma_delay_node_set_wet(ma_delay_node* pDelayNode, float value)
15532 {
15533     if (pDelayNode == NULL) {
15534         return;
15535     }
15536 
15537     ma_delay_set_wet(&pDelayNode->delay, value);
15538 }
15539 
ma_delay_node_get_wet(const ma_delay_node * pDelayNode)15540 MA_API float ma_delay_node_get_wet(const ma_delay_node* pDelayNode)
15541 {
15542     if (pDelayNode == NULL) {
15543         return 0;
15544     }
15545 
15546     return ma_delay_get_wet(&pDelayNode->delay);
15547 }
15548 
ma_delay_node_set_dry(ma_delay_node * pDelayNode,float value)15549 MA_API void ma_delay_node_set_dry(ma_delay_node* pDelayNode, float value)
15550 {
15551     if (pDelayNode == NULL) {
15552         return;
15553     }
15554 
15555     ma_delay_set_dry(&pDelayNode->delay, value);
15556 }
15557 
ma_delay_node_get_dry(const ma_delay_node * pDelayNode)15558 MA_API float ma_delay_node_get_dry(const ma_delay_node* pDelayNode)
15559 {
15560     if (pDelayNode == NULL) {
15561         return 0;
15562     }
15563 
15564     return ma_delay_get_dry(&pDelayNode->delay);
15565 }
15566 
ma_delay_node_set_decay(ma_delay_node * pDelayNode,float value)15567 MA_API void ma_delay_node_set_decay(ma_delay_node* pDelayNode, float value)
15568 {
15569     if (pDelayNode == NULL) {
15570         return;
15571     }
15572 
15573     ma_delay_set_decay(&pDelayNode->delay, value);
15574 }
15575 
ma_delay_node_get_decay(const ma_delay_node * pDelayNode)15576 MA_API float ma_delay_node_get_decay(const ma_delay_node* pDelayNode)
15577 {
15578     if (pDelayNode == NULL) {
15579         return 0;
15580     }
15581 
15582     return ma_delay_get_decay(&pDelayNode->delay);
15583 }
15584 
15585 #endif
15586