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(¬ifications);
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(¬ifications);
8329 }
8330
8331 if (pDataBuffer == NULL) {
8332 ma_pipeline_notifications_signal_all_notifications(¬ifications);
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(¬ifications);
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(¬ifications);
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(¬ifications);
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(¬ifications);
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(¬ifications);
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(¬ifications);
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(¬ifications);
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, ¬ification);
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 = ¬ification;
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(¬ification);
8535 return result;
8536 }
8537
8538 ma_resource_manager_inline_notification_wait_and_uninit(¬ification);
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(¬ifications);
9014 }
9015
9016 if (pDataStream == NULL) {
9017 ma_pipeline_notifications_signal_all_notifications(¬ifications);
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(¬ifications);
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(¬ifications);
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(¬ifications);
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(¬ifications);
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(¬ifications);
9077 ma_pipeline_notifications_release_all_fences(¬ifications);
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, ¬ifications, pSound->pResourceManagerDataSource);
13613 } else {
13614 result = ma_resource_manager_data_source_init_w(pEngine->pResourceManager, pConfig->pFilePathW, flags, ¬ifications, 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