Compare commits
374 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f40cf03f80 | |||
| 7f2cd5b8d5 | |||
| 3afbdef285 | |||
| 9a091f73aa | |||
| 4de39a8a37 | |||
| 7c8574210d | |||
| 6648ed005a | |||
| 87bae56937 | |||
| 293f5de18f | |||
| 233b9b69c4 | |||
| db514e813f | |||
| 8130543730 | |||
| b306c6a270 | |||
| 9e1f02b12a | |||
| fa84240364 | |||
| 70eb06d3bd | |||
| 959283f244 | |||
| ea59076ba9 | |||
| 1d7d8dfba0 | |||
| f48d903526 | |||
| 3bdd39fd2c | |||
| e75a053908 | |||
| 346d86ffa1 | |||
| a2f92095dc | |||
| badf36a378 | |||
| c9d288c3dc | |||
| 825d2c4466 | |||
| 6fd62e6bbc | |||
| b1893aa8f1 | |||
| df4baf8d40 | |||
| 21237008df | |||
| 587bd83cbb | |||
| b7e5451ef4 | |||
| ba84e61a18 | |||
| a7ab58259e | |||
| 18055f34bb | |||
| f6bae251bd | |||
| 3567d5cfef | |||
| 4e3b778c62 | |||
| 6315130ec6 | |||
| 6e1cd41622 | |||
| 78cdb9c1cb | |||
| 1ea69211ad | |||
| e9e8f90137 | |||
| d93552283f | |||
| 61a85dca42 | |||
| c87f207f4e | |||
| 02ae7e41f0 | |||
| 457a7279fa | |||
| fe1da60e1e | |||
| 0b70a990ca | |||
| 556160909e | |||
| c48975f4a9 | |||
| 79b4ddc27d | |||
| 0b9f03a376 | |||
| 140b9c7f9f | |||
| ac8c908283 | |||
| 8b87c10681 | |||
| 629d509072 | |||
| 5c86dd9153 | |||
| e54336996d | |||
| 1c7967fc88 | |||
| 2bc0e14abf | |||
| b348ab0155 | |||
| 346d65091a | |||
| aa75d5f8e8 | |||
| 0ac5c89157 | |||
| 47020e4092 | |||
| 02873ca300 | |||
| 11177ed19f | |||
| 682fad7d55 | |||
| 1497f5e467 | |||
| 853f27ed56 | |||
| 05d367eed5 | |||
| 97493bdfcd | |||
| 3edfb70a26 | |||
| 72e4721b2c | |||
| b255e91e08 | |||
| b261f4aeec | |||
| bd59c52309 | |||
| f15ddefce8 | |||
| 29c17fcb22 | |||
| 5a9c322c83 | |||
| cd14d18d0c | |||
| 534b43e800 | |||
| caa3d2a339 | |||
| 4c2d1bb67c | |||
| 5ba0fc51b6 | |||
| e58a24124a | |||
| 0b4646d31a | |||
| 9032fdbced | |||
| 2ee920577e | |||
| a944e19331 | |||
| 8a9ea7ce07 | |||
| 80747f440a | |||
| d40a385e07 | |||
| 7df2a50d7f | |||
| ab80abf061 | |||
| b9292a301f | |||
| 484a56499d | |||
| b12959f1d4 | |||
| 94077d5a95 | |||
| b3ab0567c3 | |||
| 7158cf58f9 | |||
| fe616c1a5a | |||
| cd16c5bcd3 | |||
| 76dfabbb45 | |||
| 46d8abf3de | |||
| 350784a946 | |||
| a65a7d139f | |||
| 2a79d124c1 | |||
| 8261dc8972 | |||
| c1daa31759 | |||
| b0e845e796 | |||
| d28ce1a841 | |||
| 1e2be9307e | |||
| 17b8dbf948 | |||
| 4663423838 | |||
| d6a1350c1f | |||
| c92e662de7 | |||
| e272f56750 | |||
| 1a83b0baa4 | |||
| 9511596a25 | |||
| 5c0724ad59 | |||
| c3b0a7fbbc | |||
| e4363a90be | |||
| 6453c9ff22 | |||
| 8c52072f43 | |||
| b6184fa2a0 | |||
| 166fd6dfc7 | |||
| 54373128ee | |||
| c74c90f686 | |||
| 01302b9715 | |||
| b5f1ff125e | |||
| 7a1135d448 | |||
| 9f9fc2333e | |||
| a497466f75 | |||
| 37b95f0f42 | |||
| 9f10bc7540 | |||
| 1fbad32949 | |||
| e1f5ed4f79 | |||
| ed5cda309c | |||
| 3435aafb34 | |||
| 3fd7c9f199 | |||
| deafb7e96f | |||
| 2e054f8011 | |||
| c13504629e | |||
| f9caab2fd5 | |||
| 57fbc6dd36 | |||
| ee3e532a54 | |||
| 178797502e | |||
| 0576191d7d | |||
| 6bc3fec34e | |||
| 2542be5db8 | |||
| 67d1aca341 | |||
| 5975db4c76 | |||
| 08d6d1fac0 | |||
| 4d971fe480 | |||
| 575790bb29 | |||
| e49ce7df95 | |||
| d672b9610f | |||
| 3889066fac | |||
| ff66923b9a | |||
| e3151f2df1 | |||
| abb81fe95c | |||
| 8ad250ccf6 | |||
| b40803cf97 | |||
| 466a1354ce | |||
| e08c1303ef | |||
| 698a4319f0 | |||
| eee86a0ae1 | |||
| d3a4b9cf20 | |||
| 48ac10d1e1 | |||
| 4b4349af52 | |||
| a4d462e39e | |||
| ef662aaddf | |||
| 22a5c65c94 | |||
| cff683a1b1 | |||
| 62d64d14bd | |||
| cf9371748a | |||
| 640d70c307 | |||
| 46788d59a8 | |||
| 01d6297bec | |||
| de5f370d09 | |||
| 47aa3e34e0 | |||
| 445cdcb82b | |||
| 34092dbfc8 | |||
| 14b986448f | |||
| a6ac898663 | |||
| 017f8944d3 | |||
| e15fd218be | |||
| 4c7021e53d | |||
| 856494d253 | |||
| a0aac6b5ec | |||
| 60c7c776b4 | |||
| 125e9226fb | |||
| 4deb3d4c6a | |||
| 3ffdbdc710 | |||
| 9b9e71ab6c | |||
| f39bbe2f4d | |||
| 79bb4d7a37 | |||
| f970144a3d | |||
| afc7e17fe6 | |||
| 047200eace | |||
| 6d5efde254 | |||
| 9da8df1b9f | |||
| ed5964c9f6 | |||
| 7e81d3ac80 | |||
| 450dcb1af3 | |||
| 38f7d29f6f | |||
| 1fe39f949a | |||
| 3fb7027682 | |||
| 14a455143f | |||
| 977bd616ff | |||
| a3ae2e71ff | |||
| 68a526a759 | |||
| 8383893c9c | |||
| 7a25af64d6 | |||
| fc905ec97f | |||
| 3081e314b7 | |||
| fcddfe6204 | |||
| 547ef1c9b7 | |||
| 928ed8bd85 | |||
| ae2cd4bea4 | |||
| b53daca554 | |||
| 1a7a9a7ed2 | |||
| 82ae0138f3 | |||
| 8d5bf8210c | |||
| ad615af1a8 | |||
| 3a34c049fa | |||
| 970c3801d9 | |||
| da76932f6b | |||
| 7dbb9f5e1a | |||
| bff9689b80 | |||
| bea73835dd | |||
| 6e1b0dbce4 | |||
| 08152a6a6d | |||
| b6747d5efc | |||
| cbabd2d13a | |||
| 546e23c0fb | |||
| 1b6d634299 | |||
| 7f911f3d12 | |||
| 75f46c6105 | |||
| 51e005369f | |||
| d628284548 | |||
| 059a25d9c5 | |||
| 6a8a756b88 | |||
| 8b6611299e | |||
| ee506b17ea | |||
| 12a8d4e491 | |||
| d1abdd100b | |||
| 192a84a106 | |||
| 0d0953aa85 | |||
| 6ab4567c57 | |||
| 4598d72a33 | |||
| 4f5106ec77 | |||
| a611cf5f26 | |||
| d726e85313 | |||
| efa270af91 | |||
| 98f6e923cc | |||
| 427bdc1d2a | |||
| 61586de203 | |||
| d46e19fb47 | |||
| dc423daa41 | |||
| 5d827878f2 | |||
| d87230b4bd | |||
| e1328d9d8a | |||
| 8036ac3781 | |||
| fc45d8ca06 | |||
| 35215b266c | |||
| 1c15cf6502 | |||
| 196289592a | |||
| 1b35118e31 | |||
| 88436b25ef | |||
| 3db49afa5b | |||
| aa98e1c493 | |||
| afb121e2ce | |||
| 855628f15f | |||
| 9091cbd016 | |||
| e82703482b | |||
| 6700c7ecc7 | |||
| 3ba0595c6a | |||
| 4bc18bba5a | |||
| 26ce355703 | |||
| f7ad7772d1 | |||
| 0eb86ea1da | |||
| 2618c21415 | |||
| 030b9554c2 | |||
| c6d8b591f6 | |||
| 3bdf611768 | |||
| 63e1900db8 | |||
| 29da9b789c | |||
| b454e7f14b | |||
| feea26496c | |||
| d36a2ef651 | |||
| e3af234720 | |||
| 5cb0c05675 | |||
| 9aa6e035bb | |||
| 7a8ebd7f4d | |||
| e32cc9ff83 | |||
| babd7fb00f | |||
| f4e5cf99dc | |||
| f20ab8a9ee | |||
| aa57d052da | |||
| c0afa7e621 | |||
| bde517a166 | |||
| 3ed9d05c38 | |||
| a4aa0dc404 | |||
| fd3c1b0af0 | |||
| 10f9ef05a2 | |||
| bdab2fc3e0 | |||
| 9c73849f3b | |||
| fde3a27d93 | |||
| 766a155fb3 | |||
| c29c001840 | |||
| ee3b7df66a | |||
| 1325ac397b | |||
| ed204e4b72 | |||
| 6099e6f41c | |||
| 4f426f6db0 | |||
| 7e38fa0e7e | |||
| d0709098cc | |||
| 3c5b15b48b | |||
| 2b8bb34ca2 | |||
| 2b0c525e53 | |||
| 6cba159210 | |||
| 1583329187 | |||
| 5a3655fea4 | |||
| 4a5b74bef0 | |||
| c77d40ba00 | |||
| 8fcf6889aa | |||
| f9ce46330c | |||
| 3b50a854ec | |||
| da0572e6b8 | |||
| eb0ce6f1a5 | |||
| b19cc09fd0 | |||
| 863565a8ed | |||
| ef0f2e5b16 | |||
| 287881b815 | |||
| 2730775e79 | |||
| 1d17b09e41 | |||
| 39a7cc444b | |||
| fe5f17ecf3 | |||
| 0bb56819a8 | |||
| 105ffd8b05 | |||
| 9bf256dcf3 | |||
| 2f5c661bb7 | |||
| ecf2a8b917 | |||
| d282fba0fe | |||
| a185b99f12 | |||
| aef76e251c | |||
| b792ccd483 | |||
| a3cabad692 | |||
| 1a9acbad95 | |||
| 381a035fdd | |||
| e1a0f523d0 | |||
| 03e36da814 | |||
| a47f065a4a | |||
| bdf9a5554b | |||
| 537c4ca36c | |||
| 2922859ea9 | |||
| 6e6823d9e4 | |||
| 568e0ae9e9 | |||
| 70bf42392d | |||
| 9d461f6d5d | |||
| 6539b67163 | |||
| 78c6fcb370 | |||
| 1f8c86d9ca | |||
| 90342e5f67 | |||
| 46eaf1fa5e | |||
| 509bd6d4f2 | |||
| a81f09d93c | |||
| f2ea656297 | |||
| c24141c5ae |
@@ -1,4 +1,4 @@
|
||||
Code of Conduct
|
||||
===============
|
||||
We don't believe we need a document telling fully grown adults how to conduct themselves within an open source
|
||||
community. All we ask is that you just don't be unpleasant and keep everything on topic.
|
||||
I don't believe we need a document telling fully grown adults how to conduct themselves within an open source
|
||||
community. All I ask is that you just don't be unpleasant and keep everything on topic.
|
||||
|
||||
@@ -9,10 +9,4 @@ assignees: ''
|
||||
|
||||
**DELETE ALL OF THIS TEXT BEFORE SUBMITTING**
|
||||
|
||||
If you think you've found a bug, it will be helpful to run with `#define MA_DEBUG_OUTPUT` above your miniaudio implementation like the code below and to include the output with this bug report:
|
||||
|
||||
#define MA_DEBUG_OUTPUT
|
||||
#define MINIAUDIO_IMPLEMENTATION
|
||||
#include "miniaudio.h"
|
||||
|
||||
If you are having issues with playback, please run the simple_payback_sine example and report whether or not it's consistent with what's happening in your program.
|
||||
If you think you've found a bug, it will be helpful to compile with `#define MA_DEBUG_OUTPUT`. If you are having issues with playback, please run the simple_payback_sine example and report whether or not it's consistent with what's happening in your program.
|
||||
|
||||
@@ -9,6 +9,6 @@ assignees: ''
|
||||
|
||||
**DELETE ALL OF THIS TEXT BEFORE SUBMITTING**
|
||||
|
||||
If you have a question about how to use the library, please read the documentation at the top of miniaudio.h and take a look at the examples. If that still doesn't answer your question, consider posting in the miniaudio subreddit at [r/miniaudio](https://www.reddit.com/r/miniaudio) or in the Discussions section here on GitHub instead. Otherwise, feel free to post your issue and we'll get to it as soon as possible.
|
||||
If you have a question about how to use the library, please read the documentation at the top of miniaudio.h and take a look at the examples. If that still doesn't answer your question, consider posting in the Discussions section here on GitHub instead. Otherwise, feel free to post your issue and we'll get to it as soon as possible.
|
||||
|
||||
If you have an issue with playback, please run the simple_playback_sine example first and check whether or not that is working. Likewise for capture, please run the simple_capture example. If these examples work, it probably (but not always) means you're doing something wrong and a question in the subreddit or the Discussions section is more appropriate.
|
||||
If you have an issue with playback, please run the simple_playback_sine example first and check whether or not that is working. Likewise for capture, please run the simple_capture example. If these examples work, it probably (but not always) means you're doing something wrong and a question in the Discussions section is more appropriate.
|
||||
|
||||
@@ -1,14 +1,49 @@
|
||||
\#issues/
|
||||
_private/
|
||||
examples/build/vc6/
|
||||
examples/build/vc15/
|
||||
examples/build/bin/
|
||||
tests/_build/bin/
|
||||
tests/_build/res/output/
|
||||
tests/_build/tcc/
|
||||
tests/_build/vc6/
|
||||
tests/_build/vc15/
|
||||
tools/_build/
|
||||
\#docs/
|
||||
/_private/
|
||||
/build/
|
||||
/debugging/
|
||||
/evaluations/
|
||||
/examples/build/bin/
|
||||
/examples/build/codelite/
|
||||
/examples/build/vc6/
|
||||
/examples/build/vc15/
|
||||
/examples/build/vc17/
|
||||
/examples/simple_playback_sine.cpp
|
||||
/external/ogg/
|
||||
/external/vorbis/
|
||||
/external/opus/
|
||||
/external/opusfile/
|
||||
/extras/osaudio/tests/build/bin/
|
||||
/extras/osaudio/tests/build/vc17/
|
||||
/extras/osaudio/tests/build/watcom-dos/
|
||||
/extras/backends/pipewire/a.out
|
||||
/extras/decoders/litewav/
|
||||
/research/_build/
|
||||
/tests/_build/bin/
|
||||
/tests/_build/res/output/
|
||||
/tests/_build/cmake-emcc/
|
||||
/tests/_build/tcc/
|
||||
/tests/_build/vc6/
|
||||
/tests/_build/vc15/
|
||||
/tests/_build/vc17/
|
||||
/tests/_build/watcom/
|
||||
/tests/_build/capture.wav
|
||||
/tests/_build/a.out
|
||||
/tests/_build/a.exe
|
||||
/tests/debugging/archive/
|
||||
/tests/*.c
|
||||
/tests/*.cpp
|
||||
/website/docs/
|
||||
*.vcxproj.user
|
||||
.vs/
|
||||
.idea/
|
||||
.vscode/
|
||||
|
||||
# Below are individual files that I may start version controlling later or delete outright.
|
||||
/examples/build/COSMO.txt
|
||||
/research/ma_fft.c
|
||||
/research/ma_hrtf.c
|
||||
/research/ma_atomic.c
|
||||
/research/miniaudio_engine.c
|
||||
/tests/stress/
|
||||
/tools/hrtfgen/
|
||||
@@ -1,3 +1,100 @@
|
||||
v0.11.23 - 2025-09-11
|
||||
=====================
|
||||
* Fixed an error in `ma_channel_map_to_string()` where the output string is not null terminated correctly.
|
||||
* Fixed an error with logging due to mishandling of va_list.
|
||||
* Fixed some errors when compiling with `MA_NO_RUNTIME_LINKING`.
|
||||
* Fixed an error with `ma_sound` initialization where the initial loop points are not set correctly.
|
||||
* Fixed an alignment error with the ring buffer.
|
||||
* Fixed a memory leak in the resource manager when opening a file fails.
|
||||
* Fixed an assertion failure in the resource manager when opening a file fails.
|
||||
* Fixed a compilation warning relating to `MA_FALLTHROUGH`
|
||||
* Fixed an undefined behavior error in the s16 to s32 conversion routine.
|
||||
* Fixed an undefined behavior error relating to MurmurHash3.
|
||||
* Fixed an undefined behavior error with the LCG random number generator.
|
||||
* Fixed a compilation error with `MA_NO_SSE2`.
|
||||
* Fixed some unused function warnings.
|
||||
* Fixed a rare, but technically possible division by zero error.
|
||||
* Some const correctness fixes for `ma_sound`.
|
||||
* Improved compatibility with old versions of GCC.
|
||||
* Miscellaneous documentation fixes.
|
||||
* WAV, FLAC and MP3 decoders have been brought up to date with dr_libs. Of particular note, this should fix some long outstanding bugs with MP3 due to metadata not being handled correctly.
|
||||
* POSIX: Added a fallback for when creation of a real-time thread fails. This fallback can be disabled with `MA_NO_PTHREAD_REALTIME_PRIORITY_FALLBACK` if you need an explicit failure.
|
||||
* POSIX: pthread.h is no longer included when `MA_NO_THREADING` is defined.
|
||||
* WASAPI: Improved handling of COM initialization and shutdown to make it a bit more robust.
|
||||
* WASAPI: Fix an error due to a missing struct member.
|
||||
* PulseAudio: Fixed a crash when requesting a channel count greater than 32.
|
||||
* AAudio: Fixed a crash when uninitializing the device while in the middle of rerouting.
|
||||
|
||||
|
||||
v0.11.22 - 2025-02-24
|
||||
=====================
|
||||
* Starting from version 0.12, miniaudio will be switching to a split .c/h pair, away from a single header. In preparation for this, a file named "miniaudio.c" has been added to repository. Currently this is just a simple wrapper around miniaudio.h and `MINIAUDIO_IMPLEMENTATION`. Nothing has changed in miniaudio.h, however when version 0.12 is released you will need to use miniaudio.c for the implementation. It's recommended you start the transition away from `MINIAUDIO_IMPLEMENTATION` and towards miniaudio.c. If you want to keep building your project as a single translation unit, you can do `#include "miniaudio.c"` which will continue to be supported with version 0.12 and beyond.
|
||||
* In the extras folder, the `miniaudio_libvorbis.h` and `miniaudio_libopus.h` files have been deprecated. They have been replaced with versions in the `extras/decoders` folder. They are now split into a separate .c and .h files. The old files still exist for compatibility, but you need to transition over to the new versions. The transition should be trivial. Compile the .c files like a normal source file, and include the .h file like a normal header.
|
||||
* Add `MA_SOUND_FLAG_LOOPING` and `MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_LOOPING` flags. These can be used to initialize sounds and resource managed data sources to loop by default. This is the recommended way to enable looping for streams. The `isLooping` config option in `ma_sound_config` and `ma_resource_manager_data_source_config` has been deprecated. If you are using those, you should switch to the new flag or else you'll get compiler errors when upgrading to a future version.
|
||||
* `ma_rb_commit_read()`, `ma_rb_commit_write()`, `ma_pcm_rb_commit_read()` and `ma_pcm_rb_commit_write()` no longer return `MA_AT_END`. The reason for this change is that there's no real notion of an "end" in a ring buffer which makes this result code confusing. In addition, it's possible for these functions to return something other than `MA_SUCCESS` even when the operation completed successfully which adds to the confusion. The correct way to check if there is any more room in the ring buffer is to look at the frame count returned by `*rb_acquire_read/write()`.
|
||||
* The `ma_pcm_rb` data source implementation has been modified to pad output data with silence if there is not enough data in the ring buffer to fill the request. What this means is for an `ma_pcm_rb`, `ma_data_source_read_pcm_frames()` should no longer return a frame count of less than what you requested, and will therefore never return `MA_AT_END` which does not make sense for a ring buffer since it does not have the notion of an end. This change should make it much easier to use a ring buffer as the data source for a `ma_sound`.
|
||||
* There has been a minor change to `ma_calculate_buffer_size_in_milliseconds_from_frames()` to have it return a value rounded up to the nearest integer.
|
||||
* When initialization of a decoder fails, it will now return the first error code encountered rather than always returning `MA_NO_BACKEND` regardless of the error.
|
||||
* Add `ma_device_id_equal()` for comparing device IDs.
|
||||
* Add support for `MA_NO_RUNTIME_LINKING` to the AAudio backend.
|
||||
* Fix a buffer overrun bug with `ma_sound` processing.
|
||||
* Fix a bug relating to node detachment.
|
||||
* Fix a bug where amplification with `ma_device_set_master_volume()` does not work.
|
||||
* Fix a bug where sounds loaded with `MA_SOUND_FLAG_DECODE` do not loop.
|
||||
* Fix a bug with initialization of the band pass biquad filter.
|
||||
* Fix a bug where a device would output distorted audio if initialized without a data callback.
|
||||
* Fix various compiler and static analysis warnings.
|
||||
* Various documentation updates.
|
||||
* WASAPI: Fix an error when stopping the device. The "Failed to reset internal playback device." error should be significantly reduced in frequency.
|
||||
* WASAPI: Fix an error when stopping the device where it was possible miniaudio would not wait for the device to be drained due to an error with the wait time calculation.
|
||||
* WASAPI: Fix a COM related crash with device rerouting.
|
||||
* DirectSound: Add support for specifying an explicit window handle for SetCooperativeLevel().
|
||||
* ALSA: Fix a bug where a playback device can fail to start.
|
||||
* ALSA: Fix some warnings relating to unhandled return value of `read()`.
|
||||
* Web: Fix ScriptProcessorNode path when compiling with `--closure=1`. Note that the Audio Worklets path is not currently working due to the callback specified in `emscripten_create_wasm_audio_worklet_processor_async` never getting fired.
|
||||
* Web: Fix an error with the unlocked notification when compiling as C++.
|
||||
* Web: Fix a JavaScript error when initializing and then uninitializing a context before any interactivity.
|
||||
* Web: miniaudio will now enable threading when the `-pthread` command line flag is used.
|
||||
* Web: Infrastructure has been added to support configurable buffer sizes. In practice this is still restricted to 128 frames, but once Emscripten adds full support for configuration of the buffer size, it will be trivial to add support to miniaudio.
|
||||
* AAudio: Fix some crashes relating to stream rerouting.
|
||||
* AAudio: Fix an error where the device is silenced after rerouting. With this change, miniaudio will no longer give AAudio a hint to use your supplied period size which will therefore result in AAudio using its default latency configuration. If you want AAudio to try to use the period size you supply in the device config, which is the old behaviour, set `aaudio.allowSetBufferCapacity` to true in the device config. Note, however, if you do this you may end up with errors when rerouting between devices.
|
||||
* AAudio: The default minimum SDK version has been increased from 26 to 27 when enabling AAudio. If you need to support version 26, you can use `#define MA_AAUDIO_MIN_ANDROID_SDK_VERSION 26`.
|
||||
* AAudio: Fix ma_device_get_info() implementation.
|
||||
* AAudio: Fix an error when an assertion can trigger due to AAudio reporting a frame count of 0.
|
||||
* PulseAudio: Add a configuration option to control the PulseAudio-defined channel map to use.
|
||||
* PulseAudio: Fix an extremely unlikely race condition with device initialization.
|
||||
* PulseAudio: Fix a bug with the miniaudio-generated stream name. Previously this would create names like "miniaudi0" when it was supposed to be "miniaudio:0".
|
||||
* iOS: Fix an error when trying to capture audio from the simulator with iOS version 16 and newer.
|
||||
* sndio: Fix a crash with device uninitialization.
|
||||
|
||||
|
||||
v0.11.21 - 2023-11-15
|
||||
=====================
|
||||
* Add new ma_device_notification_type_unlocked notification. This is used on Web and will be fired after the user has performed a gesture and thus unlocked the ability to play audio.
|
||||
* Web: Fix an error where the buffer size is incorrectly calculated.
|
||||
* Core Audio: Fix a -Wshadow warning.
|
||||
|
||||
|
||||
v0.11.20 - 2023-11-10
|
||||
=====================
|
||||
* Fix a compilation error with iOS.
|
||||
* Fix an error when dynamically linking libraries when forcing the UWP build on desktop.
|
||||
|
||||
|
||||
v0.11.19 - 2023-11-04
|
||||
=====================
|
||||
* Fix a bug where `ma_decoder_init_file()` can incorrectly return successfully.
|
||||
* Fix a crash when using a node with more than 2 outputs.
|
||||
* Fix a bug where `ma_standard_sample_rate_11025` uses the incorrect rate.
|
||||
* Fix a bug in `ma_noise` where only white noise would be generated even when specifying pink or Brownian.
|
||||
* Fix an SSE related bug when converting from mono streams.
|
||||
* Documentation fixes.
|
||||
* Remove the use of some deprecated functions.
|
||||
* Improvements to runtime linking on Apple platforms.
|
||||
* Web / Emscripten: Audio will no longer attempt to unlock in response to the "touchstart" event. This addresses an issue with iOS and Safari. This results in a change of behavior if you were previously depending on starting audio when the user's finger first touches the screen. Audio will now only unlock when the user's finger is lifted. See this discussion for details: https://github.com/mackron/miniaudio/issues/759
|
||||
* Web / Emscripten: Fix an error when using a sample rate of 0 in the device config.
|
||||
|
||||
|
||||
v0.11.18 - 2023-08-07
|
||||
=====================
|
||||
* Fix some AIFF compatibility issues.
|
||||
@@ -143,7 +240,7 @@ v0.11.7 - 2022-02-06
|
||||
|
||||
v0.11.6 - 2022-01-22
|
||||
====================
|
||||
* WASAPI: Fix a bug where the device is not stopped when an error occurrs when writing to a playback device.
|
||||
* WASAPI: Fix a bug where the device is not stopped when an error occurs when writing to a playback device.
|
||||
* PulseAudio: Fix a rare crash due to a division by zero.
|
||||
* The node graph can now be used as a node. This allows node graphs to be connected to other node graphs.
|
||||
* Fix a crash with high-pass and band-pass filters.
|
||||
@@ -235,7 +332,7 @@ v0.11.0 - 2021-12-18
|
||||
- Add support for disabling denormals on the audio thread.
|
||||
- Add a delay/echo effect called ma_delay.
|
||||
- Add a stereo pan effect called ma_panner.
|
||||
- Add a spataializer effect called ma_spatializer.
|
||||
- Add a spatializer effect called ma_spatializer.
|
||||
- Add support for amplification for device master volume.
|
||||
- Remove dependency on MA_MAX_CHANNELS from filters and data conversion.
|
||||
- Increase MA_MAX_CHANNELS from 32 to 254.
|
||||
@@ -437,7 +534,7 @@ v0.10.26 - 2020-11-24
|
||||
|
||||
v0.10.25 - 2020-11-15
|
||||
- PulseAudio: Fix a bug where the stop callback isn't fired.
|
||||
- WebAudio: Fix an error that occurs when Emscripten increases the size of it's heap.
|
||||
- WebAudio: Fix an error that occurs when Emscripten increases the size of its heap.
|
||||
- Custom Backends: Change the onContextInit and onDeviceInit callbacks to take a parameter which is a pointer to the config that was
|
||||
passed into ma_context_init() and ma_device_init(). This replaces the deviceType parameter of onDeviceInit.
|
||||
- Fix compilation warnings on older versions of GCC.
|
||||
@@ -821,7 +918,7 @@ v0.9 - 2019-03-06
|
||||
- API CHANGE: Add log level to the log callback. New signature:
|
||||
- void on_log(ma_context* pContext, ma_device* pDevice, ma_uint32 logLevel, const char* message)
|
||||
- API CHANGE: Changes to result codes. Constants have changed and unused codes have been removed. If you're
|
||||
a binding mainainer you will need to update your result code constants.
|
||||
a binding maintainer you will need to update your result code constants.
|
||||
- API CHANGE: Change the order of the ma_backend enums to priority order. If you are a binding maintainer, you
|
||||
will need to update.
|
||||
- API CHANGE: Rename mal_dsp to ma_pcm_converter. All functions have been renamed from mal_dsp_*() to
|
||||
@@ -930,7 +1027,7 @@ v0.8 - 2018-07-05
|
||||
- Changed MAL_IMPLEMENTATION to MINI_AL_IMPLEMENTATION for consistency with other libraries. The old
|
||||
way is still supported for now, but you should update as it may be removed in the future.
|
||||
- API CHANGE: Replace device enumeration APIs. mal_enumerate_devices() has been replaced with
|
||||
mal_context_get_devices(). An additional low-level device enumration API has been introduced called
|
||||
mal_context_get_devices(). An additional low-level device enumeration API has been introduced called
|
||||
mal_context_enumerate_devices() which uses a callback to report devices.
|
||||
- API CHANGE: Rename mal_get_sample_size_in_bytes() to mal_get_bytes_per_sample() and add
|
||||
mal_get_bytes_per_frame().
|
||||
@@ -1080,4 +1177,4 @@ v0.2 - 2016-10-28
|
||||
- Added initial implementation of the OpenSL|ES backend.
|
||||
|
||||
v0.1 - 2016-10-21
|
||||
- Initial versioned release.
|
||||
- Initial versioned release.
|
||||
|
||||
@@ -0,0 +1,866 @@
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
# Extract version from miniaudio.h
|
||||
file(READ "${CMAKE_CURRENT_SOURCE_DIR}/miniaudio.h" MINIAUDIO_HEADER_CONTENTS)
|
||||
string(REGEX MATCH "#define MA_VERSION_MAJOR[ \t]+([0-9]+)" _major_match "${MINIAUDIO_HEADER_CONTENTS}")
|
||||
set(MA_VERSION_MAJOR "${CMAKE_MATCH_1}")
|
||||
string(REGEX MATCH "#define MA_VERSION_MINOR[ \t]+([0-9]+)" _minor_match "${MINIAUDIO_HEADER_CONTENTS}")
|
||||
set(MA_VERSION_MINOR "${CMAKE_MATCH_1}")
|
||||
string(REGEX MATCH "#define MA_VERSION_REVISION[ \t]+([0-9]+)" _revision_match "${MINIAUDIO_HEADER_CONTENTS}")
|
||||
set(MA_VERSION_REVISION "${CMAKE_MATCH_1}")
|
||||
set(MINIAUDIO_VERSION "${MA_VERSION_MAJOR}.${MA_VERSION_MINOR}.${MA_VERSION_REVISION}")
|
||||
|
||||
project(miniaudio VERSION ${MINIAUDIO_VERSION})
|
||||
|
||||
|
||||
# Options
|
||||
option(MINIAUDIO_BUILD_EXAMPLES "Build miniaudio examples" OFF)
|
||||
option(MINIAUDIO_BUILD_TESTS "Build miniaudio tests" OFF)
|
||||
option(MINIAUDIO_BUILD_TOOLS "Build miniaudio development tools. Leave this disabled unless you know what you're doing. If you enable this and you get build errors, you clearly do not know what you're doing and yet you still enabled this option. Why would you do that?" OFF)
|
||||
option(MINIAUDIO_FORCE_CXX "Force compilation as C++" OFF)
|
||||
option(MINIAUDIO_FORCE_C89 "Force compilation as C89" OFF)
|
||||
option(MINIAUDIO_NO_EXTRA_NODES "Do not build extra node graph nodes" OFF)
|
||||
option(MINIAUDIO_NO_LIBVORBIS "Disable miniaudio_libvorbis" OFF)
|
||||
option(MINIAUDIO_NO_LIBOPUS "Disable miniaudio_libopus" OFF)
|
||||
option(MINIAUDIO_NO_WASAPI "Disable the WASAPI backend" OFF)
|
||||
option(MINIAUDIO_NO_DSOUND "Disable the DirectSound backend" OFF)
|
||||
option(MINIAUDIO_NO_WINMM "Disable the WinMM backend" OFF)
|
||||
option(MINIAUDIO_NO_ALSA "Disable the ALSA backend" OFF)
|
||||
option(MINIAUDIO_NO_PULSEAUDIO "Disable the PulseAudio backend" OFF)
|
||||
option(MINIAUDIO_NO_JACK "Disable the JACK backend" OFF)
|
||||
option(MINIAUDIO_NO_COREAUDIO "Disable the CoreAudio backend" OFF)
|
||||
option(MINIAUDIO_NO_SNDIO "Disable the sndio backend" OFF)
|
||||
option(MINIAUDIO_NO_AUDIO4 "Disable the audio(4) backend" OFF)
|
||||
option(MINIAUDIO_NO_OSS "Disable the OSS backend" OFF)
|
||||
option(MINIAUDIO_NO_AAUDIO "Disable the AAudio backend" OFF)
|
||||
option(MINIAUDIO_NO_OPENSL "Disable the OpenSL|ES backend" OFF)
|
||||
option(MINIAUDIO_NO_WEBAUDIO "Disable the Web Audio backend" OFF)
|
||||
option(MINIAUDIO_NO_CUSTOM "Disable support for custom backends" OFF)
|
||||
option(MINIAUDIO_NO_NULL "Disable the null backend" OFF)
|
||||
option(MINIAUDIO_ENABLE_ONLY_SPECIFIC_BACKENDS "Only enable specific backends. Backends can be enabled with MINIAUDIO_ENABLE_[BACKEND]." OFF)
|
||||
option(MINIAUDIO_ENABLE_WASAPI "Enable the WASAPI backend" OFF)
|
||||
option(MINIAUDIO_ENABLE_DSOUND "Enable the DirectSound backend" OFF)
|
||||
option(MINIAUDIO_ENABLE_WINMM "Enable the WinMM backend" OFF)
|
||||
option(MINIAUDIO_ENABLE_ALSA "Enable the ALSA backend" OFF)
|
||||
option(MINIAUDIO_ENABLE_PULSEAUDIO "Enable the PulseAudio backend" OFF)
|
||||
option(MINIAUDIO_ENABLE_JACK "Enable the JACK backend" OFF)
|
||||
option(MINIAUDIO_ENABLE_COREAUDIO "Enable the CoreAudio backend" OFF)
|
||||
option(MINIAUDIO_ENABLE_SNDIO "Enable the sndio backend" OFF)
|
||||
option(MINIAUDIO_ENABLE_AUDIO4 "Enable the audio(4) backend" OFF)
|
||||
option(MINIAUDIO_ENABLE_OSS "Enable the OSS backend" OFF)
|
||||
option(MINIAUDIO_ENABLE_AAUDIO "Enable the AAudio backend" OFF)
|
||||
option(MINIAUDIO_ENABLE_OPENSL "Enable the OpenSL|ES backend" OFF)
|
||||
option(MINIAUDIO_ENABLE_WEBAUDIO "Enable the Web Audio backend" OFF)
|
||||
option(MINIAUDIO_ENABLE_CUSTOM "Enable support for custom backends" OFF)
|
||||
option(MINIAUDIO_ENABLE_NULL "Enable the null backend" OFF)
|
||||
option(MINIAUDIO_NO_DECODING "Disable decoding APIs" OFF)
|
||||
option(MINIAUDIO_NO_ENCODING "Disable encoding APIs" OFF)
|
||||
option(MINIAUDIO_NO_WAV "Disable the built-in WAV decoder" OFF)
|
||||
option(MINIAUDIO_NO_FLAC "Disable the built-in FLAC decoder" OFF)
|
||||
option(MINIAUDIO_NO_MP3 "Disable the built-in MP3 decoder" OFF)
|
||||
option(MINIAUDIO_NO_DEVICEIO "Disable audio playback and capture" OFF)
|
||||
option(MINIAUDIO_NO_RESOURCE_MANAGER "Disable the resource manager API" OFF)
|
||||
option(MINIAUDIO_NO_NODE_GRAPH "Disable the node graph API" OFF)
|
||||
option(MINIAUDIO_NO_ENGINE "Disable the high-level engine API" OFF)
|
||||
option(MINIAUDIO_NO_THREADING "Disable threading. Must be used with MINIAUDIO_NO_DEVICEIO." OFF)
|
||||
option(MINIAUDIO_NO_GENERATION "Disable generation APIs such as ma_waveform and ma_noise" OFF)
|
||||
option(MINIAUDIO_NO_SSE2 "Disable SSE2 optimizations" OFF)
|
||||
option(MINIAUDIO_NO_AVX2 "Disable AVX2 optimizations" OFF)
|
||||
option(MINIAUDIO_NO_NEON "Disable NEON optimizations" OFF)
|
||||
option(MINIAUDIO_NO_RUNTIME_LINKING "Disable runtime linking" OFF)
|
||||
option(MINIAUDIO_USE_STDINT "Use <stdint.h> for sized types" OFF)
|
||||
option(MINIAUDIO_DEBUG_OUTPUT "Enable stdout debug output" OFF)
|
||||
|
||||
|
||||
include(GNUInstallDirs)
|
||||
|
||||
# Construct compiler options.
|
||||
set(COMPILE_OPTIONS)
|
||||
|
||||
# Store libraries to install
|
||||
# When installing any header that imports miniaudio.h from a relative path, we
|
||||
# need to maintain its place in the directory tree so it can find Miniaudio
|
||||
set(LIBS_TO_INSTALL)
|
||||
|
||||
|
||||
# Special rules for Emscripten.
|
||||
#
|
||||
# - MINIAUDIO_FORCE_C89 is not supported.
|
||||
# - MINIAUDIO_NO_RUNTIME_LINKING must be enabled.
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
|
||||
set(MINIAUDIO_FORCE_C89 OFF)
|
||||
set(MINIAUDIO_NO_RUNTIME_LINKING ON)
|
||||
|
||||
# This is a hack to work around some errors relating to generation of the pkg-config file.
|
||||
set(MINIAUDIO_ENABLE_ONLY_SPECIFIC_BACKENDS ON)
|
||||
set(MINIAUDIO_ENABLE_WEBAUDIO ON)
|
||||
endif()
|
||||
|
||||
|
||||
if(MINIAUDIO_FORCE_CXX AND MINIAUDIO_FORCE_C89)
|
||||
message(FATAL_ERROR "MINIAUDIO_FORCE_CXX and MINIAUDIO_FORCE_C89 cannot be enabled at the same time.")
|
||||
endif()
|
||||
|
||||
if(MINIAUDIO_FORCE_CXX)
|
||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
message(STATUS "Compiling as C++ (GNU/Clang)")
|
||||
list(APPEND COMPILE_OPTIONS -x c++)
|
||||
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
|
||||
message(STATUS "Compiling as C++ (MSVC)")
|
||||
list(APPEND COMPILE_OPTIONS /TP)
|
||||
else()
|
||||
message(WARNING "MINIAUDIO_FORCE_CXX is enabled but the compiler does not support it. Ignoring.")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(MINIAUDIO_FORCE_C89)
|
||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
message(STATUS "Compiling as C89")
|
||||
list(APPEND COMPILE_OPTIONS -std=c89)
|
||||
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
|
||||
message(WARNING "MSVC does not support forcing C89. MINIAUDIO_FORCE_C89 ignored.")
|
||||
else()
|
||||
message(WARNING "MINIAUDIO_FORCE_C89 is enabled but the compiler does not support it. Ignoring.")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Warnings
|
||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
list(APPEND COMPILE_OPTIONS -Wall -Wextra -Wpedantic)
|
||||
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
|
||||
#list(APPEND COMPILE_OPTIONS /W4)
|
||||
endif()
|
||||
|
||||
|
||||
# Construct compiler defines
|
||||
set(COMPILE_DEFINES)
|
||||
|
||||
if(MINIAUDIO_NO_WASAPI)
|
||||
list(APPEND COMPILE_DEFINES MA_NO_WASAPI)
|
||||
endif()
|
||||
if(MINIAUDIO_NO_DSOUND)
|
||||
list(APPEND COMPILE_DEFINES MA_NO_DSOUND)
|
||||
endif()
|
||||
if(MINIAUDIO_NO_WINMM)
|
||||
list(APPEND COMPILE_DEFINES MA_NO_WINMM)
|
||||
endif()
|
||||
if(MINIAUDIO_NO_ALSA)
|
||||
list(APPEND COMPILE_DEFINES MA_NO_ALSA)
|
||||
endif()
|
||||
if(MINIAUDIO_NO_PULSEAUDIO)
|
||||
list(APPEND COMPILE_DEFINES MA_NO_PULSEAUDIO)
|
||||
endif()
|
||||
if(MINIAUDIO_NO_JACK)
|
||||
list(APPEND COMPILE_DEFINES MA_NO_JACK)
|
||||
endif()
|
||||
if(MINIAUDIO_NO_COREAUDIO)
|
||||
list(APPEND COMPILE_DEFINES MA_NO_COREAUDIO)
|
||||
endif()
|
||||
if(MINIAUDIO_NO_SNDIO)
|
||||
list(APPEND COMPILE_DEFINES MA_NO_SNDIO)
|
||||
endif()
|
||||
if(MINIAUDIO_NO_AUDIO4)
|
||||
list(APPEND COMPILE_DEFINES MA_NO_AUDIO4)
|
||||
endif()
|
||||
if(MINIAUDIO_NO_OSS)
|
||||
list(APPEND COMPILE_DEFINES MA_NO_OSS)
|
||||
endif()
|
||||
if(MINIAUDIO_NO_AAUDIO)
|
||||
list(APPEND COMPILE_DEFINES MA_NO_AAUDIO)
|
||||
endif()
|
||||
if(MINIAUDIO_NO_OPENSL)
|
||||
list(APPEND COMPILE_DEFINES MA_NO_OPENSL)
|
||||
endif()
|
||||
if(MINIAUDIO_NO_WEBAUDIO)
|
||||
list(APPEND COMPILE_DEFINES MA_NO_WEBAUDIO)
|
||||
endif()
|
||||
if(MINIAUDIO_NO_CUSTOM)
|
||||
list(APPEND COMPILE_DEFINES MA_NO_CUSTOM)
|
||||
endif()
|
||||
if(MINIAUDIO_NO_NULL)
|
||||
list(APPEND COMPILE_DEFINES MA_NO_NULL)
|
||||
endif()
|
||||
if(MINIAUDIO_ENABLE_ONLY_SPECIFIC_BACKENDS)
|
||||
list(APPEND COMPILE_DEFINES MA_ENABLE_ONLY_SPECIFIC_BACKENDS)
|
||||
|
||||
if(MINIAUDIO_ENABLE_WASAPI)
|
||||
list(APPEND COMPILE_DEFINES MA_ENABLE_WASAPI)
|
||||
endif()
|
||||
if(MINIAUDIO_ENABLE_DSOUND)
|
||||
list(APPEND COMPILE_DEFINES MA_ENABLE_DSOUND)
|
||||
endif()
|
||||
if(MINIAUDIO_ENABLE_WINMM)
|
||||
list(APPEND COMPILE_DEFINES MA_ENABLE_WINMM)
|
||||
endif()
|
||||
if(MINIAUDIO_ENABLE_ALSA)
|
||||
list(APPEND COMPILE_DEFINES MA_ENABLE_ALSA)
|
||||
endif()
|
||||
if(MINIAUDIO_ENABLE_PULSEAUDIO)
|
||||
list(APPEND COMPILE_DEFINES MA_ENABLE_PULSEAUDIO)
|
||||
endif()
|
||||
if(MINIAUDIO_ENABLE_JACK)
|
||||
list(APPEND COMPILE_DEFINES MA_ENABLE_JACK)
|
||||
endif()
|
||||
if(MINIAUDIO_ENABLE_COREAUDIO)
|
||||
list(APPEND COMPILE_DEFINES MA_ENABLE_COREAUDIO)
|
||||
endif()
|
||||
if(MINIAUDIO_ENABLE_SNDIO)
|
||||
list(APPEND COMPILE_DEFINES MA_ENABLE_SNDIO)
|
||||
endif()
|
||||
if(MINIAUDIO_ENABLE_AUDIO4)
|
||||
list(APPEND COMPILE_DEFINES MA_ENABLE_AUDIO4)
|
||||
endif()
|
||||
if(MINIAUDIO_ENABLE_OSS)
|
||||
list(APPEND COMPILE_DEFINES MA_ENABLE_OSS)
|
||||
endif()
|
||||
if(MINIAUDIO_ENABLE_AAUDIO)
|
||||
list(APPEND COMPILE_DEFINES MA_ENABLE_AAUDIO)
|
||||
endif()
|
||||
if(MINIAUDIO_ENABLE_OPENSL)
|
||||
list(APPEND COMPILE_DEFINES MA_ENABLE_OPENSL)
|
||||
endif()
|
||||
if(MINIAUDIO_ENABLE_WEBAUDIO)
|
||||
list(APPEND COMPILE_DEFINES MA_ENABLE_WEBAUDIO)
|
||||
endif()
|
||||
if(MINIAUDIO_ENABLE_CUSTOM)
|
||||
list(APPEND COMPILE_DEFINES MA_ENABLE_CUSTOM)
|
||||
endif()
|
||||
if(MINIAUDIO_ENABLE_NULL)
|
||||
list(APPEND COMPILE_DEFINES MA_ENABLE_NULL)
|
||||
endif()
|
||||
endif()
|
||||
if(MINIAUDIO_NO_DECODING)
|
||||
list(APPEND COMPILE_DEFINES MA_NO_DECODING)
|
||||
endif()
|
||||
if(MINIAUDIO_NO_ENCODING)
|
||||
list(APPEND COMPILE_DEFINES MA_NO_ENCODING)
|
||||
endif()
|
||||
if(MINIAUDIO_NO_WAV)
|
||||
list(APPEND COMPILE_DEFINES MA_NO_WAV)
|
||||
endif()
|
||||
if(MINIAUDIO_NO_FLAC)
|
||||
list(APPEND COMPILE_DEFINES MA_NO_FLAC)
|
||||
endif()
|
||||
if(MINIAUDIO_NO_MP3)
|
||||
list(APPEND COMPILE_DEFINES MA_NO_MP3)
|
||||
endif()
|
||||
if(MINIAUDIO_NO_DEVICEIO)
|
||||
list(APPEND COMPILE_DEFINES MA_NO_DEVICE_IO)
|
||||
endif()
|
||||
if(MINIAUDIO_NO_RESOURCE_MANAGER)
|
||||
list(APPEND COMPILE_DEFINES MA_NO_RESOURCE_MANAGER)
|
||||
endif()
|
||||
if(MINIAUDIO_NO_NODE_GRAPH)
|
||||
list(APPEND COMPILE_DEFINES MA_NO_NODE_GRAPH)
|
||||
endif()
|
||||
if(MINIAUDIO_NO_ENGINE)
|
||||
list(APPEND COMPILE_DEFINES MA_NO_ENGINE)
|
||||
endif()
|
||||
if(MINIAUDIO_NO_THREADING)
|
||||
list(APPEND COMPILE_DEFINES MA_NO_THREADING)
|
||||
endif()
|
||||
if(MINIAUDIO_NO_GENERATION)
|
||||
list(APPEND COMPILE_DEFINES MA_NO_GENERATION)
|
||||
endif()
|
||||
if(MINIAUDIO_NO_SSE2)
|
||||
list(APPEND COMPILE_DEFINES MA_NO_SSE2)
|
||||
endif()
|
||||
if(MINIAUDIO_NO_AVX2)
|
||||
list(APPEND COMPILE_DEFINES MA_NO_AVX2)
|
||||
endif()
|
||||
if(MINIAUDIO_NO_NEON)
|
||||
list(APPEND COMPILE_DEFINES MA_NO_NEON)
|
||||
endif()
|
||||
if(MINIAUDIO_NO_RUNTIME_LINKING)
|
||||
list(APPEND COMPILE_DEFINES MA_NO_RUNTIME_LINKING)
|
||||
endif()
|
||||
if(MINIAUDIO_USE_STDINT)
|
||||
list(APPEND COMPILE_DEFINES MA_USE_STDINT)
|
||||
endif()
|
||||
if(MINIAUDIO_DEBUG_OUTPUT)
|
||||
list(APPEND COMPILE_DEFINES MA_DEBUG_OUTPUT)
|
||||
endif()
|
||||
|
||||
|
||||
# External Libraries
|
||||
function(add_libogg_subdirectory)
|
||||
if(NOT TARGET ogg)
|
||||
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/external/ogg/CMakeLists.txt)
|
||||
message(STATUS "Building libogg from source.")
|
||||
add_subdirectory(external/ogg)
|
||||
else()
|
||||
message(STATUS "libogg not found.")
|
||||
endif()
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(add_libvorbis_subdirectory)
|
||||
if(NOT TARGET vorbis)
|
||||
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/external/vorbis/CMakeLists.txt)
|
||||
add_libogg_subdirectory()
|
||||
if(TARGET ogg)
|
||||
message(STATUS "Building libvorbis from source.")
|
||||
add_subdirectory(external/vorbis)
|
||||
else()
|
||||
message(STATUS "libogg not found. miniaudio_libvorbis will be excluded.")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(add_libopus_subdirectory)
|
||||
if(NOT TARGET opus)
|
||||
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/external/opus/CMakeLists.txt)
|
||||
message(STATUS "Building libopus from source.")
|
||||
set(OPUS_BUILD_TESTING OFF)
|
||||
add_subdirectory(external/opus)
|
||||
else()
|
||||
message(STATUS "libopus not found. miniaudio_libopus will be excluded.")
|
||||
endif()
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(add_libopusfile_subdirectory)
|
||||
if(NOT TARGET opusfile)
|
||||
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/external/opusfile/CMakeLists.txt)
|
||||
add_libogg_subdirectory()
|
||||
if(TARGET ogg)
|
||||
add_libopus_subdirectory()
|
||||
if(TARGET opus)
|
||||
message(STATUS "Building libopusfile from source.")
|
||||
set(OP_DISABLE_HTTP TRUE)
|
||||
set(OP_DISABLE_DOCS TRUE)
|
||||
set(OP_DISABLE_EXAMPLES TRUE)
|
||||
add_subdirectory(external/opusfile)
|
||||
else()
|
||||
message(STATUS "libopus not found. miniaudio_libopus will be excluded.")
|
||||
endif()
|
||||
else()
|
||||
message(STATUS "libogg not found. miniaudio_libopus will be excluded.")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
|
||||
# vorbisfile
|
||||
#
|
||||
# The vorbisfile target is required for miniaudio_libvorbis. If the vorbisfile target has already been
|
||||
# defined we'll just use that. Otherwise we'll try to use pkg-config. If that fails, as a last resort
|
||||
# we'll allow building it from source from the external/vorbis directory.
|
||||
if(NOT MINIAUDIO_NO_LIBVORBIS)
|
||||
if(NOT TARGET vorbisfile)
|
||||
# Try pkg-config first
|
||||
find_package(PkgConfig QUIET)
|
||||
if(PKG_CONFIG_FOUND)
|
||||
pkg_check_modules(PC_VORBISFILE vorbisfile)
|
||||
endif()
|
||||
|
||||
if(PC_VORBISFILE_FOUND)
|
||||
message(STATUS "Found vorbisfile via pkg-config: ${PC_VORBISFILE_LIBRARIES}")
|
||||
set(HAS_LIBVORBIS TRUE)
|
||||
else()
|
||||
# Fallback to building from source.
|
||||
add_libvorbis_subdirectory()
|
||||
if(NOT TARGET vorbisfile)
|
||||
message(STATUS "libvorbisfile not found. miniaudio_libvorbis will be excluded.")
|
||||
else()
|
||||
set(HAS_LIBVORBIS TRUE)
|
||||
endif()
|
||||
endif()
|
||||
else()
|
||||
message(STATUS "libvorbisfile already found.")
|
||||
set(HAS_LIBVORBIS TRUE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# opusfile
|
||||
#
|
||||
# This is the same as vorbisfile above, but for opusfile.
|
||||
if(NOT MINIAUDIO_NO_LIBOPUS)
|
||||
if(NOT TARGET opusfile)
|
||||
# Try pkg-config first
|
||||
find_package(PkgConfig QUIET)
|
||||
if(PKG_CONFIG_FOUND)
|
||||
pkg_check_modules(PC_OPUSFILE opusfile)
|
||||
endif()
|
||||
|
||||
if(PC_OPUSFILE_FOUND)
|
||||
message(STATUS "Found opusfile via pkg-config: ${PC_OPUSFILE_LIBRARIES}")
|
||||
set(HAS_LIBOPUS TRUE)
|
||||
else()
|
||||
# Fallback to building from source.
|
||||
add_libopusfile_subdirectory()
|
||||
if(NOT TARGET opusfile)
|
||||
message(STATUS "libopusfile not found. miniaudio_libopus will be excluded.")
|
||||
else()
|
||||
set(HAS_LIBOPUS TRUE)
|
||||
endif()
|
||||
endif()
|
||||
else()
|
||||
message(STATUS "libopusfile already found.")
|
||||
set(HAS_LIBOPUS TRUE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
find_library(SDL2_LIBRARY NAMES SDL2)
|
||||
if(SDL2_LIBRARY)
|
||||
message(STATUS "Found SDL2: ${SDL2_LIBRARY}")
|
||||
set(HAS_SDL2 TRUE)
|
||||
else()
|
||||
message(STATUS "SDL2 not found. SDL2 examples will be excluded.")
|
||||
endif()
|
||||
|
||||
# SteamAudio has an annoying SDK setup. In the lib folder there is a folder for each platform. We need to specify the
|
||||
# platform we're compiling for.
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64")
|
||||
# Assume 64-bit. Now we need to check if it's for Windows or Linux.
|
||||
if(WIN32)
|
||||
set(STEAMAUDIO_ARCH windows-x64)
|
||||
else()
|
||||
set(STEAMAUDIO_ARCH linux-x64)
|
||||
endif()
|
||||
else()
|
||||
# Assume 32-bit. Now we need to check if it's for Windows or Linux.
|
||||
if(WIN32)
|
||||
set(STEAMAUDIO_ARCH windows-x86)
|
||||
else()
|
||||
set(STEAMAUDIO_ARCH linux-x86)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# When searching for SteamAudio, we'll support installing it in the external/steamaudio directory.
|
||||
set(STEAMAUDIO_FIND_LIBRARY_HINTS)
|
||||
list(APPEND STEAMAUDIO_FIND_LIBRARY_HINTS ${CMAKE_CURRENT_SOURCE_DIR}/external/steamaudio/lib/${STEAMAUDIO_ARCH})
|
||||
|
||||
if(WIN32)
|
||||
else()
|
||||
list(APPEND STEAMAUDIO_FIND_LIBRARY_HINTS /opt/steamaudio/lib/${STEAMAUDIO_ARCH})
|
||||
list(APPEND STEAMAUDIO_FIND_LIBRARY_HINTS /usr/local/steamaudio/lib/${STEAMAUDIO_ARCH})
|
||||
endif()
|
||||
|
||||
set(STEAMAUDIO_FIND_HEADER_HINTS)
|
||||
list(APPEND STEAMAUDIO_FIND_HEADER_HINTS ${CMAKE_CURRENT_SOURCE_DIR}/external/steamaudio/include)
|
||||
|
||||
if(WIN32)
|
||||
else()
|
||||
list(APPEND STEAMAUDIO_FIND_HEADER_HINTS /opt/steamaudio/include)
|
||||
list(APPEND STEAMAUDIO_FIND_HEADER_HINTS /usr/local/steamaudio/include)
|
||||
endif()
|
||||
|
||||
|
||||
find_library(STEAMAUDIO_LIBRARY NAMES phonon HINTS ${STEAMAUDIO_FIND_LIBRARY_HINTS})
|
||||
if(STEAMAUDIO_LIBRARY)
|
||||
message(STATUS "Found SteamAudio: ${STEAMAUDIO_LIBRARY}")
|
||||
|
||||
find_path(STEAMAUDIO_INCLUDE_DIR
|
||||
NAMES phonon.h
|
||||
HINTS ${STEAMAUDIO_FIND_HEADER_HINTS}
|
||||
)
|
||||
if(STEAMAUDIO_INCLUDE_DIR)
|
||||
message(STATUS "Found phonon.h in ${STEAMAUDIO_INCLUDE_DIR}")
|
||||
set(HAS_STEAMAUDIO TRUE)
|
||||
else()
|
||||
message(STATUS "Could not find phonon.h. miniaudio_engine_steamaudio will be excluded.")
|
||||
endif()
|
||||
else()
|
||||
message(STATUS "SteamAudio not found. miniaudio_engine_steamaudio will be excluded.")
|
||||
endif()
|
||||
|
||||
|
||||
# Link libraries
|
||||
set(COMMON_LINK_LIBRARIES)
|
||||
|
||||
if (UNIX)
|
||||
if(NOT MINIAUDIO_NO_RUNTIME_LINKING)
|
||||
# Not all platforms actually use a separate "dl" library, notably NetBSD and OpenBSD.
|
||||
find_library(LIB_DL NAMES dl)
|
||||
if(LIB_DL)
|
||||
list(APPEND COMMON_LINK_LIBRARIES ${LIB_DL}) # For dlopen(), etc. Most compilers will link to this by default, but some may not.
|
||||
endif()
|
||||
endif()
|
||||
|
||||
find_library(LIB_PTHREAD NAMES pthread)
|
||||
if(LIB_PTHREAD)
|
||||
list(APPEND COMMON_LINK_LIBRARIES ${LIB_PTHREAD}) # Some compilers will not link to pthread by default so list it here just in case.
|
||||
endif()
|
||||
|
||||
find_library(LIB_M NAMES m)
|
||||
if(LIB_M)
|
||||
list(APPEND COMMON_LINK_LIBRARIES ${LIB_M})
|
||||
endif()
|
||||
|
||||
# If we're compiling for 32-bit ARM we need to link to -latomic.
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "^arm" AND NOT CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64")
|
||||
find_library(LIB_ATOMIC NAMES atomic)
|
||||
if(LIB_ATOMIC)
|
||||
list(APPEND COMMON_LINK_LIBRARIES ${LIB_ATOMIC})
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
# Static Libraries
|
||||
add_library(miniaudio
|
||||
miniaudio.c
|
||||
miniaudio.h
|
||||
)
|
||||
|
||||
list(APPEND LIBS_TO_INSTALL miniaudio)
|
||||
install(FILES miniaudio.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/miniaudio)
|
||||
|
||||
target_include_directories(miniaudio PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_compile_options (miniaudio PRIVATE ${COMPILE_OPTIONS})
|
||||
target_compile_definitions(miniaudio PRIVATE ${COMPILE_DEFINES})
|
||||
|
||||
|
||||
add_library(libvorbis_interface INTERFACE)
|
||||
if(HAS_LIBVORBIS)
|
||||
if(TARGET vorbisfile)
|
||||
target_link_libraries(libvorbis_interface INTERFACE vorbisfile)
|
||||
elseif(PC_VORBISFILE_FOUND)
|
||||
target_link_libraries (libvorbis_interface INTERFACE ${PC_VORBISFILE_LIBRARIES})
|
||||
target_include_directories(libvorbis_interface INTERFACE ${PC_VORBISFILE_INCLUDE_DIRS})
|
||||
target_link_directories (libvorbis_interface INTERFACE ${PC_VORBISFILE_LIBRARY_DIRS})
|
||||
target_compile_options (libvorbis_interface INTERFACE ${PC_VORBISFILE_CFLAGS_OTHER})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(HAS_LIBVORBIS)
|
||||
add_library(miniaudio_libvorbis
|
||||
extras/decoders/libvorbis/miniaudio_libvorbis.c
|
||||
extras/decoders/libvorbis/miniaudio_libvorbis.h
|
||||
)
|
||||
|
||||
list(APPEND LIBS_TO_INSTALL miniaudio_libvorbis)
|
||||
install(FILES extras/decoders/libvorbis/miniaudio_libvorbis.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/miniaudio/extras/decoders/libvorbis)
|
||||
|
||||
target_compile_options (miniaudio_libvorbis PRIVATE ${COMPILE_OPTIONS})
|
||||
target_compile_definitions(miniaudio_libvorbis PRIVATE ${COMPILE_DEFINES})
|
||||
target_link_libraries (miniaudio_libvorbis PRIVATE libvorbis_interface)
|
||||
endif()
|
||||
|
||||
|
||||
add_library(libopus_interface INTERFACE)
|
||||
if(HAS_LIBOPUS)
|
||||
if(TARGET opusfile)
|
||||
target_link_libraries (libopus_interface INTERFACE opusfile)
|
||||
elseif(PC_OPUSFILE_FOUND)
|
||||
target_link_libraries (libopus_interface INTERFACE ${PC_OPUSFILE_LIBRARIES})
|
||||
target_include_directories(libopus_interface INTERFACE ${PC_OPUSFILE_INCLUDE_DIRS})
|
||||
target_link_directories (libopus_interface INTERFACE ${PC_OPUSFILE_LIBRARY_DIRS})
|
||||
target_compile_options (libopus_interface INTERFACE ${PC_OPUSFILE_CFLAGS_OTHER})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(HAS_LIBOPUS)
|
||||
add_library(miniaudio_libopus
|
||||
extras/decoders/libopus/miniaudio_libopus.c
|
||||
extras/decoders/libopus/miniaudio_libopus.h
|
||||
)
|
||||
|
||||
|
||||
list(APPEND LIBS_TO_INSTALL miniaudio_libopus)
|
||||
install(FILES extras/decoders/libopus/miniaudio_libopus.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/miniaudio/extras/decoders/libopus)
|
||||
|
||||
target_compile_options (miniaudio_libopus PRIVATE ${COMPILE_OPTIONS})
|
||||
target_compile_definitions(miniaudio_libopus PRIVATE ${COMPILE_DEFINES})
|
||||
target_link_libraries (miniaudio_libopus PRIVATE libopus_interface)
|
||||
endif()
|
||||
|
||||
|
||||
if (NOT MINIAUDIO_NO_EXTRA_NODES)
|
||||
function(add_extra_node name)
|
||||
add_library(miniaudio_${name}_node
|
||||
extras/nodes/ma_${name}_node/ma_${name}_node.c
|
||||
extras/nodes/ma_${name}_node/ma_${name}_node.h
|
||||
)
|
||||
|
||||
set(libs "${LIBS_TO_INSTALL}")
|
||||
|
||||
list(APPEND libs miniaudio_${name}_node)
|
||||
set(LIBS_TO_INSTALL "${libs}" PARENT_SCOPE) # without PARENT_SCOPE, any changes are lost
|
||||
install(FILES extras/nodes/ma_${name}_node/ma_${name}_node.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/miniaudio/extras/nodes/ma_${name}_node)
|
||||
|
||||
target_include_directories(miniaudio_${name}_node PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/extras/nodes/ma_${name}_node)
|
||||
target_compile_options (miniaudio_${name}_node PRIVATE ${COMPILE_OPTIONS})
|
||||
target_compile_definitions(miniaudio_${name}_node PRIVATE ${COMPILE_DEFINES})
|
||||
|
||||
if(MINIAUDIO_BUILD_EXAMPLES)
|
||||
add_executable(miniaudio_${name}_node_example extras/nodes/ma_${name}_node/ma_${name}_node_example.c)
|
||||
target_link_libraries(miniaudio_${name}_node_example PRIVATE miniaudio_common_options)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
add_extra_node(channel_combiner)
|
||||
add_extra_node(channel_separator)
|
||||
add_extra_node(ltrim)
|
||||
add_extra_node(reverb)
|
||||
add_extra_node(vocoder)
|
||||
endif()
|
||||
|
||||
|
||||
# Interface with common options to simplify the setup of tests and examples. Note that we don't pass
|
||||
# in COMPILE_DEFINES here because want to allow the tests and examples to define their own defines. If
|
||||
# we were to use COMPILE_DEFINES here many of the tests and examples would not compile.
|
||||
add_library(miniaudio_common_options INTERFACE)
|
||||
target_compile_options(miniaudio_common_options INTERFACE ${COMPILE_OPTIONS})
|
||||
target_link_libraries (miniaudio_common_options INTERFACE ${COMMON_LINK_LIBRARIES})
|
||||
|
||||
function(is_backend_enabled NAME)
|
||||
if (NOT MINIAUDIO_NO_${NAME} AND (NOT MINIAUDIO_ENABLE_ONLY_SPECIFIC_BACKENDS OR MINIAUDIO_ENABLE_${NAME}))
|
||||
set(${NAME}_ENABLED TRUE PARENT_SCOPE)
|
||||
else()
|
||||
set(${NAME}_ENABLED FALSE PARENT_SCOPE)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
set(LINKED_LIBS)
|
||||
|
||||
if(MINIAUDIO_NO_RUNTIME_LINKING)
|
||||
is_backend_enabled(PULSEAUDIO)
|
||||
if (PULSEAUDIO_ENABLED)
|
||||
find_package(PulseAudio)
|
||||
|
||||
if (PulseAudio_FOUND)
|
||||
target_link_libraries(miniaudio PRIVATE ${PULSEAUDIO_LIBRARY})
|
||||
target_include_directories(miniaudio SYSTEM PRIVATE ${PULSEAUDIO_INCLUDE_DIR})
|
||||
list(APPEND LINKED_LIBS libpulse)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
is_backend_enabled(ALSA)
|
||||
if (ALSA_ENABLED)
|
||||
find_package(PkgConfig QUIET)
|
||||
|
||||
if(PKG_CONFIG_FOUND)
|
||||
pkg_check_modules(PC_ALSA alsa)
|
||||
endif()
|
||||
|
||||
find_library(ALSA_LIBRARY
|
||||
NAMES asound
|
||||
HINTS ${PC_ALSA_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
if (ALSA_LIBRARY)
|
||||
find_path(ALSA_INCLUDE_DIR
|
||||
NAMES alsa/asoundlib.h
|
||||
HINTS ${PC_ALSA_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
target_link_libraries(miniaudio PRIVATE ${ALSA_LIBRARY})
|
||||
target_include_directories(miniaudio PRIVATE ${ALSA_INCLUDE_DIR})
|
||||
list(APPEND LINKED_LIBS alsa)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
is_backend_enabled(SNDIO)
|
||||
if (SNDIO_ENABLED)
|
||||
find_package(PkgConfig QUIET)
|
||||
|
||||
if(PKG_CONFIG_FOUND)
|
||||
pkg_check_modules(PC_SNDIO sndio)
|
||||
endif()
|
||||
|
||||
find_library(SNDIO_LIBRARY
|
||||
NAMES sndio
|
||||
HINTS ${PC_SNDIO_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
if (SNDIO_LIBRARY)
|
||||
target_link_libraries(miniaudio PRIVATE ${SNDIO_LIBRARY})
|
||||
list(APPEND LINKED_LIBS sndio)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
is_backend_enabled(JACK)
|
||||
if (JACK_ENABLED)
|
||||
find_package(PkgConfig QUIET)
|
||||
|
||||
if(PKG_CONFIG_FOUND)
|
||||
pkg_check_modules(PC_JACK jack)
|
||||
endif()
|
||||
|
||||
find_library(JACK_LIBRARY
|
||||
NAMES jack
|
||||
HINTS ${PC_JACK_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
if (JACK_LIBRARY)
|
||||
find_path(JACK_INCLUDE_DIR
|
||||
NAMES jack/jack.h
|
||||
HINTS ${PC_JACK_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
target_link_libraries(miniaudio PRIVATE ${JACK_LIBRARY})
|
||||
target_include_directories(miniaudio PRIVATE ${JACK_INCLUDE_DIR})
|
||||
list(APPEND LINKED_LIBS jack)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Tests
|
||||
#
|
||||
# All tests are compiled as a single translation unit. There is no need to add miniaudio as a link library.
|
||||
if(MINIAUDIO_BUILD_TESTS)
|
||||
enable_testing()
|
||||
|
||||
set(TESTS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/tests)
|
||||
|
||||
function(add_miniaudio_test name source)
|
||||
add_executable(${name} ${TESTS_DIR}/${source})
|
||||
target_link_libraries(${name} PRIVATE miniaudio_common_options)
|
||||
endfunction()
|
||||
|
||||
# Disable C++ tests when forcing C89. This is needed because we'll be passing -std=c89 which will cause errors when trying to compile a C++ file.
|
||||
if(NOT MINIAUDIO_FORCE_C89)
|
||||
# The debugging test is only used for debugging miniaudio itself. Don't do add_test() for this, and do not include it in in any automated testing.
|
||||
add_miniaudio_test(miniaudio_debugging debugging/debugging.cpp)
|
||||
|
||||
add_miniaudio_test(miniaudio_cpp cpp/cpp.cpp)
|
||||
add_test(NAME miniaudio_cpp COMMAND miniaudio_cpp --auto) # This is just the deviceio test.
|
||||
endif()
|
||||
|
||||
add_miniaudio_test(miniaudio_deviceio deviceio/deviceio.c)
|
||||
add_test(NAME miniaudio_deviceio COMMAND miniaudio_deviceio --auto)
|
||||
|
||||
add_miniaudio_test(miniaudio_conversion conversion/conversion.c)
|
||||
add_test(NAME miniaudio_conversion COMMAND miniaudio_conversion)
|
||||
|
||||
add_miniaudio_test(miniaudio_filtering filtering/filtering.c)
|
||||
add_test(NAME miniaudio_filtering COMMAND miniaudio_filtering ${CMAKE_CURRENT_SOURCE_DIR}/data/16-44100-stereo.flac)
|
||||
|
||||
add_miniaudio_test(miniaudio_generation generation/generation.c)
|
||||
add_test(NAME miniaudio_generation COMMAND miniaudio_generation)
|
||||
endif()
|
||||
|
||||
# Examples
|
||||
#
|
||||
# Like tests, all examples are compiled as a single translation unit. There is no need to add miniaudio as a link library.
|
||||
if (MINIAUDIO_BUILD_EXAMPLES)
|
||||
set(EXAMPLES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/examples)
|
||||
|
||||
function(add_miniaudio_example name source)
|
||||
add_executable(${name} ${EXAMPLES_DIR}/${source})
|
||||
target_link_libraries(${name} PRIVATE miniaudio_common_options)
|
||||
endfunction()
|
||||
|
||||
add_miniaudio_example(miniaudio_custom_backend custom_backend.c)
|
||||
|
||||
add_miniaudio_example(miniaudio_custom_decoder_engine custom_decoder_engine.c)
|
||||
if(HAS_LIBVORBIS)
|
||||
target_link_libraries(miniaudio_custom_decoder_engine PRIVATE libvorbis_interface)
|
||||
else()
|
||||
target_compile_definitions(miniaudio_custom_decoder_engine PRIVATE MA_NO_LIBVORBIS)
|
||||
message(STATUS "miniaudio_libvorbis is disabled. Vorbis support is disabled in miniaudio_custom_decoder_engine.")
|
||||
endif()
|
||||
if(HAS_LIBOPUS)
|
||||
target_link_libraries(miniaudio_custom_decoder_engine PRIVATE libopus_interface)
|
||||
else()
|
||||
target_compile_definitions(miniaudio_custom_decoder_engine PRIVATE MA_NO_LIBOPUS)
|
||||
message(STATUS "miniaudio_libopus is disabled. Opus support is disabled in miniaudio_custom_decoder_engine.")
|
||||
endif()
|
||||
|
||||
add_miniaudio_example(miniaudio_custom_decoder custom_decoder.c)
|
||||
if(HAS_LIBVORBIS)
|
||||
target_link_libraries(miniaudio_custom_decoder PRIVATE libvorbis_interface)
|
||||
else()
|
||||
target_compile_definitions(miniaudio_custom_decoder PRIVATE MA_NO_LIBVORBIS)
|
||||
message(STATUS "miniaudio_libvorbis is disabled. Vorbis support is disabled in miniaudio_custom_decoder.")
|
||||
endif()
|
||||
if(HAS_LIBOPUS)
|
||||
target_link_libraries(miniaudio_custom_decoder PRIVATE libopus_interface)
|
||||
else()
|
||||
target_compile_definitions(miniaudio_custom_decoder PRIVATE MA_NO_LIBOPUS)
|
||||
message(STATUS "miniaudio_libopus is disabled. Opus support is disabled in miniaudio_custom_decoder.")
|
||||
endif()
|
||||
|
||||
add_miniaudio_example(miniaudio_data_source_chaining data_source_chaining.c)
|
||||
add_miniaudio_example(miniaudio_duplex_effect duplex_effect.c)
|
||||
add_miniaudio_example(miniaudio_engine_advanced engine_advanced.c)
|
||||
add_miniaudio_example(miniaudio_engine_effects engine_effects.c)
|
||||
add_miniaudio_example(miniaudio_engine_hello_world engine_hello_world.c)
|
||||
|
||||
if(HAS_SDL2)
|
||||
add_miniaudio_example(miniaudio_engine_sdl engine_sdl.c)
|
||||
target_link_libraries(miniaudio_engine_sdl PRIVATE ${SDL2_LIBRARY})
|
||||
else()
|
||||
message(STATUS "SDL2 could not be found. miniaudio_engine_sdl has been excluded.")
|
||||
endif()
|
||||
|
||||
if(HAS_STEAMAUDIO)
|
||||
add_miniaudio_example(miniaudio_engine_steamaudio engine_steamaudio.c)
|
||||
target_include_directories(miniaudio_engine_steamaudio PRIVATE ${STEAMAUDIO_INCLUDE_DIR})
|
||||
target_link_libraries (miniaudio_engine_steamaudio PRIVATE ${STEAMAUDIO_LIBRARY})
|
||||
else()
|
||||
message(STATUS "SteamAudio could not be found. miniaudio_engine_steamaudio has been excluded.")
|
||||
endif()
|
||||
|
||||
add_miniaudio_example(miniaudio_hilo_interop hilo_interop.c)
|
||||
add_miniaudio_example(miniaudio_node_graph node_graph.c)
|
||||
add_miniaudio_example(miniaudio_resource_manager_advanced resource_manager_advanced.c)
|
||||
add_miniaudio_example(miniaudio_resource_manager resource_manager.c)
|
||||
add_miniaudio_example(miniaudio_simple_capture simple_capture.c)
|
||||
add_miniaudio_example(miniaudio_simple_duplex simple_duplex.c)
|
||||
add_miniaudio_example(miniaudio_simple_enumeration simple_enumeration.c)
|
||||
add_miniaudio_example(miniaudio_simple_loopback simple_loopback.c)
|
||||
add_miniaudio_example(miniaudio_simple_looping simple_looping.c)
|
||||
add_miniaudio_example(miniaudio_simple_mixing simple_mixing.c)
|
||||
add_miniaudio_example(miniaudio_simple_playback_sine simple_playback_sine.c)
|
||||
add_miniaudio_example(miniaudio_simple_playback simple_playback.c)
|
||||
add_miniaudio_example(miniaudio_simple_spatialization simple_spatialization.c)
|
||||
endif()
|
||||
|
||||
|
||||
# Tools
|
||||
if (MINIAUDIO_BUILD_TOOLS)
|
||||
set(TOOLS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/tools)
|
||||
|
||||
add_executable(madoc ${TOOLS_DIR}/madoc/madoc.c)
|
||||
endif()
|
||||
|
||||
|
||||
if(IS_ABSOLUTE "${CMAKE_INSTALL_INCLUDEDIR}")
|
||||
set(MINIAUDIO_PC_INCLUDEDIR "${CMAKE_INSTALL_INCLUDEDIR}")
|
||||
else()
|
||||
set(MINIAUDIO_PC_INCLUDEDIR "\${prefix}/${CMAKE_INSTALL_INCLUDEDIR}")
|
||||
endif()
|
||||
if(IS_ABSOLUTE "${CMAKE_INSTALL_LIBDIR}")
|
||||
set(MINIAUDIO_PC_LIBDIR "${CMAKE_INSTALL_LIBDIR}")
|
||||
else()
|
||||
set(MINIAUDIO_PC_LIBDIR "\${exec_prefix}/${CMAKE_INSTALL_LIBDIR}")
|
||||
endif()
|
||||
|
||||
string(JOIN ", " MINIAUDIO_PC_REQUIRES_PRIVATE ${LINKED_LIBS})
|
||||
|
||||
# Add vorbisfile and opusfile to pkg-config dependencies if found via pkg-config
|
||||
set(PC_REQUIRES_PRIVATE_LIST)
|
||||
if(PC_VORBISFILE_FOUND AND HAS_LIBVORBIS)
|
||||
list(APPEND PC_REQUIRES_PRIVATE_LIST "vorbisfile")
|
||||
endif()
|
||||
if(PC_OPUSFILE_FOUND AND HAS_LIBOPUS)
|
||||
list(APPEND PC_REQUIRES_PRIVATE_LIST "opusfile")
|
||||
endif()
|
||||
if(PC_REQUIRES_PRIVATE_LIST)
|
||||
if(MINIAUDIO_PC_REQUIRES_PRIVATE)
|
||||
string(APPEND MINIAUDIO_PC_REQUIRES_PRIVATE ", ")
|
||||
endif()
|
||||
string(JOIN ", " PC_REQUIRES_STR ${PC_REQUIRES_PRIVATE_LIST})
|
||||
string(APPEND MINIAUDIO_PC_REQUIRES_PRIVATE "${PC_REQUIRES_STR}")
|
||||
endif()
|
||||
list(TRANSFORM COMMON_LINK_LIBRARIES PREPEND "-l")
|
||||
string(JOIN " " MINIAUDIO_PC_LIBS_PRIVATE ${COMMON_LINK_LIBRARIES})
|
||||
list(TRANSFORM COMPILE_DEFINES PREPEND "-D")
|
||||
string(JOIN " " MINIAUDIO_PC_CFLAGS ${COMPILE_DEFINES})
|
||||
|
||||
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/miniaudio.pc.in" "${CMAKE_CURRENT_BINARY_DIR}/miniaudio.pc" @ONLY)
|
||||
|
||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/miniaudio.pc" DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
|
||||
|
||||
message(STATUS "Library list: ${LIBS_TO_INSTALL}")
|
||||
install(TARGETS ${LIBS_TO_INSTALL}
|
||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
)
|
||||
@@ -21,7 +21,7 @@ significant work without talking to me first. If I don't like it, it won't be me
|
||||
[Discord](https://discord.gg/9vpqbjU) and [Twitter](https://twitter.com/mackron).
|
||||
|
||||
Always base your pull request branch on the "dev" branch. The master branch contains the latest release, which
|
||||
means your pull request may not be including the lastest in-development changes which may result in unnecessary
|
||||
means your pull request may not be including the latest in-development changes which may result in unnecessary
|
||||
conflicts.
|
||||
|
||||
I need to review your pull requests before merging. If your pull request is non-trivial, try to break it up into
|
||||
@@ -63,7 +63,7 @@ not contribute to this project.
|
||||
|
||||
Predictable Questions
|
||||
---------------------
|
||||
### "Would you consider splitting out [some section of code] into it's own file?"
|
||||
### "Would you consider splitting out [some section of code] into its own file?"
|
||||
No, the idea is to keep everything in one place. It would be nice in specific cases to split out specific sections
|
||||
of the code, such as the resampler, for example. However, this will completely violate one of the major goals of the
|
||||
project - to have a complete audio library contained within a single file.
|
||||
|
||||
@@ -29,7 +29,7 @@ For more information, please refer to <http://unlicense.org/>
|
||||
===============================================================================
|
||||
ALTERNATIVE 2 - MIT No Attribution
|
||||
===============================================================================
|
||||
Copyright 2023 David Reid
|
||||
Copyright 2025 David Reid
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
|
||||
@@ -7,8 +7,7 @@
|
||||
|
||||
<p align="center">
|
||||
<a href="https://discord.gg/9vpqbjU"><img src="https://img.shields.io/discord/712952679415939085?label=discord&logo=discord&style=flat-square" alt="discord"></a>
|
||||
<a href="https://fosstodon.org/@mackron"><img src="https://img.shields.io/mastodon/follow/109293691403797709?color=blue&domain=https%3A%2F%2Ffosstodon.org&label=mastodon&logo=mastodon&style=flat-square" alt="mastodon"></a>
|
||||
<a href="https://www.reddit.com/r/miniaudio"><img src="https://img.shields.io/reddit/subreddit-subscribers/miniaudio?label=r%2Fminiaudio&logo=reddit&style=flat-square" alt="reddit"></a>
|
||||
<a href="https://x.com/mackron"><img alt="x" src="https://img.shields.io/twitter/url?url=https%3A%2F%2Fx.com%2Fmackron&style=flat-square&logo=x&label=%40mackron"></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
@@ -17,6 +16,7 @@
|
||||
<a href="#building">Building</a> -
|
||||
<a href="#documentation">Documentation</a> -
|
||||
<a href="#supported-platforms">Supported Platforms</a> -
|
||||
<a href="#security">Security</a> -
|
||||
<a href="#license">License</a>
|
||||
</p>
|
||||
|
||||
@@ -33,7 +33,7 @@ Features
|
||||
- High-level API for sound management, mixing, effects and optional 3D spatialization.
|
||||
- Flexible node graph system for advanced mixing and effect processing.
|
||||
- Resource management for loading sound files.
|
||||
- Decoding, with built-in support for WAV, FLAC and MP3, in addition to being able to plug in custom decoders.
|
||||
- Decoding, with built-in support for WAV, FLAC, and MP3, in addition to being able to plug in custom decoders.
|
||||
- Encoding (WAV only).
|
||||
- Data conversion.
|
||||
- Resampling, including custom resamplers.
|
||||
@@ -51,8 +51,7 @@ Examples
|
||||
This example shows one way to play a sound using the high level API.
|
||||
|
||||
```c
|
||||
#define MINIAUDIO_IMPLEMENTATION
|
||||
#include "miniaudio.h"
|
||||
#include "miniaudio/miniaudio.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
@@ -80,8 +79,7 @@ int main()
|
||||
This example shows how to decode and play a sound using the low level API.
|
||||
|
||||
```c
|
||||
#define MINIAUDIO_IMPLEMENTATION
|
||||
#include "miniaudio.h"
|
||||
#include "miniaudio/miniaudio.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
@@ -149,38 +147,26 @@ More examples can be found in the [examples](examples) folder or online here: ht
|
||||
|
||||
Building
|
||||
========
|
||||
Do the following in one source file:
|
||||
```c
|
||||
#define MINIAUDIO_IMPLEMENTATION
|
||||
#include "miniaudio.h"
|
||||
```
|
||||
Then just compile. There's no need to install any dependencies. On Windows and macOS there's no need to link
|
||||
to anything. On Linux just link to `-lpthread`, `-lm` and `-ldl`. On BSD just link to `-lpthread` and `-lm`.
|
||||
On iOS you need to compile as Objective-C.
|
||||
Just compile miniaudio.c like any other source file and include miniaudio.h like a normal header. There's no need
|
||||
to install any dependencies. On Windows and macOS there's no need to link to anything. On Linux and BSD just link
|
||||
to `-lpthread` and `-lm`. On iOS you need to compile as Objective-C. Link to `-ldl` if you get errors about
|
||||
`dlopen()`, etc.
|
||||
|
||||
If you get errors about undefined references to `__sync_val_compare_and_swap_8`, `__atomic_load_8`, etc. you
|
||||
need to link with `-latomic`.
|
||||
|
||||
If you prefer separate .h and .c files, you can find a split version of miniaudio in the extras/miniaudio_split
|
||||
folder. From here you can use miniaudio as a traditional .c and .h library - just add miniaudio.c to your source
|
||||
tree like any other source file and include miniaudio.h like a normal header. If you prefer compiling as a
|
||||
single translation unit (AKA unity builds), you can just #include the .c file in your main source file:
|
||||
```c
|
||||
#include "miniaudio.c"
|
||||
```
|
||||
Note that the split version is auto-generated using a tool and is based on the main file in the root directory.
|
||||
If you want to contribute, please make the change in the main file.
|
||||
|
||||
ABI compatibility is not guaranteed between versions so take care if compiling as a DLL/SO. The suggested way
|
||||
to integrate miniaudio is by adding it directly to your source tree.
|
||||
|
||||
You can also use CMake if that's your preference.
|
||||
|
||||
|
||||
Documentation
|
||||
=============
|
||||
Online documentation can be found here: https://miniaud.io/docs/
|
||||
|
||||
Documentation can also be found at the top of [miniaudio.h](https://raw.githubusercontent.com/mackron/miniaudio/master/miniaudio.h)
|
||||
which is always the most up-to-date and authoritive source of information on how to use miniaudio. All other
|
||||
which is always the most up-to-date and authoritative source of information on how to use miniaudio. All other
|
||||
documentation is generated from this in-code documentation.
|
||||
|
||||
|
||||
@@ -217,6 +203,14 @@ Backends
|
||||
- Custom
|
||||
|
||||
|
||||
Security
|
||||
========
|
||||
I deal with all security related issues publicly and transparently, and it can sometimes take a while before I
|
||||
get a chance to address it. If this is an issue for you, you need to use another library. The fastest way to get
|
||||
a bug fixed is to submit a pull request, but if this is impractical for you please post a ticket to the public
|
||||
GitHub issue tracker.
|
||||
|
||||
|
||||
License
|
||||
=======
|
||||
Your choice of either public domain or [MIT No Attribution](https://github.com/aws/mit-0).
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
miniaudio_h := <../miniaudio.h>;
|
||||
miniaudio_c := <../miniaudio.c>;
|
||||
|
||||
cleanup :: function(src:string) string
|
||||
{
|
||||
return @(src)
|
||||
["\r\n"] <= "\n" // Normalize line endings to "\n". Needed for very old versions of GCC.
|
||||
["\t"] <= " " // Tabs to spaces.
|
||||
;
|
||||
}
|
||||
|
||||
miniaudio_h = cleanup(@(miniaudio_h));
|
||||
miniaudio_c = cleanup(@(miniaudio_c));
|
||||
@@ -0,0 +1,289 @@
|
||||
miniaudio_h := <../miniaudio.h>;
|
||||
miniaudio_c := <../miniaudio.c>;
|
||||
dr_wav_h :: <../../dr_libs/dr_wav.h>;
|
||||
dr_flac_h :: <../../dr_libs/dr_flac.h>;
|
||||
dr_mp3_h :: <../../dr_libs/dr_mp3.h>;
|
||||
c89atomic_h :: <../../c89atomic/c89atomic.h>;
|
||||
c89atomic_c :: <../../c89atomic/c89atomic.c>;
|
||||
|
||||
minify :: function(src:string) string
|
||||
{
|
||||
return @(src)
|
||||
["/\*[^*]*\*+(?:[^/*][^*]*\*+)*/"] <= "" // Remove all block comments to keep things clean.
|
||||
["(?m)^\s*\R"] <= "" // Remove all empty lines to compress it all down.
|
||||
["[ \t]+(?=(?:\R|$))"] <= "" // Remove trailing whitespace.
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
// dr_wav
|
||||
rename_wav_namespace :: function(src:string) string
|
||||
{
|
||||
return @(src)
|
||||
["\bdrwav"] <= "ma_dr_wav"
|
||||
["\bDRWAV"] <= "MA_DR_WAV"
|
||||
["\bdr_wav"] <= "ma_dr_wav"
|
||||
["\bDR_WAV"] <= "MA_DR_WAV"
|
||||
["\bg_drwav"] <= "ma_dr_wav_g"
|
||||
|
||||
// Some common tokens will be namespaced as "ma_dr_wav" when we really want them to be "ma_".
|
||||
["\bma_dr_wav_int"] <= "ma_int"
|
||||
["\bma_dr_wav_uint"] <= "ma_uint"
|
||||
["\bma_dr_wav_bool"] <= "ma_bool"
|
||||
["\bma_dr_wav_uintptr"] <= "ma_uintptr"
|
||||
["\bMA_DR_WAV_TRUE"] <= "MA_TRUE"
|
||||
["\bMA_DR_WAV_FALSE"] <= "MA_FALSE"
|
||||
["\bMA_DR_WAV_UINT64_MAX"] <= "MA_UINT64_MAX"
|
||||
["\bMA_DR_WAV_32BIT"] <= "MA_32BIT"
|
||||
["\bMA_DR_WAV_64BIT"] <= "MA_64BIT"
|
||||
["\bMA_DR_WAV_ARM32"] <= "MA_ARM32"
|
||||
["\bMA_DR_WAV_ARM64"] <= "MA_ARM64"
|
||||
["\bMA_DR_WAV_X64"] <= "MA_X64"
|
||||
["\bMA_DR_WAV_X86"] <= "MA_X86"
|
||||
["\bMA_DR_WAV_ARM"] <= "MA_ARM"
|
||||
["\bMA_DR_WAV_API"] <= "MA_API"
|
||||
["\bMA_DR_WAV_PRIVATE"] <= "MA_PRIVATE"
|
||||
["\bMA_DR_WAV_DLL"] <= "MA_DLL"
|
||||
["\bMA_DR_WAV_DLL_IMPORT"] <= "MA_DLL_IMPORT"
|
||||
["\bMA_DR_WAV_DLL_EXPORT"] <= "MA_DLL_EXPORT"
|
||||
["\bMA_DR_WAV_DLL_PRIVATE"] <= "MA_DLL_PRIVATE"
|
||||
["\bma_dr_wav_result"] <= "ma_result"
|
||||
["\bma_dr_wav_allocation_callbacks"] <= "ma_allocation_callbacks"
|
||||
["\bMA_DR_WAV_INLINE"] <= "MA_INLINE"
|
||||
["\bMA_DR_WAV_SIZE_MAX"] <= "MA_SIZE_MAX"
|
||||
["\bma_dr_wav_result_from_errno"] <= "ma_result_from_errno"
|
||||
["\bma_dr_wav_fopen"] <= "ma_fopen"
|
||||
["\bma_dr_wav_wfopen"] <= "ma_wfopen"
|
||||
|
||||
// Result codes.
|
||||
["MA_DR_WAV_SUCCESS"] <= "MA_SUCCESS"
|
||||
["MA_DR_WAV_INVALID_ARGS"] <= "MA_INVALID_ARGS"
|
||||
["MA_DR_WAV_OUT_OF_MEMORY"] <= "MA_OUT_OF_MEMORY"
|
||||
["MA_DR_WAV_INVALID_FILE"] <= "MA_INVALID_FILE"
|
||||
["MA_DR_WAV_AT_END"] <= "MA_AT_END"
|
||||
["MA_DR_WAV_BAD_SEEK"] <= "MA_BAD_SEEK"
|
||||
;
|
||||
}
|
||||
|
||||
convert_wav_h :: function(src:string) string
|
||||
{
|
||||
stripped := @(src);
|
||||
stripped["/\* Sized Types \*/\R" : "\R/\* End Sized Types \*/" ] = "";
|
||||
stripped["/\* Decorations \*/\R" : "\R/\* End Decorations \*/" ] = "";
|
||||
stripped["/\* Result Codes \*/\R" : "\R/\* End Result Codes \*/" ] = "";
|
||||
stripped["/\* Allocation Callbacks \*/\R" : "\R/\* End Allocation Callbacks \*/" ] = "";
|
||||
|
||||
return minify(rename_wav_namespace(stripped));
|
||||
}
|
||||
|
||||
convert_wav_c :: function(src:string) string
|
||||
{
|
||||
stripped := @(src);
|
||||
stripped["/\* Architecture Detection \*/\R" : "\R/\* End Architecture Detection \*/"] = "";
|
||||
stripped["/\* Inline \*/\R" : "\R/\* End Inline \*/" ] = "";
|
||||
stripped["/\* SIZE_MAX \*/\R" : "\R/\* End SIZE_MAX \*/" ] = "";
|
||||
stripped["/\* Errno \*/\R" : "\R/\* End Errno \*/" ] = "";
|
||||
stripped["/\* fopen \*/\R" : "\R/\* End fopen \*/" ] = "";
|
||||
|
||||
return minify(rename_wav_namespace(stripped));
|
||||
}
|
||||
|
||||
miniaudio_h("/\* dr_wav_h begin \*/\R":"\R/\* dr_wav_h end \*/") = convert_wav_h(@(dr_wav_h["#ifndef dr_wav_h\R":"\R#endif /\* dr_wav_h \*/"]));
|
||||
miniaudio_h("/\* dr_wav_c begin \*/\R":"\R/\* dr_wav_c end \*/") = convert_wav_c(@(dr_wav_h["#ifndef dr_wav_c\R":"\R#endif /\* dr_wav_c \*/"]));
|
||||
|
||||
|
||||
// dr_flac
|
||||
rename_flac_namespace :: function(src:string) string
|
||||
{
|
||||
return @(src)
|
||||
["\bdrflac"] <= "ma_dr_flac"
|
||||
["\bDRFLAC"] <= "MA_DR_FLAC"
|
||||
["\bdr_flac"] <= "ma_dr_flac"
|
||||
["\bDR_FLAC"] <= "MA_DR_FLAC"
|
||||
["\bg_drflac"] <= "ma_dr_flac_g"
|
||||
|
||||
// Some common tokens will be namespaced as "ma_dr_flac" when we really want them to be "ma_".
|
||||
["\bma_dr_flac_int"] <= "ma_int"
|
||||
["\bma_dr_flac_uint"] <= "ma_uint"
|
||||
["\bma_dr_flac_bool"] <= "ma_bool"
|
||||
["\bma_dr_flac_uintptr"] <= "ma_uintptr"
|
||||
["\bMA_DR_FLAC_TRUE"] <= "MA_TRUE"
|
||||
["\bMA_DR_FLAC_FALSE"] <= "MA_FALSE"
|
||||
["\bMA_DR_FLAC_UINT64_MAX"] <= "MA_UINT64_MAX"
|
||||
["\bMA_DR_FLAC_32BIT"] <= "MA_32BIT"
|
||||
["\bMA_DR_FLAC_64BIT"] <= "MA_64BIT"
|
||||
["\bMA_DR_FLAC_ARM32"] <= "MA_ARM32"
|
||||
["\bMA_DR_FLAC_ARM64"] <= "MA_ARM64"
|
||||
["\bMA_DR_FLAC_X64"] <= "MA_X64"
|
||||
["\bMA_DR_FLAC_X86"] <= "MA_X86"
|
||||
["\bMA_DR_FLAC_ARM"] <= "MA_ARM"
|
||||
["\bMA_DR_FLAC_API"] <= "MA_API"
|
||||
["\bMA_DR_FLAC_PRIVATE"] <= "MA_PRIVATE"
|
||||
["\bMA_DR_FLAC_DLL"] <= "MA_DLL"
|
||||
["\bMA_DR_FLAC_DLL_IMPORT"] <= "MA_DLL_IMPORT"
|
||||
["\bMA_DR_FLAC_DLL_EXPORT"] <= "MA_DLL_EXPORT"
|
||||
["\bMA_DR_FLAC_DLL_PRIVATE"] <= "MA_DLL_PRIVATE"
|
||||
["\bma_dr_flac_result"] <= "ma_result"
|
||||
["\bma_dr_flac_allocation_callbacks"] <= "ma_allocation_callbacks"
|
||||
["\bMA_DR_FLAC_INLINE"] <= "MA_INLINE"
|
||||
["\bMA_DR_FLAC_SIZE_MAX"] <= "MA_SIZE_MAX"
|
||||
["\bma_dr_flac_result_from_errno"] <= "ma_result_from_errno"
|
||||
["\bma_dr_flac_fopen"] <= "ma_fopen"
|
||||
["\bma_dr_flac_wfopen"] <= "ma_wfopen"
|
||||
|
||||
// Result codes.
|
||||
["MA_DR_FLAC_SUCCESS"] <= "MA_SUCCESS"
|
||||
["MA_DR_FLAC_ERROR"] <= "MA_ERROR"
|
||||
["MA_DR_FLAC_AT_END"] <= "MA_AT_END"
|
||||
["MA_DR_FLAC_CRC_MISMATCH"] <= "MA_CRC_MISMATCH"
|
||||
;
|
||||
}
|
||||
|
||||
convert_flac_h :: function(src:string) string
|
||||
{
|
||||
stripped := @(src);
|
||||
stripped["/\* Sized Types \*/\R" : "\R/\* End Sized Types \*/" ] = "";
|
||||
stripped["/\* Architecture Detection \*/\R" : "\R/\* End Architecture Detection \*/"] = "";
|
||||
stripped["/\* Decorations \*/\R" : "\R/\* End Decorations \*/" ] = "";
|
||||
stripped["/\* Allocation Callbacks \*/\R" : "\R/\* End Allocation Callbacks \*/" ] = "";
|
||||
|
||||
return minify(rename_flac_namespace(stripped));
|
||||
}
|
||||
|
||||
convert_flac_c :: function(src:string) string
|
||||
{
|
||||
stripped := @(src);
|
||||
stripped["/\* Result Codes \*/\R" : "\R/\* End Result Codes \*/" ] = "";
|
||||
stripped["/\* Inline \*/\R" : "\R/\* End Inline \*/" ] = "";
|
||||
stripped["/\* SIZE_MAX \*/\R" : "\R/\* End SIZE_MAX \*/" ] = "";
|
||||
stripped["/\* Errno \*/\R" : "\R/\* End Errno \*/" ] = "";
|
||||
stripped["/\* fopen \*/\R" : "\R/\* End fopen \*/" ] = "";
|
||||
|
||||
return minify(rename_flac_namespace(stripped));
|
||||
}
|
||||
|
||||
miniaudio_h("/\* dr_flac_h begin \*/\R":"\R/\* dr_flac_h end \*/") = convert_flac_h(@(dr_flac_h["#ifndef dr_flac_h\R":"\R#endif /\* dr_flac_h \*/"]));
|
||||
miniaudio_h("/\* dr_flac_c begin \*/\R":"\R/\* dr_flac_c end \*/") = convert_flac_c(@(dr_flac_h["#ifndef dr_flac_c\R":"\R#endif /\* dr_flac_c \*/"]));
|
||||
|
||||
|
||||
|
||||
// dr_mp3
|
||||
rename_mp3_namespace :: function(src:string) string
|
||||
{
|
||||
return @(src)
|
||||
["\bdrmp3"] <= "ma_dr_mp3"
|
||||
["\bDRMP3"] <= "MA_DR_MP3"
|
||||
["\bdr_mp3"] <= "ma_dr_mp3"
|
||||
["\bDR_MP3"] <= "MA_DR_MP3"
|
||||
["\bg_drmp3"] <= "ma_dr_mp3_g"
|
||||
|
||||
// Some common tokens will be namespaced as "ma_dr_mp3" when we really want them to be "ma_".
|
||||
["\bma_dr_mp3_int"] <= "ma_int"
|
||||
["\bma_dr_mp3_uint"] <= "ma_uint"
|
||||
["\bma_dr_mp3_bool"] <= "ma_bool"
|
||||
["\bma_dr_mp3_uintptr"] <= "ma_uintptr"
|
||||
["\bMA_DR_MP3_TRUE"] <= "MA_TRUE"
|
||||
["\bMA_DR_MP3_FALSE"] <= "MA_FALSE"
|
||||
["\bMA_DR_MP3_UINT64_MAX"] <= "MA_UINT64_MAX"
|
||||
["\bMA_DR_MP3_32BIT"] <= "MA_32BIT"
|
||||
["\bMA_DR_MP3_64BIT"] <= "MA_64BIT"
|
||||
["\bMA_DR_MP3_ARM32"] <= "MA_ARM32"
|
||||
["\bMA_DR_MP3_ARM64"] <= "MA_ARM64"
|
||||
["\bMA_DR_MP3_X64"] <= "MA_X64"
|
||||
["\bMA_DR_MP3_X86"] <= "MA_X86"
|
||||
["\bMA_DR_MP3_ARM"] <= "MA_ARM"
|
||||
["\bMA_DR_MP3_API"] <= "MA_API"
|
||||
["\bMA_DR_MP3_PRIVATE"] <= "MA_PRIVATE"
|
||||
["\bMA_DR_MP3_DLL"] <= "MA_DLL"
|
||||
["\bMA_DR_MP3_DLL_IMPORT"] <= "MA_DLL_IMPORT"
|
||||
["\bMA_DR_MP3_DLL_EXPORT"] <= "MA_DLL_EXPORT"
|
||||
["\bMA_DR_MP3_DLL_PRIVATE"] <= "MA_DLL_PRIVATE"
|
||||
["\bma_dr_mp3_result"] <= "ma_result"
|
||||
["\bma_dr_mp3_allocation_callbacks"] <= "ma_allocation_callbacks"
|
||||
["\bMA_DR_MP3_INLINE"] <= "MA_INLINE"
|
||||
["\bMA_DR_MP3_SIZE_MAX"] <= "MA_SIZE_MAX"
|
||||
["\bma_dr_mp3_result_from_errno"] <= "ma_result_from_errno"
|
||||
["\bma_dr_mp3_fopen"] <= "ma_fopen"
|
||||
["\bma_dr_mp3_wfopen"] <= "ma_wfopen"
|
||||
|
||||
// Result codes.
|
||||
["MA_DR_MP3_SUCCESS"] <= "MA_SUCCESS"
|
||||
;
|
||||
}
|
||||
|
||||
convert_mp3_h :: function(src:string) string
|
||||
{
|
||||
stripped := @(src);
|
||||
stripped["/\* Sized Types \*/\R" : "\R/\* End Sized Types \*/" ] = "";
|
||||
stripped["/\* Decorations \*/\R" : "\R/\* End Decorations \*/" ] = "";
|
||||
stripped["/\* Result Codes \*/\R" : "\R/\* End Result Codes \*/" ] = "";
|
||||
stripped["/\* Inline \*/\R" : "\R/\* End Inline \*/" ] = "";
|
||||
stripped["/\* Allocation Callbacks \*/\R" : "\R/\* End Allocation Callbacks \*/" ] = "";
|
||||
|
||||
return minify(rename_mp3_namespace(stripped));
|
||||
}
|
||||
|
||||
convert_mp3_c :: function(src:string) string
|
||||
{
|
||||
stripped := @(src);
|
||||
stripped["/\* SIZE_MAX \*/\R" : "\R/\* End SIZE_MAX \*/" ] = "";
|
||||
stripped["/\* Errno \*/\R" : "\R/\* End Errno \*/" ] = "";
|
||||
stripped["/\* fopen \*/\R" : "\R/\* End fopen \*/" ] = "";
|
||||
|
||||
return minify(rename_mp3_namespace(stripped));
|
||||
}
|
||||
|
||||
miniaudio_h("/\* dr_mp3_h begin \*/\R":"\R/\* dr_mp3_h end \*/") = convert_mp3_h(@(dr_mp3_h["#ifndef dr_mp3_h\R":"\R#endif /\* dr_mp3_h \*/"]));
|
||||
miniaudio_h("/\* dr_mp3_c begin \*/\R":"\R/\* dr_mp3_c end \*/") = convert_mp3_c(@(dr_mp3_h["#ifndef dr_mp3_c\R":"\R#endif /\* dr_mp3_c \*/"]));
|
||||
|
||||
|
||||
// c89atomic
|
||||
rename_c89atomic_namespace :: function(src:string) string
|
||||
{
|
||||
return @(src)
|
||||
["\bc89atomic"] <= "ma_atomic"
|
||||
["\bC89ATOMIC"] <= "MA_ATOMIC"
|
||||
|
||||
// Some common tokens will be namespaced as "ma_atomic" when we really want them to be "ma_".
|
||||
["\bma_atomic_int"] <= "ma_int"
|
||||
["\bma_atomic_uint"] <= "ma_uint"
|
||||
["\bma_atomic_bool"] <= "ma_bool32"
|
||||
["\bMA_ATOMIC_32BIT"] <= "MA_32BIT"
|
||||
["\bMA_ATOMIC_64BIT"] <= "MA_64BIT"
|
||||
["\bMA_ATOMIC_ARM32"] <= "MA_ARM32"
|
||||
["\bMA_ATOMIC_ARM64"] <= "MA_ARM64"
|
||||
["\bMA_ATOMIC_X64"] <= "MA_X64"
|
||||
["\bMA_ATOMIC_X86"] <= "MA_X86"
|
||||
["\bMA_ATOMIC_ARM"] <= "MA_ARM"
|
||||
["\bMA_ATOMIC_INLINE"] <= "MA_INLINE"
|
||||
|
||||
// We have an "extern c89atomic_spinlock" in c89atomic.h, but since we're putting this into the implementation section we can just
|
||||
// drop the extern and not bother importing anything from c89atomic.c.
|
||||
["\bextern ma_atomic_spinlock"] <= "ma_atomic_spinlock"
|
||||
;
|
||||
}
|
||||
|
||||
convert_c89atomic_h :: function(src:string) string
|
||||
{
|
||||
stripped := @(src);
|
||||
stripped["/\* Sized Types \*/\R" : "\R/\* End Sized Types \*/" ] = "";
|
||||
stripped["/\* Architecture Detection \*/\R" : "\R/\* End Architecture Detection \*/"] = "";
|
||||
stripped["/\* Inline \*/\R" : "\R/\* End Inline \*/" ] = "";
|
||||
|
||||
return minify(rename_c89atomic_namespace(stripped));
|
||||
}
|
||||
|
||||
miniaudio_h("/\* c89atomic.h begin \*/\R":"\R/\* c89atomic.h end \*/") = convert_c89atomic_h(@(c89atomic_h["#ifndef c89atomic_h\R":"\R#endif /\* c89atomic_h \*/"]));
|
||||
|
||||
|
||||
// Cleanup. If we don't normalize line endings we'll fail to compile on old versions of GCC.
|
||||
cleanup :: function(src:string) string
|
||||
{
|
||||
return @(src)
|
||||
["\r\n"] <= "\n" // Normalize line endings to "\n". Needed for very old versions of GCC.
|
||||
["\t"] <= " " // Tabs to spaces.
|
||||
;
|
||||
}
|
||||
|
||||
miniaudio_h = cleanup(@(miniaudio_h));
|
||||
miniaudio_c = cleanup(@(miniaudio_c));
|
||||
@@ -0,0 +1,26 @@
|
||||
miniaudio_h :: <../miniaudio.h>;
|
||||
miniaudio_split_h := <../extras/miniaudio_split/miniaudio.h>;
|
||||
miniaudio_split_c := <../extras/miniaudio_split/miniaudio.c>;
|
||||
|
||||
header := @(miniaudio_h["/\*" : "\*/"]);
|
||||
footer := @(miniaudio_h["/\*\RThis software" : "\*/"]);
|
||||
|
||||
content_h : string;
|
||||
content_h["$"] = header;
|
||||
content_h["$"] = "\n";
|
||||
content_h["$"] = @(miniaudio_h["#ifndef miniaudio_h" : "#endif /\* miniaudio_h \*/"]);
|
||||
content_h["$"] = "\n\n";
|
||||
content_h["$"] = footer;
|
||||
content_h["$"] = "\n";
|
||||
|
||||
content_c : string;
|
||||
content_c["$"] = header;
|
||||
content_c["$"] = "\n";
|
||||
content_c["$"] = '#include "miniaudio.h"\n\n';
|
||||
content_c["$"] = @(miniaudio_h["#ifndef miniaudio_c" : "#endif /\* miniaudio_c \*/"]);
|
||||
content_c["$"] = "\n\n";
|
||||
content_c["$"] = footer;
|
||||
content_c["$"] = "\n";
|
||||
|
||||
miniaudio_split_h = content_h;
|
||||
miniaudio_split_c = content_c;
|
||||
@@ -0,0 +1,6 @@
|
||||
Sounds in this folder are used for testing purposes. They are all in the public domain. Below is a
|
||||
list of all the places I pulled these sounds from.
|
||||
|
||||
---
|
||||
|
||||
https://freesound.org/people/josefpres/sounds/788664/
|
||||
@@ -2,7 +2,7 @@
|
||||
This example show how a custom backend can be implemented.
|
||||
|
||||
This implements a full-featured SDL2 backend. It's intentionally built using the same paradigms as the built-in backends in order to make
|
||||
it suitable as a solid basis for a custom implementation. The SDL2 backend can be disabled with MA_NO_SDL, exactly like the build-in
|
||||
it suitable as a solid basis for a custom implementation. The SDL2 backend can be disabled with MA_NO_SDL, exactly like the built-in
|
||||
backends. It supports both runtime and compile-time linking and respects the MA_NO_RUNTIME_LINKING option. It also works on Emscripten
|
||||
which requires the `-s USE_SDL=2` option.
|
||||
|
||||
@@ -23,8 +23,7 @@ Custom backends are identified with the `ma_backend_custom` backend type. For th
|
||||
`ma_backend_custom` backend type because otherwise the built-in backends would always get chosen first and none of the code for the custom
|
||||
backends would actually get hit. By default, the `ma_backend_custom` backend is the lowest priority backend, except for `ma_backend_null`.
|
||||
*/
|
||||
#define MINIAUDIO_IMPLEMENTATION
|
||||
#include "../miniaudio.h"
|
||||
#include "../miniaudio.c"
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <emscripten.h>
|
||||
@@ -180,9 +179,6 @@ static ma_result ma_context_enumerate_devices__sdl(ma_context* pContext, ma_enum
|
||||
ma_bool32 cbResult;
|
||||
int iDevice;
|
||||
|
||||
MA_ASSERT(pContext != NULL);
|
||||
MA_ASSERT(callback != NULL);
|
||||
|
||||
/* Playback */
|
||||
if (!isTerminated) {
|
||||
int deviceCount = ((MA_PFN_SDL_GetNumAudioDevices)pContextEx->sdl.SDL_GetNumAudioDevices)(0);
|
||||
@@ -241,8 +237,6 @@ static ma_result ma_context_get_device_info__sdl(ma_context* pContext, ma_device
|
||||
const char* pDeviceName;
|
||||
#endif
|
||||
|
||||
MA_ASSERT(pContext != NULL);
|
||||
|
||||
if (pDeviceID == NULL) {
|
||||
if (deviceType == ma_device_type_playback) {
|
||||
pDeviceInfo->id.custom.i = 0;
|
||||
@@ -266,7 +260,7 @@ static ma_result ma_context_get_device_info__sdl(ma_context* pContext, ma_device
|
||||
of the device's _actual_ ideal format.
|
||||
|
||||
Note: With Emscripten, it looks like non-zero values need to be specified for desiredSpec. Whatever is specified in
|
||||
desiredSpec will be used by SDL since it uses it just does it's own format conversion internally. Therefore, from what
|
||||
desiredSpec will be used by SDL since it uses it just does its own format conversion internally. Therefore, from what
|
||||
I can tell, there's no real way to know the device's actual format which means I'm just going to fall back to the full
|
||||
range of channels and sample rates on Emscripten builds.
|
||||
*/
|
||||
@@ -322,8 +316,6 @@ void ma_audio_callback_capture__sdl(void* pUserData, ma_uint8* pBuffer, int buff
|
||||
{
|
||||
ma_device_ex* pDeviceEx = (ma_device_ex*)pUserData;
|
||||
|
||||
MA_ASSERT(pDeviceEx != NULL);
|
||||
|
||||
ma_device_handle_backend_data_callback((ma_device*)pDeviceEx, NULL, pBuffer, (ma_uint32)bufferSizeInBytes / ma_get_bytes_per_frame(pDeviceEx->device.capture.internalFormat, pDeviceEx->device.capture.internalChannels));
|
||||
}
|
||||
|
||||
@@ -331,8 +323,6 @@ void ma_audio_callback_playback__sdl(void* pUserData, ma_uint8* pBuffer, int buf
|
||||
{
|
||||
ma_device_ex* pDeviceEx = (ma_device_ex*)pUserData;
|
||||
|
||||
MA_ASSERT(pDeviceEx != NULL);
|
||||
|
||||
ma_device_handle_backend_data_callback((ma_device*)pDeviceEx, pBuffer, NULL, (ma_uint32)bufferSizeInBytes / ma_get_bytes_per_frame(pDeviceEx->device.playback.internalFormat, pDeviceEx->device.playback.internalChannels));
|
||||
}
|
||||
|
||||
@@ -344,9 +334,6 @@ static ma_result ma_device_init_internal__sdl(ma_device_ex* pDeviceEx, const ma_
|
||||
const char* pDeviceName;
|
||||
int deviceID;
|
||||
|
||||
MA_ASSERT(pDeviceEx != NULL);
|
||||
MA_ASSERT(pDescriptor != NULL);
|
||||
|
||||
/*
|
||||
SDL is a little bit awkward with specifying the buffer size, You need to specify the size of the buffer in frames, but since we may
|
||||
have requested a period size in milliseconds we'll need to convert, which depends on the sample rate. But there's a possibility that
|
||||
@@ -430,8 +417,6 @@ static ma_result ma_device_init__sdl(ma_device* pDevice, const ma_device_config*
|
||||
ma_context_ex* pContextEx = (ma_context_ex*)pDevice->pContext;
|
||||
ma_result result;
|
||||
|
||||
MA_ASSERT(pDevice != NULL);
|
||||
|
||||
/* SDL does not support loopback mode, so must return MA_DEVICE_TYPE_NOT_SUPPORTED if it's requested. */
|
||||
if (pConfig->deviceType == ma_device_type_loopback) {
|
||||
return MA_DEVICE_TYPE_NOT_SUPPORTED;
|
||||
@@ -463,8 +448,6 @@ static ma_result ma_device_uninit__sdl(ma_device* pDevice)
|
||||
ma_device_ex* pDeviceEx = (ma_device_ex*)pDevice;
|
||||
ma_context_ex* pContextEx = (ma_context_ex*)pDevice->pContext;
|
||||
|
||||
MA_ASSERT(pDevice != NULL);
|
||||
|
||||
if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) {
|
||||
((MA_PFN_SDL_CloseAudioDevice)pContextEx->sdl.SDL_CloseAudioDevice)(pDeviceEx->sdl.deviceIDCapture);
|
||||
}
|
||||
@@ -481,8 +464,6 @@ static ma_result ma_device_start__sdl(ma_device* pDevice)
|
||||
ma_device_ex* pDeviceEx = (ma_device_ex*)pDevice;
|
||||
ma_context_ex* pContextEx = (ma_context_ex*)pDevice->pContext;
|
||||
|
||||
MA_ASSERT(pDevice != NULL);
|
||||
|
||||
if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) {
|
||||
((MA_PFN_SDL_PauseAudioDevice)pContextEx->sdl.SDL_PauseAudioDevice)(pDeviceEx->sdl.deviceIDCapture, 0);
|
||||
}
|
||||
@@ -499,8 +480,6 @@ static ma_result ma_device_stop__sdl(ma_device* pDevice)
|
||||
ma_device_ex* pDeviceEx = (ma_device_ex*)pDevice;
|
||||
ma_context_ex* pContextEx = (ma_context_ex*)pDevice->pContext;
|
||||
|
||||
MA_ASSERT(pDevice != NULL);
|
||||
|
||||
if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) {
|
||||
((MA_PFN_SDL_PauseAudioDevice)pContextEx->sdl.SDL_PauseAudioDevice)(pDeviceEx->sdl.deviceIDCapture, 1);
|
||||
}
|
||||
@@ -516,8 +495,6 @@ static ma_result ma_context_uninit__sdl(ma_context* pContext)
|
||||
{
|
||||
ma_context_ex* pContextEx = (ma_context_ex*)pContext;
|
||||
|
||||
MA_ASSERT(pContext != NULL);
|
||||
|
||||
((MA_PFN_SDL_QuitSubSystem)pContextEx->sdl.SDL_QuitSubSystem)(MA_SDL_INIT_AUDIO);
|
||||
|
||||
/* Close the handle to the SDL shared object last. */
|
||||
@@ -545,8 +522,6 @@ static ma_result ma_context_init__sdl(ma_context* pContext, const ma_context_con
|
||||
#endif
|
||||
};
|
||||
|
||||
MA_ASSERT(pContext != NULL);
|
||||
|
||||
(void)pConfig;
|
||||
|
||||
/* Check if we have SDL2 installed somewhere. If not it's not usable and we need to abort. */
|
||||
@@ -641,15 +616,8 @@ Main program starts here.
|
||||
|
||||
void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount)
|
||||
{
|
||||
MA_ASSERT(pDevice->playback.channels == DEVICE_CHANNELS);
|
||||
|
||||
if (pDevice->type == ma_device_type_playback) {
|
||||
ma_waveform* pSineWave;
|
||||
|
||||
pSineWave = (ma_waveform*)pDevice->pUserData;
|
||||
MA_ASSERT(pSineWave != NULL);
|
||||
|
||||
ma_waveform_read_pcm_frames(pSineWave, pOutput, frameCount, NULL);
|
||||
ma_waveform_read_pcm_frames((ma_waveform*)pDevice->pUserData, pOutput, frameCount, NULL);
|
||||
}
|
||||
|
||||
if (pDevice->type == ma_device_type_duplex) {
|
||||
@@ -737,4 +705,4 @@ int main(int argc, char** argv)
|
||||
(void)argv;
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,167 +15,12 @@ the decoder via the decoder config (`ma_decoder_config`). You need to implement
|
||||
of your custom decoders. See `ma_decoding_backend_vtable` for the functions you need to implement.
|
||||
The `onInitFile`, `onInitFileW` and `onInitMemory` functions are optional.
|
||||
*/
|
||||
#define MA_NO_VORBIS /* Disable the built-in Vorbis decoder to ensure the libvorbis decoder is picked. */
|
||||
#define MA_NO_OPUS /* Disable the (not yet implemented) built-in Opus decoder to ensure the libopus decoder is picked. */
|
||||
#define MINIAUDIO_IMPLEMENTATION
|
||||
#include "../miniaudio.h"
|
||||
#include "../extras/miniaudio_libvorbis.h"
|
||||
#include "../extras/miniaudio_libopus.h"
|
||||
#include "../miniaudio.c"
|
||||
#include "../extras/decoders/libvorbis/miniaudio_libvorbis.c"
|
||||
#include "../extras/decoders/libopus/miniaudio_libopus.c"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
static ma_result ma_decoding_backend_init__libvorbis(void* pUserData, ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void* pReadSeekTellUserData, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend)
|
||||
{
|
||||
ma_result result;
|
||||
ma_libvorbis* pVorbis;
|
||||
|
||||
(void)pUserData;
|
||||
|
||||
pVorbis = (ma_libvorbis*)ma_malloc(sizeof(*pVorbis), pAllocationCallbacks);
|
||||
if (pVorbis == NULL) {
|
||||
return MA_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
result = ma_libvorbis_init(onRead, onSeek, onTell, pReadSeekTellUserData, pConfig, pAllocationCallbacks, pVorbis);
|
||||
if (result != MA_SUCCESS) {
|
||||
ma_free(pVorbis, pAllocationCallbacks);
|
||||
return result;
|
||||
}
|
||||
|
||||
*ppBackend = pVorbis;
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
|
||||
static ma_result ma_decoding_backend_init_file__libvorbis(void* pUserData, const char* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend)
|
||||
{
|
||||
ma_result result;
|
||||
ma_libvorbis* pVorbis;
|
||||
|
||||
(void)pUserData;
|
||||
|
||||
pVorbis = (ma_libvorbis*)ma_malloc(sizeof(*pVorbis), pAllocationCallbacks);
|
||||
if (pVorbis == NULL) {
|
||||
return MA_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
result = ma_libvorbis_init_file(pFilePath, pConfig, pAllocationCallbacks, pVorbis);
|
||||
if (result != MA_SUCCESS) {
|
||||
ma_free(pVorbis, pAllocationCallbacks);
|
||||
return result;
|
||||
}
|
||||
|
||||
*ppBackend = pVorbis;
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
|
||||
static void ma_decoding_backend_uninit__libvorbis(void* pUserData, ma_data_source* pBackend, const ma_allocation_callbacks* pAllocationCallbacks)
|
||||
{
|
||||
ma_libvorbis* pVorbis = (ma_libvorbis*)pBackend;
|
||||
|
||||
(void)pUserData;
|
||||
|
||||
ma_libvorbis_uninit(pVorbis, pAllocationCallbacks);
|
||||
ma_free(pVorbis, pAllocationCallbacks);
|
||||
}
|
||||
|
||||
static ma_result ma_decoding_backend_get_channel_map__libvorbis(void* pUserData, ma_data_source* pBackend, ma_channel* pChannelMap, size_t channelMapCap)
|
||||
{
|
||||
ma_libvorbis* pVorbis = (ma_libvorbis*)pBackend;
|
||||
|
||||
(void)pUserData;
|
||||
|
||||
return ma_libvorbis_get_data_format(pVorbis, NULL, NULL, NULL, pChannelMap, channelMapCap);
|
||||
}
|
||||
|
||||
static ma_decoding_backend_vtable g_ma_decoding_backend_vtable_libvorbis =
|
||||
{
|
||||
ma_decoding_backend_init__libvorbis,
|
||||
ma_decoding_backend_init_file__libvorbis,
|
||||
NULL, /* onInitFileW() */
|
||||
NULL, /* onInitMemory() */
|
||||
ma_decoding_backend_uninit__libvorbis
|
||||
};
|
||||
|
||||
|
||||
|
||||
static ma_result ma_decoding_backend_init__libopus(void* pUserData, ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void* pReadSeekTellUserData, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend)
|
||||
{
|
||||
ma_result result;
|
||||
ma_libopus* pOpus;
|
||||
|
||||
(void)pUserData;
|
||||
|
||||
pOpus = (ma_libopus*)ma_malloc(sizeof(*pOpus), pAllocationCallbacks);
|
||||
if (pOpus == NULL) {
|
||||
return MA_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
result = ma_libopus_init(onRead, onSeek, onTell, pReadSeekTellUserData, pConfig, pAllocationCallbacks, pOpus);
|
||||
if (result != MA_SUCCESS) {
|
||||
ma_free(pOpus, pAllocationCallbacks);
|
||||
return result;
|
||||
}
|
||||
|
||||
*ppBackend = pOpus;
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
|
||||
static ma_result ma_decoding_backend_init_file__libopus(void* pUserData, const char* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend)
|
||||
{
|
||||
ma_result result;
|
||||
ma_libopus* pOpus;
|
||||
|
||||
(void)pUserData;
|
||||
|
||||
pOpus = (ma_libopus*)ma_malloc(sizeof(*pOpus), pAllocationCallbacks);
|
||||
if (pOpus == NULL) {
|
||||
return MA_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
result = ma_libopus_init_file(pFilePath, pConfig, pAllocationCallbacks, pOpus);
|
||||
if (result != MA_SUCCESS) {
|
||||
ma_free(pOpus, pAllocationCallbacks);
|
||||
return result;
|
||||
}
|
||||
|
||||
*ppBackend = pOpus;
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
|
||||
static void ma_decoding_backend_uninit__libopus(void* pUserData, ma_data_source* pBackend, const ma_allocation_callbacks* pAllocationCallbacks)
|
||||
{
|
||||
ma_libopus* pOpus = (ma_libopus*)pBackend;
|
||||
|
||||
(void)pUserData;
|
||||
|
||||
ma_libopus_uninit(pOpus, pAllocationCallbacks);
|
||||
ma_free(pOpus, pAllocationCallbacks);
|
||||
}
|
||||
|
||||
static ma_result ma_decoding_backend_get_channel_map__libopus(void* pUserData, ma_data_source* pBackend, ma_channel* pChannelMap, size_t channelMapCap)
|
||||
{
|
||||
ma_libopus* pOpus = (ma_libopus*)pBackend;
|
||||
|
||||
(void)pUserData;
|
||||
|
||||
return ma_libopus_get_data_format(pOpus, NULL, NULL, NULL, pChannelMap, channelMapCap);
|
||||
}
|
||||
|
||||
static ma_decoding_backend_vtable g_ma_decoding_backend_vtable_libopus =
|
||||
{
|
||||
ma_decoding_backend_init__libopus,
|
||||
ma_decoding_backend_init_file__libopus,
|
||||
NULL, /* onInitFileW() */
|
||||
NULL, /* onInitMemory() */
|
||||
ma_decoding_backend_uninit__libopus
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount)
|
||||
{
|
||||
@@ -206,8 +51,8 @@ int main(int argc, char** argv)
|
||||
*/
|
||||
ma_decoding_backend_vtable* pCustomBackendVTables[] =
|
||||
{
|
||||
&g_ma_decoding_backend_vtable_libvorbis,
|
||||
&g_ma_decoding_backend_vtable_libopus
|
||||
ma_decoding_backend_libvorbis,
|
||||
ma_decoding_backend_libopus
|
||||
};
|
||||
|
||||
|
||||
@@ -219,7 +64,7 @@ int main(int argc, char** argv)
|
||||
|
||||
/* Initialize the decoder. */
|
||||
decoderConfig = ma_decoder_config_init_default();
|
||||
decoderConfig.pCustomBackendUserData = NULL; /* In this example our backend objects are contained within a ma_decoder_ex object to avoid a malloc. Our vtables need to know about this. */
|
||||
decoderConfig.pCustomBackendUserData = NULL; /* None of our decoders require user data, so this can be set to null. */
|
||||
decoderConfig.ppCustomBackendVTables = pCustomBackendVTables;
|
||||
decoderConfig.customBackendCount = sizeof(pCustomBackendVTables) / sizeof(pCustomBackendVTables[0]);
|
||||
|
||||
@@ -267,4 +112,4 @@ int main(int argc, char** argv)
|
||||
ma_decoder_uninit(&decoder);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,166 +5,12 @@ This is the same as the custom_decoder example, only it's used with the high lev
|
||||
rather than the low level decoding API. You can use this to add support for Opus to your games, for
|
||||
example (via libopus).
|
||||
*/
|
||||
#define MA_NO_VORBIS /* Disable the built-in Vorbis decoder to ensure the libvorbis decoder is picked. */
|
||||
#define MA_NO_OPUS /* Disable the (not yet implemented) built-in Opus decoder to ensure the libopus decoder is picked. */
|
||||
#define MINIAUDIO_IMPLEMENTATION
|
||||
#include "../miniaudio.h"
|
||||
#include "../extras/miniaudio_libvorbis.h"
|
||||
#include "../extras/miniaudio_libopus.h"
|
||||
#include "../miniaudio.c"
|
||||
#include "../extras/decoders/libvorbis/miniaudio_libvorbis.c"
|
||||
#include "../extras/decoders/libopus/miniaudio_libopus.c"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
static ma_result ma_decoding_backend_init__libvorbis(void* pUserData, ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void* pReadSeekTellUserData, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend)
|
||||
{
|
||||
ma_result result;
|
||||
ma_libvorbis* pVorbis;
|
||||
|
||||
(void)pUserData;
|
||||
|
||||
pVorbis = (ma_libvorbis*)ma_malloc(sizeof(*pVorbis), pAllocationCallbacks);
|
||||
if (pVorbis == NULL) {
|
||||
return MA_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
result = ma_libvorbis_init(onRead, onSeek, onTell, pReadSeekTellUserData, pConfig, pAllocationCallbacks, pVorbis);
|
||||
if (result != MA_SUCCESS) {
|
||||
ma_free(pVorbis, pAllocationCallbacks);
|
||||
return result;
|
||||
}
|
||||
|
||||
*ppBackend = pVorbis;
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
|
||||
static ma_result ma_decoding_backend_init_file__libvorbis(void* pUserData, const char* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend)
|
||||
{
|
||||
ma_result result;
|
||||
ma_libvorbis* pVorbis;
|
||||
|
||||
(void)pUserData;
|
||||
|
||||
pVorbis = (ma_libvorbis*)ma_malloc(sizeof(*pVorbis), pAllocationCallbacks);
|
||||
if (pVorbis == NULL) {
|
||||
return MA_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
result = ma_libvorbis_init_file(pFilePath, pConfig, pAllocationCallbacks, pVorbis);
|
||||
if (result != MA_SUCCESS) {
|
||||
ma_free(pVorbis, pAllocationCallbacks);
|
||||
return result;
|
||||
}
|
||||
|
||||
*ppBackend = pVorbis;
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
|
||||
static void ma_decoding_backend_uninit__libvorbis(void* pUserData, ma_data_source* pBackend, const ma_allocation_callbacks* pAllocationCallbacks)
|
||||
{
|
||||
ma_libvorbis* pVorbis = (ma_libvorbis*)pBackend;
|
||||
|
||||
(void)pUserData;
|
||||
|
||||
ma_libvorbis_uninit(pVorbis, pAllocationCallbacks);
|
||||
ma_free(pVorbis, pAllocationCallbacks);
|
||||
}
|
||||
|
||||
static ma_result ma_decoding_backend_get_channel_map__libvorbis(void* pUserData, ma_data_source* pBackend, ma_channel* pChannelMap, size_t channelMapCap)
|
||||
{
|
||||
ma_libvorbis* pVorbis = (ma_libvorbis*)pBackend;
|
||||
|
||||
(void)pUserData;
|
||||
|
||||
return ma_libvorbis_get_data_format(pVorbis, NULL, NULL, NULL, pChannelMap, channelMapCap);
|
||||
}
|
||||
|
||||
static ma_decoding_backend_vtable g_ma_decoding_backend_vtable_libvorbis =
|
||||
{
|
||||
ma_decoding_backend_init__libvorbis,
|
||||
ma_decoding_backend_init_file__libvorbis,
|
||||
NULL, /* onInitFileW() */
|
||||
NULL, /* onInitMemory() */
|
||||
ma_decoding_backend_uninit__libvorbis
|
||||
};
|
||||
|
||||
|
||||
|
||||
static ma_result ma_decoding_backend_init__libopus(void* pUserData, ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void* pReadSeekTellUserData, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend)
|
||||
{
|
||||
ma_result result;
|
||||
ma_libopus* pOpus;
|
||||
|
||||
(void)pUserData;
|
||||
|
||||
pOpus = (ma_libopus*)ma_malloc(sizeof(*pOpus), pAllocationCallbacks);
|
||||
if (pOpus == NULL) {
|
||||
return MA_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
result = ma_libopus_init(onRead, onSeek, onTell, pReadSeekTellUserData, pConfig, pAllocationCallbacks, pOpus);
|
||||
if (result != MA_SUCCESS) {
|
||||
ma_free(pOpus, pAllocationCallbacks);
|
||||
return result;
|
||||
}
|
||||
|
||||
*ppBackend = pOpus;
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
|
||||
static ma_result ma_decoding_backend_init_file__libopus(void* pUserData, const char* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend)
|
||||
{
|
||||
ma_result result;
|
||||
ma_libopus* pOpus;
|
||||
|
||||
(void)pUserData;
|
||||
|
||||
pOpus = (ma_libopus*)ma_malloc(sizeof(*pOpus), pAllocationCallbacks);
|
||||
if (pOpus == NULL) {
|
||||
return MA_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
result = ma_libopus_init_file(pFilePath, pConfig, pAllocationCallbacks, pOpus);
|
||||
if (result != MA_SUCCESS) {
|
||||
ma_free(pOpus, pAllocationCallbacks);
|
||||
return result;
|
||||
}
|
||||
|
||||
*ppBackend = pOpus;
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
|
||||
static void ma_decoding_backend_uninit__libopus(void* pUserData, ma_data_source* pBackend, const ma_allocation_callbacks* pAllocationCallbacks)
|
||||
{
|
||||
ma_libopus* pOpus = (ma_libopus*)pBackend;
|
||||
|
||||
(void)pUserData;
|
||||
|
||||
ma_libopus_uninit(pOpus, pAllocationCallbacks);
|
||||
ma_free(pOpus, pAllocationCallbacks);
|
||||
}
|
||||
|
||||
static ma_result ma_decoding_backend_get_channel_map__libopus(void* pUserData, ma_data_source* pBackend, ma_channel* pChannelMap, size_t channelMapCap)
|
||||
{
|
||||
ma_libopus* pOpus = (ma_libopus*)pBackend;
|
||||
|
||||
(void)pUserData;
|
||||
|
||||
return ma_libopus_get_data_format(pOpus, NULL, NULL, NULL, pChannelMap, channelMapCap);
|
||||
}
|
||||
|
||||
static ma_decoding_backend_vtable g_ma_decoding_backend_vtable_libopus =
|
||||
{
|
||||
ma_decoding_backend_init__libopus,
|
||||
ma_decoding_backend_init_file__libopus,
|
||||
NULL, /* onInitFileW() */
|
||||
NULL, /* onInitMemory() */
|
||||
ma_decoding_backend_uninit__libopus
|
||||
};
|
||||
|
||||
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
@@ -180,8 +26,8 @@ int main(int argc, char** argv)
|
||||
*/
|
||||
ma_decoding_backend_vtable* pCustomBackendVTables[] =
|
||||
{
|
||||
&g_ma_decoding_backend_vtable_libvorbis,
|
||||
&g_ma_decoding_backend_vtable_libopus
|
||||
ma_decoding_backend_libvorbis,
|
||||
ma_decoding_backend_libopus
|
||||
};
|
||||
|
||||
|
||||
@@ -226,5 +72,8 @@ int main(int argc, char** argv)
|
||||
printf("Press Enter to quit...");
|
||||
getchar();
|
||||
|
||||
ma_engine_uninit(&engine);
|
||||
ma_resource_manager_uninit(&resourceManager);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,9 +31,7 @@ starting the chain from the start again. It is also seeking the head data source
|
||||
so that playback starts from the start as expected. You do not need to seek non-head items back to
|
||||
the start as miniaudio will do that for you internally.
|
||||
*/
|
||||
#define MA_EXPERIMENTAL__DATA_LOOPING_AND_CHAINING
|
||||
#define MINIAUDIO_IMPLEMENTATION
|
||||
#include "../miniaudio.h"
|
||||
#include "../miniaudio.c"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
@@ -49,7 +47,11 @@ ma_decoder* g_pDecoders;
|
||||
|
||||
static ma_data_source* next_callback_tail(ma_data_source* pDataSource)
|
||||
{
|
||||
MA_ASSERT(g_decoderCount > 0); /* <-- We check for this in main() so should never happen. */
|
||||
(void)pDataSource; /* Unused. */
|
||||
|
||||
if (g_decoderCount > 0) { /* <-- We check for this in main() so should never happen. */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
This will be fired when the last item in the chain has reached the end. In this example we want
|
||||
@@ -131,15 +133,15 @@ int main(int argc, char** argv)
|
||||
deviceConfig.dataCallback = data_callback;
|
||||
deviceConfig.pUserData = NULL;
|
||||
|
||||
if (ma_device_init(NULL, &deviceConfig, &device) != MA_SUCCESS) {
|
||||
result = ma_device_init(NULL, &deviceConfig, &device);
|
||||
if (result != MA_SUCCESS) {
|
||||
printf("Failed to open playback device.\n");
|
||||
result = -1;
|
||||
goto done_decoders;
|
||||
}
|
||||
|
||||
if (ma_device_start(&device) != MA_SUCCESS) {
|
||||
result = ma_device_start(&device);
|
||||
if (result != MA_SUCCESS) {
|
||||
printf("Failed to start playback device.\n");
|
||||
result = -1;
|
||||
goto done;
|
||||
}
|
||||
|
||||
@@ -156,4 +158,4 @@ done_decoders:
|
||||
free(g_pDecoders);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,13 +6,12 @@ called `ma_vocoder_node` is used to achieve the effect which can be found in the
|
||||
the miniaudio repository. The vocoder node uses https://github.com/blastbay/voclib to achieve the
|
||||
effect.
|
||||
*/
|
||||
#define MINIAUDIO_IMPLEMENTATION
|
||||
#include "../miniaudio.h"
|
||||
#include "../miniaudio.c"
|
||||
#include "../extras/nodes/ma_vocoder_node/ma_vocoder_node.c"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#define DEVICE_FORMAT ma_format_f32; /* Must always be f32 for this example because the node graph system only works with this. */
|
||||
#define DEVICE_FORMAT ma_format_f32 /* Must always be f32 for this example because the node graph system only works with this. */
|
||||
#define DEVICE_CHANNELS 1 /* For this example, always set to 1. */
|
||||
|
||||
static ma_waveform g_sourceData; /* The underlying data source of the source node. */
|
||||
@@ -24,8 +23,13 @@ static ma_node_graph g_nodeGraph;
|
||||
|
||||
void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount)
|
||||
{
|
||||
MA_ASSERT(pDevice->capture.format == pDevice->playback.format);
|
||||
MA_ASSERT(pDevice->capture.channels == pDevice->playback.channels);
|
||||
/*
|
||||
This example assumes the playback and capture sides use the same format and channel count. The
|
||||
format must be f32.
|
||||
*/
|
||||
if (pDevice->capture.format != DEVICE_FORMAT || pDevice->playback.format != DEVICE_FORMAT || pDevice->capture.channels != pDevice->playback.channels) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
The node graph system is a pulling style of API. At the lowest level of the chain will be a
|
||||
|
||||
@@ -14,8 +14,7 @@ Using a shared resource manager, as we do in this example, is useful for when yo
|
||||
multiple engines so that you can output to multiple playback devices simultaneoulys. An example
|
||||
might be a local co-op multiplayer game where each player has their own headphones.
|
||||
*/
|
||||
#define MINIAUDIO_IMPLEMENTATION
|
||||
#include "../miniaudio.h"
|
||||
#include "../miniaudio.c"
|
||||
|
||||
#define MAX_DEVICES 2
|
||||
#define MAX_SOUNDS 32
|
||||
|
||||
@@ -13,8 +13,7 @@ This example is playing only a single sound at a time which means only a single
|
||||
it being used. If you want to play multiple sounds at the same time, even if they're for the same
|
||||
sound file, you need multiple `ma_sound` objects.
|
||||
*/
|
||||
#define MINIAUDIO_IMPLEMENTATION
|
||||
#include "../miniaudio.h"
|
||||
#include "../miniaudio.c"
|
||||
|
||||
#define DELAY_IN_SECONDS 0.2f
|
||||
#define DECAY 0.25f /* Volume falloff for each echo. */
|
||||
@@ -101,4 +100,4 @@ int main(int argc, char** argv)
|
||||
ma_engine_uninit(&g_engine);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,7 @@ This example demonstrates how to initialize an audio engine and play a sound.
|
||||
|
||||
This will play the sound specified on the command line.
|
||||
*/
|
||||
#define MINIAUDIO_IMPLEMENTATION
|
||||
#include "../miniaudio.h"
|
||||
#include "../miniaudio.c"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
@@ -9,11 +9,10 @@ This example will load the sound specified on the command line and rotate it aro
|
||||
head.
|
||||
*/
|
||||
#define MA_NO_DEVICE_IO /* <-- Disables the `ma_device` API. We don't need that in this example since SDL will be doing that part for us. */
|
||||
#define MINIAUDIO_IMPLEMENTATION
|
||||
#include "../miniaudio.h"
|
||||
#include "../miniaudio.c"
|
||||
|
||||
#define SDL_MAIN_HANDLED
|
||||
#include <SDL.h> /* Change this to your include location. Might be <SDL2/SDL.h>. */
|
||||
#include <SDL2/SDL.h> /* Change this to your include location. Might be <SDL.h>. */
|
||||
|
||||
#define CHANNELS 2 /* Must be stereo for this example. */
|
||||
#define SAMPLE_RATE 48000
|
||||
@@ -23,8 +22,12 @@ static ma_sound g_sound; /* This example will play only a single soun
|
||||
|
||||
void data_callback(void* pUserData, ma_uint8* pBuffer, int bufferSizeInBytes)
|
||||
{
|
||||
ma_uint32 bufferSizeInFrames;
|
||||
|
||||
(void)pUserData;
|
||||
|
||||
/* Reading is just a matter of reading straight from the engine. */
|
||||
ma_uint32 bufferSizeInFrames = (ma_uint32)bufferSizeInBytes / ma_get_bytes_per_frame(ma_format_f32, ma_engine_get_channels(&g_engine));
|
||||
bufferSizeInFrames = (ma_uint32)bufferSizeInBytes / ma_get_bytes_per_frame(ma_format_f32, ma_engine_get_channels(&g_engine));
|
||||
ma_engine_read_pcm_frames(&g_engine, pBuffer, bufferSizeInFrames, NULL);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,23 +8,26 @@ By implementing this as a node, it can be plugged into any position within the g
|
||||
channel count of this node is always stereo.
|
||||
|
||||
Steam Audio requires fixed sized processing, the size of which must be specified at initialization
|
||||
time of the IPLBinauralEffect and IPLHRTF objects. This creates a problem because the node graph
|
||||
will at times need to break down processing into smaller chunks for it's internal processing. The
|
||||
node graph internally will read into a temporary buffer which is then mixed into the final output
|
||||
buffer. This temporary buffer is allocated on the stack and is a fixed size. However, variability
|
||||
comes into play because the channel count of the node is variable. It's not safe to just blindly
|
||||
process the effect with the frame count specified in miniaudio's node processing callback. Doing so
|
||||
results in glitching. To work around this, this example is just setting the update size to a known
|
||||
value that works (256). If it's set to something too big it'll exceed miniaudio's processing size
|
||||
used by the node graph. Alternatively you could use some kind of intermediary cache which
|
||||
accumulates input data until enough is available and then do the processing. Ideally, Steam Audio
|
||||
would support variable sized updates which would avoid this whole mess entirely.
|
||||
time of the IPLBinauralEffect and IPLHRTF objects. To ensure miniaudio and Steam Audio are
|
||||
consistent, you must set the period size in the engine config to be consistent with the frame size
|
||||
you specify in your IPLAudioSettings object. If for some reason you want the period size of the
|
||||
engine to be different to that of your Steam Audio configuration, you'll need to implement a sort
|
||||
of buffering solution to your node.
|
||||
*/
|
||||
#define MINIAUDIO_IMPLEMENTATION
|
||||
#include "../miniaudio.h"
|
||||
#include "../miniaudio.c"
|
||||
|
||||
#include <stdint.h> /* Required for uint32_t which is used by STEAMAUDIO_VERSION, and a random use of uint8_t. If there's a Steam Audio maintainer reading this, that needs to be fixed to use IPLuint32 and IPLuint8. */
|
||||
|
||||
/* Need to silence some warnings from the Steam Audio headers. */
|
||||
#if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)))
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wlong-long"
|
||||
#pragma GCC diagnostic ignored "-Wpedantic"
|
||||
#endif
|
||||
#include <phonon.h> /* Steam Audio */
|
||||
#include <stdint.h> /* Required for uint32_t which is used by STEAMAUDIO_VERSION. That dependency needs to be removed from Steam Audio - use IPLuint32 or "unsigned int" instead! */
|
||||
#if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)))
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
#define FORMAT ma_format_f32 /* Must be floating point. */
|
||||
#define CHANNELS 2 /* Must be stereo for this example. */
|
||||
@@ -98,6 +101,7 @@ static void ma_steamaudio_binaural_node_process_pcm_frames(ma_node* pNode, const
|
||||
ma_uint32 totalFramesToProcess = *pFrameCountOut;
|
||||
ma_uint32 totalFramesProcessed = 0;
|
||||
|
||||
MA_ZERO_OBJECT(&binauralParams);
|
||||
binauralParams.direction.x = pBinauralNode->direction.x;
|
||||
binauralParams.direction.y = pBinauralNode->direction.y;
|
||||
binauralParams.direction.z = pBinauralNode->direction.z;
|
||||
@@ -123,7 +127,7 @@ static void ma_steamaudio_binaural_node_process_pcm_frames(ma_node* pNode, const
|
||||
pBinauralNode->ppBuffersIn[0] = (float*)ma_offset_pcm_frames_const_ptr_f32(ppFramesIn[0], totalFramesProcessed, 1);
|
||||
} else {
|
||||
/* Slow path. Need to deinterleave the input data. */
|
||||
ma_deinterleave_pcm_frames(ma_format_f32, inputBufferDesc.numChannels, framesToProcessThisIteration, ma_offset_pcm_frames_const_ptr_f32(ppFramesIn[0], totalFramesProcessed, inputBufferDesc.numChannels), pBinauralNode->ppBuffersIn);
|
||||
ma_deinterleave_pcm_frames(ma_format_f32, inputBufferDesc.numChannels, framesToProcessThisIteration, ma_offset_pcm_frames_const_ptr_f32(ppFramesIn[0], totalFramesProcessed, inputBufferDesc.numChannels), (void**)&pBinauralNode->ppBuffersIn[0]);
|
||||
}
|
||||
|
||||
inputBufferDesc.data = pBinauralNode->ppBuffersIn;
|
||||
@@ -133,7 +137,7 @@ static void ma_steamaudio_binaural_node_process_pcm_frames(ma_node* pNode, const
|
||||
iplBinauralEffectApply(pBinauralNode->iplEffect, &binauralParams, &inputBufferDesc, &outputBufferDesc);
|
||||
|
||||
/* Interleave straight into the output buffer. */
|
||||
ma_interleave_pcm_frames(ma_format_f32, 2, framesToProcessThisIteration, pBinauralNode->ppBuffersOut, ma_offset_pcm_frames_ptr_f32(ppFramesOut[0], totalFramesProcessed, 2));
|
||||
ma_interleave_pcm_frames(ma_format_f32, 2, framesToProcessThisIteration, (const void**)&pBinauralNode->ppBuffersOut[0], ma_offset_pcm_frames_ptr_f32(ppFramesOut[0], totalFramesProcessed, 2));
|
||||
|
||||
/* Advance. */
|
||||
totalFramesProcessed += framesToProcessThisIteration;
|
||||
@@ -284,8 +288,16 @@ int main(int argc, char** argv)
|
||||
|
||||
/* The engine needs to be initialized first. */
|
||||
engineConfig = ma_engine_config_init();
|
||||
engineConfig.channels = CHANNELS;
|
||||
engineConfig.sampleRate = SAMPLE_RATE;
|
||||
engineConfig.channels = CHANNELS;
|
||||
engineConfig.sampleRate = SAMPLE_RATE;
|
||||
|
||||
/*
|
||||
Steam Audio requires processing in fixed sized chunks. Setting the period size in the engine config will
|
||||
ensure our updates happen in predicably sized chunks as required by Steam Audio.
|
||||
|
||||
Note that the configuration of Steam Audio below (IPLAudioSettings) will use this variable to specify the
|
||||
update size to ensure it remains consistent.
|
||||
*/
|
||||
engineConfig.periodSizeInFrames = 256;
|
||||
|
||||
result = ma_engine_init(&engineConfig, &g_engine);
|
||||
@@ -305,6 +317,9 @@ int main(int argc, char** argv)
|
||||
be documented. If this is for some kind of buffer management with FFT or something, then this
|
||||
need not be exposed to the public API. There should be no need for the public API to require a
|
||||
fixed sized update.
|
||||
|
||||
It's important that this be set to the periodSizeInFrames specified in the engine config above.
|
||||
This ensures updates on both the miniaudio side and the Steam Audio side are consistent.
|
||||
*/
|
||||
iplAudioSettings.frameSize = engineConfig.periodSizeInFrames;
|
||||
|
||||
@@ -322,7 +337,8 @@ int main(int argc, char** argv)
|
||||
|
||||
/* IPLHRTF */
|
||||
MA_ZERO_OBJECT(&iplHRTFSettings);
|
||||
iplHRTFSettings.type = IPL_HRTFTYPE_DEFAULT;
|
||||
iplHRTFSettings.type = IPL_HRTFTYPE_DEFAULT;
|
||||
iplHRTFSettings.volume = 1;
|
||||
|
||||
result = ma_result_from_IPLerror(iplHRTFCreate(iplContext, &iplAudioSettings, &iplHRTFSettings, &iplHRTF));
|
||||
if (result != MA_SUCCESS) {
|
||||
@@ -429,4 +445,4 @@ int main(int argc, char** argv)
|
||||
ma_engine_uninit(&g_engine);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,7 @@ Instead you would probably want to do a custom data source that handles underrun
|
||||
the ring buffer and deals with desyncs between capture and playback. In the future this example
|
||||
may be updated to make use of a more advanced data source that handles all of this.
|
||||
*/
|
||||
#define MINIAUDIO_IMPLEMENTATION
|
||||
#include "../miniaudio.h"
|
||||
#include "../miniaudio.c"
|
||||
|
||||
static ma_pcm_rb rb;
|
||||
static ma_device device;
|
||||
@@ -28,6 +27,8 @@ void capture_data_callback(ma_device* pDevice, void* pFramesOut, const void* pFr
|
||||
ma_result result;
|
||||
ma_uint32 framesWritten;
|
||||
|
||||
(void)pFramesOut;
|
||||
|
||||
/* We need to write to the ring buffer. Need to do this in a loop. */
|
||||
framesWritten = 0;
|
||||
while (framesWritten < frameCount) {
|
||||
@@ -44,7 +45,7 @@ void capture_data_callback(ma_device* pDevice, void* pFramesOut, const void* pFr
|
||||
}
|
||||
|
||||
/* Copy the data from the capture buffer to the ring buffer. */
|
||||
ma_copy_pcm_frames(pMappedBuffer, ma_offset_pcm_frames_const_ptr_f32(pFramesIn, framesWritten, pDevice->capture.channels), framesToWrite, pDevice->capture.format, pDevice->capture.channels);
|
||||
ma_copy_pcm_frames(pMappedBuffer, ma_offset_pcm_frames_const_ptr_f32((const float*)pFramesIn, framesWritten, pDevice->capture.channels), framesToWrite, pDevice->capture.format, pDevice->capture.channels);
|
||||
|
||||
result = ma_pcm_rb_commit_write(&rb, framesToWrite);
|
||||
if (result != MA_SUCCESS) {
|
||||
@@ -125,9 +126,6 @@ int main(int argc, char** argv)
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Make sure the sound is set to looping or else it'll stop if the ring buffer runs out of data. */
|
||||
ma_sound_set_looping(&sound, MA_TRUE);
|
||||
|
||||
/* Link the starting of the device and sound together. */
|
||||
ma_device_start(&device);
|
||||
ma_sound_start(&sound);
|
||||
|
||||
@@ -51,8 +51,7 @@ pass and echo effects so that one of them becomes more obvious than the other.
|
||||
|
||||
When you want to read from the graph, you simply call `ma_node_graph_read_pcm_frames()`.
|
||||
*/
|
||||
#define MINIAUDIO_IMPLEMENTATION
|
||||
#include "../miniaudio.h"
|
||||
#include "../miniaudio.c"
|
||||
|
||||
/* Data Format */
|
||||
#define FORMAT ma_format_f32 /* Must always be f32. */
|
||||
@@ -81,8 +80,6 @@ static int g_soundNodeCount;
|
||||
|
||||
void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount)
|
||||
{
|
||||
MA_ASSERT(pDevice->playback.channels == CHANNELS);
|
||||
|
||||
/*
|
||||
Hearing the output of the node graph is as easy as reading straight into the output buffer. You just need to
|
||||
make sure you use a consistent data format or else you'll need to do your own conversion.
|
||||
@@ -90,6 +87,7 @@ void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uin
|
||||
ma_node_graph_read_pcm_frames(&g_nodeGraph, pOutput, frameCount, NULL);
|
||||
|
||||
(void)pInput; /* Unused. */
|
||||
(void)pDevice; /* Unused. */
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
|
||||
@@ -24,8 +24,7 @@ data from the data source. This means the resource manager will ensure all sound
|
||||
set, each sound will have their own formats and you'll need to do the necessary data conversion yourself.
|
||||
*/
|
||||
#define MA_NO_ENGINE /* We're intentionally not using the ma_engine API here. */
|
||||
#define MINIAUDIO_IMPLEMENTATION
|
||||
#include "../miniaudio.h"
|
||||
#include "../miniaudio.c"
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <emscripten.h>
|
||||
@@ -33,7 +32,6 @@ set, each sound will have their own formats and you'll need to do the necessary
|
||||
void main_loop__em(void* pUserData)
|
||||
{
|
||||
ma_resource_manager* pResourceManager = (ma_resource_manager*)pUserData;
|
||||
MA_ASSERT(pResourceManager != NULL);
|
||||
|
||||
/*
|
||||
The Emscripten build does not support threading which means we need to process jobs manually. If
|
||||
|
||||
@@ -15,8 +15,7 @@ In this example we show how you can create a data source, mix them with other da
|
||||
threads to manage internally and how to implement your own custom job thread.
|
||||
*/
|
||||
#define MA_NO_ENGINE /* We're intentionally not using the ma_engine API here. */
|
||||
#define MINIAUDIO_IMPLEMENTATION
|
||||
#include "../miniaudio.h"
|
||||
#include "../miniaudio.c"
|
||||
|
||||
static ma_resource_manager_data_source g_dataSources[16];
|
||||
static ma_uint32 g_dataSourceCount;
|
||||
@@ -32,8 +31,6 @@ static ma_result ma_data_source_read_pcm_frames_f32_ex(ma_data_source* pDataSour
|
||||
This function is intended to be used when the format and channel count of the data source is
|
||||
known beforehand. The idea is to avoid overhead due to redundant calls to ma_data_source_get_data_format().
|
||||
*/
|
||||
MA_ASSERT(pDataSource != NULL);
|
||||
|
||||
if (dataSourceFormat == ma_format_f32) {
|
||||
/* Fast path. No conversion necessary. */
|
||||
return ma_data_source_read_pcm_frames(pDataSource, pFramesOut, frameCount, pFramesRead);
|
||||
@@ -43,6 +40,10 @@ static ma_result ma_data_source_read_pcm_frames_f32_ex(ma_data_source* pDataSour
|
||||
ma_uint64 totalFramesRead;
|
||||
ma_uint8 temp[MA_DATA_CONVERTER_STACK_BUFFER_SIZE];
|
||||
ma_uint64 tempCapInFrames = sizeof(temp) / ma_get_bytes_per_frame(dataSourceFormat, dataSourceChannels);
|
||||
|
||||
if (pFramesRead != NULL) {
|
||||
*pFramesRead = 0;
|
||||
}
|
||||
|
||||
totalFramesRead = 0;
|
||||
while (totalFramesRead < frameCount) {
|
||||
@@ -52,7 +53,10 @@ static ma_result ma_data_source_read_pcm_frames_f32_ex(ma_data_source* pDataSour
|
||||
framesToRead = tempCapInFrames;
|
||||
}
|
||||
|
||||
result = ma_data_source_read_pcm_frames(pDataSource, pFramesOut, framesToRead, &framesJustRead);
|
||||
result = ma_data_source_read_pcm_frames(pDataSource, temp, framesToRead, &framesJustRead);
|
||||
if (result != MA_SUCCESS) {
|
||||
break;
|
||||
}
|
||||
|
||||
ma_convert_pcm_frames_format(ma_offset_pcm_frames_ptr_f32(pFramesOut, totalFramesRead, dataSourceChannels), ma_format_f32, temp, dataSourceFormat, framesJustRead, dataSourceChannels, ma_dither_mode_none);
|
||||
totalFramesRead += framesJustRead;
|
||||
@@ -62,6 +66,10 @@ static ma_result ma_data_source_read_pcm_frames_f32_ex(ma_data_source* pDataSour
|
||||
}
|
||||
}
|
||||
|
||||
if (pFramesRead != NULL) {
|
||||
*pFramesRead = totalFramesRead;
|
||||
}
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -112,7 +120,7 @@ MA_API ma_result ma_data_source_read_pcm_frames_and_mix_f32(ma_data_source* pDat
|
||||
|
||||
result = ma_data_source_read_pcm_frames_f32_ex(pDataSource, temp, framesToRead, &framesJustRead, format, channels);
|
||||
|
||||
ma_mix_pcm_frames_f32(ma_offset_pcm_frames_ptr(pFramesOut, totalFramesRead, ma_format_f32, channels), temp, framesJustRead, channels, volume);
|
||||
ma_mix_pcm_frames_f32(ma_offset_pcm_frames_ptr_f32(pFramesOut, totalFramesRead, channels), temp, framesJustRead, channels, volume);
|
||||
totalFramesRead += framesJustRead;
|
||||
|
||||
if (result != MA_SUCCESS) {
|
||||
@@ -136,10 +144,6 @@ void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uin
|
||||
*/
|
||||
ma_uint32 iDataSource;
|
||||
|
||||
MA_ASSERT(pDevice->playback.format == ma_format_f32);
|
||||
|
||||
(void)pInput; /* Unused. */
|
||||
|
||||
/*
|
||||
If the device was configured with noPreSilencedOutputBuffer then you would need to silence the
|
||||
buffer here, or make sure the first data source to be mixed is copied rather than mixed.
|
||||
@@ -150,12 +154,15 @@ void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uin
|
||||
for (iDataSource = 0; iDataSource < g_dataSourceCount; iDataSource += 1) {
|
||||
ma_data_source_read_pcm_frames_and_mix_f32(&g_dataSources[iDataSource], (float*)pOutput, frameCount, NULL, /* volume = */1);
|
||||
}
|
||||
|
||||
/* Unused. */
|
||||
(void)pInput;
|
||||
(void)pDevice;
|
||||
}
|
||||
|
||||
static ma_thread_result MA_THREADCALL custom_job_thread(void* pUserData)
|
||||
{
|
||||
ma_resource_manager* pResourceManager = (ma_resource_manager*)pUserData;
|
||||
MA_ASSERT(pResourceManager != NULL);
|
||||
|
||||
for (;;) {
|
||||
ma_result result;
|
||||
@@ -191,8 +198,8 @@ static ma_thread_result MA_THREADCALL custom_job_thread(void* pUserData)
|
||||
event is received which means the `result != MA_SUCCESS` logic above will catch it. If you do not check the
|
||||
return value of ma_resource_manager_next_job() you will want to check for MA_RESOURCE_MANAGER_JOB_QUIT like the code below.
|
||||
*/
|
||||
if (job.toc.breakup.code == MA_RESOURCE_MANAGER_JOB_QUIT) {
|
||||
printf("CUSTOM JOB THREAD TERMINATING VIA MA_RESOURCE_MANAGER_JOB_QUIT... ");
|
||||
if (job.toc.breakup.code == MA_JOB_TYPE_QUIT) {
|
||||
printf("CUSTOM JOB THREAD TERMINATING VIA MA_JOB_TYPE_QUIT... ");
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -269,7 +276,7 @@ int main(int argc, char** argv)
|
||||
ma_thread_create(&jobThread, ma_thread_priority_default, 0, custom_job_thread, &resourceManager, NULL);
|
||||
|
||||
/* Create each data source from the resource manager. Note that the caller is the owner. */
|
||||
for (iFile = 0; iFile < ma_countof(g_dataSources) && iFile < argc-1; iFile += 1) {
|
||||
for (iFile = 0; iFile < (int)ma_countof(g_dataSources) && iFile < argc-1; iFile += 1) {
|
||||
result = ma_resource_manager_data_source_init(
|
||||
&resourceManager,
|
||||
argv[iFile+1],
|
||||
|
||||
@@ -8,18 +8,14 @@ Capturing works in a very similar way to playback. The only difference is the di
|
||||
the application sending data to the device, the device will send data to the application. This example just writes the
|
||||
data received by the microphone straight to a WAV file.
|
||||
*/
|
||||
#define MINIAUDIO_IMPLEMENTATION
|
||||
#include "../miniaudio.h"
|
||||
#include "../miniaudio.c"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount)
|
||||
{
|
||||
ma_encoder* pEncoder = (ma_encoder*)pDevice->pUserData;
|
||||
MA_ASSERT(pEncoder != NULL);
|
||||
|
||||
ma_encoder_write_pcm_frames(pEncoder, pInput, frameCount, NULL);
|
||||
ma_encoder_write_pcm_frames((ma_encoder*)pDevice->pUserData, pInput, frameCount, NULL);
|
||||
|
||||
(void)pOutput;
|
||||
}
|
||||
|
||||
@@ -10,8 +10,7 @@ glitching which the backend may not be able to recover from. For this reason, mi
|
||||
sample rate for both capture and playback. If internally the native sample rates differ, miniaudio will perform the
|
||||
sample rate conversion for you automatically.
|
||||
*/
|
||||
#define MINIAUDIO_IMPLEMENTATION
|
||||
#include "../miniaudio.h"
|
||||
#include "../miniaudio.c"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
@@ -23,8 +22,10 @@ void main_loop__em()
|
||||
|
||||
void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount)
|
||||
{
|
||||
MA_ASSERT(pDevice->capture.format == pDevice->playback.format);
|
||||
MA_ASSERT(pDevice->capture.channels == pDevice->playback.channels);
|
||||
/* This example assumes the playback and capture sides use the same format and channel count. */
|
||||
if (pDevice->capture.format != pDevice->playback.format || pDevice->capture.channels != pDevice->playback.channels) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* In this example the format and channel count are the same for both input and output which means we can just memcpy(). */
|
||||
MA_COPY_MEMORY(pOutput, pInput, frameCount * ma_get_bytes_per_frame(pDevice->capture.format, pDevice->capture.channels));
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
/*
|
||||
Demonstrates how to enumerate over devices.
|
||||
|
||||
Device enumaration requires a `ma_context` object which is initialized with `ma_context_init()`. Conceptually, the
|
||||
Device enumeration requires a `ma_context` object which is initialized with `ma_context_init()`. Conceptually, the
|
||||
context sits above a device. You can have many devices to one context.
|
||||
|
||||
If you use device enumeration, you should explicitly specify the same context you used for enumeration in the call to
|
||||
`ma_device_init()` when you initialize your devices.
|
||||
*/
|
||||
#define MINIAUDIO_IMPLEMENTATION
|
||||
#include "../miniaudio.h"
|
||||
#include "../miniaudio.c"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
@@ -10,18 +10,14 @@ used indirectly with PulseAudio by choosing the appropriate loopback device afte
|
||||
To use loopback mode you just need to set the device type to ma_device_type_loopback and set the capture device config
|
||||
properties. The output buffer in the callback will be null whereas the input buffer will be valid.
|
||||
*/
|
||||
#define MINIAUDIO_IMPLEMENTATION
|
||||
#include "../miniaudio.h"
|
||||
#include "../miniaudio.c"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount)
|
||||
{
|
||||
ma_encoder* pEncoder = (ma_encoder*)pDevice->pUserData;
|
||||
MA_ASSERT(pEncoder != NULL);
|
||||
|
||||
ma_encoder_write_pcm_frames(pEncoder, pInput, frameCount, NULL);
|
||||
ma_encoder_write_pcm_frames((ma_encoder*)pDevice->pUserData, pInput, frameCount, NULL);
|
||||
|
||||
(void)pOutput;
|
||||
}
|
||||
|
||||
@@ -5,8 +5,7 @@ This example uses a decoder as the data source. Decoders can be used with the `m
|
||||
supports looping via the `ma_data_source_read_pcm_frames()` API. To use it, all you need to do is pass a pointer to the
|
||||
decoder straight into `ma_data_source_read_pcm_frames()` and it will just work.
|
||||
*/
|
||||
#define MINIAUDIO_IMPLEMENTATION
|
||||
#include "../miniaudio.h"
|
||||
#include "../miniaudio.c"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
@@ -4,15 +4,14 @@ Demonstrates one way to load multiple files and play them all back at the same t
|
||||
When mixing multiple sounds together, you should not create multiple devices. Instead you should create only a single
|
||||
device and then mix your sounds together which you can do by simply summing their samples together. The simplest way to
|
||||
do this is to use floating point samples and use miniaudio's built-in clipper to handling clipping for you. (Clipping
|
||||
is when sample are clampled to their minimum and maximum range, which for floating point is -1..1.)
|
||||
is when sample are clamped to their minimum and maximum range, which for floating point is -1..1.)
|
||||
|
||||
```
|
||||
Usage: simple_mixing [input file 0] [input file 1] ... [input file n]
|
||||
Example: simple_mixing file1.wav file2.flac
|
||||
```
|
||||
*/
|
||||
#define MINIAUDIO_IMPLEMENTATION
|
||||
#include "../miniaudio.h"
|
||||
#include "../miniaudio.c"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
@@ -29,7 +28,7 @@ ma_bool32* g_pDecodersAtEnd;
|
||||
|
||||
ma_event g_stopEvent; /* <-- Signaled by the audio thread, waited on by the main thread. */
|
||||
|
||||
ma_bool32 are_all_decoders_at_end()
|
||||
ma_bool32 are_all_decoders_at_end(void)
|
||||
{
|
||||
ma_uint32 iDecoder;
|
||||
for (iDecoder = 0; iDecoder < g_decoderCount; ++iDecoder) {
|
||||
@@ -87,8 +86,7 @@ void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uin
|
||||
float* pOutputF32 = (float*)pOutput;
|
||||
ma_uint32 iDecoder;
|
||||
|
||||
MA_ASSERT(pDevice->playback.format == SAMPLE_FORMAT); /* <-- Important for this example. */
|
||||
|
||||
/* This example assumes the device was configured to use ma_format_f32. */
|
||||
for (iDecoder = 0; iDecoder < g_decoderCount; ++iDecoder) {
|
||||
if (!g_pDecodersAtEnd[iDecoder]) {
|
||||
ma_uint32 framesRead = read_and_mix_pcm_frames_f32(&g_pDecoders[iDecoder], pOutputF32, frameCount);
|
||||
@@ -107,6 +105,7 @@ void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uin
|
||||
}
|
||||
|
||||
(void)pInput;
|
||||
(void)pDevice;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
|
||||
@@ -10,8 +10,7 @@ device and can be used independently of it. This example only plays back a singl
|
||||
back multiple files by simple loading multiple decoders and mixing them (do not create multiple devices to do this). See
|
||||
the simple_mixing example for how best to do this.
|
||||
*/
|
||||
#define MINIAUDIO_IMPLEMENTATION
|
||||
#include "../miniaudio.h"
|
||||
#include "../miniaudio.c"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
@@ -14,8 +14,7 @@ This example works with Emscripten.
|
||||
*/
|
||||
#define MA_NO_DECODING
|
||||
#define MA_NO_ENCODING
|
||||
#define MINIAUDIO_IMPLEMENTATION
|
||||
#include "../miniaudio.h"
|
||||
#include "../miniaudio.c"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
@@ -33,14 +32,7 @@ void main_loop__em()
|
||||
|
||||
void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount)
|
||||
{
|
||||
ma_waveform* pSineWave;
|
||||
|
||||
MA_ASSERT(pDevice->playback.channels == DEVICE_CHANNELS);
|
||||
|
||||
pSineWave = (ma_waveform*)pDevice->pUserData;
|
||||
MA_ASSERT(pSineWave != NULL);
|
||||
|
||||
ma_waveform_read_pcm_frames(pSineWave, pOutput, frameCount, NULL);
|
||||
ma_waveform_read_pcm_frames((ma_waveform*)pDevice->pUserData, pOutput, frameCount, NULL);
|
||||
|
||||
(void)pInput; /* Unused. */
|
||||
}
|
||||
@@ -83,6 +75,7 @@ int main(int argc, char** argv)
|
||||
#endif
|
||||
|
||||
ma_device_uninit(&device);
|
||||
ma_waveform_uninit(&sineWave); /* Uninitialize the waveform after the device so we don't pull it from under the device while it's being reference in the data callback. */
|
||||
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
Demonstrates how to do basic spatialization via the high level API.
|
||||
|
||||
You can position and orientate sounds to create a simple spatialization effect. This example shows
|
||||
how to do this.
|
||||
|
||||
In addition to positioning sounds, there is the concept of a listener. This can also be positioned
|
||||
and orientated to help with spatialization.
|
||||
|
||||
This example only covers the basics to get your started. See the documentation for more detailed
|
||||
information on the available features.
|
||||
|
||||
To use this example, pass in the path of a sound as the first argument. The sound will be
|
||||
positioned in front of the listener, while the listener rotates on the the spot to create an
|
||||
orbiting effect. Terminate the program with Ctrl+C.
|
||||
*/
|
||||
#include "../miniaudio.c"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <math.h> /* For sinf() and cosf() */
|
||||
|
||||
/* Silence warning about unreachable code for MSVC. */
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4702)
|
||||
#endif
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
ma_result result;
|
||||
ma_engine engine;
|
||||
ma_sound sound;
|
||||
float listenerAngle = 0;
|
||||
|
||||
if (argc < 2) {
|
||||
printf("No input file.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
result = ma_engine_init(NULL, &engine);
|
||||
if (result != MA_SUCCESS) {
|
||||
printf("Failed to initialize engine.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
result = ma_sound_init_from_file(&engine, argv[1], 0, NULL, NULL, &sound);
|
||||
if (result != MA_SUCCESS) {
|
||||
printf("Failed to load sound: %s\n", argv[1]);
|
||||
ma_engine_uninit(&engine);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* This sets the position of the sound. miniaudio follows the same coordinate system as OpenGL, where -Z is forward. */
|
||||
ma_sound_set_position(&sound, 0, 0, -1);
|
||||
|
||||
/*
|
||||
This sets the position of the listener. The second parameter is the listener index. If you have only a single listener, which is
|
||||
most likely, just use 0. The position defaults to (0,0,0).
|
||||
*/
|
||||
ma_engine_listener_set_position(&engine, 0, 0, 0, 0);
|
||||
|
||||
|
||||
/* Sounds are stopped by default. We'll start it once initial parameters have been setup. */
|
||||
ma_sound_start(&sound);
|
||||
|
||||
|
||||
/* Rotate the listener on the spot to create an orbiting effect. */
|
||||
for (;;) {
|
||||
listenerAngle += 0.01f;
|
||||
ma_engine_listener_set_direction(&engine, 0, (float)sin(listenerAngle), 0, (float)cos(listenerAngle));
|
||||
|
||||
ma_sleep(1);
|
||||
}
|
||||
|
||||
|
||||
/* Won't actually get here, but do this to tear down. */
|
||||
ma_sound_uninit(&sound);
|
||||
ma_engine_uninit(&engine);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
@@ -1,104 +0,0 @@
|
||||
/*
|
||||
IMPORTANT NOTE: Cosmopolitan is not officially supported by miniaudio. This file was added just as
|
||||
a way to play around and experiment with Cosmopolitan as a proof of concept and to test the viability
|
||||
of supporting such a compiler. If you get compilation or runtime errors you're on your own.
|
||||
|
||||
---------------------------------------------------------------------------------------------------
|
||||
|
||||
This is a version of windows.h for compiling with Cosmopolitan. It's not complete. It's intended to
|
||||
define some missing items from cosmopolitan.h. Hopefully as the project develops we can eventually
|
||||
eliminate all of the content in this file.
|
||||
*/
|
||||
#ifndef _WINDOWS_
|
||||
#define _WINDOWS_
|
||||
|
||||
#define WINAPI
|
||||
#define STDMETHODCALLTYPE
|
||||
#define CALLBACK
|
||||
|
||||
typedef uint64_t HWND;
|
||||
typedef uint64_t HANDLE;
|
||||
typedef uint64_t HKEY;
|
||||
typedef uint64_t HWAVEIN;
|
||||
typedef uint64_t HWAVEOUT;
|
||||
typedef uint32_t HRESULT;
|
||||
typedef uint8_t BYTE;
|
||||
typedef uint16_t WORD;
|
||||
typedef uint32_t DWORD;
|
||||
typedef uint64_t DWORDLONG;
|
||||
typedef int32_t BOOL;
|
||||
typedef int32_t LONG; /* `long` is always 32-bit on Windows. */
|
||||
typedef int64_t LONGLONG;
|
||||
typedef uint32_t ULONG; /* `long` is always 32-bit on Windows. */
|
||||
typedef uint64_t ULONGLONG;
|
||||
typedef char16_t WCHAR;
|
||||
typedef unsigned int UINT;
|
||||
typedef char CHAR;
|
||||
typedef uint64_t ULONG_PTR; /* Everything is 64-bit with Cosmopolitan. */
|
||||
typedef ULONG_PTR DWORD_PTR;
|
||||
|
||||
#define TRUE 1
|
||||
#define FALSE 0
|
||||
|
||||
#define WAIT_OBJECT_0 0
|
||||
#define INFINITE 0xFFFFFFFF
|
||||
|
||||
#define CP_UTF8 65001
|
||||
|
||||
#define FAILED(hr) ((hr) < 0)
|
||||
#define SUCCEEDED(hr) ((hr) >= 0)
|
||||
|
||||
#define NOERROR 0
|
||||
#define S_OK 0
|
||||
#define S_FALSE 1
|
||||
#define E_POINTER ((HRESULT)0x80004003)
|
||||
#define E_UNEXPECTED ((HRESULT)0x8000FFFF)
|
||||
#define E_NOTIMPL ((HRESULT)0x80004001)
|
||||
#define E_OUTOFMEMORY ((HRESULT)0x8007000E)
|
||||
#define E_INVALIDARG ((HRESULT)0x80070057)
|
||||
#define E_NOINTERFACE ((HRESULT)0x80004002)
|
||||
#define E_HANDLE ((HRESULT)0x80070006)
|
||||
#define E_ABORT ((HRESULT)0x80004004)
|
||||
#define E_FAIL ((HRESULT)0x80004005)
|
||||
#define E_ACCESSDENIED ((HRESULT)0x80070005)
|
||||
|
||||
#define ERROR_SUCCESS 0
|
||||
#define ERROR_FILE_NOT_FOUND 2
|
||||
#define ERROR_PATH_NOT_FOUND 3
|
||||
#define ERROR_TOO_MANY_OPEN_FILES 4
|
||||
#define ERROR_ACCESS_DENIED 5
|
||||
#define ERROR_NOT_ENOUGH_MEMORY 8
|
||||
#define ERROR_HANDLE_EOF 38
|
||||
#define ERROR_INVALID_PARAMETER 87
|
||||
#define ERROR_DISK_FULL 112
|
||||
#define ERROR_SEM_TIMEOUT 121
|
||||
#define ERROR_NEGATIVE_SEEK 131
|
||||
|
||||
typedef struct
|
||||
{
|
||||
unsigned long Data1;
|
||||
unsigned short Data2;
|
||||
unsigned short Data3;
|
||||
unsigned char Data4[8];
|
||||
} GUID, IID;
|
||||
|
||||
typedef int64_t LARGE_INTEGER;
|
||||
|
||||
|
||||
|
||||
#define HKEY_LOCAL_MACHINE ((HKEY)(ULONG_PTR)(0x80000002))
|
||||
#define KEY_READ 0x00020019
|
||||
|
||||
|
||||
static HANDLE CreateEventA(struct NtSecurityAttributes* lpEventAttributes, bool32 bManualReset, bool32 bInitialState, const char* lpName)
|
||||
{
|
||||
assert(lpName == NULL); /* If this is ever triggered we'll need to do a ANSI-to-Unicode conversion. */
|
||||
return (HANDLE)CreateEvent(lpEventAttributes, bManualReset, bInitialState, (const char16_t*)lpName);
|
||||
}
|
||||
|
||||
static BOOL IsEqualGUID(const GUID* a, const GUID* b)
|
||||
{
|
||||
return memcmp(a, b, sizeof(GUID)) == 0;
|
||||
}
|
||||
|
||||
#endif /* _WINDOWS_ */
|
||||
@@ -0,0 +1,536 @@
|
||||
#ifndef miniaudio_libopus_c
|
||||
#define miniaudio_libopus_c
|
||||
|
||||
#include "miniaudio_libopus.h"
|
||||
|
||||
#if !defined(MA_NO_LIBOPUS)
|
||||
#include <opusfile.h>
|
||||
#endif
|
||||
|
||||
#include <string.h> /* For memset(). */
|
||||
#include <assert.h>
|
||||
|
||||
static ma_result ma_libopus_ds_read(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead)
|
||||
{
|
||||
return ma_libopus_read_pcm_frames((ma_libopus*)pDataSource, pFramesOut, frameCount, pFramesRead);
|
||||
}
|
||||
|
||||
static ma_result ma_libopus_ds_seek(ma_data_source* pDataSource, ma_uint64 frameIndex)
|
||||
{
|
||||
return ma_libopus_seek_to_pcm_frame((ma_libopus*)pDataSource, frameIndex);
|
||||
}
|
||||
|
||||
static ma_result ma_libopus_ds_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap)
|
||||
{
|
||||
return ma_libopus_get_data_format((ma_libopus*)pDataSource, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap);
|
||||
}
|
||||
|
||||
static ma_result ma_libopus_ds_get_cursor(ma_data_source* pDataSource, ma_uint64* pCursor)
|
||||
{
|
||||
return ma_libopus_get_cursor_in_pcm_frames((ma_libopus*)pDataSource, pCursor);
|
||||
}
|
||||
|
||||
static ma_result ma_libopus_ds_get_length(ma_data_source* pDataSource, ma_uint64* pLength)
|
||||
{
|
||||
return ma_libopus_get_length_in_pcm_frames((ma_libopus*)pDataSource, pLength);
|
||||
}
|
||||
|
||||
static ma_data_source_vtable g_ma_libopus_ds_vtable =
|
||||
{
|
||||
ma_libopus_ds_read,
|
||||
ma_libopus_ds_seek,
|
||||
ma_libopus_ds_get_data_format,
|
||||
ma_libopus_ds_get_cursor,
|
||||
ma_libopus_ds_get_length,
|
||||
NULL, /* onSetLooping */
|
||||
0 /* flags */
|
||||
};
|
||||
|
||||
|
||||
#if !defined(MA_NO_LIBOPUS)
|
||||
static int ma_libopus_of_callback__read(void* pUserData, unsigned char* pBufferOut, int bytesToRead)
|
||||
{
|
||||
ma_libopus* pOpus = (ma_libopus*)pUserData;
|
||||
ma_result result;
|
||||
size_t bytesRead;
|
||||
|
||||
result = pOpus->onRead(pOpus->pReadSeekTellUserData, (void*)pBufferOut, bytesToRead, &bytesRead);
|
||||
|
||||
if (result != MA_SUCCESS) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return (int)bytesRead;
|
||||
}
|
||||
|
||||
static int ma_libopus_of_callback__seek(void* pUserData, ogg_int64_t offset, int whence)
|
||||
{
|
||||
ma_libopus* pOpus = (ma_libopus*)pUserData;
|
||||
ma_result result;
|
||||
ma_seek_origin origin;
|
||||
|
||||
if (whence == SEEK_SET) {
|
||||
origin = ma_seek_origin_start;
|
||||
} else if (whence == SEEK_END) {
|
||||
origin = ma_seek_origin_end;
|
||||
} else {
|
||||
origin = ma_seek_origin_current;
|
||||
}
|
||||
|
||||
result = pOpus->onSeek(pOpus->pReadSeekTellUserData, offset, origin);
|
||||
if (result != MA_SUCCESS) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static opus_int64 ma_libopus_of_callback__tell(void* pUserData)
|
||||
{
|
||||
ma_libopus* pOpus = (ma_libopus*)pUserData;
|
||||
ma_result result;
|
||||
ma_int64 cursor;
|
||||
|
||||
if (pOpus->onTell == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
result = pOpus->onTell(pOpus->pReadSeekTellUserData, &cursor);
|
||||
if (result != MA_SUCCESS) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return cursor;
|
||||
}
|
||||
#endif
|
||||
|
||||
static ma_result ma_libopus_init_internal(const ma_decoding_backend_config* pConfig, ma_libopus* pOpus)
|
||||
{
|
||||
ma_result result;
|
||||
ma_data_source_config dataSourceConfig;
|
||||
|
||||
if (pOpus == NULL) {
|
||||
return MA_INVALID_ARGS;
|
||||
}
|
||||
|
||||
memset(pOpus, 0, sizeof(*pOpus));
|
||||
pOpus->format = ma_format_f32; /* f32 by default. */
|
||||
|
||||
if (pConfig != NULL && (pConfig->preferredFormat == ma_format_f32 || pConfig->preferredFormat == ma_format_s16)) {
|
||||
pOpus->format = pConfig->preferredFormat;
|
||||
} else {
|
||||
/* Getting here means something other than f32 and s16 was specified. Just leave this unset to use the default format. */
|
||||
}
|
||||
|
||||
dataSourceConfig = ma_data_source_config_init();
|
||||
dataSourceConfig.vtable = &g_ma_libopus_ds_vtable;
|
||||
|
||||
result = ma_data_source_init(&dataSourceConfig, &pOpus->ds);
|
||||
if (result != MA_SUCCESS) {
|
||||
return result; /* Failed to initialize the base data source. */
|
||||
}
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
|
||||
MA_API ma_result ma_libopus_init(ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void* pReadSeekTellUserData, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_libopus* pOpus)
|
||||
{
|
||||
ma_result result;
|
||||
|
||||
(void)pAllocationCallbacks; /* Can't seem to find a way to configure memory allocations in libopus. */
|
||||
|
||||
result = ma_libopus_init_internal(pConfig, pOpus);
|
||||
if (result != MA_SUCCESS) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (onRead == NULL || onSeek == NULL) {
|
||||
return MA_INVALID_ARGS; /* onRead and onSeek are mandatory. */
|
||||
}
|
||||
|
||||
pOpus->onRead = onRead;
|
||||
pOpus->onSeek = onSeek;
|
||||
pOpus->onTell = onTell;
|
||||
pOpus->pReadSeekTellUserData = pReadSeekTellUserData;
|
||||
|
||||
#if !defined(MA_NO_LIBOPUS)
|
||||
{
|
||||
int libopusResult;
|
||||
OpusFileCallbacks libopusCallbacks;
|
||||
|
||||
/* We can now initialize the Opus decoder. This must be done after we've set up the callbacks. */
|
||||
libopusCallbacks.read = ma_libopus_of_callback__read;
|
||||
libopusCallbacks.seek = ma_libopus_of_callback__seek;
|
||||
libopusCallbacks.close = NULL;
|
||||
libopusCallbacks.tell = ma_libopus_of_callback__tell;
|
||||
|
||||
pOpus->of = op_open_callbacks(pOpus, &libopusCallbacks, NULL, 0, &libopusResult);
|
||||
if (pOpus->of == NULL) {
|
||||
return MA_INVALID_FILE;
|
||||
}
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
#else
|
||||
{
|
||||
/* libopus is disabled. */
|
||||
return MA_NOT_IMPLEMENTED;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
MA_API ma_result ma_libopus_init_file(const char* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_libopus* pOpus)
|
||||
{
|
||||
ma_result result;
|
||||
|
||||
(void)pAllocationCallbacks; /* Can't seem to find a way to configure memory allocations in libopus. */
|
||||
|
||||
result = ma_libopus_init_internal(pConfig, pOpus);
|
||||
if (result != MA_SUCCESS) {
|
||||
return result;
|
||||
}
|
||||
|
||||
#if !defined(MA_NO_LIBOPUS)
|
||||
{
|
||||
int libopusResult;
|
||||
|
||||
pOpus->of = op_open_file(pFilePath, &libopusResult);
|
||||
if (pOpus->of == NULL) {
|
||||
return MA_INVALID_FILE;
|
||||
}
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
#else
|
||||
{
|
||||
/* libopus is disabled. */
|
||||
(void)pFilePath;
|
||||
return MA_NOT_IMPLEMENTED;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
MA_API void ma_libopus_uninit(ma_libopus* pOpus, const ma_allocation_callbacks* pAllocationCallbacks)
|
||||
{
|
||||
if (pOpus == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
(void)pAllocationCallbacks;
|
||||
|
||||
#if !defined(MA_NO_LIBOPUS)
|
||||
{
|
||||
op_free((OggOpusFile*)pOpus->of);
|
||||
}
|
||||
#else
|
||||
{
|
||||
/* libopus is disabled. Should never hit this since initialization would have failed. */
|
||||
assert(MA_FALSE);
|
||||
}
|
||||
#endif
|
||||
|
||||
ma_data_source_uninit(&pOpus->ds);
|
||||
}
|
||||
|
||||
MA_API ma_result ma_libopus_read_pcm_frames(ma_libopus* pOpus, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead)
|
||||
{
|
||||
if (pFramesRead != NULL) {
|
||||
*pFramesRead = 0;
|
||||
}
|
||||
|
||||
if (frameCount == 0) {
|
||||
return MA_INVALID_ARGS;
|
||||
}
|
||||
|
||||
if (pOpus == NULL) {
|
||||
return MA_INVALID_ARGS;
|
||||
}
|
||||
|
||||
#if !defined(MA_NO_LIBOPUS)
|
||||
{
|
||||
/* We always use floating point format. */
|
||||
ma_result result = MA_SUCCESS; /* Must be initialized to MA_SUCCESS. */
|
||||
ma_uint64 totalFramesRead;
|
||||
ma_format format;
|
||||
ma_uint32 channels;
|
||||
|
||||
ma_libopus_get_data_format(pOpus, &format, &channels, NULL, NULL, 0);
|
||||
|
||||
totalFramesRead = 0;
|
||||
while (totalFramesRead < frameCount) {
|
||||
long libopusResult;
|
||||
ma_uint64 framesToRead;
|
||||
ma_uint64 framesRemaining;
|
||||
|
||||
framesRemaining = (frameCount - totalFramesRead);
|
||||
framesToRead = 1024;
|
||||
if (framesToRead > framesRemaining) {
|
||||
framesToRead = framesRemaining;
|
||||
}
|
||||
|
||||
if (format == ma_format_f32) {
|
||||
libopusResult = op_read_float((OggOpusFile*)pOpus->of, (float* )ma_offset_pcm_frames_ptr(pFramesOut, totalFramesRead, format, channels), (int)(framesToRead * channels), NULL);
|
||||
} else {
|
||||
libopusResult = op_read ((OggOpusFile*)pOpus->of, (opus_int16*)ma_offset_pcm_frames_ptr(pFramesOut, totalFramesRead, format, channels), (int)(framesToRead * channels), NULL);
|
||||
}
|
||||
|
||||
if (libopusResult < 0) {
|
||||
result = MA_ERROR; /* Error while decoding. */
|
||||
break;
|
||||
} else {
|
||||
totalFramesRead += libopusResult;
|
||||
|
||||
if (libopusResult == 0) {
|
||||
result = MA_AT_END;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pFramesRead != NULL) {
|
||||
*pFramesRead = totalFramesRead;
|
||||
}
|
||||
|
||||
if (result == MA_SUCCESS && totalFramesRead == 0) {
|
||||
result = MA_AT_END;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
#else
|
||||
{
|
||||
/* libopus is disabled. Should never hit this since initialization would have failed. */
|
||||
assert(MA_FALSE);
|
||||
|
||||
(void)pFramesOut;
|
||||
(void)frameCount;
|
||||
(void)pFramesRead;
|
||||
|
||||
return MA_NOT_IMPLEMENTED;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
MA_API ma_result ma_libopus_seek_to_pcm_frame(ma_libopus* pOpus, ma_uint64 frameIndex)
|
||||
{
|
||||
if (pOpus == NULL) {
|
||||
return MA_INVALID_ARGS;
|
||||
}
|
||||
|
||||
#if !defined(MA_NO_LIBOPUS)
|
||||
{
|
||||
int libopusResult = op_pcm_seek((OggOpusFile*)pOpus->of, (ogg_int64_t)frameIndex);
|
||||
if (libopusResult != 0) {
|
||||
if (libopusResult == OP_ENOSEEK) {
|
||||
return MA_INVALID_OPERATION; /* Not seekable. */
|
||||
} else if (libopusResult == OP_EINVAL) {
|
||||
return MA_INVALID_ARGS;
|
||||
} else {
|
||||
return MA_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
#else
|
||||
{
|
||||
/* libopus is disabled. Should never hit this since initialization would have failed. */
|
||||
assert(MA_FALSE);
|
||||
|
||||
(void)frameIndex;
|
||||
|
||||
return MA_NOT_IMPLEMENTED;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
MA_API ma_result ma_libopus_get_data_format(ma_libopus* pOpus, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap)
|
||||
{
|
||||
/* Defaults for safety. */
|
||||
if (pFormat != NULL) {
|
||||
*pFormat = ma_format_unknown;
|
||||
}
|
||||
if (pChannels != NULL) {
|
||||
*pChannels = 0;
|
||||
}
|
||||
if (pSampleRate != NULL) {
|
||||
*pSampleRate = 0;
|
||||
}
|
||||
if (pChannelMap != NULL) {
|
||||
memset(pChannelMap, 0, sizeof(*pChannelMap) * channelMapCap);
|
||||
}
|
||||
|
||||
if (pOpus == NULL) {
|
||||
return MA_INVALID_OPERATION;
|
||||
}
|
||||
|
||||
if (pFormat != NULL) {
|
||||
*pFormat = pOpus->format;
|
||||
}
|
||||
|
||||
#if !defined(MA_NO_LIBOPUS)
|
||||
{
|
||||
ma_uint32 channels = op_channel_count((OggOpusFile*)pOpus->of, -1);
|
||||
|
||||
if (pChannels != NULL) {
|
||||
*pChannels = channels;
|
||||
}
|
||||
|
||||
if (pSampleRate != NULL) {
|
||||
*pSampleRate = 48000;
|
||||
}
|
||||
|
||||
if (pChannelMap != NULL) {
|
||||
ma_channel_map_init_standard(ma_standard_channel_map_vorbis, pChannelMap, channelMapCap, channels);
|
||||
}
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
#else
|
||||
{
|
||||
/* libopus is disabled. Should never hit this since initialization would have failed. */
|
||||
assert(MA_FALSE);
|
||||
return MA_NOT_IMPLEMENTED;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
MA_API ma_result ma_libopus_get_cursor_in_pcm_frames(ma_libopus* pOpus, ma_uint64* pCursor)
|
||||
{
|
||||
if (pCursor == NULL) {
|
||||
return MA_INVALID_ARGS;
|
||||
}
|
||||
|
||||
*pCursor = 0; /* Safety. */
|
||||
|
||||
if (pOpus == NULL) {
|
||||
return MA_INVALID_ARGS;
|
||||
}
|
||||
|
||||
#if !defined(MA_NO_LIBOPUS)
|
||||
{
|
||||
ogg_int64_t offset = op_pcm_tell((OggOpusFile*)pOpus->of);
|
||||
if (offset < 0) {
|
||||
return MA_INVALID_FILE;
|
||||
}
|
||||
|
||||
*pCursor = (ma_uint64)offset;
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
#else
|
||||
{
|
||||
/* libopus is disabled. Should never hit this since initialization would have failed. */
|
||||
assert(MA_FALSE);
|
||||
return MA_NOT_IMPLEMENTED;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
MA_API ma_result ma_libopus_get_length_in_pcm_frames(ma_libopus* pOpus, ma_uint64* pLength)
|
||||
{
|
||||
if (pLength == NULL) {
|
||||
return MA_INVALID_ARGS;
|
||||
}
|
||||
|
||||
*pLength = 0; /* Safety. */
|
||||
|
||||
if (pOpus == NULL) {
|
||||
return MA_INVALID_ARGS;
|
||||
}
|
||||
|
||||
#if !defined(MA_NO_LIBOPUS)
|
||||
{
|
||||
ogg_int64_t length = op_pcm_total((OggOpusFile*)pOpus->of, -1);
|
||||
if (length < 0) {
|
||||
return MA_ERROR;
|
||||
}
|
||||
|
||||
*pLength = (ma_uint64)length;
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
#else
|
||||
{
|
||||
/* libopus is disabled. Should never hit this since initialization would have failed. */
|
||||
assert(MA_FALSE);
|
||||
return MA_NOT_IMPLEMENTED;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
The code below defines the vtable that you'll plug into your `ma_decoder_config` object.
|
||||
*/
|
||||
#if !defined(MA_NO_LIBOPUS)
|
||||
static ma_result ma_decoding_backend_init__libopus(void* pUserData, ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void* pReadSeekTellUserData, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend)
|
||||
{
|
||||
ma_result result;
|
||||
ma_libopus* pOpus;
|
||||
|
||||
(void)pUserData;
|
||||
|
||||
pOpus = (ma_libopus*)ma_malloc(sizeof(*pOpus), pAllocationCallbacks);
|
||||
if (pOpus == NULL) {
|
||||
return MA_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
result = ma_libopus_init(onRead, onSeek, onTell, pReadSeekTellUserData, pConfig, pAllocationCallbacks, pOpus);
|
||||
if (result != MA_SUCCESS) {
|
||||
ma_free(pOpus, pAllocationCallbacks);
|
||||
return result;
|
||||
}
|
||||
|
||||
*ppBackend = pOpus;
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
|
||||
static ma_result ma_decoding_backend_init_file__libopus(void* pUserData, const char* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend)
|
||||
{
|
||||
ma_result result;
|
||||
ma_libopus* pOpus;
|
||||
|
||||
(void)pUserData;
|
||||
|
||||
pOpus = (ma_libopus*)ma_malloc(sizeof(*pOpus), pAllocationCallbacks);
|
||||
if (pOpus == NULL) {
|
||||
return MA_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
result = ma_libopus_init_file(pFilePath, pConfig, pAllocationCallbacks, pOpus);
|
||||
if (result != MA_SUCCESS) {
|
||||
ma_free(pOpus, pAllocationCallbacks);
|
||||
return result;
|
||||
}
|
||||
|
||||
*ppBackend = pOpus;
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
|
||||
static void ma_decoding_backend_uninit__libopus(void* pUserData, ma_data_source* pBackend, const ma_allocation_callbacks* pAllocationCallbacks)
|
||||
{
|
||||
ma_libopus* pOpus = (ma_libopus*)pBackend;
|
||||
|
||||
(void)pUserData;
|
||||
|
||||
ma_libopus_uninit(pOpus, pAllocationCallbacks);
|
||||
ma_free(pOpus, pAllocationCallbacks);
|
||||
}
|
||||
|
||||
static ma_decoding_backend_vtable ma_gDecodingBackendVTable_libopus =
|
||||
{
|
||||
ma_decoding_backend_init__libopus,
|
||||
ma_decoding_backend_init_file__libopus,
|
||||
NULL, /* onInitFileW() */
|
||||
NULL, /* onInitMemory() */
|
||||
ma_decoding_backend_uninit__libopus
|
||||
};
|
||||
ma_decoding_backend_vtable* ma_decoding_backend_libopus = &ma_gDecodingBackendVTable_libopus;
|
||||
#else
|
||||
ma_decoding_backend_vtable* ma_decoding_backend_libopus = NULL;
|
||||
#endif
|
||||
|
||||
#endif /* miniaudio_libopus_c */
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
This implements a data source that decodes Opus streams via libopus + libopusfile
|
||||
|
||||
This object can be plugged into any `ma_data_source_*()` API and can also be used as a custom
|
||||
decoding backend. See the custom_decoder example.
|
||||
*/
|
||||
#ifndef miniaudio_libopus_h
|
||||
#define miniaudio_libopus_h
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "../../../miniaudio.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
ma_data_source_base ds; /* The libopus decoder can be used independently as a data source. */
|
||||
ma_read_proc onRead;
|
||||
ma_seek_proc onSeek;
|
||||
ma_tell_proc onTell;
|
||||
void* pReadSeekTellUserData;
|
||||
ma_format format; /* Will be either f32 or s16. */
|
||||
/*OggOpusFile**/ void* of; /* Typed as void* so we can avoid a dependency on opusfile in the header section. */
|
||||
} ma_libopus;
|
||||
|
||||
MA_API ma_result ma_libopus_init(ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void* pReadSeekTellUserData, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_libopus* pOpus);
|
||||
MA_API ma_result ma_libopus_init_file(const char* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_libopus* pOpus);
|
||||
MA_API void ma_libopus_uninit(ma_libopus* pOpus, const ma_allocation_callbacks* pAllocationCallbacks);
|
||||
MA_API ma_result ma_libopus_read_pcm_frames(ma_libopus* pOpus, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead);
|
||||
MA_API ma_result ma_libopus_seek_to_pcm_frame(ma_libopus* pOpus, ma_uint64 frameIndex);
|
||||
MA_API ma_result ma_libopus_get_data_format(ma_libopus* pOpus, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap);
|
||||
MA_API ma_result ma_libopus_get_cursor_in_pcm_frames(ma_libopus* pOpus, ma_uint64* pCursor);
|
||||
MA_API ma_result ma_libopus_get_length_in_pcm_frames(ma_libopus* pOpus, ma_uint64* pLength);
|
||||
|
||||
/* Decoding backend vtable. This is what you'll plug into ma_decoder_config.pBackendVTables. No user data required. */
|
||||
extern ma_decoding_backend_vtable* ma_decoding_backend_libopus;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* miniaudio_libopus_h */
|
||||
|
||||
@@ -0,0 +1,591 @@
|
||||
#ifndef miniaudio_libvorbis_c
|
||||
#define miniaudio_libvorbis_c
|
||||
|
||||
#include "miniaudio_libvorbis.h"
|
||||
|
||||
#if !defined(MA_NO_LIBVORBIS)
|
||||
#ifndef OV_EXCLUDE_STATIC_CALLBACKS
|
||||
#define OV_EXCLUDE_STATIC_CALLBACKS
|
||||
#endif
|
||||
#include <vorbis/vorbisfile.h>
|
||||
#endif
|
||||
|
||||
#include <string.h> /* For memset(). */
|
||||
#include <assert.h>
|
||||
|
||||
static ma_result ma_libvorbis_ds_read(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead)
|
||||
{
|
||||
return ma_libvorbis_read_pcm_frames((ma_libvorbis*)pDataSource, pFramesOut, frameCount, pFramesRead);
|
||||
}
|
||||
|
||||
static ma_result ma_libvorbis_ds_seek(ma_data_source* pDataSource, ma_uint64 frameIndex)
|
||||
{
|
||||
return ma_libvorbis_seek_to_pcm_frame((ma_libvorbis*)pDataSource, frameIndex);
|
||||
}
|
||||
|
||||
static ma_result ma_libvorbis_ds_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap)
|
||||
{
|
||||
return ma_libvorbis_get_data_format((ma_libvorbis*)pDataSource, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap);
|
||||
}
|
||||
|
||||
static ma_result ma_libvorbis_ds_get_cursor(ma_data_source* pDataSource, ma_uint64* pCursor)
|
||||
{
|
||||
return ma_libvorbis_get_cursor_in_pcm_frames((ma_libvorbis*)pDataSource, pCursor);
|
||||
}
|
||||
|
||||
static ma_result ma_libvorbis_ds_get_length(ma_data_source* pDataSource, ma_uint64* pLength)
|
||||
{
|
||||
return ma_libvorbis_get_length_in_pcm_frames((ma_libvorbis*)pDataSource, pLength);
|
||||
}
|
||||
|
||||
static ma_data_source_vtable g_ma_libvorbis_ds_vtable =
|
||||
{
|
||||
ma_libvorbis_ds_read,
|
||||
ma_libvorbis_ds_seek,
|
||||
ma_libvorbis_ds_get_data_format,
|
||||
ma_libvorbis_ds_get_cursor,
|
||||
ma_libvorbis_ds_get_length,
|
||||
NULL, /* onSetLooping */
|
||||
0 /* flags */
|
||||
};
|
||||
|
||||
|
||||
#if !defined(MA_NO_LIBVORBIS)
|
||||
static size_t ma_libvorbis_vf_callback__read(void* pBufferOut, size_t size, size_t count, void* pUserData)
|
||||
{
|
||||
ma_libvorbis* pVorbis = (ma_libvorbis*)pUserData;
|
||||
ma_result result;
|
||||
size_t bytesToRead;
|
||||
size_t bytesRead;
|
||||
|
||||
/* For consistency with fread(). If `size` of `count` is 0, return 0 immediately without changing anything. */
|
||||
if (size == 0 || count == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bytesToRead = size * count;
|
||||
result = pVorbis->onRead(pVorbis->pReadSeekTellUserData, pBufferOut, bytesToRead, &bytesRead);
|
||||
if (result != MA_SUCCESS) {
|
||||
/* Not entirely sure what to return here. What if an error occurs, but some data was read and bytesRead is > 0? */
|
||||
return 0;
|
||||
}
|
||||
|
||||
return bytesRead / size;
|
||||
}
|
||||
|
||||
static int ma_libvorbis_vf_callback__seek(void* pUserData, ogg_int64_t offset, int whence)
|
||||
{
|
||||
ma_libvorbis* pVorbis = (ma_libvorbis*)pUserData;
|
||||
ma_result result;
|
||||
ma_seek_origin origin;
|
||||
|
||||
if (whence == SEEK_SET) {
|
||||
origin = ma_seek_origin_start;
|
||||
} else if (whence == SEEK_END) {
|
||||
origin = ma_seek_origin_end;
|
||||
} else {
|
||||
origin = ma_seek_origin_current;
|
||||
}
|
||||
|
||||
result = pVorbis->onSeek(pVorbis->pReadSeekTellUserData, offset, origin);
|
||||
if (result != MA_SUCCESS) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static long ma_libvorbis_vf_callback__tell(void* pUserData)
|
||||
{
|
||||
ma_libvorbis* pVorbis = (ma_libvorbis*)pUserData;
|
||||
ma_result result;
|
||||
ma_int64 cursor;
|
||||
|
||||
result = pVorbis->onTell(pVorbis->pReadSeekTellUserData, &cursor);
|
||||
if (result != MA_SUCCESS) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return (long)cursor;
|
||||
}
|
||||
#endif
|
||||
|
||||
static ma_result ma_libvorbis_init_internal(const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_libvorbis* pVorbis)
|
||||
{
|
||||
if (pVorbis == NULL) {
|
||||
return MA_INVALID_ARGS;
|
||||
}
|
||||
|
||||
memset(pVorbis, 0, sizeof(*pVorbis));
|
||||
pVorbis->format = ma_format_f32; /* f32 by default. */
|
||||
|
||||
if (pConfig != NULL && (pConfig->preferredFormat == ma_format_f32 || pConfig->preferredFormat == ma_format_s16)) {
|
||||
pVorbis->format = pConfig->preferredFormat;
|
||||
} else {
|
||||
/* Getting here means something other than f32 and s16 was specified. Just leave this unset to use the default format. */
|
||||
}
|
||||
|
||||
#if !defined(MA_NO_LIBVORBIS)
|
||||
{
|
||||
ma_result result;
|
||||
ma_data_source_config dataSourceConfig;
|
||||
|
||||
dataSourceConfig = ma_data_source_config_init();
|
||||
dataSourceConfig.vtable = &g_ma_libvorbis_ds_vtable;
|
||||
|
||||
result = ma_data_source_init(&dataSourceConfig, &pVorbis->ds);
|
||||
if (result != MA_SUCCESS) {
|
||||
return result; /* Failed to initialize the base data source. */
|
||||
}
|
||||
|
||||
pVorbis->vf = (OggVorbis_File*)ma_malloc(sizeof(OggVorbis_File), pAllocationCallbacks);
|
||||
if (pVorbis->vf == NULL) {
|
||||
ma_data_source_uninit(&pVorbis->ds);
|
||||
return MA_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
#else
|
||||
{
|
||||
/* libvorbis is disabled. */
|
||||
(void)pAllocationCallbacks;
|
||||
return MA_NOT_IMPLEMENTED;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
MA_API ma_result ma_libvorbis_init(ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void* pReadSeekTellUserData, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_libvorbis* pVorbis)
|
||||
{
|
||||
ma_result result;
|
||||
|
||||
(void)pAllocationCallbacks; /* Can't seem to find a way to configure memory allocations in libvorbis. */
|
||||
|
||||
if (onRead == NULL || onSeek == NULL) {
|
||||
return MA_INVALID_ARGS; /* onRead and onSeek are mandatory. */
|
||||
}
|
||||
|
||||
result = ma_libvorbis_init_internal(pConfig, pAllocationCallbacks, pVorbis);
|
||||
if (result != MA_SUCCESS) {
|
||||
return result;
|
||||
}
|
||||
|
||||
pVorbis->onRead = onRead;
|
||||
pVorbis->onSeek = onSeek;
|
||||
pVorbis->onTell = onTell;
|
||||
pVorbis->pReadSeekTellUserData = pReadSeekTellUserData;
|
||||
|
||||
#if !defined(MA_NO_LIBVORBIS)
|
||||
{
|
||||
int libvorbisResult;
|
||||
ov_callbacks libvorbisCallbacks;
|
||||
|
||||
/* We can now initialize the vorbis decoder. This must be done after we've set up the callbacks. */
|
||||
libvorbisCallbacks.read_func = ma_libvorbis_vf_callback__read;
|
||||
libvorbisCallbacks.seek_func = ma_libvorbis_vf_callback__seek;
|
||||
libvorbisCallbacks.close_func = NULL;
|
||||
libvorbisCallbacks.tell_func = ma_libvorbis_vf_callback__tell;
|
||||
|
||||
libvorbisResult = ov_open_callbacks(pVorbis, (OggVorbis_File*)pVorbis->vf, NULL, 0, libvorbisCallbacks);
|
||||
if (libvorbisResult < 0) {
|
||||
ma_data_source_uninit(&pVorbis->ds);
|
||||
ma_free(pVorbis->vf, pAllocationCallbacks);
|
||||
return MA_INVALID_FILE;
|
||||
}
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
#else
|
||||
{
|
||||
/* libvorbis is disabled. */
|
||||
return MA_NOT_IMPLEMENTED;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
MA_API ma_result ma_libvorbis_init_file(const char* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_libvorbis* pVorbis)
|
||||
{
|
||||
ma_result result;
|
||||
|
||||
(void)pAllocationCallbacks; /* Can't seem to find a way to configure memory allocations in libvorbis. */
|
||||
|
||||
result = ma_libvorbis_init_internal(pConfig, pAllocationCallbacks, pVorbis);
|
||||
if (result != MA_SUCCESS) {
|
||||
return result;
|
||||
}
|
||||
|
||||
#if !defined(MA_NO_LIBVORBIS)
|
||||
{
|
||||
int libvorbisResult;
|
||||
|
||||
libvorbisResult = ov_fopen(pFilePath, (OggVorbis_File*)pVorbis->vf);
|
||||
if (libvorbisResult < 0) {
|
||||
ma_data_source_uninit(&pVorbis->ds);
|
||||
ma_free(pVorbis->vf, pAllocationCallbacks);
|
||||
return MA_INVALID_FILE;
|
||||
}
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
#else
|
||||
{
|
||||
/* libvorbis is disabled. */
|
||||
(void)pFilePath;
|
||||
return MA_NOT_IMPLEMENTED;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
MA_API void ma_libvorbis_uninit(ma_libvorbis* pVorbis, const ma_allocation_callbacks* pAllocationCallbacks)
|
||||
{
|
||||
if (pVorbis == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
(void)pAllocationCallbacks;
|
||||
|
||||
#if !defined(MA_NO_LIBVORBIS)
|
||||
{
|
||||
ov_clear((OggVorbis_File*)pVorbis->vf);
|
||||
}
|
||||
#else
|
||||
{
|
||||
/* libvorbis is disabled. Should never hit this since initialization would have failed. */
|
||||
assert(MA_FALSE);
|
||||
}
|
||||
#endif
|
||||
|
||||
ma_data_source_uninit(&pVorbis->ds);
|
||||
ma_free(pVorbis->vf, pAllocationCallbacks);
|
||||
}
|
||||
|
||||
MA_API ma_result ma_libvorbis_read_pcm_frames(ma_libvorbis* pVorbis, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead)
|
||||
{
|
||||
if (pFramesRead != NULL) {
|
||||
*pFramesRead = 0;
|
||||
}
|
||||
|
||||
if (frameCount == 0) {
|
||||
return MA_INVALID_ARGS;
|
||||
}
|
||||
|
||||
if (pVorbis == NULL) {
|
||||
return MA_INVALID_ARGS;
|
||||
}
|
||||
|
||||
#if !defined(MA_NO_LIBVORBIS)
|
||||
{
|
||||
/* We always use floating point format. */
|
||||
ma_result result = MA_SUCCESS; /* Must be initialized to MA_SUCCESS. */
|
||||
ma_uint64 totalFramesRead;
|
||||
ma_format format;
|
||||
ma_uint32 channels;
|
||||
|
||||
ma_libvorbis_get_data_format(pVorbis, &format, &channels, NULL, NULL, 0);
|
||||
|
||||
totalFramesRead = 0;
|
||||
while (totalFramesRead < frameCount) {
|
||||
long libvorbisResult;
|
||||
ma_uint64 framesToRead;
|
||||
ma_uint64 framesRemaining;
|
||||
|
||||
framesRemaining = (frameCount - totalFramesRead);
|
||||
framesToRead = 1024;
|
||||
if (framesToRead > framesRemaining) {
|
||||
framesToRead = framesRemaining;
|
||||
}
|
||||
|
||||
if (format == ma_format_f32) {
|
||||
float** ppFramesF32;
|
||||
|
||||
libvorbisResult = ov_read_float((OggVorbis_File*)pVorbis->vf, &ppFramesF32, (int)framesToRead, NULL);
|
||||
if (libvorbisResult < 0) {
|
||||
result = MA_ERROR; /* Error while decoding. */
|
||||
break;
|
||||
} else {
|
||||
/* Frames need to be interleaved. */
|
||||
ma_interleave_pcm_frames(format, channels, libvorbisResult, (const void**)ppFramesF32, ma_offset_pcm_frames_ptr(pFramesOut, totalFramesRead, format, channels));
|
||||
totalFramesRead += libvorbisResult;
|
||||
|
||||
if (libvorbisResult == 0) {
|
||||
result = MA_AT_END;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
libvorbisResult = ov_read((OggVorbis_File*)pVorbis->vf, (char*)ma_offset_pcm_frames_ptr(pFramesOut, totalFramesRead, format, channels), (int)(framesToRead * ma_get_bytes_per_frame(format, channels)), 0, 2, 1, NULL);
|
||||
if (libvorbisResult < 0) {
|
||||
result = MA_ERROR; /* Error while decoding. */
|
||||
break;
|
||||
} else {
|
||||
/* Conveniently, there's no need to interleaving when using ov_read(). I'm not sure why ov_read_float() is different in that regard... */
|
||||
totalFramesRead += libvorbisResult / ma_get_bytes_per_frame(format, channels);
|
||||
|
||||
if (libvorbisResult == 0) {
|
||||
result = MA_AT_END;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pFramesRead != NULL) {
|
||||
*pFramesRead = totalFramesRead;
|
||||
}
|
||||
|
||||
if (result == MA_SUCCESS && totalFramesRead == 0) {
|
||||
result = MA_AT_END;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
#else
|
||||
{
|
||||
/* libvorbis is disabled. Should never hit this since initialization would have failed. */
|
||||
assert(MA_FALSE);
|
||||
|
||||
(void)pFramesOut;
|
||||
(void)frameCount;
|
||||
(void)pFramesRead;
|
||||
|
||||
return MA_NOT_IMPLEMENTED;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
MA_API ma_result ma_libvorbis_seek_to_pcm_frame(ma_libvorbis* pVorbis, ma_uint64 frameIndex)
|
||||
{
|
||||
if (pVorbis == NULL) {
|
||||
return MA_INVALID_ARGS;
|
||||
}
|
||||
|
||||
#if !defined(MA_NO_LIBVORBIS)
|
||||
{
|
||||
int libvorbisResult = ov_pcm_seek((OggVorbis_File*)pVorbis->vf, (ogg_int64_t)frameIndex);
|
||||
if (libvorbisResult != 0) {
|
||||
if (libvorbisResult == OV_ENOSEEK) {
|
||||
return MA_INVALID_OPERATION; /* Not seekable. */
|
||||
} else if (libvorbisResult == OV_EINVAL) {
|
||||
return MA_INVALID_ARGS;
|
||||
} else {
|
||||
return MA_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
#else
|
||||
{
|
||||
/* libvorbis is disabled. Should never hit this since initialization would have failed. */
|
||||
assert(MA_FALSE);
|
||||
|
||||
(void)frameIndex;
|
||||
|
||||
return MA_NOT_IMPLEMENTED;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
MA_API ma_result ma_libvorbis_get_data_format(ma_libvorbis* pVorbis, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap)
|
||||
{
|
||||
/* Defaults for safety. */
|
||||
if (pFormat != NULL) {
|
||||
*pFormat = ma_format_unknown;
|
||||
}
|
||||
if (pChannels != NULL) {
|
||||
*pChannels = 0;
|
||||
}
|
||||
if (pSampleRate != NULL) {
|
||||
*pSampleRate = 0;
|
||||
}
|
||||
if (pChannelMap != NULL) {
|
||||
memset(pChannelMap, 0, sizeof(*pChannelMap) * channelMapCap);
|
||||
}
|
||||
|
||||
if (pVorbis == NULL) {
|
||||
return MA_INVALID_OPERATION;
|
||||
}
|
||||
|
||||
if (pFormat != NULL) {
|
||||
*pFormat = pVorbis->format;
|
||||
}
|
||||
|
||||
#if !defined(MA_NO_LIBVORBIS)
|
||||
{
|
||||
vorbis_info* pInfo = ov_info((OggVorbis_File*)pVorbis->vf, 0);
|
||||
if (pInfo == NULL) {
|
||||
return MA_INVALID_OPERATION;
|
||||
}
|
||||
|
||||
if (pChannels != NULL) {
|
||||
*pChannels = pInfo->channels;
|
||||
}
|
||||
|
||||
if (pSampleRate != NULL) {
|
||||
*pSampleRate = pInfo->rate;
|
||||
}
|
||||
|
||||
if (pChannelMap != NULL) {
|
||||
ma_channel_map_init_standard(ma_standard_channel_map_vorbis, pChannelMap, channelMapCap, pInfo->channels);
|
||||
}
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
#else
|
||||
{
|
||||
/* libvorbis is disabled. Should never hit this since initialization would have failed. */
|
||||
assert(MA_FALSE);
|
||||
return MA_NOT_IMPLEMENTED;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
MA_API ma_result ma_libvorbis_get_cursor_in_pcm_frames(ma_libvorbis* pVorbis, ma_uint64* pCursor)
|
||||
{
|
||||
if (pCursor == NULL) {
|
||||
return MA_INVALID_ARGS;
|
||||
}
|
||||
|
||||
*pCursor = 0; /* Safety. */
|
||||
|
||||
if (pVorbis == NULL) {
|
||||
return MA_INVALID_ARGS;
|
||||
}
|
||||
|
||||
#if !defined(MA_NO_LIBVORBIS)
|
||||
{
|
||||
ogg_int64_t offset = ov_pcm_tell((OggVorbis_File*)pVorbis->vf);
|
||||
if (offset < 0) {
|
||||
return MA_INVALID_FILE;
|
||||
}
|
||||
|
||||
*pCursor = (ma_uint64)offset;
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
#else
|
||||
{
|
||||
/* libvorbis is disabled. Should never hit this since initialization would have failed. */
|
||||
assert(MA_FALSE);
|
||||
return MA_NOT_IMPLEMENTED;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
MA_API ma_result ma_libvorbis_get_length_in_pcm_frames(ma_libvorbis* pVorbis, ma_uint64* pLength)
|
||||
{
|
||||
if (pLength == NULL) {
|
||||
return MA_INVALID_ARGS;
|
||||
}
|
||||
|
||||
*pLength = 0; /* Safety. */
|
||||
|
||||
if (pVorbis == NULL) {
|
||||
return MA_INVALID_ARGS;
|
||||
}
|
||||
|
||||
#if !defined(MA_NO_LIBVORBIS)
|
||||
{
|
||||
/*
|
||||
Will work in the supermajority of cases where a file has a single logical bitstream. Concatenated streams
|
||||
are much harder to determine the length of since they can have sample rate changes, but they should be
|
||||
extremely rare outside of unseekable livestreams anyway.
|
||||
*/
|
||||
if (ov_streams((OggVorbis_File*)pVorbis->vf) == 1) {
|
||||
ogg_int64_t length = ov_pcm_total((OggVorbis_File*)pVorbis->vf, 0);
|
||||
if(length != OV_EINVAL) {
|
||||
*pLength = (ma_uint64)length;
|
||||
} else {
|
||||
/* Unseekable. */
|
||||
}
|
||||
} else {
|
||||
/* Concatenated stream. */
|
||||
}
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
#else
|
||||
{
|
||||
/* libvorbis is disabled. Should never hit this since initialization would have failed. */
|
||||
assert(MA_FALSE);
|
||||
return MA_NOT_IMPLEMENTED;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
The code below defines the vtable that you'll plug into your `ma_decoder_config` object.
|
||||
*/
|
||||
#if !defined(MA_NO_LIBVORBIS)
|
||||
static ma_result ma_decoding_backend_init__libvorbis(void* pUserData, ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void* pReadSeekTellUserData, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend)
|
||||
{
|
||||
ma_result result;
|
||||
ma_libvorbis* pVorbis;
|
||||
|
||||
(void)pUserData;
|
||||
|
||||
pVorbis = (ma_libvorbis*)ma_malloc(sizeof(*pVorbis), pAllocationCallbacks);
|
||||
if (pVorbis == NULL) {
|
||||
return MA_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
result = ma_libvorbis_init(onRead, onSeek, onTell, pReadSeekTellUserData, pConfig, pAllocationCallbacks, pVorbis);
|
||||
if (result != MA_SUCCESS) {
|
||||
ma_free(pVorbis, pAllocationCallbacks);
|
||||
return result;
|
||||
}
|
||||
|
||||
*ppBackend = pVorbis;
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
|
||||
static ma_result ma_decoding_backend_init_file__libvorbis(void* pUserData, const char* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend)
|
||||
{
|
||||
ma_result result;
|
||||
ma_libvorbis* pVorbis;
|
||||
|
||||
(void)pUserData;
|
||||
|
||||
pVorbis = (ma_libvorbis*)ma_malloc(sizeof(*pVorbis), pAllocationCallbacks);
|
||||
if (pVorbis == NULL) {
|
||||
return MA_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
result = ma_libvorbis_init_file(pFilePath, pConfig, pAllocationCallbacks, pVorbis);
|
||||
if (result != MA_SUCCESS) {
|
||||
ma_free(pVorbis, pAllocationCallbacks);
|
||||
return result;
|
||||
}
|
||||
|
||||
*ppBackend = pVorbis;
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
|
||||
static void ma_decoding_backend_uninit__libvorbis(void* pUserData, ma_data_source* pBackend, const ma_allocation_callbacks* pAllocationCallbacks)
|
||||
{
|
||||
ma_libvorbis* pVorbis = (ma_libvorbis*)pBackend;
|
||||
|
||||
(void)pUserData;
|
||||
|
||||
ma_libvorbis_uninit(pVorbis, pAllocationCallbacks);
|
||||
ma_free(pVorbis, pAllocationCallbacks);
|
||||
}
|
||||
|
||||
|
||||
static ma_decoding_backend_vtable ma_gDecodingBackendVTable_libvorbis =
|
||||
{
|
||||
ma_decoding_backend_init__libvorbis,
|
||||
ma_decoding_backend_init_file__libvorbis,
|
||||
NULL, /* onInitFileW() */
|
||||
NULL, /* onInitMemory() */
|
||||
ma_decoding_backend_uninit__libvorbis
|
||||
};
|
||||
ma_decoding_backend_vtable* ma_decoding_backend_libvorbis = &ma_gDecodingBackendVTable_libvorbis;
|
||||
#else
|
||||
ma_decoding_backend_vtable* ma_decoding_backend_libvorbis = NULL;
|
||||
#endif
|
||||
|
||||
#endif /* miniaudio_libvorbis_c */
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
This implements a data source that decodes Vorbis streams via libvorbis + libvorbisfile
|
||||
|
||||
This object can be plugged into any `ma_data_source_*()` API and can also be used as a custom
|
||||
decoding backend. See the custom_decoder example.
|
||||
*/
|
||||
#ifndef miniaudio_libvorbis_h
|
||||
#define miniaudio_libvorbis_h
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "../../../miniaudio.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
ma_data_source_base ds; /* The libvorbis decoder can be used independently as a data source. */
|
||||
ma_read_proc onRead;
|
||||
ma_seek_proc onSeek;
|
||||
ma_tell_proc onTell;
|
||||
void* pReadSeekTellUserData;
|
||||
ma_format format; /* Will be either f32 or s16. */
|
||||
/*OggVorbis_File**/ void* vf; /* Typed as void* so we can avoid a dependency on opusfile in the header section. */
|
||||
} ma_libvorbis;
|
||||
|
||||
MA_API ma_result ma_libvorbis_init(ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void* pReadSeekTellUserData, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_libvorbis* pVorbis);
|
||||
MA_API ma_result ma_libvorbis_init_file(const char* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_libvorbis* pVorbis);
|
||||
MA_API void ma_libvorbis_uninit(ma_libvorbis* pVorbis, const ma_allocation_callbacks* pAllocationCallbacks);
|
||||
MA_API ma_result ma_libvorbis_read_pcm_frames(ma_libvorbis* pVorbis, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead);
|
||||
MA_API ma_result ma_libvorbis_seek_to_pcm_frame(ma_libvorbis* pVorbis, ma_uint64 frameIndex);
|
||||
MA_API ma_result ma_libvorbis_get_data_format(ma_libvorbis* pVorbis, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap);
|
||||
MA_API ma_result ma_libvorbis_get_cursor_in_pcm_frames(ma_libvorbis* pVorbis, ma_uint64* pCursor);
|
||||
MA_API ma_result ma_libvorbis_get_length_in_pcm_frames(ma_libvorbis* pVorbis, ma_uint64* pLength);
|
||||
|
||||
/* Decoding backend vtable. This is what you'll plug into ma_decoder_config.pBackendVTables. No user data required. */
|
||||
extern ma_decoding_backend_vtable* ma_decoding_backend_libvorbis;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* miniaudio_libvorbis_h */
|
||||
@@ -1,3 +1,5 @@
|
||||
/* THIS HAS BEEN DEPRECATED! Use the libopus decoder in extras/decoders/libopus instead. */
|
||||
|
||||
/*
|
||||
This implements a data source that decodes Opus streams via libopus + libopusfile
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* THIS HAS BEEN DEPRECATED! Use the libvorbis decoder in extras/decoders/libvorbis instead. */
|
||||
|
||||
/*
|
||||
This implements a data source that decodes Vorbis streams via libvorbis + libvorbisfile
|
||||
|
||||
@@ -326,7 +328,7 @@ MA_API ma_result ma_libvorbis_read_pcm_frames(ma_libvorbis* pVorbis, void* pFram
|
||||
}
|
||||
}
|
||||
} else {
|
||||
libvorbisResult = ov_read(&pVorbis->vf, ma_offset_pcm_frames_ptr(pFramesOut, totalFramesRead, format, channels), framesToRead * ma_get_bytes_per_frame(format, channels), 0, 2, 1, NULL);
|
||||
libvorbisResult = ov_read(&pVorbis->vf, (char*)ma_offset_pcm_frames_ptr(pFramesOut, totalFramesRead, format, channels), framesToRead * ma_get_bytes_per_frame(format, channels), 0, 2, 1, NULL);
|
||||
if (libvorbisResult < 0) {
|
||||
result = MA_ERROR; /* Error while decoding. */
|
||||
break;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Audio playback and capture library. Choice of public domain or MIT-0. See license statements at the end of this file.
|
||||
miniaudio - v0.11.18 - 2023-08-07
|
||||
miniaudio - v0.11.23 - 2025-09-11
|
||||
|
||||
David Reid - mackron@gmail.com
|
||||
|
||||
@@ -20,7 +20,7 @@ extern "C" {
|
||||
|
||||
#define MA_VERSION_MAJOR 0
|
||||
#define MA_VERSION_MINOR 11
|
||||
#define MA_VERSION_REVISION 18
|
||||
#define MA_VERSION_REVISION 23
|
||||
#define MA_VERSION_STRING MA_XSTRINGIFY(MA_VERSION_MAJOR) "." MA_XSTRINGIFY(MA_VERSION_MINOR) "." MA_XSTRINGIFY(MA_VERSION_REVISION)
|
||||
|
||||
#if defined(_MSC_VER) && !defined(__clang__)
|
||||
@@ -37,8 +37,7 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
#if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__)) || defined(_M_X64) || defined(__ia64) || defined(_M_IA64) || defined(__aarch64__) || defined(_M_ARM64) || defined(__powerpc64__)
|
||||
#if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__)) || defined(_M_X64) || defined(__ia64) || defined(_M_IA64) || defined(__aarch64__) || defined(_M_ARM64) || defined(__powerpc64__) || defined(__ppc64__)
|
||||
#define MA_SIZEOF_PTR 8
|
||||
#else
|
||||
#define MA_SIZEOF_PTR 4
|
||||
@@ -102,7 +101,7 @@ typedef void* ma_handle;
|
||||
typedef void* ma_ptr;
|
||||
|
||||
/*
|
||||
ma_proc is annoying because when compiling with GCC we get pendantic warnings about converting
|
||||
ma_proc is annoying because when compiling with GCC we get pedantic warnings about converting
|
||||
between `void*` and `void (*)()`. We can't use `void (*)()` with MSVC however, because we'll get
|
||||
warning C4191 about "type cast between incompatible function types". To work around this I'm going
|
||||
to use a different data type depending on the compiler.
|
||||
@@ -128,6 +127,8 @@ typedef ma_uint16 wchar_t;
|
||||
#define MA_SIZE_MAX 0xFFFFFFFF /* When SIZE_MAX is not defined by the standard library just default to the maximum 32-bit unsigned integer. */
|
||||
#endif
|
||||
|
||||
#define MA_UINT64_MAX (((ma_uint64)0xFFFFFFFF << 32) | (ma_uint64)0xFFFFFFFF) /* Weird shifting syntax is for VC6 compatibility. */
|
||||
|
||||
|
||||
/* Platform/backend detection. */
|
||||
#if defined(_WIN32) || defined(__COSMOPOLITAN__)
|
||||
@@ -136,29 +137,55 @@ typedef ma_uint16 wchar_t;
|
||||
#define MA_WIN32_UWP
|
||||
#elif defined(WINAPI_FAMILY) && (defined(WINAPI_FAMILY_GAMES) && WINAPI_FAMILY == WINAPI_FAMILY_GAMES)
|
||||
#define MA_WIN32_GDK
|
||||
#elif defined(NXDK)
|
||||
#define MA_WIN32_NXDK
|
||||
#else
|
||||
#define MA_WIN32_DESKTOP
|
||||
#endif
|
||||
|
||||
/* The original Xbox. */
|
||||
#if defined(NXDK) /* <-- Add other Xbox compiler toolchains here, and then add a toolchain-specific define in case we need to discriminate between them later. */
|
||||
#define MA_XBOX
|
||||
|
||||
#if defined(NXDK)
|
||||
#define MA_XBOX_NXDK
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#if !defined(_WIN32) /* If it's not Win32, assume POSIX. */
|
||||
#if defined(__MSDOS__) || defined(MSDOS) || defined(_MSDOS) || defined(__DOS__)
|
||||
#define MA_DOS
|
||||
|
||||
/* No threading allowed on DOS. */
|
||||
#ifndef MA_NO_THREADING
|
||||
#define MA_NO_THREADING
|
||||
#endif
|
||||
|
||||
/* No runtime linking allowed on DOS. */
|
||||
#ifndef MA_NO_RUNTIME_LINKING
|
||||
#define MA_NO_RUNTIME_LINKING
|
||||
#endif
|
||||
#endif
|
||||
#if !defined(MA_WIN32) && !defined(MA_DOS) /* If it's not Win32, assume POSIX. */
|
||||
#define MA_POSIX
|
||||
|
||||
/*
|
||||
Use the MA_NO_PTHREAD_IN_HEADER option at your own risk. This is intentionally undocumented.
|
||||
You can use this to avoid including pthread.h in the header section. The downside is that it
|
||||
results in some fixed sized structures being declared for the various types that are used in
|
||||
miniaudio. The risk here is that these types might be too small for a given platform. This
|
||||
risk is yours to take and no support will be offered if you enable this option.
|
||||
*/
|
||||
#ifndef MA_NO_PTHREAD_IN_HEADER
|
||||
#include <pthread.h> /* Unfortunate #include, but needed for pthread_t, pthread_mutex_t and pthread_cond_t types. */
|
||||
typedef pthread_t ma_pthread_t;
|
||||
typedef pthread_mutex_t ma_pthread_mutex_t;
|
||||
typedef pthread_cond_t ma_pthread_cond_t;
|
||||
#else
|
||||
typedef ma_uintptr ma_pthread_t;
|
||||
typedef union ma_pthread_mutex_t { char __data[40]; ma_uint64 __alignment; } ma_pthread_mutex_t;
|
||||
typedef union ma_pthread_cond_t { char __data[48]; ma_uint64 __alignment; } ma_pthread_cond_t;
|
||||
#if !defined(MA_NO_THREADING)
|
||||
/*
|
||||
Use the MA_NO_PTHREAD_IN_HEADER option at your own risk. This is intentionally undocumented.
|
||||
You can use this to avoid including pthread.h in the header section. The downside is that it
|
||||
results in some fixed sized structures being declared for the various types that are used in
|
||||
miniaudio. The risk here is that these types might be too small for a given platform. This
|
||||
risk is yours to take and no support will be offered if you enable this option.
|
||||
*/
|
||||
#ifndef MA_NO_PTHREAD_IN_HEADER
|
||||
#include <pthread.h> /* Unfortunate #include, but needed for pthread_t, pthread_mutex_t and pthread_cond_t types. */
|
||||
typedef pthread_t ma_pthread_t;
|
||||
typedef pthread_mutex_t ma_pthread_mutex_t;
|
||||
typedef pthread_cond_t ma_pthread_cond_t;
|
||||
#else
|
||||
typedef ma_uintptr ma_pthread_t;
|
||||
typedef union ma_pthread_mutex_t { char __data[40]; ma_uint64 __alignment; } ma_pthread_mutex_t;
|
||||
typedef union ma_pthread_cond_t { char __data[48]; ma_uint64 __alignment; } ma_pthread_cond_t;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(__unix__)
|
||||
@@ -185,8 +212,11 @@ typedef ma_uint16 wchar_t;
|
||||
#if defined(__PROSPERO__)
|
||||
#define MA_PROSPERO
|
||||
#endif
|
||||
#if defined(__NX__)
|
||||
#define MA_NX
|
||||
#if defined(__3DS__)
|
||||
#define MA_3DS
|
||||
#endif
|
||||
#if defined(__SWITCH__) || defined(__NX__)
|
||||
#define MA_SWITCH
|
||||
#endif
|
||||
#if defined(__BEOS__) || defined(__HAIKU__)
|
||||
#define MA_BEOS
|
||||
@@ -196,12 +226,13 @@ typedef ma_uint16 wchar_t;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(__has_c_attribute)
|
||||
#if __has_c_attribute(fallthrough)
|
||||
#define MA_FALLTHROUGH [[fallthrough]]
|
||||
#endif
|
||||
#if !defined(MA_FALLTHROUGH) && defined(__cplusplus) && __cplusplus >= 201703L
|
||||
#define MA_FALLTHROUGH [[fallthrough]]
|
||||
#endif
|
||||
#if !defined(MA_FALLTHROUGH) && defined(__has_attribute) && (defined(__clang__) || defined(__GNUC__))
|
||||
#if !defined(MA_FALLTHROUGH) && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202000L
|
||||
#define MA_FALLTHROUGH [[fallthrough]]
|
||||
#endif
|
||||
#if !defined(MA_FALLTHROUGH) && defined(__has_attribute)
|
||||
#if __has_attribute(fallthrough)
|
||||
#define MA_FALLTHROUGH __attribute__((fallthrough))
|
||||
#endif
|
||||
@@ -238,7 +269,7 @@ typedef ma_uint16 wchar_t;
|
||||
#define MA_NO_INLINE __attribute__((noinline))
|
||||
#else
|
||||
#define MA_INLINE MA_GNUC_INLINE_HINT
|
||||
#define MA_NO_INLINE __attribute__((noinline))
|
||||
#define MA_NO_INLINE
|
||||
#endif
|
||||
#elif defined(__WATCOMC__)
|
||||
#define MA_INLINE __inline
|
||||
@@ -296,7 +327,7 @@ Special wchar_t type to ensure any structures in the public sections that refere
|
||||
consistent size across all platforms.
|
||||
|
||||
On Windows, wchar_t is 2 bytes, whereas everywhere else it's 4 bytes. Since Windows likes to use
|
||||
wchar_t for it's IDs, we need a special explicitly sized wchar type that is always 2 bytes on all
|
||||
wchar_t for its IDs, we need a special explicitly sized wchar type that is always 2 bytes on all
|
||||
platforms.
|
||||
*/
|
||||
#if !defined(MA_POSIX) && defined(MA_WIN32)
|
||||
@@ -322,7 +353,7 @@ MA_LOG_LEVEL_INFO
|
||||
callback.
|
||||
|
||||
MA_LOG_LEVEL_WARNING
|
||||
Warnings. You should enable this in you development builds and action them when encounted. These
|
||||
Warnings. You should enable this in you development builds and action them when encountered. These
|
||||
logs usually indicate a potential problem or misconfiguration, but still allow you to keep
|
||||
running. This will never be called from within the data callback.
|
||||
|
||||
@@ -571,7 +602,7 @@ typedef enum
|
||||
ma_standard_sample_rate_192000 = 192000,
|
||||
|
||||
ma_standard_sample_rate_16000 = 16000, /* Extreme lows */
|
||||
ma_standard_sample_rate_11025 = 11250,
|
||||
ma_standard_sample_rate_11025 = 11025,
|
||||
ma_standard_sample_rate_8000 = 8000,
|
||||
|
||||
ma_standard_sample_rate_352800 = 352800, /* Extreme highs */
|
||||
@@ -621,7 +652,7 @@ typedef struct
|
||||
|
||||
typedef struct
|
||||
{
|
||||
ma_int32 state;
|
||||
ma_uint32 state;
|
||||
} ma_lcg;
|
||||
|
||||
|
||||
@@ -1694,7 +1725,7 @@ MA_API void ma_resampler_uninit(ma_resampler* pResampler, const ma_allocation_ca
|
||||
/*
|
||||
Converts the given input data.
|
||||
|
||||
Both the input and output frames must be in the format specified in the config when the resampler was initilized.
|
||||
Both the input and output frames must be in the format specified in the config when the resampler was initialized.
|
||||
|
||||
On input, [pFrameCountOut] contains the number of output frames to process. On output it contains the number of output frames that
|
||||
were actually processed, which may be less than the requested amount which will happen if there's not enough input data. You can use
|
||||
@@ -1754,7 +1785,7 @@ input frames.
|
||||
MA_API ma_result ma_resampler_get_expected_output_frame_count(const ma_resampler* pResampler, ma_uint64 inputFrameCount, ma_uint64* pOutputFrameCount);
|
||||
|
||||
/*
|
||||
Resets the resampler's timer and clears it's internal cache.
|
||||
Resets the resampler's timer and clears its internal cache.
|
||||
*/
|
||||
MA_API ma_result ma_resampler_reset(ma_resampler* pResampler);
|
||||
|
||||
@@ -1975,7 +2006,7 @@ MA_API void ma_channel_map_init_standard(ma_standard_channel_map standardChannel
|
||||
/*
|
||||
Copies a channel map.
|
||||
|
||||
Both input and output channel map buffers must have a capacity of at at least `channels`.
|
||||
Both input and output channel map buffers must have a capacity of at least `channels`.
|
||||
*/
|
||||
MA_API void ma_channel_map_copy(ma_channel* pOut, const ma_channel* pIn, ma_uint32 channels);
|
||||
|
||||
@@ -2114,6 +2145,8 @@ MA_API void ma_data_source_uninit(ma_data_source* pDataSource);
|
||||
MA_API ma_result ma_data_source_read_pcm_frames(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); /* Must support pFramesOut = NULL in which case a forward seek should be performed. */
|
||||
MA_API ma_result ma_data_source_seek_pcm_frames(ma_data_source* pDataSource, ma_uint64 frameCount, ma_uint64* pFramesSeeked); /* Can only seek forward. Equivalent to ma_data_source_read_pcm_frames(pDataSource, NULL, frameCount, &framesRead); */
|
||||
MA_API ma_result ma_data_source_seek_to_pcm_frame(ma_data_source* pDataSource, ma_uint64 frameIndex);
|
||||
MA_API ma_result ma_data_source_seek_seconds(ma_data_source* pDataSource, float secondCount, float* pSecondsSeeked); /* Can only seek forward. Abstraction to ma_data_source_seek_pcm_frames() */
|
||||
MA_API ma_result ma_data_source_seek_to_second(ma_data_source* pDataSource, float seekPointInSeconds); /* Abstraction to ma_data_source_seek_to_pcm_frame() */
|
||||
MA_API ma_result ma_data_source_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap);
|
||||
MA_API ma_result ma_data_source_get_cursor_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pCursor);
|
||||
MA_API ma_result ma_data_source_get_length_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pLength); /* Returns MA_NOT_IMPLEMENTED if the length is unknown or cannot be determined. Decoders can return this. */
|
||||
@@ -2479,6 +2512,12 @@ MA_API ma_result ma_event_wait(ma_event* pEvent);
|
||||
Signals the specified auto-reset event.
|
||||
*/
|
||||
MA_API ma_result ma_event_signal(ma_event* pEvent);
|
||||
|
||||
|
||||
MA_API ma_result ma_semaphore_init(int initialValue, ma_semaphore* pSemaphore);
|
||||
MA_API void ma_semaphore_uninit(ma_semaphore* pSemaphore);
|
||||
MA_API ma_result ma_semaphore_wait(ma_semaphore* pSemaphore);
|
||||
MA_API ma_result ma_semaphore_release(ma_semaphore* pSemaphore);
|
||||
#endif /* MA_NO_THREADING */
|
||||
|
||||
|
||||
@@ -2570,7 +2609,7 @@ Job Queue
|
||||
/*
|
||||
Slot Allocator
|
||||
--------------
|
||||
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
|
||||
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 allocate an index that can be used
|
||||
as the insertion point for an object.
|
||||
|
||||
Slots are reference counted to help mitigate the ABA problem in the lock-free queue we use for tracking jobs.
|
||||
@@ -2832,7 +2871,7 @@ This section contains the APIs for device playback and capture. Here is where yo
|
||||
************************************************************************************************************************************************************/
|
||||
#ifndef MA_NO_DEVICE_IO
|
||||
/* Some backends are only supported on certain platforms. */
|
||||
#if defined(MA_WIN32)
|
||||
#if defined(MA_WIN32) && !defined(MA_XBOX)
|
||||
#define MA_SUPPORT_WASAPI
|
||||
|
||||
#if defined(MA_WIN32_DESKTOP) /* DirectSound and WinMM backends are only supported on desktops. */
|
||||
@@ -3013,7 +3052,8 @@ typedef enum
|
||||
ma_device_notification_type_stopped,
|
||||
ma_device_notification_type_rerouted,
|
||||
ma_device_notification_type_interruption_began,
|
||||
ma_device_notification_type_interruption_ended
|
||||
ma_device_notification_type_interruption_ended,
|
||||
ma_device_notification_type_unlocked
|
||||
} ma_device_notification_type;
|
||||
|
||||
typedef struct
|
||||
@@ -3302,6 +3342,8 @@ typedef union
|
||||
int nullbackend; /* The null backend uses an integer for device IDs. */
|
||||
} ma_device_id;
|
||||
|
||||
MA_API ma_bool32 ma_device_id_equal(const ma_device_id* pA, const ma_device_id* pB);
|
||||
|
||||
|
||||
typedef struct ma_context_config ma_context_config;
|
||||
typedef struct ma_device_config ma_device_config;
|
||||
@@ -3339,7 +3381,7 @@ struct ma_device_config
|
||||
ma_uint32 periods;
|
||||
ma_performance_profile performanceProfile;
|
||||
ma_bool8 noPreSilencedOutputBuffer; /* When set to true, the contents of the output buffer passed into the data callback will be left undefined rather than initialized to silence. */
|
||||
ma_bool8 noClip; /* When set to true, the contents of the output buffer passed into the data callback will be clipped after returning. Only applies when the playback sample format is f32. */
|
||||
ma_bool8 noClip; /* When set to true, the contents of the output buffer passed into the data callback will not be clipped after returning. Only applies when the playback sample format is f32. */
|
||||
ma_bool8 noDisableDenormals; /* Do not disable denormals when firing the data callback. */
|
||||
ma_bool8 noFixedSizedCallback; /* Disables strict fixed-sized data callbacks. Setting this to true will result in the period size being treated only as a hint to the backend. This is an optimization for those who don't need fixed sized callbacks. */
|
||||
ma_device_data_proc dataCallback;
|
||||
@@ -3389,6 +3431,7 @@ struct ma_device_config
|
||||
{
|
||||
const char* pStreamNamePlayback;
|
||||
const char* pStreamNameCapture;
|
||||
int channelMap;
|
||||
} pulse;
|
||||
struct
|
||||
{
|
||||
@@ -3408,6 +3451,7 @@ struct ma_device_config
|
||||
ma_aaudio_allowed_capture_policy allowedCapturePolicy;
|
||||
ma_bool32 noAutoStartAfterReroute;
|
||||
ma_bool32 enableCompatibilityWorkarounds;
|
||||
ma_bool32 allowSetBufferCapacity;
|
||||
} aaudio;
|
||||
};
|
||||
|
||||
@@ -3480,7 +3524,7 @@ and on output returns detailed information about the device in `ma_device_info`.
|
||||
case when the device ID is NULL, in which case information about the default device needs to be retrieved.
|
||||
|
||||
Once the context has been created and the device ID retrieved (if using anything other than the default device), the device can be created.
|
||||
This is a little bit more complicated than initialization of the context due to it's more complicated configuration. When initializing a
|
||||
This is a little bit more complicated than initialization of the context due to its more complicated configuration. When initializing a
|
||||
device, a duplex device may be requested. This means a separate data format needs to be specified for both playback and capture. On input,
|
||||
the data format is set to what the application wants. On output it's set to the native format which should match as closely as possible to
|
||||
the requested format. The conversion between the format requested by the application and the device's native format will be handled
|
||||
@@ -3501,10 +3545,10 @@ asynchronous reading and writing, `onDeviceStart()` and `onDeviceStop()` should
|
||||
The handling of data delivery between the application and the device is the most complicated part of the process. To make this a bit
|
||||
easier, some helper callbacks are available. If the backend uses a blocking read/write style of API, the `onDeviceRead()` and
|
||||
`onDeviceWrite()` callbacks can optionally be implemented. These are blocking and work just like reading and writing from a file. If the
|
||||
backend uses a callback for data delivery, that callback must call `ma_device_handle_backend_data_callback()` from within it's callback.
|
||||
backend uses a callback for data delivery, that callback must call `ma_device_handle_backend_data_callback()` from within its callback.
|
||||
This allows miniaudio to then process any necessary data conversion and then pass it to the miniaudio data callback.
|
||||
|
||||
If the backend requires absolute flexibility with it's data delivery, it can optionally implement the `onDeviceDataLoop()` callback
|
||||
If the backend requires absolute flexibility with its data delivery, it can optionally implement the `onDeviceDataLoop()` callback
|
||||
which will allow it to implement the logic that will run on the audio thread. This is much more advanced and is completely optional.
|
||||
|
||||
The audio thread should run data delivery logic in a loop while `ma_device_get_state() == ma_device_state_started` and no errors have been
|
||||
@@ -3544,6 +3588,10 @@ struct ma_context_config
|
||||
void* pUserData;
|
||||
ma_allocation_callbacks allocationCallbacks;
|
||||
struct
|
||||
{
|
||||
ma_handle hWnd; /* HWND. Optional window handle to pass into SetCooperativeLevel(). Will default to the foreground window, and if that fails, the desktop window. */
|
||||
} dsound;
|
||||
struct
|
||||
{
|
||||
ma_bool32 useVerboseDeviceEnumeration;
|
||||
} alsa;
|
||||
@@ -3632,6 +3680,7 @@ struct ma_context
|
||||
#ifdef MA_SUPPORT_DSOUND
|
||||
struct
|
||||
{
|
||||
ma_handle hWnd; /* Can be null. */
|
||||
ma_handle hDSoundDLL;
|
||||
ma_proc DirectSoundCreate;
|
||||
ma_proc DirectSoundEnumerateA;
|
||||
@@ -3679,6 +3728,7 @@ struct ma_context
|
||||
ma_proc snd_pcm_hw_params_set_rate_resample;
|
||||
ma_proc snd_pcm_hw_params_set_rate;
|
||||
ma_proc snd_pcm_hw_params_set_rate_near;
|
||||
ma_proc snd_pcm_hw_params_set_rate_minmax;
|
||||
ma_proc snd_pcm_hw_params_set_buffer_size_near;
|
||||
ma_proc snd_pcm_hw_params_set_periods_near;
|
||||
ma_proc snd_pcm_hw_params_set_access;
|
||||
@@ -4238,6 +4288,8 @@ struct ma_device
|
||||
{
|
||||
/*AAudioStream**/ ma_ptr pStreamPlayback;
|
||||
/*AAudioStream**/ ma_ptr pStreamCapture;
|
||||
ma_mutex rerouteLock;
|
||||
ma_atomic_bool32 isTearingDown;
|
||||
ma_aaudio_usage usage;
|
||||
ma_aaudio_content_type contentType;
|
||||
ma_aaudio_input_preset inputPreset;
|
||||
@@ -4661,6 +4713,10 @@ Retrieves basic information about every active playback and/or capture device.
|
||||
This function will allocate memory internally for the device lists and return a pointer to them through the `ppPlaybackDeviceInfos` and `ppCaptureDeviceInfos`
|
||||
parameters. If you do not want to incur the overhead of these allocations consider using `ma_context_enumerate_devices()` which will instead use a callback.
|
||||
|
||||
Note that this only retrieves the ID and name/description of the device. The reason for only retrieving basic information is that it would otherwise require
|
||||
opening the backend device in order to probe it for more detailed information which can be inefficient. Consider using `ma_context_get_device_info()` for this,
|
||||
but don't call it from within the enumeration callback.
|
||||
|
||||
|
||||
Parameters
|
||||
----------
|
||||
@@ -4702,7 +4758,7 @@ The returned pointers will become invalid upon the next call this this function,
|
||||
|
||||
See Also
|
||||
--------
|
||||
ma_context_get_devices()
|
||||
ma_context_enumerate_devices()
|
||||
*/
|
||||
MA_API ma_result ma_context_get_devices(ma_context* pContext, ma_device_info** ppPlaybackDeviceInfos, ma_uint32* pPlaybackDeviceCount, ma_device_info** ppCaptureDeviceInfos, ma_uint32* pCaptureDeviceCount);
|
||||
|
||||
@@ -4841,7 +4897,7 @@ from a microphone. Whether or not you should send or receive data from the devic
|
||||
playback, capture, full-duplex or loopback. (Note that loopback mode is only supported on select backends.) Sending and receiving audio data to and from the
|
||||
device is done via a callback which is fired by miniaudio at periodic time intervals.
|
||||
|
||||
The frequency at which data is delivered to and from a device depends on the size of it's period. The size of the period can be defined in terms of PCM frames
|
||||
The frequency at which data is delivered to and from a device depends on the size of its period. The size of the period can be defined in terms of PCM frames
|
||||
or milliseconds, whichever is more convenient. Generally speaking, the smaller the period, the lower the latency at the expense of higher CPU usage and
|
||||
increased risk of glitching due to the more frequent and granular data deliver intervals. The size of a period will depend on your requirements, but
|
||||
miniaudio's defaults should work fine for most scenarios. If you're building a game you should leave this fairly small, whereas if you're building a simple
|
||||
@@ -4915,7 +4971,7 @@ then be set directly on the structure. Below are the members of the `ma_device_c
|
||||
|
||||
performanceProfile
|
||||
A hint to miniaudio as to the performance requirements of your program. Can be either `ma_performance_profile_low_latency` (default) or
|
||||
`ma_performance_profile_conservative`. This mainly affects the size of default buffers and can usually be left at it's default value.
|
||||
`ma_performance_profile_conservative`. This mainly affects the size of default buffers and can usually be left at its default value.
|
||||
|
||||
noPreSilencedOutputBuffer
|
||||
When set to true, the contents of the output buffer passed into the data callback will be left undefined. When set to false (default), the contents of
|
||||
@@ -4923,8 +4979,8 @@ then be set directly on the structure. Below are the members of the `ma_device_c
|
||||
callback will write to every sample in the output buffer, or if you are doing your own clearing.
|
||||
|
||||
noClip
|
||||
When set to true, the contents of the output buffer passed into the data callback will be clipped after returning. When set to false (default), the
|
||||
contents of the output buffer are left alone after returning and it will be left up to the backend itself to decide whether or not the clip. This only
|
||||
When set to true, the contents of the output buffer are left alone after returning and it will be left up to the backend itself to decide whether or
|
||||
not to clip. When set to false (default), the contents of the output buffer passed into the data callback will be clipped after returning. This only
|
||||
applies when the playback sample format is f32.
|
||||
|
||||
noDisableDenormals
|
||||
@@ -4955,7 +5011,7 @@ then be set directly on the structure. Below are the members of the `ma_device_c
|
||||
A pointer that will passed to callbacks in pBackendVTable.
|
||||
|
||||
resampling.linear.lpfOrder
|
||||
The linear resampler applies a low-pass filter as part of it's processing for anti-aliasing. This setting controls the order of the filter. The higher
|
||||
The linear resampler applies a low-pass filter as part of its processing for anti-aliasing. This setting controls the order of the filter. The higher
|
||||
the value, the better the quality, in general. Setting this to 0 will disable low-pass filtering altogether. The maximum value is
|
||||
`MA_MAX_FILTER_ORDER`. The default value is `min(4, MA_MAX_FILTER_ORDER)`.
|
||||
|
||||
@@ -5032,6 +5088,9 @@ then be set directly on the structure. Below are the members of the `ma_device_c
|
||||
pulse.pStreamNameCapture
|
||||
PulseAudio only. Sets the stream name for capture.
|
||||
|
||||
pulse.channelMap
|
||||
PulseAudio only. Sets the channel map that is requested from PulseAudio. See MA_PA_CHANNEL_MAP_* constants. Defaults to MA_PA_CHANNEL_MAP_AIFF.
|
||||
|
||||
coreaudio.allowNominalSampleRateChange
|
||||
Core Audio only. Desktop only. When enabled, allows the sample rate of the device to be changed at the operating system level. This
|
||||
is disabled by default in order to prevent intrusive changes to the user's system. This is useful if you want to use a sample rate
|
||||
@@ -5205,7 +5264,7 @@ Unsafe. It is not safe to call this inside any callback.
|
||||
|
||||
Remarks
|
||||
-------
|
||||
You only need to use this function if you want to configure the context differently to it's defaults. You should never use this function if you want to manage
|
||||
You only need to use this function if you want to configure the context differently to its defaults. You should never use this function if you want to manage
|
||||
your own context.
|
||||
|
||||
See the documentation for `ma_context_init()` for information on the different context configuration options.
|
||||
@@ -5437,8 +5496,6 @@ speakers or received from the microphone which can in turn result in de-syncs.
|
||||
|
||||
Do not call this in any callback.
|
||||
|
||||
This will be called implicitly by `ma_device_uninit()`.
|
||||
|
||||
|
||||
See Also
|
||||
--------
|
||||
@@ -5972,7 +6029,7 @@ Utilities
|
||||
************************************************************************************************************************************************************/
|
||||
|
||||
/*
|
||||
Calculates a buffer size in milliseconds from the specified number of frames and sample rate.
|
||||
Calculates a buffer size in milliseconds (rounded up) from the specified number of frames and sample rate.
|
||||
*/
|
||||
MA_API ma_uint32 ma_calculate_buffer_size_in_milliseconds_from_frames(ma_uint32 bufferSizeInFrames, ma_uint32 sampleRate);
|
||||
|
||||
@@ -6229,7 +6286,7 @@ struct ma_decoder
|
||||
void* pInputCache; /* In input format. Can be null if it's not needed. */
|
||||
ma_uint64 inputCacheCap; /* The capacity of the input cache. */
|
||||
ma_uint64 inputCacheConsumed; /* The number of frames that have been consumed in the cache. Used for determining the next valid frame. */
|
||||
ma_uint64 inputCacheRemaining; /* The number of valid frames remaining in the cahce. */
|
||||
ma_uint64 inputCacheRemaining; /* The number of valid frames remaining in the cache. */
|
||||
ma_allocation_callbacks allocationCallbacks;
|
||||
union
|
||||
{
|
||||
@@ -6270,7 +6327,7 @@ This is not thread safe without your own synchronization.
|
||||
MA_API ma_result ma_decoder_read_pcm_frames(ma_decoder* pDecoder, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead);
|
||||
|
||||
/*
|
||||
Seeks to a PCM frame based on it's absolute index.
|
||||
Seeks to a PCM frame based on its absolute index.
|
||||
|
||||
This is not thread safe without your own synchronization.
|
||||
*/
|
||||
@@ -6475,7 +6532,7 @@ MA_API ma_noise_config ma_noise_config_init(ma_format format, ma_uint32 channels
|
||||
|
||||
typedef struct
|
||||
{
|
||||
ma_data_source_vtable ds;
|
||||
ma_data_source_base ds;
|
||||
ma_noise_config config;
|
||||
ma_lcg lcg;
|
||||
union
|
||||
@@ -6533,7 +6590,8 @@ typedef enum
|
||||
MA_RESOURCE_MANAGER_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. */
|
||||
MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC = 0x00000004, /* When set, the resource manager will load the data source asynchronously. */
|
||||
MA_RESOURCE_MANAGER_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(). */
|
||||
MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_UNKNOWN_LENGTH = 0x00000010 /* Gives the resource manager a hint that the length of the data source is unknown and calling `ma_data_source_get_length_in_pcm_frames()` should be avoided. */
|
||||
MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_UNKNOWN_LENGTH = 0x00000010, /* Gives the resource manager a hint that the length of the data source is unknown and calling `ma_data_source_get_length_in_pcm_frames()` should be avoided. */
|
||||
MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_LOOPING = 0x00000020 /* When set, configures the data source to loop by default. */
|
||||
} ma_resource_manager_data_source_flags;
|
||||
|
||||
|
||||
@@ -6601,8 +6659,8 @@ typedef struct
|
||||
ma_uint64 rangeEndInPCMFrames;
|
||||
ma_uint64 loopPointBegInPCMFrames;
|
||||
ma_uint64 loopPointEndInPCMFrames;
|
||||
ma_bool32 isLooping;
|
||||
ma_uint32 flags;
|
||||
ma_bool32 isLooping; /* Deprecated. Use the MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_LOOPING flag in `flags` instead. */
|
||||
} ma_resource_manager_data_source_config;
|
||||
|
||||
MA_API ma_resource_manager_data_source_config ma_resource_manager_data_source_config_init(void);
|
||||
@@ -6845,6 +6903,16 @@ Node Graph
|
||||
/* Use this when the bus count is determined by the node instance rather than the vtable. */
|
||||
#define MA_NODE_BUS_COUNT_UNKNOWN 255
|
||||
|
||||
|
||||
/* For some internal memory management of ma_node_graph. */
|
||||
typedef struct
|
||||
{
|
||||
size_t offset;
|
||||
size_t sizeInBytes;
|
||||
unsigned char _data[1];
|
||||
} ma_stack;
|
||||
|
||||
|
||||
typedef struct ma_node_graph ma_node_graph;
|
||||
typedef void ma_node;
|
||||
|
||||
@@ -6873,7 +6941,7 @@ typedef struct
|
||||
/*
|
||||
Extended processing callback. This callback is used for effects that process input and output
|
||||
at different rates (i.e. they perform resampling). This is similar to the simple version, only
|
||||
they take two seperate frame counts: one for input, and one for output.
|
||||
they take two separate frame counts: one for input, and one for output.
|
||||
|
||||
On input, `pFrameCountOut` is equal to the capacity of the output buffer for each bus, whereas
|
||||
`pFrameCountIn` will be equal to the number of PCM frames in each of the buffers in `ppFramesIn`.
|
||||
@@ -6884,7 +6952,7 @@ typedef struct
|
||||
void (* onProcess)(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut);
|
||||
|
||||
/*
|
||||
A callback for retrieving the number of a input frames that are required to output the
|
||||
A callback for retrieving the number of input frames that are required to output the
|
||||
specified number of output frames. You would only want to implement this when the node performs
|
||||
resampling. This is optional, even for nodes that perform resampling, but it does offer a
|
||||
small reduction in latency as it allows miniaudio to calculate the exact number of input frames
|
||||
@@ -6969,10 +7037,14 @@ typedef struct ma_node_base ma_node_base;
|
||||
struct ma_node_base
|
||||
{
|
||||
/* These variables are set once at startup. */
|
||||
ma_node_graph* pNodeGraph; /* The graph this node belongs to. */
|
||||
ma_node_graph* pNodeGraph; /* The graph this node belongs to. */
|
||||
const ma_node_vtable* vtable;
|
||||
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. */
|
||||
ma_uint16 cachedDataCapInFramesPerBus; /* The capacity of the input data cache in frames, per bus. */
|
||||
ma_uint32 inputBusCount;
|
||||
ma_uint32 outputBusCount;
|
||||
ma_node_input_bus* pInputBuses;
|
||||
ma_node_output_bus* pOutputBuses;
|
||||
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. */
|
||||
ma_uint16 cachedDataCapInFramesPerBus; /* The capacity of the input data cache in frames, per bus. */
|
||||
|
||||
/* These variables are read and written only from the audio thread. */
|
||||
ma_uint16 cachedFrameCountOut;
|
||||
@@ -6980,13 +7052,9 @@ struct ma_node_base
|
||||
ma_uint16 consumedFrameCountIn;
|
||||
|
||||
/* These variables are read and written between different threads. */
|
||||
MA_ATOMIC(4, ma_node_state) state; /* When set to stopped, nothing will be read, regardless of the times in stateTimes. */
|
||||
MA_ATOMIC(8, 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. */
|
||||
MA_ATOMIC(8, 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()`. */
|
||||
ma_uint32 inputBusCount;
|
||||
ma_uint32 outputBusCount;
|
||||
ma_node_input_bus* pInputBuses;
|
||||
ma_node_output_bus* pOutputBuses;
|
||||
MA_ATOMIC(4, ma_node_state) state; /* When set to stopped, nothing will be read, regardless of the times in stateTimes. */
|
||||
MA_ATOMIC(8, 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. */
|
||||
MA_ATOMIC(8, 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()`. */
|
||||
|
||||
/* Memory management. */
|
||||
ma_node_input_bus _inputBuses[MA_MAX_NODE_LOCAL_BUS_COUNT];
|
||||
@@ -7022,7 +7090,8 @@ MA_API ma_result ma_node_set_time(ma_node* pNode, ma_uint64 localTime);
|
||||
typedef struct
|
||||
{
|
||||
ma_uint32 channels;
|
||||
ma_uint16 nodeCacheCapInFrames;
|
||||
ma_uint32 processingSizeInFrames; /* This is the preferred processing size for node processing callbacks unless overridden by a node itself. Can be 0 in which case it will be based on the frame count passed into ma_node_graph_read_pcm_frames(), but will not be well defined. */
|
||||
size_t preMixStackSizeInBytes; /* Defaults to 512KB per channel. Reducing this will save memory, but the depth of your node graph will be more restricted. */
|
||||
} ma_node_graph_config;
|
||||
|
||||
MA_API ma_node_graph_config ma_node_graph_config_init(ma_uint32 channels);
|
||||
@@ -7033,10 +7102,15 @@ struct ma_node_graph
|
||||
/* Immutable. */
|
||||
ma_node_base base; /* The node graph itself is a node so it can be connected as an input to different node graph. This has zero inputs and calls ma_node_graph_read_pcm_frames() to generate it's output. */
|
||||
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(). */
|
||||
ma_uint16 nodeCacheCapInFrames;
|
||||
float* pProcessingCache; /* This will be allocated when processingSizeInFrames is non-zero. This is needed because ma_node_graph_read_pcm_frames() can be called with a variable number of frames, and we may need to do some buffering in situations where the caller requests a frame count that's not a multiple of processingSizeInFrames. */
|
||||
ma_uint32 processingCacheFramesRemaining;
|
||||
ma_uint32 processingSizeInFrames;
|
||||
|
||||
/* Read and written by multiple threads. */
|
||||
MA_ATOMIC(4, ma_bool32) isReading;
|
||||
|
||||
/* Modified only by the audio thread. */
|
||||
ma_stack* pPreMixStack;
|
||||
};
|
||||
|
||||
MA_API ma_result ma_node_graph_init(const ma_node_graph_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_node_graph* pNodeGraph);
|
||||
@@ -7321,6 +7395,7 @@ typedef enum
|
||||
MA_SOUND_FLAG_ASYNC = 0x00000004, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC */
|
||||
MA_SOUND_FLAG_WAIT_INIT = 0x00000008, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT */
|
||||
MA_SOUND_FLAG_UNKNOWN_LENGTH = 0x00000010, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_UNKNOWN_LENGTH */
|
||||
MA_SOUND_FLAG_LOOPING = 0x00000020, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_LOOPING */
|
||||
|
||||
/* ma_sound specific flags. */
|
||||
MA_SOUND_FLAG_NO_DEFAULT_ATTACHMENT = 0x00001000, /* Do not attach to the endpoint by default. Useful for when setting up nodes in a complex graph system. */
|
||||
@@ -7360,7 +7435,7 @@ MA_API ma_engine_node_config ma_engine_node_config_init(ma_engine* pEngine, ma_e
|
||||
/* Base node object for both ma_sound and ma_sound_group. */
|
||||
typedef struct
|
||||
{
|
||||
ma_node_base baseNode; /* Must be the first member for compatiblity with the ma_node API. */
|
||||
ma_node_base baseNode; /* Must be the first member for compatibility with the ma_node API. */
|
||||
ma_engine* pEngine; /* A pointer to the engine. Set based on the value from the config. */
|
||||
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. */
|
||||
ma_uint32 volumeSmoothTimeInPCMFrames;
|
||||
@@ -7420,13 +7495,13 @@ typedef struct
|
||||
ma_uint64 rangeEndInPCMFrames;
|
||||
ma_uint64 loopPointBegInPCMFrames;
|
||||
ma_uint64 loopPointEndInPCMFrames;
|
||||
ma_bool32 isLooping;
|
||||
ma_sound_end_proc endCallback; /* Fired when the sound reaches the end. Will be fired from the audio thread. Do not restart, uninitialize or otherwise change the state of the sound from here. Instead fire an event or set a variable to indicate to a different thread to change the start of the sound. Will not be fired in response to a scheduled stop with ma_sound_set_stop_time_*(). */
|
||||
void* pEndCallbackUserData;
|
||||
#ifndef MA_NO_RESOURCE_MANAGER
|
||||
ma_resource_manager_pipeline_notifications initNotifications;
|
||||
#endif
|
||||
ma_fence* pDoneFence; /* Deprecated. Use initNotifications instead. Released when the resource manager has finished decoding the entire sound. Not used with streams. */
|
||||
ma_bool32 isLooping; /* Deprecated. Use the MA_SOUND_FLAG_LOOPING flag in `flags` instead. */
|
||||
} ma_sound_config;
|
||||
|
||||
MA_API ma_sound_config ma_sound_config_init(void); /* Deprecated. Will be removed in version 0.12. Use ma_sound_config_2() instead. */
|
||||
@@ -7484,12 +7559,13 @@ typedef struct
|
||||
ma_log* pLog; /* When set to NULL, will use the context's log. */
|
||||
ma_uint32 listenerCount; /* Must be between 1 and MA_ENGINE_MAX_LISTENERS. */
|
||||
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. */
|
||||
ma_uint32 sampleRate; /* The sample rate. When set to 0 will use the native channel count of the device. */
|
||||
ma_uint32 sampleRate; /* The sample rate. When set to 0 will use the native sample rate of the device. */
|
||||
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.*/
|
||||
ma_uint32 periodSizeInMilliseconds; /* Used if periodSizeInFrames is unset. */
|
||||
ma_uint32 gainSmoothTimeInFrames; /* The number of frames to interpolate the gain of spatialized sounds across. If set to 0, will use gainSmoothTimeInMilliseconds. */
|
||||
ma_uint32 gainSmoothTimeInMilliseconds; /* When set to 0, gainSmoothTimeInFrames will be used. If both are set to 0, a default value will be used. */
|
||||
ma_uint32 defaultVolumeSmoothTimeInPCMFrames; /* Defaults to 0. Controls the default amount of smoothing to apply to volume changes to sounds. High values means more smoothing at the expense of high latency (will take longer to reach the new volume). */
|
||||
ma_uint32 preMixStackSizeInBytes; /* A stack is used for internal processing in the node graph. This allows you to configure the size of this stack. Smaller values will reduce the maximum depth of your node graph. You should rarely need to modify this. */
|
||||
ma_allocation_callbacks allocationCallbacks;
|
||||
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(). */
|
||||
ma_bool32 noDevice; /* When set to true, don't create a default device. ma_engine_read_pcm_frames() can be called manually to read data. */
|
||||
@@ -7504,12 +7580,12 @@ MA_API ma_engine_config ma_engine_config_init(void);
|
||||
|
||||
struct ma_engine
|
||||
{
|
||||
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. */
|
||||
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. */
|
||||
#if !defined(MA_NO_RESOURCE_MANAGER)
|
||||
ma_resource_manager* pResourceManager;
|
||||
#endif
|
||||
#if !defined(MA_NO_DEVICE_IO)
|
||||
ma_device* pDevice; /* Optionally set via the config, otherwise allocated by the engine in ma_engine_init(). */
|
||||
ma_device* pDevice; /* Optionally set via the config, otherwise allocated by the engine in ma_engine_init(). */
|
||||
#endif
|
||||
ma_log* pLog;
|
||||
ma_uint32 sampleRate;
|
||||
@@ -7518,10 +7594,10 @@ struct ma_engine
|
||||
ma_allocation_callbacks allocationCallbacks;
|
||||
ma_bool8 ownsResourceManager;
|
||||
ma_bool8 ownsDevice;
|
||||
ma_spinlock inlinedSoundLock; /* For synchronizing access so the inlined sound list. */
|
||||
ma_sound_inlined* pInlinedSoundHead; /* The first inlined sound. Inlined sounds are tracked in a linked list. */
|
||||
MA_ATOMIC(4, ma_uint32) inlinedSoundCount; /* The total number of allocated inlined sound objects. Used for debugging. */
|
||||
ma_uint32 gainSmoothTimeInFrames; /* The number of frames to interpolate the gain of spatialized sounds across. */
|
||||
ma_spinlock inlinedSoundLock; /* For synchronizing access to the inlined sound list. */
|
||||
ma_sound_inlined* pInlinedSoundHead; /* The first inlined sound. Inlined sounds are tracked in a linked list. */
|
||||
MA_ATOMIC(4, ma_uint32) inlinedSoundCount; /* The total number of allocated inlined sound objects. Used for debugging. */
|
||||
ma_uint32 gainSmoothTimeInFrames; /* The number of frames to interpolate the gain of spatialized sounds across. */
|
||||
ma_uint32 defaultVolumeSmoothTimeInPCMFrames;
|
||||
ma_mono_expansion_mode monoExpansionMode;
|
||||
ma_engine_process_proc onProcess;
|
||||
@@ -7646,11 +7722,12 @@ MA_API void ma_sound_set_looping(ma_sound* pSound, ma_bool32 isLooping);
|
||||
MA_API ma_bool32 ma_sound_is_looping(const ma_sound* pSound);
|
||||
MA_API ma_bool32 ma_sound_at_end(const ma_sound* pSound);
|
||||
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(). */
|
||||
MA_API ma_result ma_sound_get_data_format(ma_sound* pSound, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap);
|
||||
MA_API ma_result ma_sound_get_cursor_in_pcm_frames(ma_sound* pSound, ma_uint64* pCursor);
|
||||
MA_API ma_result ma_sound_get_length_in_pcm_frames(ma_sound* pSound, ma_uint64* pLength);
|
||||
MA_API ma_result ma_sound_get_cursor_in_seconds(ma_sound* pSound, float* pCursor);
|
||||
MA_API ma_result ma_sound_get_length_in_seconds(ma_sound* pSound, float* pLength);
|
||||
MA_API ma_result ma_sound_seek_to_second(ma_sound* pSound, float seekPointInSeconds); /* Abstraction to ma_sound_seek_to_pcm_frame() */
|
||||
MA_API ma_result ma_sound_get_data_format(const ma_sound* pSound, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap);
|
||||
MA_API ma_result ma_sound_get_cursor_in_pcm_frames(const ma_sound* pSound, ma_uint64* pCursor);
|
||||
MA_API ma_result ma_sound_get_length_in_pcm_frames(const ma_sound* pSound, ma_uint64* pLength);
|
||||
MA_API ma_result ma_sound_get_cursor_in_seconds(const ma_sound* pSound, float* pCursor);
|
||||
MA_API ma_result ma_sound_get_length_in_seconds(const ma_sound* pSound, float* pLength);
|
||||
MA_API ma_result ma_sound_set_end_callback(ma_sound* pSound, ma_sound_end_proc callback, void* pUserData);
|
||||
|
||||
MA_API ma_result ma_sound_group_init(ma_engine* pEngine, ma_uint32 flags, ma_sound_group* pParentGroup, ma_sound_group* pGroup);
|
||||
@@ -7748,7 +7825,7 @@ For more information, please refer to <http://unlicense.org/>
|
||||
===============================================================================
|
||||
ALTERNATIVE 2 - MIT No Attribution
|
||||
===============================================================================
|
||||
Copyright 2023 David Reid
|
||||
Copyright 2025 David Reid
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
#ifndef miniaudio_channel_combiner_node_c
|
||||
#define miniaudio_channel_combiner_node_c
|
||||
|
||||
#include "ma_channel_combiner_node.h"
|
||||
|
||||
#include <string.h> /* For memset(). */
|
||||
|
||||
MA_API ma_channel_combiner_node_config ma_channel_combiner_node_config_init(ma_uint32 channels)
|
||||
{
|
||||
ma_channel_combiner_node_config config;
|
||||
|
||||
MA_ZERO_OBJECT(&config);
|
||||
memset(&config, 0, sizeof(config));
|
||||
config.nodeConfig = ma_node_config_init(); /* Input and output channels will be set in ma_channel_combiner_node_init(). */
|
||||
config.channels = channels;
|
||||
|
||||
@@ -43,7 +47,7 @@ MA_API ma_result ma_channel_combiner_node_init(ma_node_graph* pNodeGraph, const
|
||||
return MA_INVALID_ARGS;
|
||||
}
|
||||
|
||||
MA_ZERO_OBJECT(pCombinerNode);
|
||||
memset(pCombinerNode, 0, sizeof(*pCombinerNode));
|
||||
|
||||
if (pConfig == NULL) {
|
||||
return MA_INVALID_ARGS;
|
||||
@@ -74,4 +78,6 @@ MA_API void ma_channel_combiner_node_uninit(ma_channel_combiner_node* pCombinerN
|
||||
{
|
||||
/* The base node is always uninitialized first. */
|
||||
ma_node_uninit(pCombinerNode, pAllocationCallbacks);
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* miniaudio_channel_combiner_node_c */
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
/* Include ma_channel_combiner_node.h after miniaudio.h */
|
||||
#ifndef ma_channel_combiner_node_h
|
||||
#define ma_channel_combiner_node_h
|
||||
#ifndef miniaudio_channel_combiner_node_h
|
||||
#define miniaudio_channel_combiner_node_h
|
||||
|
||||
#include "../../../miniaudio.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -9,7 +11,7 @@ extern "C" {
|
||||
typedef struct
|
||||
{
|
||||
ma_node_config nodeConfig;
|
||||
ma_uint32 channels; /* The number of channels of the source, which will be the same as the output. Must be 1 or 2. The excite bus must always have one channel. */
|
||||
ma_uint32 channels;
|
||||
} ma_channel_combiner_node_config;
|
||||
|
||||
MA_API ma_channel_combiner_node_config ma_channel_combiner_node_config_init(ma_uint32 channels);
|
||||
@@ -27,4 +29,4 @@ MA_API void ma_channel_combiner_node_uninit(ma_channel_combiner_node* pSeparator
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* ma_reverb_node_h */
|
||||
#endif /* miniaudio_channel_combiner_node_h */
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
/* The channel separtor example also demonstrates how to use the combiner. */
|
||||
/* The channel separator example also demonstrates how to use the combiner. */
|
||||
#include "../ma_channel_separator_node/ma_channel_separator_node_example.c"
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
#ifndef miniaudio_channel_separator_node_c
|
||||
#define miniaudio_channel_separator_node_c
|
||||
|
||||
#include "ma_channel_separator_node.h"
|
||||
|
||||
#include <string.h> /* For memset(). */
|
||||
|
||||
MA_API ma_channel_separator_node_config ma_channel_separator_node_config_init(ma_uint32 channels)
|
||||
{
|
||||
ma_channel_separator_node_config config;
|
||||
|
||||
MA_ZERO_OBJECT(&config);
|
||||
memset(&config, 0, sizeof(config));
|
||||
config.nodeConfig = ma_node_config_init(); /* Input and output channels will be set in ma_channel_separator_node_init(). */
|
||||
config.channels = channels;
|
||||
|
||||
@@ -43,7 +47,7 @@ MA_API ma_result ma_channel_separator_node_init(ma_node_graph* pNodeGraph, const
|
||||
return MA_INVALID_ARGS;
|
||||
}
|
||||
|
||||
MA_ZERO_OBJECT(pSeparatorNode);
|
||||
memset(pSeparatorNode, 0, sizeof(*pSeparatorNode));
|
||||
|
||||
if (pConfig == NULL) {
|
||||
return MA_INVALID_ARGS;
|
||||
@@ -79,3 +83,5 @@ MA_API void ma_channel_separator_node_uninit(ma_channel_separator_node* pSeparat
|
||||
/* The base node is always uninitialized first. */
|
||||
ma_node_uninit(pSeparatorNode, pAllocationCallbacks);
|
||||
}
|
||||
|
||||
#endif /* miniaudio_channel_separator_node_c */
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
/* Include ma_channel_separator_node.h after miniaudio.h */
|
||||
#ifndef ma_channel_separator_node_h
|
||||
#define ma_channel_separator_node_h
|
||||
#ifndef miniaudio_channel_separator_node_h
|
||||
#define miniaudio_channel_separator_node_h
|
||||
|
||||
#include "../../../miniaudio.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -9,7 +11,7 @@ extern "C" {
|
||||
typedef struct
|
||||
{
|
||||
ma_node_config nodeConfig;
|
||||
ma_uint32 channels; /* The number of channels of the source, which will be the same as the output. Must be 1 or 2. The excite bus must always have one channel. */
|
||||
ma_uint32 channels;
|
||||
} ma_channel_separator_node_config;
|
||||
|
||||
MA_API ma_channel_separator_node_config ma_channel_separator_node_config_init(ma_uint32 channels);
|
||||
@@ -26,4 +28,4 @@ MA_API void ma_channel_separator_node_uninit(ma_channel_separator_node* pSeparat
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* ma_reverb_node_h */
|
||||
#endif /* miniaudio_channel_separator_node_h */
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#define MINIAUDIO_IMPLEMENTATION
|
||||
#include "../../../miniaudio.h"
|
||||
#include "../../../miniaudio.c"
|
||||
#include "ma_channel_separator_node.c"
|
||||
#include "../ma_channel_combiner_node/ma_channel_combiner_node.c"
|
||||
|
||||
@@ -146,4 +145,4 @@ done0: ma_device_uninit(&device);
|
||||
(void)argv;
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
|
||||
#define MINIAUDIO_IMPLEMENTATION
|
||||
#include "../../../miniaudio.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#define DEVICE_FORMAT ma_format_f32 /* Must always be f32 for this example because the node graph system only works with this. */
|
||||
#define DEVICE_CHANNELS 2
|
||||
#define DEVICE_SAMPLE_RATE 48000
|
||||
|
||||
static ma_audio_buffer_ref g_dataSupply; /* The underlying data source of the source node. */
|
||||
static ma_data_source_node g_dataSupplyNode; /* The node that will sit at the root level. Will be reading data from g_dataSupply. */
|
||||
static ma_delay_node g_delayNode; /* The delay node. */
|
||||
static ma_node_graph g_nodeGraph; /* The main node graph that we'll be feeding data through. */
|
||||
|
||||
void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount)
|
||||
{
|
||||
MA_ASSERT(pDevice->capture.format == pDevice->playback.format && pDevice->capture.format == ma_format_f32);
|
||||
MA_ASSERT(pDevice->capture.channels == pDevice->playback.channels);
|
||||
|
||||
/*
|
||||
The node graph system is a pulling style of API. At the lowest level of the chain will be a
|
||||
node acting as a data source for the purpose of delivering the initial audio data. In our case,
|
||||
the data source is our `pInput` buffer. We need to update the underlying data source so that it
|
||||
read data from `pInput`.
|
||||
*/
|
||||
ma_audio_buffer_ref_set_data(&g_dataSupply, pInput, frameCount);
|
||||
|
||||
/* With the source buffer configured we can now read directly from the node graph. */
|
||||
ma_node_graph_read_pcm_frames(&g_nodeGraph, pOutput, frameCount, NULL);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
ma_result result;
|
||||
ma_device_config deviceConfig;
|
||||
ma_device device;
|
||||
ma_node_graph_config nodeGraphConfig;
|
||||
ma_delay_node_config delayNodeConfig;
|
||||
ma_data_source_node_config dataSupplyNodeConfig;
|
||||
|
||||
deviceConfig = ma_device_config_init(ma_device_type_duplex);
|
||||
deviceConfig.capture.pDeviceID = NULL;
|
||||
deviceConfig.capture.format = DEVICE_FORMAT;
|
||||
deviceConfig.capture.channels = DEVICE_CHANNELS;
|
||||
deviceConfig.capture.shareMode = ma_share_mode_shared;
|
||||
deviceConfig.playback.pDeviceID = NULL;
|
||||
deviceConfig.playback.format = DEVICE_FORMAT;
|
||||
deviceConfig.playback.channels = DEVICE_CHANNELS;
|
||||
deviceConfig.dataCallback = data_callback;
|
||||
result = ma_device_init(NULL, &deviceConfig, &device);
|
||||
if (result != MA_SUCCESS) {
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/* Node graph. */
|
||||
nodeGraphConfig = ma_node_graph_config_init(device.capture.channels);
|
||||
|
||||
result = ma_node_graph_init(&nodeGraphConfig, NULL, &g_nodeGraph);
|
||||
if (result != MA_SUCCESS) {
|
||||
printf("Failed to initialize node graph.");
|
||||
goto done0;
|
||||
}
|
||||
|
||||
|
||||
/* Delay. Attached straight to the endpoint. */
|
||||
delayNodeConfig = ma_delay_node_config_init(device.capture.channels, device.sampleRate, (100 * device.sampleRate) / 1000, 0.5f);
|
||||
|
||||
result = ma_delay_node_init(&g_nodeGraph, &delayNodeConfig, NULL, &g_delayNode);
|
||||
if (result != MA_SUCCESS) {
|
||||
printf("Failed to initialize delay node.");
|
||||
goto done1;
|
||||
}
|
||||
|
||||
ma_node_attach_output_bus(&g_delayNode, 0, ma_node_graph_get_endpoint(&g_nodeGraph), 0);
|
||||
|
||||
|
||||
/* Data supply. Attached to input bus 0 of the delay node. */
|
||||
result = ma_audio_buffer_ref_init(device.capture.format, device.capture.channels, NULL, 0, &g_dataSupply);
|
||||
if (result != MA_SUCCESS) {
|
||||
printf("Failed to initialize audio buffer for source.");
|
||||
goto done2;
|
||||
}
|
||||
|
||||
dataSupplyNodeConfig = ma_data_source_node_config_init(&g_dataSupply);
|
||||
|
||||
result = ma_data_source_node_init(&g_nodeGraph, &dataSupplyNodeConfig, NULL, &g_dataSupplyNode);
|
||||
if (result != MA_SUCCESS) {
|
||||
printf("Failed to initialize source node.");
|
||||
goto done2;
|
||||
}
|
||||
|
||||
ma_node_attach_output_bus(&g_dataSupplyNode, 0, &g_delayNode, 0);
|
||||
|
||||
|
||||
|
||||
|
||||
ma_device_start(&device);
|
||||
|
||||
printf("Press Enter to quit...\n");
|
||||
getchar();
|
||||
|
||||
/* It's important that we stop the device first or else we'll uninitialize the graph from under the device. */
|
||||
ma_device_stop(&device);
|
||||
|
||||
/*done3:*/ ma_data_source_node_uninit(&g_dataSupplyNode, NULL);
|
||||
done2: ma_delay_node_uninit(&g_delayNode, NULL);
|
||||
done1: ma_node_graph_uninit(&g_nodeGraph, NULL);
|
||||
done0: ma_device_uninit(&device);
|
||||
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,11 +1,19 @@
|
||||
#ifndef miniaudio_ltrim_node_c
|
||||
#define miniaudio_ltrim_node_c
|
||||
|
||||
#include "ma_ltrim_node.h"
|
||||
|
||||
#include <string.h> /* For memset(). */
|
||||
|
||||
#ifndef ma_min
|
||||
#define ma_min(a, b) (((a) < (b)) ? (a) : (b))
|
||||
#endif
|
||||
|
||||
MA_API ma_ltrim_node_config ma_ltrim_node_config_init(ma_uint32 channels, float threshold)
|
||||
{
|
||||
ma_ltrim_node_config config;
|
||||
|
||||
MA_ZERO_OBJECT(&config);
|
||||
memset(&config, 0, sizeof(config));
|
||||
config.nodeConfig = ma_node_config_init(); /* Input and output channels will be set in ma_ltrim_node_init(). */
|
||||
config.channels = channels;
|
||||
config.threshold = threshold;
|
||||
@@ -59,8 +67,8 @@ static ma_node_vtable g_ma_ltrim_node_vtable =
|
||||
{
|
||||
ma_ltrim_node_process_pcm_frames,
|
||||
NULL,
|
||||
1, /* 1 input channel. */
|
||||
1, /* 1 output channel. */
|
||||
1, /* 1 input bus. */
|
||||
1, /* 1 output bus. */
|
||||
MA_NODE_FLAG_DIFFERENT_PROCESSING_RATES
|
||||
};
|
||||
|
||||
@@ -73,7 +81,7 @@ MA_API ma_result ma_ltrim_node_init(ma_node_graph* pNodeGraph, const ma_ltrim_no
|
||||
return MA_INVALID_ARGS;
|
||||
}
|
||||
|
||||
MA_ZERO_OBJECT(pTrimNode);
|
||||
memset(pTrimNode, 0, sizeof(*pTrimNode));
|
||||
|
||||
if (pConfig == NULL) {
|
||||
return MA_INVALID_ARGS;
|
||||
@@ -100,3 +108,5 @@ MA_API void ma_ltrim_node_uninit(ma_ltrim_node* pTrimNode, const ma_allocation_c
|
||||
/* The base node is always uninitialized first. */
|
||||
ma_node_uninit(pTrimNode, pAllocationCallbacks);
|
||||
}
|
||||
|
||||
#endif /* miniaudio_ltrim_node_c */
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
/* Include ma_ltrim_node.h after miniaudio.h */
|
||||
#ifndef ma_ltrim_node_h
|
||||
#define ma_ltrim_node_h
|
||||
#ifndef miniaudio_ltrim_node_h
|
||||
#define miniaudio_ltrim_node_h
|
||||
|
||||
#include "../../../miniaudio.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -32,4 +34,4 @@ MA_API void ma_ltrim_node_uninit(ma_ltrim_node* pTrimNode, const ma_allocation_c
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* ma_ltrim_node_h */
|
||||
#endif /* miniaudio_ltrim_node_h */
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#define MINIAUDIO_IMPLEMENTATION
|
||||
#include "../../../miniaudio.h"
|
||||
#include "../../../miniaudio.c"
|
||||
#include "ma_ltrim_node.c"
|
||||
|
||||
#include <stdio.h>
|
||||
@@ -112,4 +111,4 @@ done1: ma_node_graph_uninit(&g_nodeGraph, NULL);
|
||||
done0: ma_device_uninit(&device);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
#ifndef miniaudio_reverb_node_c
|
||||
#define miniaudio_reverb_node_c
|
||||
|
||||
#define VERBLIB_IMPLEMENTATION
|
||||
#include "ma_reverb_node.h"
|
||||
|
||||
#include <string.h> /* For memset(). */
|
||||
|
||||
MA_API ma_reverb_node_config ma_reverb_node_config_init(ma_uint32 channels, ma_uint32 sampleRate)
|
||||
{
|
||||
ma_reverb_node_config config;
|
||||
|
||||
MA_ZERO_OBJECT(&config);
|
||||
memset(&config, 0, sizeof(config));
|
||||
config.nodeConfig = ma_node_config_init(); /* Input and output channels will be set in ma_reverb_node_init(). */
|
||||
config.channels = channels;
|
||||
config.sampleRate = sampleRate;
|
||||
@@ -34,8 +38,8 @@ static ma_node_vtable g_ma_reverb_node_vtable =
|
||||
{
|
||||
ma_reverb_node_process_pcm_frames,
|
||||
NULL,
|
||||
1, /* 1 input channel. */
|
||||
1, /* 1 output channel. */
|
||||
1, /* 1 input bus. */
|
||||
1, /* 1 output bus. */
|
||||
MA_NODE_FLAG_CONTINUOUS_PROCESSING /* Reverb requires continuous processing to ensure the tail get's processed. */
|
||||
};
|
||||
|
||||
@@ -48,7 +52,7 @@ MA_API ma_result ma_reverb_node_init(ma_node_graph* pNodeGraph, const ma_reverb_
|
||||
return MA_INVALID_ARGS;
|
||||
}
|
||||
|
||||
MA_ZERO_OBJECT(pReverbNode);
|
||||
memset(pReverbNode, 0, sizeof(*pReverbNode));
|
||||
|
||||
if (pConfig == NULL) {
|
||||
return MA_INVALID_ARGS;
|
||||
@@ -76,3 +80,5 @@ MA_API void ma_reverb_node_uninit(ma_reverb_node* pReverbNode, const ma_allocati
|
||||
/* The base node is always uninitialized first. */
|
||||
ma_node_uninit(pReverbNode, pAllocationCallbacks);
|
||||
}
|
||||
|
||||
#endif /* miniaudio_reverb_node_c */
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/* Include ma_reverb_node.h after miniaudio.h */
|
||||
#ifndef ma_reverb_node_h
|
||||
#define ma_reverb_node_h
|
||||
#ifndef miniaudio_reverb_node_h
|
||||
#define miniaudio_reverb_node_h
|
||||
|
||||
#include "../../../miniaudio.h"
|
||||
#include "verblib.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
@@ -39,4 +40,4 @@ MA_API void ma_reverb_node_uninit(ma_reverb_node* pReverbNode, const ma_allocati
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* ma_reverb_node_h */
|
||||
#endif /* miniaudio_reverb_node_h */
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#define MINIAUDIO_IMPLEMENTATION
|
||||
#include "../../../miniaudio.h"
|
||||
#include "../../../miniaudio.c"
|
||||
#include "ma_reverb_node.c"
|
||||
|
||||
#include <stdio.h>
|
||||
@@ -15,8 +14,13 @@ static ma_node_graph g_nodeGraph; /* The main node graph that we'l
|
||||
|
||||
void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount)
|
||||
{
|
||||
MA_ASSERT(pDevice->capture.format == pDevice->playback.format && pDevice->capture.format == ma_format_f32);
|
||||
MA_ASSERT(pDevice->capture.channels == pDevice->playback.channels);
|
||||
/*
|
||||
This example assumes the playback and capture sides use the same format and channel count. The
|
||||
format must be f32.
|
||||
*/
|
||||
if (pDevice->capture.format != DEVICE_FORMAT || pDevice->playback.format != DEVICE_FORMAT || pDevice->capture.channels != pDevice->playback.channels) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
The node graph system is a pulling style of API. At the lowest level of the chain will be a
|
||||
@@ -115,4 +119,4 @@ done0: ma_device_uninit(&device);
|
||||
(void)argv;
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,13 +248,23 @@ extern "C" {
|
||||
#include <math.h>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define VERBLIB_INLINE __forceinline
|
||||
#define VERBLIB_INLINE __forceinline
|
||||
#elif defined(__GNUC__)
|
||||
#if defined(__STRICT_ANSI__)
|
||||
#define VERBLIB_GNUC_INLINE_HINT __inline__
|
||||
#else
|
||||
#define VERBLIB_GNUC_INLINE_HINT inline
|
||||
#endif
|
||||
|
||||
#if (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 2)) || defined(__clang__)
|
||||
#define VERBLIB_INLINE VERBLIB_GNUC_INLINE_HINT __attribute__((always_inline))
|
||||
#else
|
||||
#define VERBLIB_INLINE VERBLIB_GNUC_INLINE_HINT
|
||||
#endif
|
||||
#elif defined(__WATCOMC__)
|
||||
#define VERBLIB_INLINE __inline
|
||||
#else
|
||||
#ifdef __GNUC__
|
||||
#define VERBLIB_INLINE inline __attribute__((always_inline))
|
||||
#else
|
||||
#define VERBLIB_INLINE inline
|
||||
#endif
|
||||
#define VERBLIB_INLINE
|
||||
#endif
|
||||
|
||||
#define verblib_max(x, y) (((x) > (y)) ? (x) : (y))
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
#ifndef miniaudio_vocoder_node_c
|
||||
#define miniaudio_vocoder_node_c
|
||||
|
||||
#define VOCLIB_IMPLEMENTATION
|
||||
#include "ma_vocoder_node.h"
|
||||
|
||||
#include <string.h> /* For memset(). */
|
||||
|
||||
MA_API ma_vocoder_node_config ma_vocoder_node_config_init(ma_uint32 channels, ma_uint32 sampleRate)
|
||||
{
|
||||
ma_vocoder_node_config config;
|
||||
|
||||
MA_ZERO_OBJECT(&config);
|
||||
memset(&config, 0, sizeof(config));
|
||||
config.nodeConfig = ma_node_config_init(); /* Input and output channels will be set in ma_vocoder_node_init(). */
|
||||
config.channels = channels;
|
||||
config.sampleRate = sampleRate;
|
||||
@@ -30,8 +34,8 @@ static ma_node_vtable g_ma_vocoder_node_vtable =
|
||||
{
|
||||
ma_vocoder_node_process_pcm_frames,
|
||||
NULL,
|
||||
2, /* 2 input channels. */
|
||||
1, /* 1 output channel. */
|
||||
2, /* 2 input buses. */
|
||||
1, /* 1 output bus. */
|
||||
0
|
||||
};
|
||||
|
||||
@@ -46,7 +50,7 @@ MA_API ma_result ma_vocoder_node_init(ma_node_graph* pNodeGraph, const ma_vocode
|
||||
return MA_INVALID_ARGS;
|
||||
}
|
||||
|
||||
MA_ZERO_OBJECT(pVocoderNode);
|
||||
memset(pVocoderNode, 0, sizeof(*pVocoderNode));
|
||||
|
||||
if (pConfig == NULL) {
|
||||
return MA_INVALID_ARGS;
|
||||
@@ -78,3 +82,5 @@ MA_API void ma_vocoder_node_uninit(ma_vocoder_node* pVocoderNode, const ma_alloc
|
||||
/* The base node must always be initialized first. */
|
||||
ma_node_uninit(pVocoderNode, pAllocationCallbacks);
|
||||
}
|
||||
|
||||
#endif /* miniaudio_vocoder_node_c */
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
/* Include ma_vocoder_node.h after miniaudio.h */
|
||||
#ifndef ma_vocoder_node_h
|
||||
#define ma_vocoder_node_h
|
||||
#ifndef miniaudio_vocoder_node_h
|
||||
#define miniaudio_vocoder_node_h
|
||||
|
||||
#include "../../../miniaudio.h"
|
||||
#include "voclib.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
The vocoder node has two inputs and one output. Inputs:
|
||||
|
||||
@@ -42,4 +42,4 @@ MA_API void ma_vocoder_node_uninit(ma_vocoder_node* pVocoderNode, const ma_alloc
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* ma_vocoder_node_h */
|
||||
#endif /* miniaudio_vocoder_node_h */
|
||||
|
||||
@@ -6,8 +6,7 @@ called `ma_vocoder_node` is used to achieve the effect which can be found in the
|
||||
the miniaudio repository. The vocoder node uses https://github.com/blastbay/voclib to achieve the
|
||||
effect.
|
||||
*/
|
||||
#define MINIAUDIO_IMPLEMENTATION
|
||||
#include "../../../miniaudio.h"
|
||||
#include "../../../miniaudio.c"
|
||||
#include "ma_vocoder_node.c"
|
||||
|
||||
#include <stdio.h>
|
||||
@@ -24,8 +23,13 @@ static ma_node_graph g_nodeGraph;
|
||||
|
||||
void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount)
|
||||
{
|
||||
MA_ASSERT(pDevice->capture.format == pDevice->playback.format);
|
||||
MA_ASSERT(pDevice->capture.channels == pDevice->playback.channels);
|
||||
/*
|
||||
This example assumes the playback and capture sides use the same format and channel count. The
|
||||
format must be f32.
|
||||
*/
|
||||
if (pDevice->capture.format != DEVICE_FORMAT || pDevice->playback.format != DEVICE_FORMAT || pDevice->capture.channels != pDevice->playback.channels) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
The node graph system is a pulling style of API. At the lowest level of the chain will be a
|
||||
|
||||
@@ -151,13 +151,23 @@ extern "C" {
|
||||
#include <assert.h>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define VOCLIB_INLINE __forceinline
|
||||
#define VOCLIB_INLINE __forceinline
|
||||
#elif defined(__GNUC__)
|
||||
#if defined(__STRICT_ANSI__)
|
||||
#define VOCLIB_GNUC_INLINE_HINT __inline__
|
||||
#else
|
||||
#define VOCLIB_GNUC_INLINE_HINT inline
|
||||
#endif
|
||||
|
||||
#if (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 2)) || defined(__clang__)
|
||||
#define VOCLIB_INLINE VOCLIB_GNUC_INLINE_HINT __attribute__((always_inline))
|
||||
#else
|
||||
#define VOCLIB_INLINE VOCLIB_GNUC_INLINE_HINT
|
||||
#endif
|
||||
#elif defined(__WATCOMC__)
|
||||
#define VOCLIB_INLINE __inline
|
||||
#else
|
||||
#ifdef __GNUC__
|
||||
#define VOCLIB_INLINE inline __attribute__((always_inline))
|
||||
#else
|
||||
#define VOCLIB_INLINE inline
|
||||
#endif
|
||||
#define VOCLIB_INLINE
|
||||
#endif
|
||||
|
||||
/* Filters
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
This is just a little experiment to explore some ideas for the kind of API that I would build if I
|
||||
was building my own operation system. The name "osaudio" means Operating System Audio. Or maybe you
|
||||
can think of it as Open Source Audio. It's whatever you want it to be.
|
||||
|
||||
The idea behind this project came about after considering the absurd complexity of audio APIs on
|
||||
various platforms after years of working on miniaudio. This project aims to disprove the idea that
|
||||
complete and flexible audio solutions and simple APIs are mutually exclusive and that it's possible
|
||||
to have both. I challenge anybody to prove me wrong.
|
||||
|
||||
In addition to the above, I also wanted to explore some ideas for a different API design to
|
||||
miniaudio. miniaudio uses a callback model for data transfer, whereas osaudio uses a blocking
|
||||
read/write model.
|
||||
|
||||
This project is essentially just a header file with a reference implementation that uses miniaudio
|
||||
under the hood. You can compile this very easily - just compile osaudio_miniaudio.c, and use
|
||||
osaudio.h just like any other header. There are no dependencies for the header, and the miniaudio
|
||||
implementation obviously requires miniaudio. Adjust the include path in osaudio_miniaudio.c if need
|
||||
be.
|
||||
|
||||
See osaudio.h for full documentation. Below is an example to get you started:
|
||||
|
||||
```c
|
||||
#include "osaudio.h"
|
||||
|
||||
...
|
||||
|
||||
osaudio_t audio;
|
||||
osaudio_config_t config;
|
||||
|
||||
osaudio_config_init(&config, OSAUDIO_OUTPUT);
|
||||
config.format = OSAUDIO_FORMAT_F32;
|
||||
config.channels = 2;
|
||||
config.rate = 48000;
|
||||
|
||||
osaudio_open(&audio, &config);
|
||||
|
||||
osaudio_write(audio, myAudioData, frameCount); // <-- This will block until all of the data has been sent to the device.
|
||||
|
||||
osaudio_close(audio);
|
||||
```
|
||||
|
||||
Compare the code above with the likes of other APIs like Core Audio and PipeWire. I challenge
|
||||
anybody to argue their APIs are cleaner and easier to use than this when it comes to simple audio
|
||||
playback.
|
||||
|
||||
If you have any feedback on this I'd be interested to hear it. In particular, I'd really like to
|
||||
hear from people who believe the likes of Core Audio (Apple), PipeWire, PulseAudio or any other
|
||||
audio API actually have good APIs (they don't!) and what makes their's better and/or worse than
|
||||
this project.
|
||||
@@ -0,0 +1,604 @@
|
||||
/*
|
||||
This is a simple API for low-level audio playback and capture. A reference implementation using
|
||||
miniaudio is provided in osaudio.c which can be found alongside this file. Consider all code
|
||||
public domain.
|
||||
|
||||
The idea behind this project came about after considering the absurd complexity of audio APIs on
|
||||
various platforms after years of working on miniaudio. This project aims to disprove the idea that
|
||||
complete and flexible audio solutions and simple APIs are mutually exclusive and that it's possible
|
||||
to have both. The idea of reliability through simplicity is the first and foremost goal of this
|
||||
project. The difference between this project and miniaudio is that this project is designed around
|
||||
the idea of what I would build if I was building an audio API for an operating system, such as at
|
||||
the level of WASAPI or ALSA. A cross-platform and cross-backend library like miniaudio is
|
||||
necessarily different in design, but there are indeed things that I would have done differently if
|
||||
given my time again, some of those ideas of which I'm expressing in this project.
|
||||
|
||||
---
|
||||
|
||||
The concept of low-level audio is simple - you have a device, such as a speaker system or a
|
||||
micrphone system, and then you write or read audio data to/from it. So in the case of playback, you
|
||||
need only write your raw audio data to the device which then emits it from the speakers when it's
|
||||
ready. Likewise, for capture you simply read audio data from the device which is filled with data
|
||||
by the microphone.
|
||||
|
||||
A complete low-level audio solution requires the following:
|
||||
|
||||
1) The ability to enumerate devices that are connected to the system.
|
||||
2) The ability to open and close a connection to a device.
|
||||
3) The ability to start and stop the device.
|
||||
4) The ability to write and read audio data to/from the device.
|
||||
5) The ability to query the device for its data configuration.
|
||||
6) The ability to notify the application when certain events occur, such as the device being
|
||||
stopped, or rerouted.
|
||||
|
||||
The API presented here aims to meet all of the above requirements. It uses a single-threaded
|
||||
blocking read/write model for data delivery instead of a callback model. This makes it a bit more
|
||||
flexible since it gives the application full control over the audio thread. It might also make it
|
||||
more feasible to use this API on single-threaded systems.
|
||||
|
||||
Device enumeration is achieved with a single function: osaudio_enumerate(). This function returns
|
||||
an array of osaudio_info_t structures which contain information about each device. The array is
|
||||
allocated must be freed with free(). Contained within the osaudio_info_t struct is, most
|
||||
importantly, the device ID, which is used to open a connection to the device, and the name of the
|
||||
device which can be used to display to the user. For advanced users, it also includes information
|
||||
about the device's native data configuration.
|
||||
|
||||
Opening and closing a connection to a device is achieved with osaudio_open() and osaudio_close().
|
||||
An important concept is that of the ability to configure the device. This is achieved with the
|
||||
osaudio_config_t structure which is passed to osaudio_open(). In addition to the ID of the device,
|
||||
this structure includes information about the desired format, channel count and sample rate. You
|
||||
can also configure the latency of the device, or the buffer size, which is specified in frames. A
|
||||
flags member is used for specifying additional options, such as whether or not to disable automatic
|
||||
rerouting. Finally a callback can be specified for notifications. When osaudio_open() returns, the
|
||||
config structure will be filled with the device's actual configuration. You can inspect the channel
|
||||
map from this structure to know how to arrange the channels in your audio data.
|
||||
|
||||
This API uses a blocking write/read model for pushing and pulling data to/from the device. This
|
||||
is done with the osaudio_write() and osaudio_read() functions. These functions will block until
|
||||
the requested number of frames have been processed or the device is drained or flushed with
|
||||
osaudio_drain() or osaudio_flush() respectively. It is from these functions that the device is
|
||||
started. As soon as you start writing data with osaudio_write() or reading data with
|
||||
osaudio_read(), the device will start. When the device is drained of flushed with osaudio_drain()
|
||||
or osaudio_flush(), the device will be stopped. osaudio_drain() will block until the device has
|
||||
been drained, whereas osaudio_flush() will stop playback immediately and return. You can also pause
|
||||
and resume the device with osaudio_pause() and osaudio_resume(). Since reading and writing is
|
||||
blocking, it can be useful to know how many frames can be written/read without blocking. This is
|
||||
achieved with osaudio_get_avail().
|
||||
|
||||
Querying the device's configuration is achieved with osaudio_get_info(). This function will return
|
||||
a pointer to an osaudio_info_t structure which contains information about the device, most
|
||||
importantly its name and data configuration. The name is important for displaying on a UI, and
|
||||
the data configuration is important for knowing how to format your audio data. The osaudio_info_t
|
||||
structure will contain an array of osaudio_config_t structures. This will contain one entry, which
|
||||
will contain the exact information that was returned in the config structure that was passed to
|
||||
osaudio_open().
|
||||
|
||||
A common requirement is to open a device that represents the operating system's default device.
|
||||
This is done easily by simply passing in NULL for the device ID. Below is an example for opening a
|
||||
default device:
|
||||
|
||||
int result;
|
||||
osaudio_t audio;
|
||||
osaudio_config_t config;
|
||||
|
||||
osaudio_config_init(&config, OSAUDIO_OUTPUT);
|
||||
config.format = OSAUDIO_FORMAT_F32;
|
||||
config.channels = 2;
|
||||
config.rate = 48000;
|
||||
|
||||
result = osaudio_open(&audio, &config);
|
||||
if (result != OSAUDIO_SUCCESS) {
|
||||
printf("Failed to open device.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
...
|
||||
|
||||
osaudio_close(audio);
|
||||
|
||||
In the above example, the default device is opened for playback (OSAUDIO_OUTPUT). The format is
|
||||
set to 32-bit floating point (OSAUDIO_FORMAT_F32), the channel count is set to stereo (2), and the
|
||||
sample rate is set to 48kHz. The device is then closed when we're done with it.
|
||||
|
||||
If instead we wanted to open a specific device, we can do that by passing in the device ID. Below
|
||||
is an example for how to do this:
|
||||
|
||||
int result;
|
||||
osaudio_t audio;
|
||||
osaudio_config_t config;
|
||||
unsigned int infoCount;
|
||||
osaudio_info_t* info;
|
||||
|
||||
result = osaudio_enumerate(&infoCount, &info);
|
||||
if (result != OSAUDIO_SUCCESS) {
|
||||
printf("Failed to enumerate devices.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// ... Iterate over the `info` array and find the device you want to open. Use the `direction` member to discriminate between input and output ...
|
||||
|
||||
osaudio_config_init(&config, OSAUDIO_OUTPUT);
|
||||
config.id = &info[indexOfYourChosenDevice].id;
|
||||
config.format = OSAUDIO_FORMAT_F32;
|
||||
config.channels = 2;
|
||||
config.rate = 48000;
|
||||
|
||||
osaudio_open(&audio, &config);
|
||||
|
||||
...
|
||||
|
||||
osaudio_close(audio);
|
||||
free(info); // The pointer returned by osaudio_enumerate() must be freed with free().
|
||||
|
||||
The id structure is just a 256 byte array that uniquely identifies the device. Implementations may
|
||||
have different representations for device IDs, and A 256 byte array should accommodates all
|
||||
device ID representations. Implementations are required to zero-fill unused bytes. The osaudio_id_t
|
||||
structure can be copied which makes it suitable for serialization and deserialization in situations
|
||||
where you may want to save the device ID to permanent storage so it can be stored in a config file.
|
||||
|
||||
Implementations need to do their own data conversion between the device's native data configuration
|
||||
and the requested configuration. In this case, when the format, channels and rate are specified in
|
||||
the config, they should be unchanged when osaudio_open() returns. If this is not possible,
|
||||
osaudio_open() will return OSAUDIO_FORMAT_NOT_SUPPORTED. However, there are cases where it's useful
|
||||
for a program to use the device's native configuration instead of some fixed configuration. This is
|
||||
achieved by setting the format, channels and rate to 0. Below is an example:
|
||||
|
||||
int result;
|
||||
osaudio_t audio;
|
||||
osaudio_config_t config;
|
||||
|
||||
osaudio_config_init(&config, OSAUDIO_OUTPUT);
|
||||
|
||||
result = osaudio_open(&audio, &config);
|
||||
if (result != OSAUDIO_SUCCESS) {
|
||||
printf("Failed to open device.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// ... `config` will have been updated by osaudio_open() to contain the *actual* format/channels/rate ...
|
||||
|
||||
osaudio_close(audio);
|
||||
|
||||
In addition to the code above, you can explicitly call `osaudio_get_info()` to retrieve the format
|
||||
configuration. If you need to know the native configuration before opening the device, you can use
|
||||
enumeration. The format, channels and rate will be continued in the first item in the configs array.
|
||||
|
||||
The examples above all use playback, but the same applies for capture. The only difference is that
|
||||
the direction is set to OSAUDIO_INPUT instead of OSAUDIO_OUTPUT.
|
||||
|
||||
To output audio from the speakers you need to call osaudio_write(). Likewise, to capture audio from
|
||||
a microphone you need to call osaudio_read(). These functions will block until the requested number
|
||||
of frames have been written or read. The device will start automatically. Below is an example for
|
||||
writing some data to a device:
|
||||
|
||||
int result = osaudio_write(audio, myAudioData, myAudioDataFrameCount);
|
||||
if (result == OSAUDIO_SUCCESS) {
|
||||
printf("Successfully wrote %d frames of audio data.\n", myAudioDataFrameCount);
|
||||
} else {
|
||||
printf("Failed to write audio data.\n");
|
||||
}
|
||||
|
||||
osaudio_write() and osaudio_read() will return OSAUDIO_SUCCESS if the requested number of frames
|
||||
were written or read. You cannot call osaudio_close() while a write or read operation is in
|
||||
progress.
|
||||
|
||||
If you want to write or read audio data without blocking, you can use osaudio_get_avail() to
|
||||
determine how many frames are available for writing or reading. Below is an example:
|
||||
|
||||
unsigned int framesAvailable = osaudio_get_avail(audio);
|
||||
if (result > 0) {
|
||||
printf("There are %d frames available for writing.\n", framesAvailable);
|
||||
} else {
|
||||
printf("There are no frames available for writing.\n");
|
||||
}
|
||||
|
||||
If you want to abort a blocking write or read, you can use osaudio_flush(). This will result in any
|
||||
pending write or read operation being aborted.
|
||||
|
||||
There are several ways of pausing a device. The first is to just drain or flush the device and
|
||||
simply don't do any more read/write operations. A drain and flush will put the device into a
|
||||
stopped state until the next call to either read or write, depending on the device's direction.
|
||||
If, however, this does not suit your requirements, you can use osaudio_pause() and
|
||||
osaudio_resume(). Take note, however, that these functions will result in osaudio_drain() never
|
||||
returning because it'll result in the device being in a stopped state which in turn results in the
|
||||
buffer never being read and therefore never drained.
|
||||
|
||||
Everything is thread safe with a few minor exceptions which has no practical issues for the client:
|
||||
|
||||
* You cannot call any function while osaudio_open() is still in progress.
|
||||
* You cannot call osaudio_close() while any other function is still in progress.
|
||||
* You can only call osaudio_write() and osaudio_read() from one thread at a time.
|
||||
|
||||
None of these issues should be a problem for the client in practice. You won't have a valid
|
||||
osaudio_t object until osaudio_open() has returned. For osaudio_close(), it makes no sense to
|
||||
destroy the object while it's still in use, and doing so would mean the client is using very poor
|
||||
form. For osaudio_write() and osaudio_read(), you wouldn't ever want to call this simultaneously
|
||||
across multiple threads anyway because otherwise you'd end up with garbage audio.
|
||||
|
||||
The rules above only apply when working with a single osaudio_t object. You can have multiple
|
||||
osaudio_t objects open at the same time, and you can call any function on different osaudio_t
|
||||
objects simultaneously from different threads.
|
||||
|
||||
---
|
||||
|
||||
# Feedback
|
||||
|
||||
I'm looking for feedback on the following:
|
||||
|
||||
* Are the supported formats enough? If not, what other formats are needed, and what is the
|
||||
justification for including it? Just because it's the native format on one particular
|
||||
piece of hardware is not enough. Big-endian and little-endian will never be supported. All
|
||||
formats are native-endian.
|
||||
* Are the available channel positions enough? What other positions are needed?
|
||||
* Just some general criticism would be appreciated.
|
||||
|
||||
*/
|
||||
#ifndef osaudio_h
|
||||
#define osaudio_h
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*
|
||||
Support far pointers on relevant platforms (DOS, in particular). The version of this file
|
||||
distributed with an operating system wouldn't need this because they would just have an
|
||||
OS-specific version of this file, but as a reference it's useful to use far pointers here.
|
||||
*/
|
||||
#if defined(__MSDOS__) || defined(_MSDOS) || defined(__DOS__)
|
||||
#define OSAUDIO_FAR far
|
||||
#else
|
||||
#define OSAUDIO_FAR
|
||||
#endif
|
||||
|
||||
typedef struct _osaudio_t* osaudio_t;
|
||||
typedef struct osaudio_config_t osaudio_config_t;
|
||||
typedef struct osaudio_id_t osaudio_id_t;
|
||||
typedef struct osaudio_info_t osaudio_info_t;
|
||||
typedef struct osaudio_notification_t osaudio_notification_t;
|
||||
|
||||
/* Results codes. */
|
||||
typedef int osaudio_result_t;
|
||||
#define OSAUDIO_SUCCESS 0
|
||||
#define OSAUDIO_ERROR -1
|
||||
#define OSAUDIO_INVALID_ARGS -2
|
||||
#define OSAUDIO_INVALID_OPERATION -3
|
||||
#define OSAUDIO_OUT_OF_MEMORY -4
|
||||
#define OSAUDIO_FORMAT_NOT_SUPPORTED -101 /* The requested format is not supported. */
|
||||
#define OSAUDIO_XRUN -102 /* An underrun or overrun occurred. Can be returned by osaudio_read() or osaudio_write(). */
|
||||
#define OSAUDIO_DEVICE_STOPPED -103 /* The device is stopped. Can be returned by osaudio_drain(). It is invalid to call osaudio_drain() on a device that is not running because otherwise it'll get stuck. */
|
||||
|
||||
/* Directions. Cannot be combined. Use separate osaudio_t objects for bidirectional setups. */
|
||||
typedef int osaudio_direction_t;
|
||||
#define OSAUDIO_INPUT 1
|
||||
#define OSAUDIO_OUTPUT 2
|
||||
|
||||
/* All formats are native endian and interleaved. */
|
||||
typedef int osaudio_format_t;
|
||||
#define OSAUDIO_FORMAT_UNKNOWN 0
|
||||
#define OSAUDIO_FORMAT_F32 1
|
||||
#define OSAUDIO_FORMAT_U8 2
|
||||
#define OSAUDIO_FORMAT_S16 3
|
||||
#define OSAUDIO_FORMAT_S24 4 /* Tightly packed. */
|
||||
#define OSAUDIO_FORMAT_S32 5
|
||||
|
||||
/* Channel positions. */
|
||||
typedef unsigned char osaudio_channel_t;
|
||||
#define OSAUDIO_CHANNEL_NONE 0
|
||||
#define OSAUDIO_CHANNEL_MONO 1
|
||||
#define OSAUDIO_CHANNEL_FL 2
|
||||
#define OSAUDIO_CHANNEL_FR 3
|
||||
#define OSAUDIO_CHANNEL_FC 4
|
||||
#define OSAUDIO_CHANNEL_LFE 5
|
||||
#define OSAUDIO_CHANNEL_BL 6
|
||||
#define OSAUDIO_CHANNEL_BR 7
|
||||
#define OSAUDIO_CHANNEL_FLC 8
|
||||
#define OSAUDIO_CHANNEL_FRC 9
|
||||
#define OSAUDIO_CHANNEL_BC 10
|
||||
#define OSAUDIO_CHANNEL_SL 11
|
||||
#define OSAUDIO_CHANNEL_SR 12
|
||||
#define OSAUDIO_CHANNEL_TC 13
|
||||
#define OSAUDIO_CHANNEL_TFL 14
|
||||
#define OSAUDIO_CHANNEL_TFC 15
|
||||
#define OSAUDIO_CHANNEL_TFR 16
|
||||
#define OSAUDIO_CHANNEL_TBL 17
|
||||
#define OSAUDIO_CHANNEL_TBC 18
|
||||
#define OSAUDIO_CHANNEL_TBR 19
|
||||
#define OSAUDIO_CHANNEL_AUX0 20
|
||||
#define OSAUDIO_CHANNEL_AUX1 21
|
||||
#define OSAUDIO_CHANNEL_AUX2 22
|
||||
#define OSAUDIO_CHANNEL_AUX3 23
|
||||
#define OSAUDIO_CHANNEL_AUX4 24
|
||||
#define OSAUDIO_CHANNEL_AUX5 25
|
||||
#define OSAUDIO_CHANNEL_AUX6 26
|
||||
#define OSAUDIO_CHANNEL_AUX7 27
|
||||
#define OSAUDIO_CHANNEL_AUX8 28
|
||||
#define OSAUDIO_CHANNEL_AUX9 29
|
||||
#define OSAUDIO_CHANNEL_AUX10 30
|
||||
#define OSAUDIO_CHANNEL_AUX11 31
|
||||
#define OSAUDIO_CHANNEL_AUX12 32
|
||||
#define OSAUDIO_CHANNEL_AUX13 33
|
||||
#define OSAUDIO_CHANNEL_AUX14 34
|
||||
#define OSAUDIO_CHANNEL_AUX15 35
|
||||
#define OSAUDIO_CHANNEL_AUX16 36
|
||||
#define OSAUDIO_CHANNEL_AUX17 37
|
||||
#define OSAUDIO_CHANNEL_AUX18 38
|
||||
#define OSAUDIO_CHANNEL_AUX19 39
|
||||
#define OSAUDIO_CHANNEL_AUX20 40
|
||||
#define OSAUDIO_CHANNEL_AUX21 41
|
||||
#define OSAUDIO_CHANNEL_AUX22 42
|
||||
#define OSAUDIO_CHANNEL_AUX23 43
|
||||
#define OSAUDIO_CHANNEL_AUX24 44
|
||||
#define OSAUDIO_CHANNEL_AUX25 45
|
||||
#define OSAUDIO_CHANNEL_AUX26 46
|
||||
#define OSAUDIO_CHANNEL_AUX27 47
|
||||
#define OSAUDIO_CHANNEL_AUX28 48
|
||||
#define OSAUDIO_CHANNEL_AUX29 49
|
||||
#define OSAUDIO_CHANNEL_AUX30 50
|
||||
#define OSAUDIO_CHANNEL_AUX31 51
|
||||
|
||||
/* The maximum number of channels supported. */
|
||||
#define OSAUDIO_MAX_CHANNELS 64
|
||||
|
||||
/* Notification types. */
|
||||
typedef int osaudio_notification_type_t;
|
||||
#define OSAUDIO_NOTIFICATION_STARTED 0 /* The device was started in response to a call to osaudio_write() or osaudio_read(). */
|
||||
#define OSAUDIO_NOTIFICATION_STOPPED 1 /* The device was stopped in response to a call to osaudio_drain() or osaudio_flush(). */
|
||||
#define OSAUDIO_NOTIFICATION_REROUTED 2 /* The device was rerouted. Not all implementations need to support rerouting. */
|
||||
#define OSAUDIO_NOTIFICATION_INTERRUPTION_BEGIN 3 /* The device was interrupted due to something like a phone call. */
|
||||
#define OSAUDIO_NOTIFICATION_INTERRUPTION_END 4 /* The interruption has been ended. */
|
||||
|
||||
/* Flags. */
|
||||
#define OSAUDIO_FLAG_NO_REROUTING 1 /* When set, will tell the implementation to disable automatic rerouting if possible. This is a hint and may be ignored by the implementation. */
|
||||
#define OSAUDIO_FLAG_REPORT_XRUN 2 /* When set, will tell the implementation to report underruns and overruns via osaudio_write() and osaudio_read() by aborting and returning OSAUDIO_XRUN. */
|
||||
|
||||
struct osaudio_notification_t
|
||||
{
|
||||
osaudio_notification_type_t type; /* OSAUDIO_NOTIFICATION_* */
|
||||
union
|
||||
{
|
||||
struct
|
||||
{
|
||||
int _unused;
|
||||
} started;
|
||||
struct
|
||||
{
|
||||
int _unused;
|
||||
} stopped;
|
||||
struct
|
||||
{
|
||||
int _unused;
|
||||
} rerouted;
|
||||
struct
|
||||
{
|
||||
int _unused;
|
||||
} interruption;
|
||||
} data;
|
||||
};
|
||||
|
||||
struct osaudio_id_t
|
||||
{
|
||||
char data[256];
|
||||
};
|
||||
|
||||
struct osaudio_config_t
|
||||
{
|
||||
osaudio_id_t* device_id; /* Set to NULL to use default device. When non-null, automatic routing will be disabled. */
|
||||
osaudio_direction_t direction; /* OSAUDIO_INPUT or OSAUDIO_OUTPUT. Cannot be combined. Use separate osaudio_t objects for bidirectional setups. */
|
||||
osaudio_format_t format; /* OSAUDIO_FORMAT_* */
|
||||
unsigned int channels; /* Number of channels. */
|
||||
unsigned int rate; /* Sample rate in seconds. */
|
||||
osaudio_channel_t channel_map[OSAUDIO_MAX_CHANNELS]; /* Leave all items set to 0 for defaults. */
|
||||
unsigned int buffer_size; /* In frames. Set to 0 to use the system default. */
|
||||
unsigned int flags; /* A combination of OSAUDIO_FLAG_* */
|
||||
void (* notification)(void* user_data, const osaudio_notification_t* notification); /* Called when some kind of event occurs, such as a device being closed. Never called from the audio thread. */
|
||||
void* user_data; /* Passed to notification(). */
|
||||
};
|
||||
|
||||
struct osaudio_info_t
|
||||
{
|
||||
osaudio_id_t id;
|
||||
char name[256];
|
||||
osaudio_direction_t direction; /* OSAUDIO_INPUT or OSAUDIO_OUTPUT. */
|
||||
unsigned int config_count;
|
||||
osaudio_config_t* configs;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
Enumerates the available devices.
|
||||
|
||||
On output, `count` will contain the number of items in the `info` array. The array must be freed
|
||||
with free() when it's no longer needed.
|
||||
|
||||
Use the `direction` member to discriminate between input and output devices. Below is an example:
|
||||
|
||||
unsigned int count;
|
||||
osaudio_info_t* info;
|
||||
osaudio_enumerate(&count, &info);
|
||||
|
||||
for (int i = 0; i < count; ++i) {
|
||||
if (info[i].direction == OSAUDIO_OUTPUT) {
|
||||
printf("Output device: %s\n", info[i].name);
|
||||
} else {
|
||||
printf("Input device: %s\n", info[i].name);
|
||||
}
|
||||
}
|
||||
|
||||
You can use the `id` member to open a specific device with osaudio_open(). You do not need to do
|
||||
device enumeration if you only want to open the default device.
|
||||
*/
|
||||
osaudio_result_t osaudio_enumerate(unsigned int* count, osaudio_info_t** info);
|
||||
|
||||
/*
|
||||
Initializes a default config.
|
||||
|
||||
The config object will be cleared to zero, with the direction set to `direction`. This will result
|
||||
in a configuration that uses the device's native format, channels and rate.
|
||||
|
||||
osaudio_config_t is a transparent struct. Just set the relevant fields to the desired values after
|
||||
calling this function. Example:
|
||||
|
||||
osaudio_config_t config;
|
||||
osaudio_config_init(&config, OSAUDIO_OUTPUT);
|
||||
config.format = OSAUDIO_FORMAT_F32;
|
||||
config.channels = 2;
|
||||
config.rate = 48000;
|
||||
*/
|
||||
void osaudio_config_init(osaudio_config_t* config, osaudio_direction_t direction);
|
||||
|
||||
/*
|
||||
Opens a connection to a device.
|
||||
|
||||
On input, config must be filled with the desired configuration. On output, it will be filled with
|
||||
the actual configuration.
|
||||
|
||||
Initialize the config with osaudio_config_init() and then fill in the desired configuration. Below
|
||||
is an example:
|
||||
|
||||
osaudio_config_t config;
|
||||
osaudio_config_init(&config, OSAUDIO_OUTPUT);
|
||||
config.format = OSAUDIO_FORMAT_F32;
|
||||
config.channels = 2;
|
||||
config.rate = 48000;
|
||||
|
||||
When the format, channels or rate are left at their default values, or set to 0 (or
|
||||
OSAUDIO_FORMAT_UNKNOWN for format), the native format, channels or rate will use the device's
|
||||
native configuration:
|
||||
|
||||
osaudio_config_t config;
|
||||
osaudio_config_init(&config, OSAUDIO_OUTPUT);
|
||||
config.format = OSAUDIO_FORMAT_UNKNOWN;
|
||||
config.channels = 0;
|
||||
config.rate = 0;
|
||||
|
||||
The code above is equivalent to this:
|
||||
|
||||
osaudio_config_t config;
|
||||
osaudio_config_init(&config, OSAUDIO_OUTPUT);
|
||||
|
||||
On output the config will be filled with the actual configuration. The implementation will perform
|
||||
any necessary data conversion between the requested data configuration and the device's native
|
||||
configuration. If it cannot, the function will return a OSAUDIO_FORMAT_NOT_SUPPORTED error. In this
|
||||
case the caller can decide to reinitialize the device to use its native configuration and do its
|
||||
own data conversion, or abort if it cannot do so. Use the channel map to determine the ordering of
|
||||
your channels. Automatic channel map conversion is not performed - that must be done manually by
|
||||
the caller when transferring data to/from the device.
|
||||
|
||||
Close the device with osaudio_close().
|
||||
|
||||
Returns 0 on success, any other error code on failure.
|
||||
*/
|
||||
osaudio_result_t osaudio_open(osaudio_t* audio, osaudio_config_t* config);
|
||||
|
||||
/*
|
||||
Closes a connection to a device.
|
||||
|
||||
As soon as this function is called, the device should be considered invalid and unusable. Do not
|
||||
attempt to use the audio object once this function has been called.
|
||||
|
||||
It's invalid to call this while any other function is still running. You can use osaudio_flush() to
|
||||
quickly abort any pending writes or reads. You can also use osaudio_drain() to wait for all pending
|
||||
writes or reads to complete.
|
||||
|
||||
Returns 0 on success, < 0 on failure.
|
||||
*/
|
||||
osaudio_result_t osaudio_close(osaudio_t audio);
|
||||
|
||||
/*
|
||||
Writes audio data to the device.
|
||||
|
||||
This will block until all data has been written or the device is closed.
|
||||
|
||||
You can only write from a single thread at any given time. If you want to write from multiple
|
||||
threads, you need to use your own synchronization mechanism.
|
||||
|
||||
This will automatically start the device if frame_count is > 0 and it's not in a paused state.
|
||||
|
||||
Use osaudio_get_avail() to determine how much data can be written without blocking.
|
||||
|
||||
Returns 0 on success, < 0 on failure.
|
||||
*/
|
||||
osaudio_result_t osaudio_write(osaudio_t audio, const void OSAUDIO_FAR* data, unsigned int frame_count);
|
||||
|
||||
/*
|
||||
Reads audio data from the device.
|
||||
|
||||
This will block until the requested number of frames has been read or the device is closed.
|
||||
|
||||
You can only read from a single thread at any given time. If you want to read from multiple
|
||||
threads, you need to use your own synchronization mechanism.
|
||||
|
||||
This will automatically start the device if frame_count is > 0 and it's not in a paused state.
|
||||
|
||||
Use osaudio_get_avail() to determine how much data can be read without blocking.
|
||||
|
||||
Returns 0 on success, < 0 on failure.
|
||||
*/
|
||||
osaudio_result_t osaudio_read(osaudio_t audio, void OSAUDIO_FAR* data, unsigned int frame_count);
|
||||
|
||||
/*
|
||||
Drains the device.
|
||||
|
||||
This will block until all pending reads or writes have completed.
|
||||
|
||||
If after calling this function another call to osaudio_write() or osaudio_read() is made, the
|
||||
device will be resumed like normal.
|
||||
|
||||
It is invalid to call this while the device is paused.
|
||||
|
||||
Returns 0 on success, < 0 on failure.
|
||||
*/
|
||||
osaudio_result_t osaudio_drain(osaudio_t audio);
|
||||
|
||||
/*
|
||||
Flushes the device.
|
||||
|
||||
This will immediately flush any pending reads or writes. It will not block. Any in-progress reads
|
||||
or writes will return immediately.
|
||||
|
||||
If after calling this function another thread starts reading or writing, the device will be resumed
|
||||
like normal.
|
||||
|
||||
Returns 0 on success, < 0 on failure.
|
||||
*/
|
||||
osaudio_result_t osaudio_flush(osaudio_t audio);
|
||||
|
||||
/*
|
||||
Pauses or resumes the device.
|
||||
|
||||
Pausing a device will trigger a OSAUDIO_NOTIFICATION_STOPPED notification. Resuming a device will
|
||||
trigger a OSAUDIO_NOTIFICATION_STARTED notification.
|
||||
|
||||
Returns 0 on success, < 0 on failure.
|
||||
*/
|
||||
osaudio_result_t osaudio_pause(osaudio_t audio);
|
||||
|
||||
/*
|
||||
Resumes the device.
|
||||
|
||||
Returns 0 on success, < 0 on failure.
|
||||
*/
|
||||
osaudio_result_t osaudio_resume(osaudio_t audio);
|
||||
|
||||
/*
|
||||
Returns the number of frames that can be read or written without blocking.
|
||||
*/
|
||||
unsigned int osaudio_get_avail(osaudio_t audio);
|
||||
|
||||
/*
|
||||
Gets information about the device.
|
||||
|
||||
There will be one item in the configs array which will contain the device's current configuration,
|
||||
the contents of which will match that of the config that was returned by osaudio_open().
|
||||
|
||||
Returns NULL on failure. Do not free the returned pointer. It's up to the implementation to manage
|
||||
the memory of this object.
|
||||
*/
|
||||
const osaudio_info_t* osaudio_get_info(osaudio_t audio);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* osaudio_h */
|
||||
@@ -0,0 +1,948 @@
|
||||
/*
|
||||
Consider this a reference implementation of osaudio. It uses miniaudio under the hood. You can add
|
||||
this file directly to your source tree, but you may need to update the miniaudio path.
|
||||
|
||||
This will use a mutex in osaudio_read() and osaudio_write(). It's a low-contention lock that's only
|
||||
used for the purpose of osaudio_drain(), but it's still a lock nonetheless. I'm not worrying about
|
||||
this too much right now because this is just an example implementation, but I might improve on this
|
||||
at a later date.
|
||||
*/
|
||||
#ifndef osaudio_miniaudio_c
|
||||
#define osaudio_miniaudio_c
|
||||
|
||||
#include "osaudio.h"
|
||||
|
||||
/*
|
||||
If you would rather define your own implementation of miniaudio, define OSAUDIO_NO_MINIAUDIO_IMPLEMENTATION. If you do this,
|
||||
you need to make sure you include the implmeentation before osaudio.c. This would only really be useful if you are wanting
|
||||
to do a unity build which uses other parts of miniaudio that this file is currently excluding.
|
||||
*/
|
||||
#ifndef OSAUDIO_NO_MINIAUDIO_IMPLEMENTATION
|
||||
#define MA_API static
|
||||
#define MA_NO_DECODING
|
||||
#define MA_NO_ENCODING
|
||||
#define MA_NO_RESOURCE_MANAGER
|
||||
#define MA_NO_NODE_GRAPH
|
||||
#define MA_NO_ENGINE
|
||||
#define MA_NO_GENERATION
|
||||
#define MINIAUDIO_IMPLEMENTATION
|
||||
#include "../../miniaudio.h"
|
||||
#endif
|
||||
|
||||
struct _osaudio_t
|
||||
{
|
||||
ma_device device;
|
||||
osaudio_info_t info;
|
||||
osaudio_config_t config; /* info.configs will point to this. */
|
||||
ma_pcm_rb buffer;
|
||||
ma_semaphore bufferSemaphore; /* The semaphore for controlling access to the buffer. The audio thread will release the semaphore. The read and write functions will wait on it. */
|
||||
ma_atomic_bool32 isActive; /* Starts off as false. Set to true when config.buffer_size data has been written in the case of playback, or as soon as osaudio_read() is called in the case of capture. */
|
||||
ma_atomic_bool32 isPaused;
|
||||
ma_atomic_bool32 isFlushed; /* When set, activation of the device will flush any data that's currently in the buffer. Defaults to false, and will be set to true in osaudio_drain() and osaudio_flush(). */
|
||||
ma_atomic_bool32 xrunDetected; /* Used for detecting when an xrun has occurred and returning from osaudio_read/write() when OSAUDIO_FLAG_REPORT_XRUN is enabled. */
|
||||
ma_spinlock activateLock; /* Used for starting and stopping the device. Needed because two variables control this - isActive and isPaused. */
|
||||
ma_mutex drainLock; /* Used for osaudio_drain(). For mutal exclusion between drain() and read()/write(). Technically results in a lock in read()/write(), but not overthinking that since this is just a reference for now. */
|
||||
};
|
||||
|
||||
|
||||
static ma_bool32 osaudio_g_is_backend_known = MA_FALSE;
|
||||
static ma_backend osaudio_g_backend = ma_backend_wasapi;
|
||||
static ma_context osaudio_g_context;
|
||||
static ma_mutex osaudio_g_context_lock; /* Only used for device enumeration. Created and destroyed with our context. */
|
||||
static ma_uint32 osaudio_g_refcount = 0;
|
||||
static ma_spinlock osaudio_g_lock = 0;
|
||||
|
||||
|
||||
static osaudio_result_t osaudio_result_from_miniaudio(ma_result result)
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
case MA_SUCCESS: return OSAUDIO_SUCCESS;
|
||||
case MA_INVALID_ARGS: return OSAUDIO_INVALID_ARGS;
|
||||
case MA_INVALID_OPERATION: return OSAUDIO_INVALID_OPERATION;
|
||||
case MA_OUT_OF_MEMORY: return OSAUDIO_OUT_OF_MEMORY;
|
||||
default: return OSAUDIO_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
static ma_format osaudio_format_to_miniaudio(osaudio_format_t format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case OSAUDIO_FORMAT_F32: return ma_format_f32;
|
||||
case OSAUDIO_FORMAT_U8: return ma_format_u8;
|
||||
case OSAUDIO_FORMAT_S16: return ma_format_s16;
|
||||
case OSAUDIO_FORMAT_S24: return ma_format_s24;
|
||||
case OSAUDIO_FORMAT_S32: return ma_format_s32;
|
||||
default: return ma_format_unknown;
|
||||
}
|
||||
}
|
||||
|
||||
static osaudio_format_t osaudio_format_from_miniaudio(ma_format format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case ma_format_f32: return OSAUDIO_FORMAT_F32;
|
||||
case ma_format_u8: return OSAUDIO_FORMAT_U8;
|
||||
case ma_format_s16: return OSAUDIO_FORMAT_S16;
|
||||
case ma_format_s24: return OSAUDIO_FORMAT_S24;
|
||||
case ma_format_s32: return OSAUDIO_FORMAT_S32;
|
||||
default: return OSAUDIO_FORMAT_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static osaudio_channel_t osaudio_channel_from_miniaudio(ma_channel channel)
|
||||
{
|
||||
/* Channel positions between here and miniaudio will remain in sync. */
|
||||
return (osaudio_channel_t)channel;
|
||||
}
|
||||
|
||||
static ma_channel osaudio_channel_to_miniaudio(osaudio_channel_t channel)
|
||||
{
|
||||
/* Channel positions between here and miniaudio will remain in sync. */
|
||||
return (ma_channel)channel;
|
||||
}
|
||||
|
||||
|
||||
static void osaudio_dummy_data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount)
|
||||
{
|
||||
(void)pDevice;
|
||||
(void)pOutput;
|
||||
(void)pInput;
|
||||
(void)frameCount;
|
||||
}
|
||||
|
||||
static osaudio_result_t osaudio_determine_miniaudio_backend(ma_backend* pBackend, ma_device* pDummyDevice)
|
||||
{
|
||||
ma_device dummyDevice;
|
||||
ma_device_config dummyDeviceConfig;
|
||||
ma_result result;
|
||||
|
||||
/*
|
||||
To do this we initialize a dummy device. We allow the caller to make use of this device as an optimization. This is
|
||||
only used by osaudio_enumerate_devices() because that can make use of the context from the dummy device rather than
|
||||
having to create its own. pDummyDevice can be null.
|
||||
*/
|
||||
if (pDummyDevice == NULL) {
|
||||
pDummyDevice = &dummyDevice;
|
||||
}
|
||||
|
||||
dummyDeviceConfig = ma_device_config_init(ma_device_type_playback);
|
||||
dummyDeviceConfig.dataCallback = osaudio_dummy_data_callback;
|
||||
|
||||
result = ma_device_init(NULL, &dummyDeviceConfig, pDummyDevice);
|
||||
if (result != MA_SUCCESS || pDummyDevice->pContext->backend == ma_backend_null) {
|
||||
/* Failed to open a default playback device. Try capture. */
|
||||
if (result == MA_SUCCESS) {
|
||||
/* This means we successfully initialize a device, but its backend is null. It could be that there's no playback devices attached. Try capture. */
|
||||
ma_device_uninit(pDummyDevice);
|
||||
}
|
||||
|
||||
dummyDeviceConfig = ma_device_config_init(ma_device_type_capture);
|
||||
result = ma_device_init(NULL, &dummyDeviceConfig, pDummyDevice);
|
||||
}
|
||||
|
||||
if (result != MA_SUCCESS) {
|
||||
return osaudio_result_from_miniaudio(result);
|
||||
}
|
||||
|
||||
*pBackend = pDummyDevice->pContext->backend;
|
||||
|
||||
/* We're done. */
|
||||
if (pDummyDevice == &dummyDevice) {
|
||||
ma_device_uninit(&dummyDevice);
|
||||
}
|
||||
|
||||
return OSAUDIO_SUCCESS;
|
||||
}
|
||||
|
||||
static osaudio_result_t osaudio_ref_context_nolock()
|
||||
{
|
||||
/* Initialize the global context if necessary. */
|
||||
if (osaudio_g_refcount == 0) {
|
||||
osaudio_result_t result;
|
||||
|
||||
/* If we haven't got a known context, we'll need to determine it here. */
|
||||
if (osaudio_g_is_backend_known == MA_FALSE) {
|
||||
result = osaudio_determine_miniaudio_backend(&osaudio_g_backend, NULL);
|
||||
if (result != OSAUDIO_SUCCESS) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
result = osaudio_result_from_miniaudio(ma_context_init(&osaudio_g_backend, 1, NULL, &osaudio_g_context));
|
||||
if (result != OSAUDIO_SUCCESS) {
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Need a mutex for device enumeration. */
|
||||
ma_mutex_init(&osaudio_g_context_lock);
|
||||
}
|
||||
|
||||
osaudio_g_refcount += 1;
|
||||
|
||||
return OSAUDIO_SUCCESS;
|
||||
}
|
||||
|
||||
static osaudio_result_t osaudio_unref_context_nolock()
|
||||
{
|
||||
if (osaudio_g_refcount == 0) {
|
||||
return OSAUDIO_INVALID_OPERATION;
|
||||
}
|
||||
|
||||
osaudio_g_refcount -= 1;
|
||||
|
||||
/* Uninitialize the context if we don't have any more references. */
|
||||
if (osaudio_g_refcount == 0) {
|
||||
ma_context_uninit(&osaudio_g_context);
|
||||
ma_mutex_uninit(&osaudio_g_context_lock);
|
||||
}
|
||||
|
||||
return OSAUDIO_SUCCESS;
|
||||
}
|
||||
|
||||
static ma_context* osaudio_ref_context()
|
||||
{
|
||||
osaudio_result_t result;
|
||||
|
||||
ma_spinlock_lock(&osaudio_g_lock);
|
||||
{
|
||||
result = osaudio_ref_context_nolock();
|
||||
}
|
||||
ma_spinlock_unlock(&osaudio_g_lock);
|
||||
|
||||
if (result != OSAUDIO_SUCCESS) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return &osaudio_g_context;
|
||||
}
|
||||
|
||||
static osaudio_result_t osaudio_unref_context()
|
||||
{
|
||||
osaudio_result_t result;
|
||||
|
||||
ma_spinlock_lock(&osaudio_g_lock);
|
||||
{
|
||||
result = osaudio_unref_context_nolock();
|
||||
}
|
||||
ma_spinlock_unlock(&osaudio_g_lock);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
static void osaudio_info_from_miniaudio(osaudio_info_t* info, const ma_device_info* infoMA)
|
||||
{
|
||||
unsigned int iNativeConfig;
|
||||
|
||||
/* It just so happens, by absolutely total coincidence, that the size of the ID and name are the same between here and miniaudio. What are the odds?! */
|
||||
memcpy(info->id.data, &infoMA->id, sizeof(info->id.data));
|
||||
memcpy(info->name, infoMA->name, sizeof(info->name));
|
||||
|
||||
info->config_count = (unsigned int)infoMA->nativeDataFormatCount;
|
||||
for (iNativeConfig = 0; iNativeConfig < info->config_count; iNativeConfig += 1) {
|
||||
unsigned int iChannel;
|
||||
|
||||
info->configs[iNativeConfig].device_id = &info->id;
|
||||
info->configs[iNativeConfig].direction = info->direction;
|
||||
info->configs[iNativeConfig].format = osaudio_format_from_miniaudio(infoMA->nativeDataFormats[iNativeConfig].format);
|
||||
info->configs[iNativeConfig].channels = (unsigned int)infoMA->nativeDataFormats[iNativeConfig].channels;
|
||||
info->configs[iNativeConfig].rate = (unsigned int)infoMA->nativeDataFormats[iNativeConfig].sampleRate;
|
||||
|
||||
/* Apparently miniaudio does not report channel positions. I don't know why I'm not doing that. */
|
||||
for (iChannel = 0; iChannel < info->configs[iNativeConfig].channels; iChannel += 1) {
|
||||
info->configs[iNativeConfig].channel_map[iChannel] = OSAUDIO_CHANNEL_NONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static osaudio_result_t osaudio_enumerate_nolock(unsigned int* count, osaudio_info_t** info, ma_context* pContext)
|
||||
{
|
||||
osaudio_result_t result;
|
||||
ma_device_info* pPlaybackInfos;
|
||||
ma_uint32 playbackCount;
|
||||
ma_device_info* pCaptureInfos;
|
||||
ma_uint32 captureCount;
|
||||
ma_uint32 iInfo;
|
||||
size_t allocSize;
|
||||
osaudio_info_t* pRunningInfo;
|
||||
osaudio_config_t* pRunningConfig;
|
||||
|
||||
/* We now need to retrieve the device information from miniaudio. */
|
||||
result = osaudio_result_from_miniaudio(ma_context_get_devices(pContext, &pPlaybackInfos, &playbackCount, &pCaptureInfos, &captureCount));
|
||||
if (result != OSAUDIO_SUCCESS) {
|
||||
osaudio_unref_context();
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
Because the caller needs to free the returned pointer it's important that we keep it all in one allocation. Because there can be
|
||||
a variable number of native configs we'll have to compute the size of the allocation first, and then do a second pass to fill
|
||||
out the data.
|
||||
*/
|
||||
allocSize = ((size_t)playbackCount + (size_t)captureCount) * sizeof(osaudio_info_t);
|
||||
|
||||
/* Now we need to iterate over each playback and capture device and add up the number of native configs. */
|
||||
for (iInfo = 0; iInfo < playbackCount; iInfo += 1) {
|
||||
ma_context_get_device_info(pContext, ma_device_type_playback, &pPlaybackInfos[iInfo].id, &pPlaybackInfos[iInfo]);
|
||||
allocSize += pPlaybackInfos[iInfo].nativeDataFormatCount * sizeof(osaudio_config_t);
|
||||
}
|
||||
for (iInfo = 0; iInfo < captureCount; iInfo += 1) {
|
||||
ma_context_get_device_info(pContext, ma_device_type_capture, &pCaptureInfos[iInfo].id, &pCaptureInfos[iInfo]);
|
||||
allocSize += pCaptureInfos[iInfo].nativeDataFormatCount * sizeof(osaudio_config_t);
|
||||
}
|
||||
|
||||
/* Now that we know the size of the allocation we can allocate it. */
|
||||
*info = (osaudio_info_t*)calloc(1, allocSize);
|
||||
if (*info == NULL) {
|
||||
osaudio_unref_context();
|
||||
return OSAUDIO_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
pRunningInfo = *info;
|
||||
pRunningConfig = (osaudio_config_t*)(((unsigned char*)*info) + (((size_t)playbackCount + (size_t)captureCount) * sizeof(osaudio_info_t)));
|
||||
|
||||
for (iInfo = 0; iInfo < playbackCount; iInfo += 1) {
|
||||
pRunningInfo->direction = OSAUDIO_OUTPUT;
|
||||
pRunningInfo->configs = pRunningConfig;
|
||||
osaudio_info_from_miniaudio(pRunningInfo, &pPlaybackInfos[iInfo]);
|
||||
|
||||
pRunningConfig += pRunningInfo->config_count;
|
||||
pRunningInfo += 1;
|
||||
}
|
||||
|
||||
for (iInfo = 0; iInfo < captureCount; iInfo += 1) {
|
||||
pRunningInfo->direction = OSAUDIO_INPUT;
|
||||
pRunningInfo->configs = pRunningConfig;
|
||||
osaudio_info_from_miniaudio(pRunningInfo, &pPlaybackInfos[iInfo]);
|
||||
|
||||
pRunningConfig += pRunningInfo->config_count;
|
||||
pRunningInfo += 1;
|
||||
}
|
||||
|
||||
*count = (unsigned int)(playbackCount + captureCount);
|
||||
|
||||
return OSAUDIO_SUCCESS;
|
||||
}
|
||||
|
||||
osaudio_result_t osaudio_enumerate(unsigned int* count, osaudio_info_t** info)
|
||||
{
|
||||
osaudio_result_t result;
|
||||
ma_context* pContext = NULL;
|
||||
|
||||
if (count != NULL) {
|
||||
*count = 0;
|
||||
}
|
||||
if (info != NULL) {
|
||||
*info = NULL;
|
||||
}
|
||||
|
||||
if (count == NULL || info == NULL) {
|
||||
return OSAUDIO_INVALID_ARGS;
|
||||
}
|
||||
|
||||
pContext = osaudio_ref_context();
|
||||
if (pContext == NULL) {
|
||||
return OSAUDIO_ERROR;
|
||||
}
|
||||
|
||||
ma_mutex_lock(&osaudio_g_context_lock);
|
||||
{
|
||||
result = osaudio_enumerate_nolock(count, info, pContext);
|
||||
}
|
||||
ma_mutex_unlock(&osaudio_g_context_lock);
|
||||
|
||||
/* We're done. We can now return. */
|
||||
osaudio_unref_context();
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
void osaudio_config_init(osaudio_config_t* config, osaudio_direction_t direction)
|
||||
{
|
||||
if (config == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
memset(config, 0, sizeof(*config));
|
||||
config->direction = direction;
|
||||
}
|
||||
|
||||
|
||||
static void osaudio_data_callback_playback(osaudio_t audio, void* pOutput, ma_uint32 frameCount)
|
||||
{
|
||||
/*
|
||||
If there's content in the buffer, read from it and release the semaphore. There needs to be a whole frameCount chunk
|
||||
in the buffer so we can keep everything in nice clean chunks. When we read from the buffer, we release a semaphore
|
||||
which will allow the main thread to write more data to the buffer.
|
||||
*/
|
||||
ma_uint32 framesToRead;
|
||||
ma_uint32 framesProcessed;
|
||||
void* pBuffer;
|
||||
|
||||
framesToRead = ma_pcm_rb_available_read(&audio->buffer);
|
||||
if (framesToRead > frameCount) {
|
||||
framesToRead = frameCount;
|
||||
}
|
||||
|
||||
framesProcessed = framesToRead;
|
||||
|
||||
/* For robustness we should run this in a loop in case the buffer wraps around. */
|
||||
while (frameCount > 0) {
|
||||
framesToRead = frameCount;
|
||||
|
||||
ma_pcm_rb_acquire_read(&audio->buffer, &framesToRead, &pBuffer);
|
||||
if (framesToRead == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
memcpy(pOutput, pBuffer, framesToRead * ma_get_bytes_per_frame(audio->device.playback.format, audio->device.playback.channels));
|
||||
ma_pcm_rb_commit_read(&audio->buffer, framesToRead);
|
||||
|
||||
frameCount -= framesToRead;
|
||||
pOutput = ((unsigned char*)pOutput) + (framesToRead * ma_get_bytes_per_frame(audio->device.playback.format, audio->device.playback.channels));
|
||||
}
|
||||
|
||||
/* Make sure we release the semaphore if we ended up reading anything. */
|
||||
if (framesProcessed > 0) {
|
||||
ma_semaphore_release(&audio->bufferSemaphore);
|
||||
}
|
||||
|
||||
if (frameCount > 0) {
|
||||
/* Underrun. Pad with silence. */
|
||||
ma_silence_pcm_frames(pOutput, frameCount, audio->device.playback.format, audio->device.playback.channels);
|
||||
ma_atomic_bool32_set(&audio->xrunDetected, MA_TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
static void osaudio_data_callback_capture(osaudio_t audio, const void* pInput, ma_uint32 frameCount)
|
||||
{
|
||||
/* If there's space in the buffer, write to it and release the semaphore. The semaphore is only released on full-chunk boundaries. */
|
||||
ma_uint32 framesToWrite;
|
||||
ma_uint32 framesProcessed;
|
||||
void* pBuffer;
|
||||
|
||||
framesToWrite = ma_pcm_rb_available_write(&audio->buffer);
|
||||
if (framesToWrite > frameCount) {
|
||||
framesToWrite = frameCount;
|
||||
}
|
||||
|
||||
framesProcessed = framesToWrite;
|
||||
|
||||
while (frameCount > 0) {
|
||||
framesToWrite = frameCount;
|
||||
|
||||
ma_pcm_rb_acquire_write(&audio->buffer, &framesToWrite, &pBuffer);
|
||||
if (framesToWrite == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
memcpy(pBuffer, pInput, framesToWrite * ma_get_bytes_per_frame(audio->device.capture.format, audio->device.capture.channels));
|
||||
ma_pcm_rb_commit_write(&audio->buffer, framesToWrite);
|
||||
|
||||
frameCount -= framesToWrite;
|
||||
pInput = ((unsigned char*)pInput) + (framesToWrite * ma_get_bytes_per_frame(audio->device.capture.format, audio->device.capture.channels));
|
||||
}
|
||||
|
||||
/* Make sure we release the semaphore if we ended up reading anything. */
|
||||
if (framesProcessed > 0) {
|
||||
ma_semaphore_release(&audio->bufferSemaphore);
|
||||
}
|
||||
|
||||
if (frameCount > 0) {
|
||||
/* Overrun. Not enough room to move our input data into the buffer. */
|
||||
ma_atomic_bool32_set(&audio->xrunDetected, MA_TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
static void osaudio_nofication_callback(const ma_device_notification* pNotification)
|
||||
{
|
||||
osaudio_t audio = (osaudio_t)pNotification->pDevice->pUserData;
|
||||
|
||||
if (audio->config.notification != NULL) {
|
||||
osaudio_notification_t notification;
|
||||
|
||||
switch (pNotification->type)
|
||||
{
|
||||
case ma_device_notification_type_started:
|
||||
{
|
||||
notification.type = OSAUDIO_NOTIFICATION_STARTED;
|
||||
} break;
|
||||
case ma_device_notification_type_stopped:
|
||||
{
|
||||
notification.type = OSAUDIO_NOTIFICATION_STOPPED;
|
||||
} break;
|
||||
case ma_device_notification_type_rerouted:
|
||||
{
|
||||
notification.type = OSAUDIO_NOTIFICATION_REROUTED;
|
||||
} break;
|
||||
case ma_device_notification_type_interruption_began:
|
||||
{
|
||||
notification.type = OSAUDIO_NOTIFICATION_INTERRUPTION_BEGIN;
|
||||
} break;
|
||||
case ma_device_notification_type_interruption_ended:
|
||||
{
|
||||
notification.type = OSAUDIO_NOTIFICATION_INTERRUPTION_END;
|
||||
} break;
|
||||
}
|
||||
|
||||
audio->config.notification(audio->config.user_data, ¬ification);
|
||||
}
|
||||
}
|
||||
|
||||
static void osaudio_data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount)
|
||||
{
|
||||
osaudio_t audio = (osaudio_t)pDevice->pUserData;
|
||||
|
||||
if (audio->info.direction == OSAUDIO_OUTPUT) {
|
||||
osaudio_data_callback_playback(audio, pOutput, frameCount);
|
||||
} else {
|
||||
osaudio_data_callback_capture(audio, pInput, frameCount);
|
||||
}
|
||||
}
|
||||
|
||||
osaudio_result_t osaudio_open(osaudio_t* audio, osaudio_config_t* config)
|
||||
{
|
||||
osaudio_result_t result;
|
||||
ma_context* pContext = NULL;
|
||||
ma_device_config deviceConfig;
|
||||
ma_device_info deviceInfo;
|
||||
int periodCount = 2;
|
||||
unsigned int iChannel;
|
||||
|
||||
if (audio != NULL) {
|
||||
*audio = NULL; /* Safety. */
|
||||
}
|
||||
|
||||
if (audio == NULL || config == NULL) {
|
||||
return OSAUDIO_INVALID_ARGS;
|
||||
}
|
||||
|
||||
pContext = osaudio_ref_context(); /* Will be unreferenced in osaudio_close(). */
|
||||
if (pContext == NULL) {
|
||||
return OSAUDIO_ERROR;
|
||||
}
|
||||
|
||||
*audio = (osaudio_t)calloc(1, sizeof(**audio));
|
||||
if (*audio == NULL) {
|
||||
osaudio_unref_context();
|
||||
return OSAUDIO_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
if (config->direction == OSAUDIO_OUTPUT) {
|
||||
deviceConfig = ma_device_config_init(ma_device_type_playback);
|
||||
deviceConfig.playback.format = osaudio_format_to_miniaudio(config->format);
|
||||
deviceConfig.playback.channels = (ma_uint32)config->channels;
|
||||
|
||||
if (config->channel_map[0] != OSAUDIO_CHANNEL_NONE) {
|
||||
for (iChannel = 0; iChannel < config->channels; iChannel += 1) {
|
||||
deviceConfig.playback.pChannelMap[iChannel] = osaudio_channel_to_miniaudio(config->channel_map[iChannel]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
deviceConfig = ma_device_config_init(ma_device_type_capture);
|
||||
deviceConfig.capture.format = osaudio_format_to_miniaudio(config->format);
|
||||
deviceConfig.capture.channels = (ma_uint32)config->channels;
|
||||
|
||||
if (config->channel_map[0] != OSAUDIO_CHANNEL_NONE) {
|
||||
for (iChannel = 0; iChannel < config->channels; iChannel += 1) {
|
||||
deviceConfig.capture.pChannelMap[iChannel] = osaudio_channel_to_miniaudio(config->channel_map[iChannel]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deviceConfig.sampleRate = (ma_uint32)config->rate;
|
||||
|
||||
/* If the buffer size is 0, we'll default to 10ms. */
|
||||
deviceConfig.periodSizeInFrames = (ma_uint32)config->buffer_size;
|
||||
if (deviceConfig.periodSizeInFrames == 0) {
|
||||
deviceConfig.periodSizeInMilliseconds = 10;
|
||||
}
|
||||
|
||||
deviceConfig.dataCallback = osaudio_data_callback;
|
||||
deviceConfig.pUserData = *audio;
|
||||
|
||||
if ((config->flags & OSAUDIO_FLAG_NO_REROUTING) != 0) {
|
||||
deviceConfig.wasapi.noAutoStreamRouting = MA_TRUE;
|
||||
}
|
||||
|
||||
if (config->notification != NULL) {
|
||||
deviceConfig.notificationCallback = osaudio_nofication_callback;
|
||||
}
|
||||
|
||||
result = osaudio_result_from_miniaudio(ma_device_init(pContext, &deviceConfig, &((*audio)->device)));
|
||||
if (result != OSAUDIO_SUCCESS) {
|
||||
free(*audio);
|
||||
osaudio_unref_context();
|
||||
return result;
|
||||
}
|
||||
|
||||
/* The input config needs to be updated with actual values. */
|
||||
if (config->direction == OSAUDIO_OUTPUT) {
|
||||
config->format = osaudio_format_from_miniaudio((*audio)->device.playback.format);
|
||||
config->channels = (unsigned int)(*audio)->device.playback.channels;
|
||||
|
||||
for (iChannel = 0; iChannel < config->channels; iChannel += 1) {
|
||||
config->channel_map[iChannel] = osaudio_channel_from_miniaudio((*audio)->device.playback.channelMap[iChannel]);
|
||||
}
|
||||
} else {
|
||||
config->format = osaudio_format_from_miniaudio((*audio)->device.capture.format);
|
||||
config->channels = (unsigned int)(*audio)->device.capture.channels;
|
||||
|
||||
for (iChannel = 0; iChannel < config->channels; iChannel += 1) {
|
||||
config->channel_map[iChannel] = osaudio_channel_from_miniaudio((*audio)->device.capture.channelMap[iChannel]);
|
||||
}
|
||||
}
|
||||
|
||||
config->rate = (unsigned int)(*audio)->device.sampleRate;
|
||||
|
||||
if (deviceConfig.periodSizeInFrames == 0) {
|
||||
if (config->direction == OSAUDIO_OUTPUT) {
|
||||
config->buffer_size = (int)(*audio)->device.playback.internalPeriodSizeInFrames;
|
||||
} else {
|
||||
config->buffer_size = (int)(*audio)->device.capture.internalPeriodSizeInFrames;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* The device object needs to have its local info built. We can get the ID and name from miniaudio. */
|
||||
result = osaudio_result_from_miniaudio(ma_device_get_info(&(*audio)->device, (*audio)->device.type, &deviceInfo));
|
||||
if (result == MA_SUCCESS) {
|
||||
memcpy((*audio)->info.id.data, &deviceInfo.id, sizeof((*audio)->info.id.data));
|
||||
memcpy((*audio)->info.name, deviceInfo.name, sizeof((*audio)->info.name));
|
||||
}
|
||||
|
||||
(*audio)->info.direction = config->direction;
|
||||
(*audio)->info.config_count = 1;
|
||||
(*audio)->info.configs = &(*audio)->config;
|
||||
(*audio)->config = *config;
|
||||
(*audio)->config.device_id = &(*audio)->info.id;
|
||||
|
||||
|
||||
/* We need a ring buffer. */
|
||||
result = osaudio_result_from_miniaudio(ma_pcm_rb_init(osaudio_format_to_miniaudio(config->format), (ma_uint32)config->channels, (ma_uint32)config->buffer_size * periodCount, NULL, NULL, &(*audio)->buffer));
|
||||
if (result != OSAUDIO_SUCCESS) {
|
||||
ma_device_uninit(&(*audio)->device);
|
||||
free(*audio);
|
||||
osaudio_unref_context();
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Now we need a semaphore to control access to the ring buffer to block read/write when necessary. */
|
||||
result = osaudio_result_from_miniaudio(ma_semaphore_init((config->direction == OSAUDIO_OUTPUT) ? periodCount : 0, &(*audio)->bufferSemaphore));
|
||||
if (result != OSAUDIO_SUCCESS) {
|
||||
ma_pcm_rb_uninit(&(*audio)->buffer);
|
||||
ma_device_uninit(&(*audio)->device);
|
||||
free(*audio);
|
||||
osaudio_unref_context();
|
||||
return result;
|
||||
}
|
||||
|
||||
return OSAUDIO_SUCCESS;
|
||||
}
|
||||
|
||||
osaudio_result_t osaudio_close(osaudio_t audio)
|
||||
{
|
||||
if (audio == NULL) {
|
||||
return OSAUDIO_INVALID_ARGS;
|
||||
}
|
||||
|
||||
ma_device_uninit(&audio->device);
|
||||
osaudio_unref_context();
|
||||
|
||||
return OSAUDIO_SUCCESS;
|
||||
}
|
||||
|
||||
static void osaudio_activate(osaudio_t audio)
|
||||
{
|
||||
ma_spinlock_lock(&audio->activateLock);
|
||||
{
|
||||
if (ma_atomic_bool32_get(&audio->isActive) == MA_FALSE) {
|
||||
ma_atomic_bool32_set(&audio->isActive, MA_TRUE);
|
||||
|
||||
/* If we need to flush, do so now before starting the device. */
|
||||
if (ma_atomic_bool32_get(&audio->isFlushed) == MA_TRUE) {
|
||||
ma_pcm_rb_reset(&audio->buffer);
|
||||
ma_atomic_bool32_set(&audio->isFlushed, MA_FALSE);
|
||||
}
|
||||
|
||||
/* If we're not paused, start the device. */
|
||||
if (ma_atomic_bool32_get(&audio->isPaused) == MA_FALSE) {
|
||||
ma_device_start(&audio->device);
|
||||
}
|
||||
}
|
||||
}
|
||||
ma_spinlock_unlock(&audio->activateLock);
|
||||
}
|
||||
|
||||
osaudio_result_t osaudio_write(osaudio_t audio, const void* data, unsigned int frame_count)
|
||||
{
|
||||
if (audio == NULL) {
|
||||
return OSAUDIO_INVALID_ARGS;
|
||||
}
|
||||
|
||||
ma_mutex_lock(&audio->drainLock);
|
||||
{
|
||||
/* Don't return until everything has been written. */
|
||||
while (frame_count > 0) {
|
||||
ma_uint32 framesToWrite = frame_count;
|
||||
ma_uint32 framesAvailableInBuffer;
|
||||
|
||||
/* There should be enough data available in the buffer now, but check anyway. */
|
||||
framesAvailableInBuffer = ma_pcm_rb_available_write(&audio->buffer);
|
||||
if (framesAvailableInBuffer > 0) {
|
||||
void* pBuffer;
|
||||
|
||||
if (framesToWrite > framesAvailableInBuffer) {
|
||||
framesToWrite = framesAvailableInBuffer;
|
||||
}
|
||||
|
||||
ma_pcm_rb_acquire_write(&audio->buffer, &framesToWrite, &pBuffer);
|
||||
{
|
||||
ma_copy_pcm_frames(pBuffer, data, framesToWrite, audio->device.playback.format, audio->device.playback.channels);
|
||||
}
|
||||
ma_pcm_rb_commit_write(&audio->buffer, framesToWrite);
|
||||
|
||||
frame_count -= (unsigned int)framesToWrite;
|
||||
data = (const void*)((const unsigned char*)data + (framesToWrite * ma_get_bytes_per_frame(audio->device.playback.format, audio->device.playback.channels)));
|
||||
|
||||
if (framesToWrite > 0) {
|
||||
osaudio_activate(audio);
|
||||
}
|
||||
} else {
|
||||
/* If we get here it means there's not enough data available in the buffer. We need to wait for more. */
|
||||
ma_semaphore_wait(&audio->bufferSemaphore);
|
||||
|
||||
/* If we're not active it probably means we've flushed. This write needs to be aborted. */
|
||||
if (ma_atomic_bool32_get(&audio->isActive) == MA_FALSE) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ma_mutex_unlock(&audio->drainLock);
|
||||
|
||||
if ((audio->config.flags & OSAUDIO_FLAG_REPORT_XRUN) != 0) {
|
||||
if (ma_atomic_bool32_get(&audio->xrunDetected)) {
|
||||
ma_atomic_bool32_set(&audio->xrunDetected, MA_FALSE);
|
||||
return OSAUDIO_XRUN;
|
||||
}
|
||||
}
|
||||
|
||||
return OSAUDIO_SUCCESS;
|
||||
}
|
||||
|
||||
osaudio_result_t osaudio_read(osaudio_t audio, void* data, unsigned int frame_count)
|
||||
{
|
||||
if (audio == NULL) {
|
||||
return OSAUDIO_INVALID_ARGS;
|
||||
}
|
||||
|
||||
ma_mutex_lock(&audio->drainLock);
|
||||
{
|
||||
while (frame_count > 0) {
|
||||
ma_uint32 framesToRead = frame_count;
|
||||
ma_uint32 framesAvailableInBuffer;
|
||||
|
||||
/* There should be enough data available in the buffer now, but check anyway. */
|
||||
framesAvailableInBuffer = ma_pcm_rb_available_read(&audio->buffer);
|
||||
if (framesAvailableInBuffer > 0) {
|
||||
void* pBuffer;
|
||||
|
||||
if (framesToRead > framesAvailableInBuffer) {
|
||||
framesToRead = framesAvailableInBuffer;
|
||||
}
|
||||
|
||||
ma_pcm_rb_acquire_read(&audio->buffer, &framesToRead, &pBuffer);
|
||||
{
|
||||
ma_copy_pcm_frames(data, pBuffer, framesToRead, audio->device.capture.format, audio->device.capture.channels);
|
||||
}
|
||||
ma_pcm_rb_commit_read(&audio->buffer, framesToRead);
|
||||
|
||||
frame_count -= (unsigned int)framesToRead;
|
||||
data = (void*)((unsigned char*)data + (framesToRead * ma_get_bytes_per_frame(audio->device.capture.format, audio->device.capture.channels)));
|
||||
} else {
|
||||
/* Activate the device from the get go or else we'll never end up capturing anything. */
|
||||
osaudio_activate(audio);
|
||||
|
||||
/* If we get here it means there's not enough data available in the buffer. We need to wait for more. */
|
||||
ma_semaphore_wait(&audio->bufferSemaphore);
|
||||
|
||||
/* If we're not active it probably means we've flushed. This read needs to be aborted. */
|
||||
if (ma_atomic_bool32_get(&audio->isActive) == MA_FALSE) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ma_mutex_unlock(&audio->drainLock);
|
||||
|
||||
if ((audio->config.flags & OSAUDIO_FLAG_REPORT_XRUN) != 0) {
|
||||
if (ma_atomic_bool32_get(&audio->xrunDetected)) {
|
||||
ma_atomic_bool32_set(&audio->xrunDetected, MA_FALSE);
|
||||
return OSAUDIO_XRUN;
|
||||
}
|
||||
}
|
||||
|
||||
return OSAUDIO_SUCCESS;
|
||||
}
|
||||
|
||||
osaudio_result_t osaudio_drain(osaudio_t audio)
|
||||
{
|
||||
if (audio == NULL) {
|
||||
return OSAUDIO_INVALID_ARGS;
|
||||
}
|
||||
|
||||
/* This cannot be called while the device is in a paused state. */
|
||||
if (ma_atomic_bool32_get(&audio->isPaused)) {
|
||||
return OSAUDIO_DEVICE_STOPPED;
|
||||
}
|
||||
|
||||
/* For capture we want to stop the device immediately or else we won't ever drain the buffer because miniaudio will be constantly filling it. */
|
||||
if (audio->info.direction == OSAUDIO_INPUT) {
|
||||
ma_device_stop(&audio->device);
|
||||
}
|
||||
|
||||
/*
|
||||
Mark the device as inactive *before* releasing the semaphore. When read/write completes waiting
|
||||
on the semaphore, they'll check this flag and abort.
|
||||
*/
|
||||
ma_atomic_bool32_set(&audio->isActive, MA_FALSE);
|
||||
|
||||
/*
|
||||
Again in capture mode, we need to release the semaphore before waiting for the drain lock because
|
||||
there's a chance read() will be waiting on the semaphore and will need to be woken up in order for
|
||||
it to be given to chance to return.
|
||||
*/
|
||||
if (audio->info.direction == OSAUDIO_INPUT) {
|
||||
ma_semaphore_release(&audio->bufferSemaphore);
|
||||
}
|
||||
|
||||
/* Now we need to wait for any pending reads or writes to complete. */
|
||||
ma_mutex_lock(&audio->drainLock);
|
||||
{
|
||||
/* No processing should be happening on the buffer at this point. Wait for miniaudio to consume the buffer. */
|
||||
while (ma_pcm_rb_available_read(&audio->buffer) > 0) {
|
||||
ma_sleep(1);
|
||||
}
|
||||
|
||||
/*
|
||||
At this point the buffer should be empty, and we shouldn't be in any read or write calls. If
|
||||
it's a playback device, we'll want to stop the device. There's no need to release the semaphore.
|
||||
*/
|
||||
if (audio->info.direction == OSAUDIO_OUTPUT) {
|
||||
ma_device_stop(&audio->device);
|
||||
}
|
||||
}
|
||||
ma_mutex_unlock(&audio->drainLock);
|
||||
|
||||
return OSAUDIO_SUCCESS;
|
||||
}
|
||||
|
||||
osaudio_result_t osaudio_flush(osaudio_t audio)
|
||||
{
|
||||
if (audio == NULL) {
|
||||
return OSAUDIO_INVALID_ARGS;
|
||||
}
|
||||
|
||||
/*
|
||||
First stop the device. This ensures the miniaudio background thread doesn't try modifying the
|
||||
buffer from under us while we're trying to flush it.
|
||||
*/
|
||||
ma_device_stop(&audio->device);
|
||||
|
||||
/*
|
||||
Mark the device as inactive *before* releasing the semaphore. When read/write completes waiting
|
||||
on the semaphore, they'll check this flag and abort.
|
||||
*/
|
||||
ma_atomic_bool32_set(&audio->isActive, MA_FALSE);
|
||||
|
||||
/*
|
||||
Release the semaphore after marking the device as inactive. This needs to be released in order
|
||||
to wakeup osaudio_read() and osaudio_write().
|
||||
*/
|
||||
ma_semaphore_release(&audio->bufferSemaphore);
|
||||
|
||||
/*
|
||||
The buffer should only be modified by osaudio_read() or osaudio_write(), or the miniaudio
|
||||
background thread. Therefore, we don't actually clear the buffer here. Instead we'll clear it
|
||||
in osaudio_activate(), depending on whether or not the below flag is set.
|
||||
*/
|
||||
ma_atomic_bool32_set(&audio->isFlushed, MA_TRUE);
|
||||
|
||||
return OSAUDIO_SUCCESS;
|
||||
}
|
||||
|
||||
osaudio_result_t osaudio_pause(osaudio_t audio)
|
||||
{
|
||||
osaudio_result_t result = OSAUDIO_SUCCESS;
|
||||
|
||||
if (audio == NULL) {
|
||||
return OSAUDIO_INVALID_ARGS;
|
||||
}
|
||||
|
||||
ma_spinlock_lock(&audio->activateLock);
|
||||
{
|
||||
if (ma_atomic_bool32_get(&audio->isPaused) == MA_FALSE) {
|
||||
ma_atomic_bool32_set(&audio->isPaused, MA_TRUE);
|
||||
|
||||
/* No need to stop the device if it's not active. */
|
||||
if (ma_atomic_bool32_get(&audio->isActive)) {
|
||||
result = osaudio_result_from_miniaudio(ma_device_stop(&audio->device));
|
||||
}
|
||||
}
|
||||
}
|
||||
ma_spinlock_unlock(&audio->activateLock);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
osaudio_result_t osaudio_resume(osaudio_t audio)
|
||||
{
|
||||
osaudio_result_t result = OSAUDIO_SUCCESS;
|
||||
|
||||
if (audio == NULL) {
|
||||
return OSAUDIO_INVALID_ARGS;
|
||||
}
|
||||
|
||||
ma_spinlock_lock(&audio->activateLock);
|
||||
{
|
||||
if (ma_atomic_bool32_get(&audio->isPaused)) {
|
||||
ma_atomic_bool32_set(&audio->isPaused, MA_FALSE);
|
||||
|
||||
/* Don't start the device unless it's active. */
|
||||
if (ma_atomic_bool32_get(&audio->isActive)) {
|
||||
result = osaudio_result_from_miniaudio(ma_device_start(&audio->device));
|
||||
}
|
||||
}
|
||||
}
|
||||
ma_spinlock_unlock(&audio->activateLock);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
unsigned int osaudio_get_avail(osaudio_t audio)
|
||||
{
|
||||
if (audio == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (audio->info.direction == OSAUDIO_OUTPUT) {
|
||||
return ma_pcm_rb_available_write(&audio->buffer);
|
||||
} else {
|
||||
return ma_pcm_rb_available_read(&audio->buffer);
|
||||
}
|
||||
}
|
||||
|
||||
const osaudio_info_t* osaudio_get_info(osaudio_t audio)
|
||||
{
|
||||
if (audio == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return &audio->info;
|
||||
}
|
||||
|
||||
#endif /* osaudio_miniaudio_c */
|
||||
@@ -0,0 +1,196 @@
|
||||
#include "../osaudio.h"
|
||||
|
||||
/* This example uses miniaudio for decoding audio files. */
|
||||
#define MINIAUDIO_IMPLEMENTATION
|
||||
#include "../../../miniaudio.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#define MODE_PLAYBACK 0
|
||||
#define MODE_CAPTURE 1
|
||||
#define MODE_DUPLEX 2
|
||||
|
||||
void enumerate_devices()
|
||||
{
|
||||
int result;
|
||||
unsigned int iDevice;
|
||||
unsigned int count;
|
||||
osaudio_info_t* pDeviceInfos;
|
||||
|
||||
result = osaudio_enumerate(&count, &pDeviceInfos);
|
||||
if (result != OSAUDIO_SUCCESS) {
|
||||
printf("Failed to enumerate audio devices.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
for (iDevice = 0; iDevice < count; iDevice += 1) {
|
||||
printf("(%s) %s\n", (pDeviceInfos[iDevice].direction == OSAUDIO_OUTPUT) ? "Playback" : "Capture", pDeviceInfos[iDevice].name);
|
||||
}
|
||||
|
||||
free(pDeviceInfos);
|
||||
}
|
||||
|
||||
osaudio_t open_device(int direction)
|
||||
{
|
||||
int result;
|
||||
osaudio_t audio;
|
||||
osaudio_config_t config;
|
||||
|
||||
osaudio_config_init(&config, direction);
|
||||
config.format = OSAUDIO_FORMAT_F32;
|
||||
config.channels = 2;
|
||||
config.rate = 48000;
|
||||
config.flags = OSAUDIO_FLAG_REPORT_XRUN;
|
||||
|
||||
result = osaudio_open(&audio, &config);
|
||||
if (result != OSAUDIO_SUCCESS) {
|
||||
printf("Failed to open audio device.\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return audio;
|
||||
}
|
||||
|
||||
void do_playback(int argc, char** argv)
|
||||
{
|
||||
int result;
|
||||
osaudio_t audio;
|
||||
const osaudio_config_t* config;
|
||||
const char* pFilePath = NULL;
|
||||
ma_result resultMA;
|
||||
ma_decoder_config decoderConfig;
|
||||
ma_decoder decoder;
|
||||
|
||||
audio = open_device(OSAUDIO_OUTPUT);
|
||||
if (audio == NULL) {
|
||||
printf("Failed to open audio device.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
config = &osaudio_get_info(audio)->configs[0];
|
||||
|
||||
/* We want to always use f32. */
|
||||
if (config->format == OSAUDIO_FORMAT_F32) {
|
||||
if (argc > 1) {
|
||||
pFilePath = argv[1];
|
||||
|
||||
decoderConfig = ma_decoder_config_init(ma_format_f32, (ma_uint32)config->channels, (ma_uint32)config->rate);
|
||||
|
||||
resultMA = ma_decoder_init_file(pFilePath, &decoderConfig, &decoder);
|
||||
if (resultMA == MA_SUCCESS) {
|
||||
/* Now just keep looping over each sample until we get to the end. */
|
||||
for (;;) {
|
||||
float frames[1024];
|
||||
ma_uint64 frameCount;
|
||||
|
||||
resultMA = ma_decoder_read_pcm_frames(&decoder, frames, ma_countof(frames) / config->channels, &frameCount);
|
||||
if (resultMA != MA_SUCCESS) {
|
||||
break;
|
||||
}
|
||||
|
||||
result = osaudio_write(audio, frames, (unsigned int)frameCount); /* Safe cast. */
|
||||
if (result != OSAUDIO_SUCCESS && result != OSAUDIO_XRUN) {
|
||||
printf("Error writing to audio device.");
|
||||
break;
|
||||
}
|
||||
|
||||
if (result == OSAUDIO_XRUN) {
|
||||
printf("WARNING: An xrun occurred while writing to the playback device.\n");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
printf("Failed to open file: %s\n", pFilePath);
|
||||
}
|
||||
} else {
|
||||
printf("No input file.\n");
|
||||
}
|
||||
} else {
|
||||
printf("Unsupported device format.\n");
|
||||
}
|
||||
|
||||
/* Getting here means we're done and we can tear down. */
|
||||
osaudio_close(audio);
|
||||
}
|
||||
|
||||
void do_duplex()
|
||||
{
|
||||
int result;
|
||||
osaudio_t capture;
|
||||
osaudio_t playback;
|
||||
|
||||
capture = open_device(OSAUDIO_INPUT);
|
||||
if (capture == NULL) {
|
||||
printf("Failed to open capture device.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
playback = open_device(OSAUDIO_OUTPUT);
|
||||
if (playback == NULL) {
|
||||
osaudio_close(capture);
|
||||
printf("Failed to open playback device.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
float frames[1024];
|
||||
unsigned int frameCount;
|
||||
|
||||
frameCount = ma_countof(frames) / osaudio_get_info(capture)->configs[0].channels;
|
||||
|
||||
/* Capture. */
|
||||
result = osaudio_read(capture, frames, frameCount);
|
||||
if (result != OSAUDIO_SUCCESS && result != OSAUDIO_XRUN) {
|
||||
printf("Error reading from capture device.\n");
|
||||
break;
|
||||
}
|
||||
|
||||
if (result == OSAUDIO_XRUN) {
|
||||
printf("WARNING: An xrun occurred while reading from the capture device.\n");
|
||||
}
|
||||
|
||||
|
||||
/* Playback. */
|
||||
result = osaudio_write(playback, frames, frameCount);
|
||||
if (result != OSAUDIO_SUCCESS && result != OSAUDIO_XRUN) {
|
||||
printf("Error writing to playback device.\n");
|
||||
break;
|
||||
}
|
||||
|
||||
if (result == OSAUDIO_XRUN) {
|
||||
printf("WARNING: An xrun occurred while writing to the playback device.\n");
|
||||
}
|
||||
}
|
||||
|
||||
osaudio_close(capture);
|
||||
osaudio_close(playback);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
int mode = MODE_PLAYBACK;
|
||||
int iarg;
|
||||
|
||||
enumerate_devices();
|
||||
|
||||
for (iarg = 0; iarg < argc; iarg += 1) {
|
||||
if (strcmp(argv[iarg], "capture") == 0) {
|
||||
mode = MODE_CAPTURE;
|
||||
} else if (strcmp(argv[iarg], "duplex") == 0) {
|
||||
mode = MODE_DUPLEX;
|
||||
}
|
||||
}
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case MODE_PLAYBACK: do_playback(argc, argv); break;
|
||||
case MODE_CAPTURE: break;
|
||||
case MODE_DUPLEX: do_duplex(); break;
|
||||
}
|
||||
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
#include "../osaudio.h"
|
||||
#include "../../decoders/litewav/litewav.c"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h> /* free() */
|
||||
|
||||
#if defined(__MSDOS__) || defined(__DOS__)
|
||||
#include <dos.h>
|
||||
#define OSAUDIO_DOS
|
||||
#endif
|
||||
|
||||
const char* format_to_string(osaudio_format_t format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case OSAUDIO_FORMAT_F32: return "F32";
|
||||
case OSAUDIO_FORMAT_U8: return "U8";
|
||||
case OSAUDIO_FORMAT_S16: return "S16";
|
||||
case OSAUDIO_FORMAT_S24: return "S24";
|
||||
case OSAUDIO_FORMAT_S32: return "S32";
|
||||
default: return "Unknown Format";
|
||||
}
|
||||
}
|
||||
|
||||
void enumerate_devices()
|
||||
{
|
||||
osaudio_result_t result;
|
||||
osaudio_info_t* pDeviceInfos;
|
||||
unsigned int deviceCount;
|
||||
unsigned int iDevice;
|
||||
|
||||
result = osaudio_enumerate(&deviceCount, &pDeviceInfos);
|
||||
if (result != OSAUDIO_SUCCESS) {
|
||||
printf("Failed to enumerate devices.");
|
||||
return;
|
||||
}
|
||||
|
||||
for (iDevice = 0; iDevice < deviceCount; iDevice += 1) {
|
||||
osaudio_info_t* pDeviceInfo = &pDeviceInfos[iDevice];
|
||||
|
||||
printf("Device %u: [%s] %s\n", iDevice, (pDeviceInfo->direction == OSAUDIO_OUTPUT) ? "Playback" : "Capture", pDeviceInfo->name);
|
||||
|
||||
#if 0
|
||||
{
|
||||
unsigned int iFormat;
|
||||
|
||||
printf(" Native Formats\n");
|
||||
for (iFormat = 0; iFormat < pDeviceInfo->config_count; iFormat += 1) {
|
||||
osaudio_config_t* pConfig = &pDeviceInfo->configs[iFormat];
|
||||
printf(" %s %uHz %u channels\n", format_to_string(pConfig->format), pConfig->rate, pConfig->channels);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
free(pDeviceInfos);
|
||||
}
|
||||
|
||||
extern int g_TESTING;
|
||||
|
||||
#include <string.h>
|
||||
|
||||
/* Sine wave generation. */
|
||||
#include <math.h>
|
||||
|
||||
#if defined(OSAUDIO_DOS)
|
||||
/* For farmalloc(). */
|
||||
static void OSAUDIO_FAR* far_malloc(unsigned int sz)
|
||||
{
|
||||
unsigned int segment;
|
||||
unsigned int err;
|
||||
|
||||
err = _dos_allocmem(sz >> 4, &segment);
|
||||
if (err == 0) {
|
||||
return MK_FP(segment, 0);
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
#else
|
||||
#define far_malloc malloc
|
||||
#endif
|
||||
|
||||
static char OSAUDIO_FAR* gen_sine_u8(unsigned long frameCount, unsigned int channels, unsigned int sampleRate)
|
||||
{
|
||||
float phase = 0;
|
||||
float phaseIncrement = 2 * 3.14159265f * 220.0f / 44100.0f;
|
||||
unsigned long iFrame;
|
||||
char OSAUDIO_FAR* pData;
|
||||
char OSAUDIO_FAR* pRunningData;
|
||||
|
||||
pData = (char OSAUDIO_FAR*)far_malloc(frameCount * channels);
|
||||
if (pData == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pRunningData = pData;
|
||||
|
||||
for (iFrame = 0; iFrame < frameCount; iFrame += 1) {
|
||||
unsigned int iChannel;
|
||||
float sample = (float)sin(phase) * 0.2f;
|
||||
sample = (sample + 1.0f) * 127.5f;
|
||||
|
||||
for (iChannel = 0; iChannel < channels; iChannel += 1) {
|
||||
pRunningData[iChannel] = (unsigned char)sample;
|
||||
}
|
||||
|
||||
pRunningData += channels;
|
||||
phase += phaseIncrement;
|
||||
}
|
||||
|
||||
return pData;
|
||||
}
|
||||
|
||||
static short OSAUDIO_FAR* gen_sine_s16(unsigned long frameCount, unsigned int channels, unsigned int sampleRate)
|
||||
{
|
||||
float phase = 0;
|
||||
float phaseIncrement = 2 * 3.14159265f * 220.0f / 44100.0f;
|
||||
unsigned long iFrame;
|
||||
short OSAUDIO_FAR* pData;
|
||||
short OSAUDIO_FAR* pRunningData;
|
||||
|
||||
pData = (short OSAUDIO_FAR*)far_malloc(frameCount * channels * sizeof(short));
|
||||
if (pData == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pRunningData = pData;
|
||||
|
||||
for (iFrame = 0; iFrame < frameCount; iFrame += 1) {
|
||||
unsigned int iChannel;
|
||||
float sample = (float)sin(phase) * 0.2f;
|
||||
sample = sample * 32767.5f;
|
||||
|
||||
for (iChannel = 0; iChannel < channels; iChannel += 1) {
|
||||
pRunningData[iChannel] = (short)sample;
|
||||
}
|
||||
|
||||
pRunningData += channels;
|
||||
phase += phaseIncrement;
|
||||
}
|
||||
|
||||
return pData;
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//float sinePhase = 0;
|
||||
//float sinePhaseIncrement = 0;
|
||||
//float sineVolume = 0.2f;
|
||||
//
|
||||
//static void sine_init()
|
||||
//{
|
||||
// sinePhase = 0;
|
||||
// sinePhaseIncrement = 2 * 3.14159265f * 440.0f / 44100.0f;
|
||||
//}
|
||||
//
|
||||
//static void sine_u8(unsigned char* dst, unsigned int frameCount, unsigned int channels)
|
||||
//{
|
||||
// unsigned int iFrame;
|
||||
//
|
||||
// for (iFrame = 0; iFrame < frameCount; iFrame += 1) {
|
||||
// unsigned int iChannel;
|
||||
// float sample = (float)sin(sinePhase) * sineVolume;
|
||||
// sample = (sample + 1.0f) * 127.5f;
|
||||
//
|
||||
// for (iChannel = 0; iChannel < channels; iChannel += 1) {
|
||||
// dst[iChannel] = (unsigned char)sample;
|
||||
// }
|
||||
//
|
||||
// dst += channels;
|
||||
// sinePhase += sinePhaseIncrement;
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//
|
||||
//unsigned char data[4096];
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
osaudio_result_t result;
|
||||
osaudio_t audio;
|
||||
osaudio_config_t config;
|
||||
void OSAUDIO_FAR* pSineWave;
|
||||
unsigned long sineWaveFrameCount;
|
||||
unsigned long sineWaveCursor = 0;
|
||||
|
||||
enumerate_devices();
|
||||
|
||||
osaudio_config_init(&config, OSAUDIO_OUTPUT);
|
||||
config.format = OSAUDIO_FORMAT_S16;
|
||||
config.channels = 2;
|
||||
config.rate = 44100;
|
||||
|
||||
result = osaudio_open(&audio, &config);
|
||||
if (result != OSAUDIO_SUCCESS) {
|
||||
printf("Failed to initialize audio.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
printf("Device: %s (%s %uHz %u channels)\n", osaudio_get_info(audio)->name, format_to_string(config.format), config.rate, config.channels);
|
||||
|
||||
//printf("sizeof(void*) = %u\n", (unsigned int)sizeof(void far *));
|
||||
|
||||
/* 5 seconds. */
|
||||
sineWaveFrameCount = config.rate * 1;
|
||||
|
||||
if (config.format == OSAUDIO_FORMAT_U8) {
|
||||
pSineWave = gen_sine_u8(sineWaveFrameCount, config.channels, config.rate);
|
||||
} else {
|
||||
pSineWave = gen_sine_s16(sineWaveFrameCount, config.channels, config.rate);
|
||||
}
|
||||
|
||||
if (pSineWave == NULL) {
|
||||
printf("Failed to generate sine wave.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (config.format == OSAUDIO_FORMAT_U8) {
|
||||
/*unsigned int framesToSilence = config.rate;
|
||||
while (framesToSilence > 0) {
|
||||
unsigned int framesToWrite;
|
||||
char silence[256];
|
||||
memset(silence, 128, sizeof(silence));
|
||||
|
||||
framesToWrite = framesToSilence;
|
||||
if (framesToWrite > sizeof(silence) / config.channels) {
|
||||
framesToWrite = sizeof(silence) / config.channels;
|
||||
}
|
||||
|
||||
osaudio_write(audio, silence, framesToWrite);
|
||||
framesToSilence -= framesToWrite;
|
||||
}*/
|
||||
|
||||
while (sineWaveCursor < sineWaveFrameCount) {
|
||||
unsigned long framesToWrite = sineWaveFrameCount - sineWaveCursor;
|
||||
if (framesToWrite > 0xFFFF) {
|
||||
framesToWrite = 0xFFFF;
|
||||
}
|
||||
|
||||
//printf("Writing sine wave: %u\n", (unsigned int)framesToWrite);
|
||||
|
||||
osaudio_write(audio, (char OSAUDIO_FAR*)pSineWave + (sineWaveCursor * config.channels), (unsigned int)framesToWrite);
|
||||
sineWaveCursor += framesToWrite;
|
||||
|
||||
//printf("TRACE 0\n");
|
||||
//sine_u8(data, frameCount, config.channels);
|
||||
//printf("TRACE: %d\n", frameCount);
|
||||
//osaudio_write(audio, data, frameCount);
|
||||
//printf("DONE LOOP\n");
|
||||
}
|
||||
} else if (config.format == OSAUDIO_FORMAT_S16) {
|
||||
while (sineWaveCursor < sineWaveFrameCount) {
|
||||
unsigned long framesToWrite = sineWaveFrameCount - sineWaveCursor;
|
||||
if (framesToWrite > 0xFFFF) {
|
||||
framesToWrite = 0xFFFF;
|
||||
}
|
||||
|
||||
osaudio_write(audio, (short OSAUDIO_FAR*)pSineWave + (sineWaveCursor * config.channels), (unsigned int)framesToWrite);
|
||||
sineWaveCursor += framesToWrite;
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(OSAUDIO_DOS)
|
||||
printf("Processing...\n");
|
||||
for (;;) {
|
||||
/* Temporary. Just spinning here to ensure the program stays active. */
|
||||
//delay(1);
|
||||
if (g_TESTING > 0) {
|
||||
//printf("TESTING: %d\n", g_TESTING);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
printf("Shutting down... ");
|
||||
osaudio_close(audio);
|
||||
printf("Done.\n");
|
||||
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
#define MINIAUDIO_IMPLEMENTATION
|
||||
#include "miniaudio.h"
|
||||
@@ -0,0 +1,15 @@
|
||||
prefix=@CMAKE_INSTALL_PREFIX@
|
||||
exec_prefix=${prefix}
|
||||
includedir=@MINIAUDIO_PC_INCLUDEDIR@
|
||||
libdir=@MINIAUDIO_PC_LIBDIR@
|
||||
|
||||
Name: miniaudio
|
||||
Description: An audio playback and capture library.
|
||||
URL: https://miniaud.io/
|
||||
License: Unlicense OR MIT-0
|
||||
Version: @PROJECT_VERSION@
|
||||
|
||||
Requires.private: @MINIAUDIO_PC_REQUIRES_PRIVATE@
|
||||
Cflags: -I${includedir} @MINIAUDIO_PC_CFLAGS@
|
||||
Libs: -L${libdir} -lminiaudio
|
||||
Libs.private: @MINIAUDIO_PC_LIBS_PRIVATE@
|
||||
@@ -10,6 +10,8 @@ Output files will be placed in the "res/output" folder.
|
||||
|
||||
Emscripten
|
||||
----------
|
||||
On Linux, do `source ~/emsdk/emsdk_env.sh` before compiling.
|
||||
|
||||
On Windows, you need to move into the build and run emsdk_env.bat from a command prompt using an absolute
|
||||
path like "C:\emsdk\emsdk_env.bat". Note that PowerShell doesn't work for me for some reason. Example:
|
||||
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
set DJGPP=C:\DJGPP\DJGPP.ENV
|
||||
set PATH=C:\DJGPP\BIN;%PATH%
|
||||
@@ -0,0 +1,15 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/caches
|
||||
/.idea/libraries
|
||||
/.idea/modules.xml
|
||||
/.idea/workspace.xml
|
||||
/.idea/navEditor.xml
|
||||
/.idea/assetWizardSettings.xml
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
local.properties
|
||||
@@ -0,0 +1 @@
|
||||
/build
|
||||
@@ -0,0 +1,56 @@
|
||||
plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "io.miniaud.miniaudiotester"
|
||||
compileSdk = 35
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "io.miniaud.miniaudiotester"
|
||||
minSdk = 26
|
||||
targetSdk = 35
|
||||
versionCode = 1
|
||||
versionName = "1.0"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = false
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "11"
|
||||
}
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path = file("src/main/cpp/CMakeLists.txt")
|
||||
version = "3.22.1"
|
||||
}
|
||||
}
|
||||
buildFeatures {
|
||||
viewBinding = true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
implementation(libs.androidx.core.ktx)
|
||||
implementation(libs.androidx.appcompat)
|
||||
implementation(libs.material)
|
||||
implementation(libs.androidx.constraintlayout)
|
||||
testImplementation(libs.junit)
|
||||
androidTestImplementation(libs.androidx.junit)
|
||||
androidTestImplementation(libs.androidx.espresso.core)
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
@@ -0,0 +1,24 @@
|
||||
package io.miniaud.miniaudiotester
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("io.miniaud.miniaudiotester", appContext.packageName)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.MiniaudioTester"
|
||||
tools:targetApi="31">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,37 @@
|
||||
# For more information about using CMake with Android Studio, read the
|
||||
# documentation: https://d.android.com/studio/projects/add-native-code.html.
|
||||
# For more examples on how to use CMake, see https://github.com/android/ndk-samples.
|
||||
|
||||
# Sets the minimum CMake version required for this project.
|
||||
cmake_minimum_required(VERSION 3.22.1)
|
||||
|
||||
# Declares the project name. The project name can be accessed via ${ PROJECT_NAME},
|
||||
# Since this is the top level CMakeLists.txt, the project name is also accessible
|
||||
# with ${CMAKE_PROJECT_NAME} (both CMake variables are in-sync within the top level
|
||||
# build script scope).
|
||||
project("miniaudiotester")
|
||||
|
||||
# Creates and names a library, sets it as either STATIC
|
||||
# or SHARED, and provides the relative paths to its source code.
|
||||
# You can define multiple libraries, and CMake builds them for you.
|
||||
# Gradle automatically packages shared libraries with your APK.
|
||||
#
|
||||
# In this top level CMakeLists.txt, ${CMAKE_PROJECT_NAME} is used to define
|
||||
# the target library name; in the sub-module's CMakeLists.txt, ${PROJECT_NAME}
|
||||
# is preferred for the same purpose.
|
||||
#
|
||||
# In order to load a library into your app from Java/Kotlin, you must call
|
||||
# System.loadLibrary() and pass the name of the library defined here;
|
||||
# for GameActivity/NativeActivity derived applications, the same library name must be
|
||||
# used in the AndroidManifest.xml file.
|
||||
add_library(${CMAKE_PROJECT_NAME} SHARED
|
||||
# List C/C++ source files with relative paths to this CMakeLists.txt.
|
||||
native-lib.cpp)
|
||||
|
||||
# Specifies libraries CMake should link to your target library. You
|
||||
# can link libraries from various origins, such as libraries defined in this
|
||||
# build script, prebuilt third-party libraries, or Android system libraries.
|
||||
target_link_libraries(${CMAKE_PROJECT_NAME}
|
||||
# List libraries link to the target library
|
||||
android
|
||||
log)
|
||||
@@ -0,0 +1,173 @@
|
||||
#include <jni.h>
|
||||
#include <string>
|
||||
|
||||
#define MA_DEBUG_OUTPUT
|
||||
#include "../../../../../../../miniaudio.c" /* Android projects have very deep folder structures... */
|
||||
|
||||
typedef enum
|
||||
{
|
||||
BACKEND_AUTO,
|
||||
BACKEND_AAUDIO,
|
||||
BACKEND_OPENSL
|
||||
} backend_choice_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
ma_device device;
|
||||
ma_waveform waveform;
|
||||
bool hasDevice;
|
||||
bool hasError; /* Will be set to true if something went wrong. */
|
||||
std::string errorMessage; /* Will be an empty string if there is no error message. */
|
||||
} audio_state_t;
|
||||
|
||||
static void audio_state_set_error(audio_state_t* pAudioState, const char* pMessage)
|
||||
{
|
||||
assert(pAudioState != nullptr);
|
||||
|
||||
pAudioState->hasError = true;
|
||||
pAudioState->errorMessage = pMessage;
|
||||
}
|
||||
|
||||
|
||||
static void data_callback(ma_device* pDevice, void* pFramesOut, const void* pFramesIn, ma_uint32 frameCount)
|
||||
{
|
||||
auto* pAudioState = (audio_state_t*)pDevice->pUserData;
|
||||
assert(pAudioState != nullptr);
|
||||
|
||||
ma_waveform_read_pcm_frames(&pAudioState->waveform, pFramesOut, frameCount, nullptr);
|
||||
}
|
||||
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_io_miniaud_miniaudiotester_MainActivity_UninitializeAudio(JNIEnv *env, jobject, jlong audioState)
|
||||
{
|
||||
auto* pAudioState = (audio_state_t*)audioState;
|
||||
if (pAudioState == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (pAudioState->hasDevice) {
|
||||
ma_device_uninit(&pAudioState->device);
|
||||
ma_waveform_uninit(&pAudioState->waveform);
|
||||
pAudioState->hasDevice = false;
|
||||
}
|
||||
|
||||
pAudioState->hasError = false;
|
||||
pAudioState->errorMessage = "";
|
||||
|
||||
return (jlong)pAudioState;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_io_miniaud_miniaudiotester_MainActivity_PlayAudio(JNIEnv *env, jobject, jlong audioState, int backend)
|
||||
{
|
||||
auto* pAudioState = (audio_state_t*)audioState;
|
||||
ma_result result;
|
||||
|
||||
if (pAudioState == nullptr) {
|
||||
pAudioState = new audio_state_t;
|
||||
pAudioState->hasDevice = false;
|
||||
pAudioState->hasError = false;
|
||||
}
|
||||
|
||||
/* If we don't have a device, create one. */
|
||||
if (!pAudioState->hasDevice) {
|
||||
ma_context_config contextConfig = ma_context_config_init();
|
||||
ma_backend pBackends[1];
|
||||
size_t backendCount;
|
||||
|
||||
if (backend == BACKEND_AUTO) {
|
||||
backendCount = 0;
|
||||
} else {
|
||||
backendCount = 1;
|
||||
if (backend == BACKEND_AAUDIO) {
|
||||
pBackends[0] = ma_backend_aaudio;
|
||||
} else if (backend == BACKEND_OPENSL) {
|
||||
pBackends[0] = ma_backend_opensl;
|
||||
} else {
|
||||
backendCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
ma_device_config deviceConfig = ma_device_config_init(ma_device_type_playback);
|
||||
deviceConfig.dataCallback = data_callback;
|
||||
deviceConfig.pUserData = pAudioState;
|
||||
|
||||
result = ma_device_init_ex((backendCount == 0) ? nullptr : pBackends, backendCount, &contextConfig, &deviceConfig, &pAudioState->device);
|
||||
if (result != MA_SUCCESS) {
|
||||
audio_state_set_error(pAudioState, (std::string("Failed to initialize device. ") + ma_result_description(result)).c_str());
|
||||
pAudioState->hasDevice = false;
|
||||
}
|
||||
|
||||
/* Before starting the device we will need a waveform object. This should never fail to initialize. */
|
||||
ma_waveform_config waveformConfig = ma_waveform_config_init(pAudioState->device.playback.format, pAudioState->device.playback.channels, pAudioState->device.sampleRate, ma_waveform_type_sine, 0.2, 400);
|
||||
ma_waveform_init(&waveformConfig, &pAudioState->waveform);
|
||||
|
||||
pAudioState->hasDevice = true;
|
||||
}
|
||||
|
||||
/* At this point we should have a device. Start it. */
|
||||
result = ma_device_start(&pAudioState->device);
|
||||
if (result != MA_SUCCESS) {
|
||||
audio_state_set_error(pAudioState, (std::string("Failed to start device. ") + ma_result_description(result)).c_str());
|
||||
}
|
||||
|
||||
return (jlong)pAudioState;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_io_miniaud_miniaudiotester_MainActivity_PauseAudio(JNIEnv *env, jobject, jlong audioState)
|
||||
{
|
||||
auto* pAudioState = (audio_state_t*)audioState;
|
||||
if (pAudioState == nullptr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!pAudioState->hasError) {
|
||||
if (pAudioState->hasDevice) {
|
||||
ma_result result = ma_device_stop(&pAudioState->device);
|
||||
if (result != MA_SUCCESS) {
|
||||
audio_state_set_error(pAudioState, ma_result_description(result));
|
||||
}
|
||||
} else {
|
||||
audio_state_set_error(pAudioState, "Trying to pause audio, but there is no device.");
|
||||
}
|
||||
}
|
||||
|
||||
return (jlong)pAudioState;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_io_miniaud_miniaudiotester_MainActivity_HasAudioError(JNIEnv *env, jobject, jlong audioState)
|
||||
{
|
||||
auto* pAudioState = (audio_state_t*)audioState;
|
||||
if (pAudioState == nullptr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return pAudioState->hasError;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_io_miniaud_miniaudiotester_MainActivity_GetAudioError(JNIEnv *env, jobject, jlong audioState)
|
||||
{
|
||||
auto* pAudioState = (audio_state_t*)audioState;
|
||||
if (pAudioState == nullptr) {
|
||||
return env->NewStringUTF("Out of memory");
|
||||
}
|
||||
|
||||
return env->NewStringUTF(pAudioState->errorMessage.c_str());
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_io_miniaud_miniaudiotester_MainActivity_DeleteAudioState(JNIEnv *env, jobject thiz, jlong audioState)
|
||||
{
|
||||
Java_io_miniaud_miniaudiotester_MainActivity_UninitializeAudio(env, thiz, audioState);
|
||||
delete (audio_state_t*)audioState;
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package io.miniaud.miniaudiotester
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import android.os.Bundle
|
||||
import android.widget.TextView
|
||||
import io.miniaud.miniaudiotester.databinding.ActivityMainBinding
|
||||
|
||||
enum class AudioBackend(val value: Int)
|
||||
{
|
||||
BACKEND_AUTO(0),
|
||||
BACKEND_AAUDIO(1),
|
||||
BACKEND_OPENSL(2)
|
||||
}
|
||||
|
||||
class MainActivity : AppCompatActivity()
|
||||
{
|
||||
private lateinit var binding: ActivityMainBinding
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
binding.btnPlay.setOnClickListener {
|
||||
val backend: AudioBackend;
|
||||
if (binding.radioAAudio.isChecked) {
|
||||
backend = AudioBackend.BACKEND_AAUDIO
|
||||
} else if (binding.radioOpenSL.isChecked) {
|
||||
backend = AudioBackend.BACKEND_OPENSL
|
||||
} else {
|
||||
backend = AudioBackend.BACKEND_AUTO
|
||||
}
|
||||
|
||||
audioState = PlayAudio(audioState, backend.value)
|
||||
if (HasAudioError(audioState)) {
|
||||
binding.textInfo.text = GetAudioError(audioState)
|
||||
} else {
|
||||
binding.textInfo.text = "Playing..."
|
||||
}
|
||||
}
|
||||
|
||||
binding.btnStop.setOnClickListener {
|
||||
audioState = PauseAudio(audioState)
|
||||
if (HasAudioError(audioState)) {
|
||||
binding.textInfo.text = GetAudioError(audioState)
|
||||
} else {
|
||||
binding.textInfo.text = "Paused."
|
||||
}
|
||||
}
|
||||
|
||||
binding.btnUninit.setOnClickListener {
|
||||
audioState = UninitializeAudio(audioState)
|
||||
if (HasAudioError(audioState)) {
|
||||
binding.textInfo.text = GetAudioError(audioState)
|
||||
} else {
|
||||
binding.textInfo.text = "Device uninitialized."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var audioState: Long = 0
|
||||
|
||||
external fun UninitializeAudio(audioState: Long): Long
|
||||
external fun PlayAudio(audioState: Long, backend: Int): Long
|
||||
external fun PauseAudio(audioState: Long): Long
|
||||
external fun HasAudioError(audioState: Long): Boolean
|
||||
external fun GetAudioError(audioState: Long): String
|
||||
external fun DeleteAudioState(audioState: Long)
|
||||
|
||||
companion object {
|
||||
// Used to load the 'miniaudiotester' library on application startup.
|
||||
init {
|
||||
System.loadLibrary("miniaudiotester")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#3DDC84"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
</vector>
|
||||
@@ -0,0 +1,30 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="85.84757"
|
||||
android:endY="92.4963"
|
||||
android:startX="42.9492"
|
||||
android:startY="49.59793"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
||||
android:strokeWidth="1"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,99 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<RadioGroup
|
||||
android:id="@+id/radioGroup"
|
||||
android:layout_width="320dp"
|
||||
android:layout_height="144dp"
|
||||
android:layout_marginStart="44dp"
|
||||
android:layout_marginTop="90dp"
|
||||
android:layout_marginEnd="44dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/radioAutomatic"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="true"
|
||||
android:text="@string/automatic" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/radioAAudio"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/aaudio" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/radioOpenSL"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/opensl" />
|
||||
</RadioGroup>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="46dp"
|
||||
android:layout_marginTop="44dp"
|
||||
android:layout_marginEnd="235dp"
|
||||
android:text="@string/backend_label"
|
||||
android:textSize="34sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnPlay"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="46dp"
|
||||
android:layout_marginTop="00dp"
|
||||
android:contentDescription="@string/play_button_desc"
|
||||
app:layout_constraintEnd_toStartOf="@+id/btnStop"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/radioGroup"
|
||||
app:srcCompat="@android:drawable/ic_media_play" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnStop"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="00dp"
|
||||
android:layout_marginEnd="253dp"
|
||||
android:contentDescription="@string/pause_button_desc"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/btnPlay"
|
||||
app:layout_constraintTop_toBottomOf="@+id/radioGroup"
|
||||
app:srcCompat="@android:drawable/ic_media_pause" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnUninit"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="197dp"
|
||||
android:contentDescription="@string/uninit_button_desc"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/btnStop"
|
||||
app:layout_constraintTop_toBottomOf="@+id/radioGroup"
|
||||
app:srcCompat="@android:drawable/ic_delete" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textInfo"
|
||||
android:layout_width="317dp"
|
||||
android:layout_height="353dp"
|
||||
android:layout_marginStart="46dp"
|
||||
android:layout_marginEnd="45dp"
|
||||
android:layout_marginBottom="46dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 982 B |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 2.8 KiB |