mirror of
https://github.com/mackron/miniaudio.git
synced 2026-04-29 11:44:08 +02:00
Compare commits
1194 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| afa3a0b855 | |||
| ba8d25305c | |||
| 9ab3c55587 | |||
| d7cec8ba20 | |||
| 125f181b2b | |||
| ca9a1e8493 | |||
| f68fe8b076 | |||
| bfd3c98797 | |||
| f018f3cb85 | |||
| f40376031f | |||
| b73f0182d7 | |||
| af9c82f3dc | |||
| 1129061e19 | |||
| ab15b24065 | |||
| fcb48c585a | |||
| 265d250ec2 | |||
| 50fcc2f41f | |||
| f223d3e315 | |||
| 1403dfcdc9 | |||
| a9043ba26d | |||
| 137298734e | |||
| 2cb46c7abd | |||
| 70d52e5eaa | |||
| d7802c3b14 | |||
| a92139b686 | |||
| 77074f0597 | |||
| 3e729e4b28 | |||
| b5732f515a | |||
| 52f22d6597 | |||
| 44323c9cae | |||
| a267a4ca65 | |||
| 09615e6bc1 | |||
| 3841d2858d | |||
| 12a83fcf54 | |||
| dad1060489 | |||
| 72a6203849 | |||
| 4635de9916 | |||
| 2986173662 | |||
| 1cd746c5a6 | |||
| 07c4ae67cf | |||
| dcfc24c100 | |||
| 7533c9d341 | |||
| 56ffd77769 | |||
| 55f16e62f6 | |||
| 5d8e72d54f | |||
| 1df46ae9a0 | |||
| c64330dbd9 | |||
| a513b522ab | |||
| b91098312d | |||
| d50152e382 | |||
| d654ce32a7 | |||
| 12bacf1186 | |||
| 968a8045c0 | |||
| 117366df9a | |||
| ed950dc688 | |||
| 0041150de0 | |||
| 52d09d3688 | |||
| 6922366bb1 | |||
| 4c082afe71 | |||
| a6cb08579e | |||
| deda7e500f | |||
| df6119890e | |||
| 9634bedb5b | |||
| b113d498a5 | |||
| bc52a82903 | |||
| d7ce0506f6 | |||
| 41a8ec6a8d | |||
| dea554dbb2 | |||
| e552fd20cf | |||
| 69396f97a7 | |||
| e35c1fd64b | |||
| bc69c86868 | |||
| 8b3ae59b40 | |||
| d94b45d058 | |||
| bcdb37ff66 | |||
| 7d703e60ab | |||
| 30e2ca2b46 | |||
| b5eb987b86 | |||
| c580fad1dd | |||
| 4c81dbd81f | |||
| 7700880b56 | |||
| 0e2c0e1eac | |||
| a3fd69624b | |||
| d39c874736 | |||
| 0aef190a59 | |||
| 7e944566c4 | |||
| 3ea9cc468a | |||
| 4d8bd6ed84 | |||
| 60a96123db | |||
| 282ed02f46 | |||
| 788b78169a | |||
| c0f0500e43 | |||
| 2324d5ad9e | |||
| f6662fdb2e | |||
| 2f148fdd12 | |||
| 0d7a9f960f | |||
| e0e05c7b8c | |||
| ebbe9707e2 | |||
| dbc955fb0d | |||
| d6d26deeda | |||
| dd6c57664b | |||
| e490db3085 | |||
| 6851858937 | |||
| 242cbf4d8c | |||
| 01e5042bfb | |||
| 13f50137b0 | |||
| 65a0c1f83d | |||
| d63deea939 | |||
| 37a7009b3d | |||
| 1078dc292a | |||
| b33eb2ea4f | |||
| a6a7a76e6f | |||
| 20c9f7fe0a | |||
| ce05296055 | |||
| 852e91b380 | |||
| d9590dcf6e | |||
| 9edf1a558c | |||
| f75cd3a784 | |||
| c8c11615e3 | |||
| dbf391611d | |||
| d286a97ab1 | |||
| 5ffa29a80b | |||
| ec69cafef8 | |||
| 6adcbf9034 | |||
| fd1369b3fc | |||
| a8dd56fbbe | |||
| 0f1ead0873 | |||
| e2e6bb6334 | |||
| 5ae52e1a0a | |||
| c39ace1604 | |||
| 0615ce28f1 | |||
| 0fe2f7effd | |||
| c456a2f432 | |||
| 6d20358df1 | |||
| 459fc19304 | |||
| d4382ce478 | |||
| b3340e629a | |||
| 9d0f4a4103 | |||
| db1bc8c4b7 | |||
| cbbe317adc | |||
| 172f8beae6 | |||
| cd02ebe39c | |||
| 20180b0ae5 | |||
| a04f300821 | |||
| 2ea55eaeaf | |||
| c0b57c3aea | |||
| 4b8eb8588d | |||
| b6467f3cd0 | |||
| 848025b9c0 | |||
| 5ea8bbf701 | |||
| cff6c7e28a | |||
| 445aefae06 | |||
| 2c14e2e5a7 | |||
| 2683601481 | |||
| 1e2427f5f7 | |||
| 93e74aaf91 | |||
| 89fea8a39f | |||
| 9b66a480c7 | |||
| cb26c7ec52 | |||
| 02f8a6b952 | |||
| dc72a5683b | |||
| 5c4cb49ad8 | |||
| d929fafb34 | |||
| 53fce8453c | |||
| ac12fa70e0 | |||
| 3ab152afb3 | |||
| 38e35935d7 | |||
| bbc7ad1921 | |||
| c134a1c870 | |||
| 3b5b433e0c | |||
| 847711e291 | |||
| d1f34cd5db | |||
| 75b3f7dddc | |||
| fcd386dd87 | |||
| 2302e58045 | |||
| 28de8d8947 | |||
| 18df387a84 | |||
| d30d1cfd89 | |||
| 8c2cbf6343 | |||
| f99ff4cec0 | |||
| 07ea662dae | |||
| 5a7bfd7c2c | |||
| ca6afd1f49 | |||
| 7c3b8fab04 | |||
| 7d1c994414 | |||
| af2cf5d161 | |||
| edb64e6017 | |||
| bef32b4419 | |||
| 8a43271555 | |||
| ba963e46b5 | |||
| f6b973d384 | |||
| f37ffed283 | |||
| f2b9d0b480 | |||
| dec6c16539 | |||
| 9aa4744a94 | |||
| 0cf35695c8 | |||
| 3a1b85bb53 | |||
| a187fb0450 | |||
| 8d9d61d607 | |||
| 64b3dd6f66 | |||
| f215062678 | |||
| d043ce61b3 | |||
| 13d161bc8d | |||
| df405b1fb7 | |||
| f794044d0a | |||
| 251e0e59e4 | |||
| 70ff2591bc | |||
| 1b7cc965df | |||
| f1d99a186c | |||
| cb0e6afe70 | |||
| 3ab17977ea | |||
| 89663fa647 | |||
| 347321b27c | |||
| da94cf2bc6 | |||
| 8e6283aa31 | |||
| d0b98eee6b | |||
| 74912d525b | |||
| a551f0e70b | |||
| 7dae981ad5 | |||
| 1d6fe3efc9 | |||
| 72ed924fb5 | |||
| b29541068c | |||
| c3dcf0d1db | |||
| 3a052ada0d | |||
| 7ac50d477e | |||
| a0b24f6d3d | |||
| ab06cb340f | |||
| b5213e0265 | |||
| 48cc2d3cf1 | |||
| 01b69f7323 | |||
| 17a3941c94 | |||
| 06394e8e90 | |||
| e0e26b5535 | |||
| cfed5b09a6 | |||
| 131fa04cd6 | |||
| 838174a418 | |||
| 878cf797cc | |||
| 3a84c31cff | |||
| 3d697c27e6 | |||
| 6c1206931b | |||
| 08d4c60bc3 | |||
| 91ddce1d17 | |||
| 434bfc6a0b | |||
| 461ced3280 | |||
| ce4f9aab39 | |||
| e69049e6ac | |||
| 2b49a81e87 | |||
| e7d94638cf | |||
| dc7f5286ca | |||
| 1509e9b375 | |||
| b4a26d21dd | |||
| 6c0c35387d | |||
| cbe640efbf | |||
| c440b4b670 | |||
| c1831a677f | |||
| f15717ba3e | |||
| 90aa390a08 | |||
| 2a2eb5bae4 | |||
| f0d84c6895 | |||
| 5ad9e784f9 | |||
| 2fa51de5cf | |||
| 77abc8f69c | |||
| 7c3f845658 | |||
| 86a17a30e8 | |||
| 463200d3cc | |||
| 6ca4ddefe6 | |||
| 9af808b800 | |||
| d68dd6c433 | |||
| d61eaa885e | |||
| 1438d96b38 | |||
| 26ce86a4cf | |||
| 199cc4a189 | |||
| 01fd4b7662 | |||
| bdde9c46b0 | |||
| 3277d995a3 | |||
| b90341fc83 | |||
| dd3cce3061 | |||
| e5743d666c | |||
| 629e751d56 | |||
| cdd3d39ac0 | |||
| b3f32437b8 | |||
| 2b50979bd3 | |||
| 2ead596562 | |||
| 27f7eeba91 | |||
| 51f8235bef | |||
| d1316a58cf | |||
| 98a23f9551 | |||
| 55cdba084d | |||
| 92a58fae11 | |||
| 1adad94ef8 | |||
| 994ae48feb | |||
| c50cc675c9 | |||
| 676b84cad1 | |||
| cd521f9440 | |||
| 26029b2250 | |||
| 628f2c1640 | |||
| 82ec45e349 | |||
| f028249019 | |||
| 94a79c9cda | |||
| 9a1b551658 | |||
| e9abcccd86 | |||
| c649733b34 | |||
| 4d583a4508 | |||
| 6a3d5fde05 | |||
| df88373cd0 | |||
| d4631208f9 | |||
| 8709bef653 | |||
| de3946c9ec | |||
| 12256314a9 | |||
| 39abeb167e | |||
| 3debb4a20f | |||
| c4c484794b | |||
| adce75cba6 | |||
| 660d5f6f9e | |||
| 96c4b105b0 | |||
| 9a4318997b | |||
| 280c118dfd | |||
| 999c16d834 | |||
| 88776cedb7 | |||
| e00cee2af1 | |||
| 760765ec93 | |||
| fb132046d4 | |||
| 4bed03a65e | |||
| e9eccf49a3 | |||
| 46adfae3bb | |||
| 948967dcbb | |||
| 224a4c9d3a | |||
| 4e28636ed8 | |||
| 25dcbcb9cd | |||
| e1058d1eea | |||
| 0b4a861d94 | |||
| 597654dcf6 | |||
| 0129fa3b2a | |||
| e4a6c348d5 | |||
| 01fe5fe416 | |||
| 5ef2e1ec57 | |||
| ee8a65bed9 | |||
| 44b847fbf8 | |||
| ca6361db5e | |||
| 3b0391ad44 | |||
| 61f1f8b457 | |||
| 5095548174 | |||
| 5f3de510b2 | |||
| 53116ad6da | |||
| b83869eb09 | |||
| bedfd053cb | |||
| 32cc6d53cd | |||
| bd26454c26 | |||
| d791c16d8d | |||
| 86d5f669e4 | |||
| 4d9a66289d | |||
| 8c4535c6c5 | |||
| 4bd8eb0aa0 | |||
| 882d7329f9 | |||
| fd74d344c7 | |||
| 27d2d6ac87 | |||
| 919a01ae4a | |||
| 065e6eadb5 | |||
| 962d11b4ce | |||
| 5f3fc86f89 | |||
| b62249ceaf | |||
| 25a137752a | |||
| 525b04db04 | |||
| fd89763b2a | |||
| e93e1dbba1 | |||
| 97b2db8c42 | |||
| 111d620c63 | |||
| 3b4e87848b | |||
| 35acd7a65b | |||
| 83ef69a79a | |||
| 524e3c2d06 | |||
| 92fb865387 | |||
| c44ec3f46a | |||
| 8c3b213a7c | |||
| fe31274720 | |||
| f3dfc97c2c | |||
| 6d906215f1 | |||
| 5c38d39141 | |||
| a29a3b81f3 | |||
| d32cd3f843 | |||
| 4aa3d531b4 | |||
| bfe4b07da4 | |||
| 116a06fac7 | |||
| 0d94eb7da9 | |||
| d3270d8bf1 | |||
| 4e827fa977 | |||
| d5ce388d00 | |||
| 88797e9dee | |||
| ad85d0c3c4 | |||
| b717d19099 | |||
| 28d071766d | |||
| 70a3690499 | |||
| 9128cbe35b | |||
| 44b39fe097 | |||
| 3df99ce51d | |||
| 23c3277754 | |||
| 66887aa114 | |||
| f19d26a012 | |||
| 67389d29ec | |||
| c314eb0fa5 | |||
| c64a5c7457 | |||
| e81a11e725 | |||
| 3b2af39864 | |||
| 6048a9a73e | |||
| 53b4d6a4a0 | |||
| e78a86d30b | |||
| 46b2cdd0cf | |||
| ce41f6cfc9 | |||
| da764a5a28 | |||
| e9fad62f44 | |||
| d0392288c4 | |||
| a5ef023cee | |||
| 45c7a64299 | |||
| 1d308f69bc | |||
| db6bc371eb | |||
| 1f2e59548b | |||
| edc44cbcaa | |||
| 1d0a598485 | |||
| ff9ff16341 | |||
| 7cbb204abb | |||
| 0e6eaae06b | |||
| c6429bb866 | |||
| 552cf613b3 | |||
| 114b9594c1 | |||
| e1dfc9d77f | |||
| d2a93567df | |||
| c67b29de38 | |||
| 9ebc3efa95 | |||
| e086de4eb3 | |||
| 07af5ea6f7 | |||
| f073a40a1c | |||
| d04c1985d6 | |||
| cc4db76c26 | |||
| a16756ec3b | |||
| 350cc0be1f | |||
| 7483e55aee | |||
| 0f4bc3ca9a | |||
| b0894c34b1 | |||
| fa2c1b49b3 | |||
| 3d79043d7d | |||
| 9df9b19a59 | |||
| d72811c127 | |||
| aaf868f92a | |||
| d0b8b07c49 | |||
| a4406088f4 | |||
| c6d44b67e1 | |||
| 521c224b02 | |||
| 5d1c089d15 | |||
| f270f515e4 | |||
| d10526fb3a | |||
| ef59b93a4d | |||
| b6799e92e0 | |||
| f932077982 | |||
| 5296384289 | |||
| 4cb0851ccc | |||
| 7cb349fc60 | |||
| 8c735da4eb | |||
| a2c7e697d1 | |||
| 61fd14955a | |||
| 5eafc0e9c1 | |||
| 364844231d | |||
| 631bc047d8 | |||
| bd35758b87 | |||
| 737fb79aae | |||
| fd6ee4711b | |||
| e6922b360c | |||
| 1bb2180005 | |||
| e598eb7fe6 | |||
| 9364a36f04 | |||
| ad7084c9ef | |||
| 2f18c53771 | |||
| e97ab1ef32 | |||
| d6487d0569 | |||
| 6931d7d159 | |||
| 38a543caa4 | |||
| 60d757a226 | |||
| df79b33aeb | |||
| 9d2c943862 | |||
| 4151c05973 | |||
| 442e9f4db8 | |||
| 9ea38e9f3a | |||
| af19bdb6ff | |||
| f513f462df | |||
| 4f4f93a91b | |||
| 0c41e62827 | |||
| 2f759f7b62 | |||
| 97441567fa | |||
| 248f27b6e2 | |||
| fc677beac9 | |||
| ac7a8e691b | |||
| 1c5e2400ce | |||
| 1f717d6ba7 | |||
| b41a7eabe7 | |||
| 477020295a | |||
| 8c1dc255db | |||
| dbf8e114f9 | |||
| 6d65be5e0e | |||
| 6a895501cf | |||
| 787318fd8f | |||
| 4a8467852a | |||
| 80cf7b2deb | |||
| 2db0984566 | |||
| 1d6b3c6a0f | |||
| 669ed3e844 | |||
| c32d2d0e29 | |||
| 81410769ae | |||
| 48d7493c58 | |||
| ffe558437f | |||
| 089f041120 | |||
| 2e02046c6d | |||
| b22a0cbdb1 | |||
| 57851a9cef | |||
| c8975bc979 | |||
| b3c6bcec39 | |||
| ed2c5270c8 | |||
| 3dfcefc75b | |||
| 63485c2e7d | |||
| f40cf03f80 | |||
| 7f2cd5b8d5 | |||
| 3afbdef285 | |||
| 9a091f73aa | |||
| 4de39a8a37 | |||
| 7c8574210d | |||
| 6648ed005a | |||
| 87bae56937 | |||
| 293f5de18f | |||
| 233b9b69c4 | |||
| db514e813f | |||
| 8130543730 | |||
| c3245ee3ca | |||
| b306c6a270 | |||
| 9e1f02b12a | |||
| fa84240364 | |||
| 0379f18239 | |||
| 8030f3bf7b | |||
| 70eb06d3bd | |||
| 959283f244 | |||
| ea59076ba9 | |||
| 5384cfaf12 | |||
| 1d7d8dfba0 | |||
| f48d903526 | |||
| 3bdd39fd2c | |||
| f9cb8a3e4d | |||
| 74aa94d980 | |||
| e75a053908 | |||
| dda0bd100b | |||
| 346d86ffa1 | |||
| a2f92095dc | |||
| badf36a378 | |||
| c9d288c3dc | |||
| bcb198a7e9 | |||
| 5221ee1858 | |||
| 3803df947e | |||
| e094807b46 | |||
| 733a3e3ec0 | |||
| 0c245dd597 | |||
| 8c08e14119 | |||
| fde064bf03 | |||
| 825d2c4466 | |||
| d522c8a017 | |||
| cac5825182 | |||
| 125e300c69 | |||
| c036a9ff86 | |||
| 563f1820cb | |||
| fa031e75fd | |||
| 8eba46fdee | |||
| 6fd62e6bbc | |||
| ce6c21fffc | |||
| c366358ed6 | |||
| 747faa3390 | |||
| ca3ba8c1a8 | |||
| cb95cd6521 | |||
| b37530fdd6 | |||
| b1893aa8f1 | |||
| df4baf8d40 | |||
| 21237008df | |||
| 7263de3b64 | |||
| 26a1566cb2 | |||
| df0358c870 | |||
| 587bd83cbb | |||
| 0204c7d788 | |||
| e826957a12 | |||
| f6453a1418 | |||
| c3132cb703 | |||
| f86be65f19 | |||
| b7e5451ef4 | |||
| ba84e61a18 | |||
| a7ab58259e | |||
| 7f2f0a5a36 | |||
| 5eb78ec819 | |||
| 18055f34bb | |||
| 47f08262a0 | |||
| 415c50e2f5 | |||
| db38f0b003 | |||
| f6bae251bd | |||
| 3567d5cfef | |||
| 4e3b778c62 | |||
| ba35370f74 | |||
| 6315130ec6 | |||
| 6e1cd41622 | |||
| 62c10ddd58 | |||
| d0af92764f | |||
| 59566edddf | |||
| 3f3353ee39 | |||
| a1bee2c673 | |||
| 10700ec157 | |||
| 9b6e9b6985 | |||
| 5db8fe5094 | |||
| 87dee0e20c | |||
| fa5f282d65 | |||
| fc15cc66ed | |||
| a9bdbc3033 | |||
| e6483d8bd9 | |||
| 08b8fcd4c6 | |||
| 0923a484ee | |||
| 78cdb9c1cb | |||
| 1ea69211ad | |||
| e9e8f90137 | |||
| d93552283f | |||
| 61a85dca42 | |||
| c87f207f4e | |||
| 02ae7e41f0 | |||
| 8bb1dc5a88 | |||
| 457a7279fa | |||
| fe1da60e1e | |||
| 0b70a990ca | |||
| 26c347e506 | |||
| 556160909e | |||
| 2d4cc9c910 | |||
| 20b9b9c533 | |||
| 76e97c6254 | |||
| 384afa3a5f | |||
| 397cadbd64 | |||
| c48975f4a9 | |||
| db9ad08bf0 | |||
| 79b4ddc27d | |||
| 377f589a01 | |||
| 0b9f03a376 | |||
| 140b9c7f9f | |||
| ac8c908283 | |||
| 110ded6fc6 | |||
| 276dd4419d | |||
| 2a7446fb46 | |||
| de31f9ea49 | |||
| ed65f5d7ee | |||
| ff73bd7af6 | |||
| bd75bcb592 | |||
| 93167d108e | |||
| fa4107ae95 | |||
| 2b81f75cca | |||
| 5d86a6ef82 | |||
| 5b21699ba9 | |||
| 497f18c4a4 | |||
| 4a646fe191 | |||
| b184d81a46 | |||
| ced8f9e091 | |||
| 8b3ac67c89 | |||
| 9507f61689 | |||
| 8b87c10681 | |||
| c62dc472eb | |||
| e49fc58584 | |||
| 821399dca1 | |||
| 47f9287ac8 | |||
| 3add89fdd2 | |||
| c257d19c86 | |||
| 85c005b52d | |||
| 629d509072 | |||
| 5c86dd9153 | |||
| 8bb2b1ae80 | |||
| 7bbecd211e | |||
| fca29d2d57 | |||
| 5f84285913 | |||
| 0b5430572d | |||
| 59333e9721 | |||
| e6152c0ff9 | |||
| 3b879485be | |||
| d1955c29a9 | |||
| 6ab1cc83cd | |||
| 3f9dd6ddd0 | |||
| 55564ede75 | |||
| 7b887e0ca5 | |||
| c2b45b58af | |||
| c468230e8f | |||
| 7b157815ab | |||
| 291442cd6c | |||
| d8236f9d22 | |||
| 552ef533d4 | |||
| 3191f957c3 | |||
| a0402c6f1f | |||
| 0acd0a4b78 | |||
| f0ff4b6de3 | |||
| a014181372 | |||
| 27b593c2d2 | |||
| 94b07291fc | |||
| 5b3bc33425 | |||
| 147c6620cb | |||
| 24a073eab3 | |||
| 714afe2c42 | |||
| 3df76aa05e | |||
| 0c7be93f6f | |||
| d89e7c8e5d | |||
| 9f171142cf | |||
| a432e4db4c | |||
| fabab10843 | |||
| d1643b3487 | |||
| c0336335a6 | |||
| 51715474d7 | |||
| a4072246c5 | |||
| 180c7237f8 | |||
| 1e6c01f31d | |||
| 96eea50e7d | |||
| 2833ccf4e7 | |||
| ebec10b068 | |||
| fd37406086 | |||
| b9f6d99217 | |||
| 8890eac6aa | |||
| cdfd219377 | |||
| aa1cd76b96 | |||
| ab76f7a27d | |||
| 9bce75d858 | |||
| 636fe28d86 | |||
| 017eae73ac | |||
| 57423c6cea | |||
| 515e7fc2d3 | |||
| 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 | |||
| 7bd2e6a5bd | |||
| cd16c5bcd3 | |||
| 1ab91b05c0 | |||
| 76dfabbb45 | |||
| ca4cfef267 | |||
| 569edb8436 | |||
| 46d8abf3de | |||
| c5dda3c769 | |||
| 350784a946 | |||
| a65a7d139f | |||
| 2a79d124c1 | |||
| 8261dc8972 | |||
| c1daa31759 | |||
| b0e845e796 | |||
| d28ce1a841 | |||
| 1e2be9307e | |||
| 17b8dbf948 | |||
| 4663423838 | |||
| d6a1350c1f | |||
| c92e662de7 | |||
| e272f56750 | |||
| 1a83b0baa4 | |||
| 9511596a25 | |||
| 5c0724ad59 | |||
| c3b0a7fbbc | |||
| e4363a90be | |||
| 6453c9ff22 | |||
| e79e35a5ef | |||
| 7a250aa9f9 | |||
| 8c52072f43 | |||
| b6184fa2a0 | |||
| 166fd6dfc7 | |||
| 54373128ee | |||
| c74c90f686 | |||
| 01302b9715 | |||
| b5f1ff125e | |||
| 7a1135d448 | |||
| 9f9fc2333e | |||
| 824e6aaef5 | |||
| a497466f75 | |||
| 9ed0a7c8ed | |||
| 37b95f0f42 | |||
| 7fb5ef188f | |||
| 9f10bc7540 | |||
| 60c0b9eeba | |||
| 1fbad32949 | |||
| 11f8cbc7bc | |||
| e1f5ed4f79 | |||
| ed5cda309c | |||
| 489206e7e5 | |||
| 3435aafb34 | |||
| 3541d1b8cc | |||
| 3fd7c9f199 | |||
| deafb7e96f | |||
| 2e054f8011 | |||
| c13504629e | |||
| 31c0159ad3 | |||
| 562b3483d1 | |||
| f9caab2fd5 | |||
| 57fbc6dd36 | |||
| ee3e532a54 | |||
| 178797502e | |||
| 0576191d7d | |||
| 6bc3fec34e | |||
| 2542be5db8 | |||
| 67d1aca341 | |||
| 5975db4c76 | |||
| 08d6d1fac0 | |||
| 4d971fe480 | |||
| 575790bb29 | |||
| e49ce7df95 | |||
| d672b9610f | |||
| 3889066fac | |||
| ff66923b9a | |||
| e3151f2df1 | |||
| abb81fe95c | |||
| 0ea924ae7a | |||
| 8ad250ccf6 | |||
| b40803cf97 | |||
| 466a1354ce | |||
| e08c1303ef | |||
| 698a4319f0 | |||
| eee86a0ae1 | |||
| d3a4b9cf20 | |||
| 48ac10d1e1 | |||
| 4b4349af52 | |||
| 724dac6af1 | |||
| 391cca6e79 | |||
| 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 | |||
| ca3ecd9086 | |||
| 047200eace | |||
| 6d5efde254 | |||
| 9da8df1b9f | |||
| ed5964c9f6 | |||
| 7e81d3ac80 | |||
| 450dcb1af3 | |||
| 38f7d29f6f | |||
| 1fe39f949a | |||
| 3fb7027682 | |||
| 14a455143f | |||
| 977bd616ff | |||
| a3ae2e71ff | |||
| 68a526a759 | |||
| 8383893c9c | |||
| 7a25af64d6 | |||
| fc905ec97f | |||
| 69df19f0b6 | |||
| bb9f9ca041 | |||
| 33499941ae | |||
| 3081e314b7 | |||
| fcddfe6204 | |||
| 547ef1c9b7 | |||
| 928ed8bd85 | |||
| ae2cd4bea4 | |||
| b53daca554 | |||
| 1a7a9a7ed2 | |||
| 82ae0138f3 | |||
| 8d5bf8210c | |||
| ad615af1a8 | |||
| 3a34c049fa | |||
| 970c3801d9 | |||
| da76932f6b | |||
| 7dbb9f5e1a | |||
| 22489aeff1 | |||
| 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 | |||
| f760d05c51 | |||
| ff9225a878 | |||
| 2c59b302e5 | |||
| 0eb86ea1da | |||
| 0262d89ea9 | |||
| f581a23f30 | |||
| 5a0d1ad433 | |||
| 2618c21415 | |||
| 030b9554c2 | |||
| c0be89b016 | |||
| 7c90311f5d | |||
| f07c4cd3a1 | |||
| d10a287f23 | |||
| 61ec4b17c6 | |||
| 2eb6f4eda2 | |||
| 4fc86eb190 | |||
| ff0a53fa6f | |||
| 991ae301e4 | |||
| 99c5a9c629 | |||
| 7d8e8526bc | |||
| 8c2398b1ef | |||
| 05756ff605 | |||
| 9a6eefd4a8 | |||
| ce848a353d | |||
| e290104179 | |||
| c6d8b591f6 | |||
| 521b6571f3 | |||
| 5888bfadca | |||
| 3bdf611768 | |||
| 63e1900db8 | |||
| 29da9b789c | |||
| b454e7f14b | |||
| feea26496c | |||
| d36a2ef651 | |||
| e3af234720 | |||
| 5cb0c05675 | |||
| 9aa6e035bb | |||
| 7a8ebd7f4d | |||
| e32cc9ff83 | |||
| babd7fb00f | |||
| f01ce432be | |||
| f4e5cf99dc | |||
| f20ab8a9ee | |||
| aa57d052da | |||
| c0afa7e621 | |||
| bde517a166 | |||
| 3ed9d05c38 | |||
| a4aa0dc404 | |||
| fd3c1b0af0 | |||
| 10f9ef05a2 | |||
| bdab2fc3e0 | |||
| 9c73849f3b | |||
| fde3a27d93 | |||
| 766a155fb3 | |||
| c29c001840 | |||
| ee3b7df66a | |||
| 1325ac397b | |||
| ed204e4b72 | |||
| 6099e6f41c | |||
| 4f426f6db0 | |||
| 7e38fa0e7e | |||
| d27c3e03f1 | |||
| 73e9955b1d | |||
| d0709098cc | |||
| 3c5b15b48b | |||
| 2b8bb34ca2 | |||
| 2b0c525e53 | |||
| 6cba159210 | |||
| 1583329187 | |||
| 5a3655fea4 | |||
| 4a5b74bef0 | |||
| c77d40ba00 | |||
| 8fcf6889aa | |||
| f9ce46330c | |||
| c89eb7ccff | |||
| 3b50a854ec | |||
| da0572e6b8 | |||
| eb0ce6f1a5 | |||
| 7ed8b8c5a5 | |||
| 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 | |||
| ed68fda5d0 | |||
| 7e01c6535b | |||
| 60366fe469 | |||
| 5b32ce15d1 | |||
| c24141c5ae | |||
| 7266a1a6da | |||
| 3898fff8ed | |||
| 9eca9ce0cd | |||
| d4fd8411c4 | |||
| efa9e7d727 | |||
| c24829cbb9 | |||
| fde7d20414 | |||
| c36b391cc5 | |||
| 4d23c1c5ab | |||
| 810cdc2380 | |||
| 53907863c7 | |||
| f9f542b2fb | |||
| e43457fcce | |||
| 356eb3252e | |||
| 3429769623 | |||
| f2fc207462 | |||
| ca7284fde5 | |||
| 2212965267 | |||
| 320245606a | |||
| 18e4756be3 | |||
| 8df02809b5 | |||
| 1696031633 | |||
| 90bdda29ae | |||
| ba1b349a5a | |||
| 69bc820ae8 | |||
| 052d8bd857 | |||
| 98a39ded77 | |||
| 4c7e3218e3 | |||
| b2ed26cf76 | |||
| 7f0a92a08f | |||
| ab87375257 | |||
| 0eadb0f30e | |||
| 6117016102 | |||
| dcd432bc84 | |||
| 22724a9156 | |||
| a6eb7d6a6f | |||
| e9ba163490 | |||
| f9076ef327 | |||
| eabc776898 | |||
| 4c49c49596 | |||
| 34b40bdc17 | |||
| d30b3a76a5 | |||
| e1bfeb212a | |||
| db8e77cad4 | |||
| 4548242b96 | |||
| 1177997599 | |||
| 7a804e77c2 | |||
| 5f32336a34 | |||
| 780e607210 | |||
| f625d7abd8 | |||
| a0b952eea6 | |||
| 16db85081f | |||
| e7912fa242 | |||
| 0c1c4c7ddc | |||
| 04ab2a2d7b | |||
| d76b9a1ac4 | |||
| e9b6559be1 | |||
| 1bd7713e85 | |||
| e7e666d827 | |||
| 8c59e9b736 | |||
| a2698a0048 | |||
| ea42e16a79 | |||
| 14be2bd394 | |||
| a8f3cb857e | |||
| 563e1c52cb | |||
| 4520faa1d2 | |||
| 8dec4e0b9b | |||
| 69f4a19ef5 | |||
| 9374f5e8d2 | |||
| b98acd2422 | |||
| 5c099791ee | |||
| 773d97a95c | |||
| fa7cd81027 | |||
| af46c1fcc0 | |||
| 65574f44e3 | |||
| f05bb5306d | |||
| 6eeea700f0 | |||
| 04a6fe6eea | |||
| 6cddea6fe3 | |||
| 83c221f83e | |||
| f59a2c4cba | |||
| cc2076055f | |||
| 58166e2267 | |||
| 68c8b8000e | |||
| 35403d4b9c | |||
| fbb0f9dd72 | |||
| 203de65568 | |||
| 48b2986114 | |||
| ef5861acdb | |||
| 2bdad08247 | |||
| 5da18c3ca8 | |||
| 529e70c653 | |||
| ec1364d3a1 | |||
| 9c69d50468 | |||
| 35a0c7b465 | |||
| 8042a37822 | |||
| ac09b507ef | |||
| 30c4b7130f | |||
| 83fa8f6147 |
@@ -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.
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
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.
|
||||
@@ -0,0 +1,296 @@
|
||||
---
|
||||
name: Compilation Checks
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master, dev, dev-0.12]
|
||||
pull_request:
|
||||
branches: [master, dev, dev-0.12]
|
||||
|
||||
jobs:
|
||||
linux:
|
||||
name: Linux (${{ matrix.compiler }}, ${{ matrix.config.name }})
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: ${{ matrix.config.continue_on_error || false }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
compiler: [gcc, clang]
|
||||
config:
|
||||
- name: "Default"
|
||||
cmake_args: "-DMINIAUDIO_BUILD_EXAMPLES=ON -DMINIAUDIO_BUILD_TESTS=ON"
|
||||
- name: "No Device IO"
|
||||
cmake_args: >-
|
||||
-DMINIAUDIO_NO_DEVICEIO=ON
|
||||
-DMINIAUDIO_BUILD_EXAMPLES=ON
|
||||
-DMINIAUDIO_BUILD_TESTS=ON
|
||||
- name: "Force C++"
|
||||
cmake_args: >-
|
||||
-DMINIAUDIO_FORCE_CXX=ON
|
||||
-DMINIAUDIO_BUILD_EXAMPLES=ON
|
||||
-DMINIAUDIO_BUILD_TESTS=ON
|
||||
- name: "Force C89"
|
||||
cmake_args: >-
|
||||
-DMINIAUDIO_FORCE_C89=ON
|
||||
-DMINIAUDIO_BUILD_EXAMPLES=ON
|
||||
-DMINIAUDIO_BUILD_TESTS=ON
|
||||
continue_on_error: true
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libasound2-dev libpulse-dev libjack-jackd2-dev
|
||||
|
||||
- name: Setup compiler
|
||||
run: |
|
||||
if [ "${{ matrix.compiler }}" = "clang" ]; then
|
||||
sudo apt-get install -y clang
|
||||
echo "CC=clang" >> $GITHUB_ENV
|
||||
echo "CXX=clang++" >> $GITHUB_ENV
|
||||
else
|
||||
echo "CC=gcc" >> $GITHUB_ENV
|
||||
echo "CXX=g++" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: Configure CMake
|
||||
run: cmake -B build ${{ matrix.config.cmake_args }}
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build --parallel $(nproc)
|
||||
|
||||
windows:
|
||||
name: Windows (${{ matrix.compiler }}, ${{ matrix.config.name }})
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
compiler: [msvc, mingw]
|
||||
config:
|
||||
- name: "Default"
|
||||
cmake_args: "-DMINIAUDIO_BUILD_EXAMPLES=ON -DMINIAUDIO_BUILD_TESTS=ON"
|
||||
- name: "No Device IO"
|
||||
cmake_args: >-
|
||||
-DMINIAUDIO_NO_DEVICEIO=ON
|
||||
-DMINIAUDIO_BUILD_EXAMPLES=ON
|
||||
-DMINIAUDIO_BUILD_TESTS=ON
|
||||
- name: "Force C++"
|
||||
cmake_args: >-
|
||||
-DMINIAUDIO_FORCE_CXX=ON
|
||||
-DMINIAUDIO_BUILD_EXAMPLES=ON
|
||||
-DMINIAUDIO_BUILD_TESTS=ON
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup MSVC
|
||||
if: matrix.compiler == 'msvc'
|
||||
uses: ilammy/msvc-dev-cmd@v1
|
||||
|
||||
- name: Setup MinGW
|
||||
if: matrix.compiler == 'mingw'
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
msystem: MINGW64
|
||||
update: true
|
||||
install: >-
|
||||
mingw-w64-x86_64-gcc mingw-w64-x86_64-cmake mingw-w64-x86_64-make
|
||||
|
||||
- name: Configure CMake (MSVC)
|
||||
if: matrix.compiler == 'msvc'
|
||||
run: cmake -B build ${{ matrix.config.cmake_args }}
|
||||
|
||||
- name: Configure CMake (MinGW)
|
||||
if: matrix.compiler == 'mingw'
|
||||
shell: msys2 {0}
|
||||
run: cmake -B build -G "MinGW Makefiles" ${{ matrix.config.cmake_args }}
|
||||
|
||||
- name: Build (MSVC)
|
||||
if: matrix.compiler == 'msvc'
|
||||
run: cmake --build build --parallel
|
||||
|
||||
- name: Build (MinGW)
|
||||
if: matrix.compiler == 'mingw'
|
||||
shell: msys2 {0}
|
||||
run: cmake --build build --parallel
|
||||
|
||||
macos:
|
||||
name: macOS (${{ matrix.config.name }})
|
||||
runs-on: macos-latest
|
||||
continue-on-error: ${{ matrix.config.continue_on_error || false }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
config:
|
||||
- name: "Default"
|
||||
cmake_args: "-DMINIAUDIO_BUILD_EXAMPLES=ON -DMINIAUDIO_BUILD_TESTS=ON"
|
||||
- name: "No Device IO"
|
||||
cmake_args: >-
|
||||
-DMINIAUDIO_NO_DEVICEIO=ON
|
||||
-DMINIAUDIO_BUILD_EXAMPLES=ON
|
||||
-DMINIAUDIO_BUILD_TESTS=ON
|
||||
- name: "Force C++"
|
||||
cmake_args: >-
|
||||
-DMINIAUDIO_FORCE_CXX=ON
|
||||
-DMINIAUDIO_BUILD_EXAMPLES=ON
|
||||
-DMINIAUDIO_BUILD_TESTS=ON
|
||||
- name: "Force C89"
|
||||
cmake_args: >-
|
||||
-DMINIAUDIO_FORCE_C89=ON
|
||||
-DMINIAUDIO_BUILD_EXAMPLES=ON
|
||||
-DMINIAUDIO_BUILD_TESTS=ON
|
||||
continue_on_error: true
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Configure CMake
|
||||
run: cmake -B build ${{ matrix.config.cmake_args }}
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build --parallel $(sysctl -n hw.ncpu)
|
||||
|
||||
emscripten:
|
||||
name: Emscripten (${{ matrix.config.name }})
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
config:
|
||||
- name: "Default"
|
||||
cmake_args: "-DMINIAUDIO_BUILD_TESTS=ON"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Emscripten
|
||||
uses: mymindstorm/setup-emsdk@v14
|
||||
with:
|
||||
version: latest
|
||||
|
||||
- name: Configure CMake
|
||||
run: emcmake cmake -B build ${{ matrix.config.cmake_args }}
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build --parallel $(nproc)
|
||||
|
||||
android:
|
||||
name: Android (${{ matrix.arch }}, ${{ matrix.config.name }})
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
arch: [arm64-v8a, armeabi-v7a, x86_64]
|
||||
config:
|
||||
- name: "Default"
|
||||
cmake_args: "-DMINIAUDIO_BUILD_EXAMPLES=ON -DMINIAUDIO_BUILD_TESTS=ON"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Android NDK
|
||||
uses: nttld/setup-ndk@v1
|
||||
with:
|
||||
ndk-version: r25c
|
||||
|
||||
- name: Configure CMake
|
||||
run: |
|
||||
TOOLCHAIN_FILE=$ANDROID_NDK_ROOT/build/cmake/android.toolchain.cmake
|
||||
cmake -B build \
|
||||
-DCMAKE_TOOLCHAIN_FILE=$TOOLCHAIN_FILE \
|
||||
-DANDROID_ABI=${{ matrix.arch }} \
|
||||
-DANDROID_PLATFORM=android-21 \
|
||||
${{ matrix.config.cmake_args }}
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build --parallel $(nproc)
|
||||
|
||||
freebsd:
|
||||
name: FreeBSD (${{ matrix.config.name }})
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
config:
|
||||
- {name: "Default", cmake_args: "-DMINIAUDIO_BUILD_EXAMPLES=ON -DMINIAUDIO_BUILD_TESTS=ON"}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Test on FreeBSD
|
||||
uses: vmactions/freebsd-vm@v1
|
||||
with:
|
||||
usesh: true
|
||||
prepare: |
|
||||
pkg install -y cmake
|
||||
run: |
|
||||
cmake -B build ${{ matrix.config.cmake_args }}
|
||||
cmake --build build --parallel $(sysctl -n hw.ncpu)
|
||||
|
||||
openbsd:
|
||||
name: OpenBSD (${{ matrix.config.name }})
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
config:
|
||||
- {name: "Default", cmake_args: "-DMINIAUDIO_BUILD_EXAMPLES=ON -DMINIAUDIO_BUILD_TESTS=ON"}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Test on OpenBSD
|
||||
uses: vmactions/openbsd-vm@v1
|
||||
with:
|
||||
usesh: true
|
||||
prepare: |
|
||||
pkg_add cmake
|
||||
run: |
|
||||
cmake -B build ${{ matrix.config.cmake_args }}
|
||||
cmake --build build --parallel $(sysctl -n hw.ncpu)
|
||||
|
||||
netbsd:
|
||||
name: NetBSD (${{ matrix.config.name }})
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
config:
|
||||
- {name: "Default", cmake_args: "-DMINIAUDIO_BUILD_EXAMPLES=ON -DMINIAUDIO_BUILD_TESTS=ON"}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Test on NetBSD
|
||||
uses: vmactions/netbsd-vm@v1
|
||||
with:
|
||||
usesh: true
|
||||
prepare: |
|
||||
/usr/sbin/pkg_add cmake
|
||||
run: |
|
||||
cmake -B build ${{ matrix.config.cmake_args }}
|
||||
cmake --build build
|
||||
|
||||
additional-configs:
|
||||
name: Additional Configurations (${{ matrix.config.name }})
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: ${{ matrix.config.continue_on_error || false }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
config:
|
||||
- name: "No SSE2"
|
||||
cmake_args: "-DMINIAUDIO_NO_SSE2=ON -DMINIAUDIO_BUILD_EXAMPLES=ON -DMINIAUDIO_BUILD_TESTS=ON"
|
||||
continue_on_error: true
|
||||
- name: "No Threading"
|
||||
cmake_args: "-DMINIAUDIO_NO_THREADING=ON -DMINIAUDIO_NO_DEVICEIO=ON -DMINIAUDIO_BUILD_EXAMPLES=ON -DMINIAUDIO_BUILD_TESTS=ON"
|
||||
- {name: "No Decoding", cmake_args: "-DMINIAUDIO_NO_DECODING=ON -DMINIAUDIO_BUILD_EXAMPLES=ON -DMINIAUDIO_BUILD_TESTS=ON"}
|
||||
- {name: "No Encoding", cmake_args: "-DMINIAUDIO_NO_ENCODING=ON -DMINIAUDIO_BUILD_EXAMPLES=ON -DMINIAUDIO_BUILD_TESTS=ON"}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Configure CMake
|
||||
run: cmake -B build ${{ matrix.config.cmake_args }}
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build --parallel $(nproc)
|
||||
+59
-11
@@ -1,14 +1,62 @@
|
||||
\#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/xcode/
|
||||
/tests/_build/capture.wav
|
||||
/tests/_build/a.out
|
||||
/tests/_build/a.exe
|
||||
/tests/debugging/archive/
|
||||
/tests/dreamcast/miniaudio.elf
|
||||
/tests/dreamcast/miniaudio_dreamcast_test.o
|
||||
/tests/dreamcast/romdisk.img
|
||||
/tests/dreamcast/romdisk.o
|
||||
/tests/dreamcast/romdisk/test.mp3
|
||||
/tests/xbox/bin/
|
||||
/tests/xbox/*.d
|
||||
/tests/xbox/*.obj
|
||||
/tests/xbox/*.iso
|
||||
/tests/xbox/*.exe
|
||||
/tests/xbox/test.mp3
|
||||
/tests/vita/build/
|
||||
/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/
|
||||
+160
-6
@@ -1,3 +1,157 @@
|
||||
v0.11.26 - TBD
|
||||
=====================
|
||||
* Fixed a crash when passing in null for the read or seek callbacks for a decoder.
|
||||
|
||||
|
||||
v0.11.25 - 2026-03-04
|
||||
=====================
|
||||
* Bug fixes to the WAV decoder.
|
||||
* Fixed warnings with the Emscripten build relating to the renaming of of `__EMSCRIPTEN_major/minor/tiny__` macros.
|
||||
* Win32: Fixed an error with runtime linking on the UWP build. This is actually a non issue in practice because it would require miniaudio to pass in a DLL name of longer than 2048 characters which it never does.
|
||||
|
||||
|
||||
v0.11.24 - 2026-01-17
|
||||
=====================
|
||||
* Fixed a possible glitch when processing the audio of a `ma_sound` when doing resampling.
|
||||
* Fixed a possible crash in the node graph relating to scheduled starts and stops.
|
||||
* Fixed a bug where MA_NO_DECODING would disable the WAV encoder.
|
||||
* Fixed a pthread compatibility issue, particularly with Android.
|
||||
* Fixed a possible crash in the resource manager.
|
||||
* Fixed a possible double-uninit error when a decoder fails to initialize.
|
||||
* Fixed a compilation error with the MSVC Aarch64 build.
|
||||
* Addressed a few errors found through static analysis, particularly around possible null pointer dereferences.
|
||||
* `ma_sound_is_playing()` will now correctly return false when called from inside the end callback of a sound.
|
||||
* Miscellaneous compiler compatibility and warning fixes.
|
||||
* PulseAudio: Fix a resource leak when a context fails to connect.
|
||||
* Web: Fixed an error when uninitializing a context.
|
||||
|
||||
|
||||
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.
|
||||
* Fix an error where the cursor of a Vorbis stream is incorrectly incremented.
|
||||
* Add support for setting a callback on an `ma_engine` object that get's fired after it processes a chunk of audio. This allows applications to do things such as apply a post-processing effect or output the audio to a file.
|
||||
* Add `ma_engine_get_volume()`.
|
||||
* Add `ma_sound_get_time_in_milliseconds()`.
|
||||
* Decouple `MA_API` and `MA_PRIVATE`. This relaxes applications from needing to define both of them if they're only wanting to redefine one.
|
||||
* Decoding backends will now have their onInitFile/W and onInitMemory initialization routines used where appropriate if they're defined.
|
||||
* Increase the accuracy of the linear resampler when setting the ratio with `ma_linear_resampler_set_rate_ratio()`.
|
||||
* Fix erroneous output with the linear resampler when in/out rates are the same.
|
||||
* AAudio: Fix an error where the buffer size is not configured correctly which sometimes results in excessively high latency.
|
||||
* ALSA: Fix a possible error when stopping and restarting a device.
|
||||
* PulseAudio: Minor changes to stream flags.
|
||||
* Win32: Fix an error where `CoUninialize()` is being called when the corresponding `CoInitializeEx()` fails.
|
||||
* Web / Emscripten: Add support for AudioWorklets. This is opt-in and can be enabled by defining `MA_ENABLE_AUDIO_WORKLETS`. You must compile with `-sAUDIO_WORKLET=1 -sWASM_WORKERS=1 -sASYNCIFY` for this to work. Requires at least Emscripten v3.1.32.
|
||||
|
||||
|
||||
v0.11.17 - 2023-05-27
|
||||
=====================
|
||||
* Fix compilation errors with MA_USE_STDINT.
|
||||
* Fix a possible runtime error with Windows 95/98.
|
||||
* Fix a very minor linting warning in VS2022.
|
||||
* Add support for AIFF/AIFC decoding.
|
||||
* Add support for RIFX decoding.
|
||||
* Work around some bad code generation by Clang.
|
||||
* Amalgamations of dr_wav, dr_flac, dr_mp3 and c89atomic have been updated so that they're now fully namespaced. This allows each of these libraries to be able to be used alongside miniaudio without any conflicts. In addition, some duplicate code, such as sized type declarations, result codes, etc. has been removed.
|
||||
|
||||
|
||||
v0.11.16 - 2023-05-15
|
||||
=====================
|
||||
* Fix a memory leak with `ma_sound_init_copy()`.
|
||||
@@ -114,7 +268,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.
|
||||
@@ -206,7 +360,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.
|
||||
@@ -408,7 +562,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.
|
||||
@@ -792,7 +946,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
|
||||
@@ -901,7 +1055,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().
|
||||
@@ -1051,4 +1205,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.
|
||||
|
||||
+1104
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -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.
|
||||
|
||||
|
||||
@@ -193,6 +179,8 @@ Supported Platforms
|
||||
- Android
|
||||
- Raspberry Pi
|
||||
- Emscripten / HTML5
|
||||
- Dreamcast (via KallistiOS)
|
||||
- Original Xbox (via NXDK)
|
||||
|
||||
miniaudio should compile clean on other platforms, but it will not include any support for playback or capture
|
||||
by default. To support that, you would need to implement a custom backend. You can do this without needing to
|
||||
@@ -204,19 +192,27 @@ Backends
|
||||
- DirectSound
|
||||
- WinMM
|
||||
- Core Audio (Apple)
|
||||
- ALSA
|
||||
- PipeWire
|
||||
- PulseAudio
|
||||
- JACK
|
||||
- ALSA
|
||||
- sndio (OpenBSD)
|
||||
- audio(4) (NetBSD and OpenBSD)
|
||||
- OSS (FreeBSD)
|
||||
- AAudio (Android 8.0+)
|
||||
- OpenSL|ES (Android only)
|
||||
- Web Audio (Emscripten)
|
||||
- Dreamcast (via KallistiOS)
|
||||
- XAudio (Original Xbox via NXDK)
|
||||
- Null (Silence)
|
||||
- Custom
|
||||
|
||||
|
||||
Security
|
||||
========
|
||||
See the miniaudio [security policy](.github/SECURITY.md).
|
||||
|
||||
|
||||
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,312 @@
|
||||
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>;
|
||||
c89atomic_ring_buffer_h :: <../../c89atomic/extras/c89atomic_ring_buffer.h>;
|
||||
c89atomic_ring_buffer_c :: <../../c89atomic/extras/c89atomic_ring_buffer.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 \*/"]));
|
||||
|
||||
|
||||
// Ring Buffer
|
||||
rename_c89atomic_ring_buffer_namespace :: function(src:string) string
|
||||
{
|
||||
return rename_c89atomic_namespace(src)
|
||||
["\bma_atomic_ring_buffer"] <= "ma_ring_buffer"
|
||||
["\bMA_ATOMIC"] <= "MA"
|
||||
["\bMA_RING_BUFFER_API"] <= "MA_API"
|
||||
["\bMA_RING_BUFFER_ASSERT"] <= "MA_ASSERT"
|
||||
["\bMA_RING_BUFFER_COPY_MEMORY"] <= "MA_COPY_MEMORY"
|
||||
["\bMA_RING_BUFFER_OFFSET_PTR"] <= "ma_offset_ptr"
|
||||
["\bMA_RING_BUFFER_CACHE_LINE_SIZE"] <= "MA_CACHE_LINE_SIZE"
|
||||
|
||||
// Alignment hack.
|
||||
["void\* pBuffer; "] <= "void* pBuffer;"
|
||||
;
|
||||
}
|
||||
|
||||
miniaudio_h("/\* BEG ma_ring_buffer.h \*/\R":"\R/\* END ma_ring_buffer.h \*/") = rename_c89atomic_ring_buffer_namespace(@(c89atomic_ring_buffer_h("/\* BEG c89atomic_ring_buffer.h \*/\R":"\R/\* END c89atomic_ring_buffer.h \*/")));
|
||||
miniaudio_h("/\* BEG ma_ring_buffer.c \*/\R":"\R/\* END ma_ring_buffer.c \*/") = rename_c89atomic_ring_buffer_namespace(@(c89atomic_ring_buffer_c("/\* BEG c89atomic_ring_buffer.c \*/\R":"\R/\* END c89atomic_ring_buffer.c \*/")));
|
||||
|
||||
|
||||
// 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;
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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/
|
||||
@@ -11,6 +11,10 @@ path like "C:\emsdk\emsdk_env.bat". Note that PowerShell doesn't work for me for
|
||||
emcc ../simple_playback_sine.c -o bin/simple_playback_sine.html
|
||||
emcc ../simple_playback_sine.c -o bin/simple_playback_sine.html -s WASM=0 -Wall -Wextra
|
||||
|
||||
To compile with support for Audio Worklets:
|
||||
|
||||
emcc ../simple_playback_sine.c -o bin/simple_playback_sine.html -DMA_ENABLE_AUDIO_WORKLETS -sAUDIO_WORKLET=1 -sWASM_WORKERS=1 -sASYNCIFY
|
||||
|
||||
If you output WASM it may not work when running the web page locally. To test you can run with something
|
||||
like this:
|
||||
|
||||
|
||||
+76
-667
@@ -1,30 +1,19 @@
|
||||
/*
|
||||
This example show how a custom backend can be implemented.
|
||||
This example shows how to plug in custom backends.
|
||||
|
||||
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
|
||||
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.
|
||||
|
||||
There may be times where you want to support more than one custom backend. This example has been designed to make it easy to plug-in extra
|
||||
custom backends without needing to modify any of the base miniaudio initialization code. A custom context structure is declared called
|
||||
`ma_context_ex`. The first member of this structure is a `ma_context` object which allows it to be cast between the two. The same is done
|
||||
for devices, which is called `ma_device_ex`. In these structures there is a section for each custom backend, which in this example is just
|
||||
SDL. These are only enabled at compile time if `MA_SUPPORT_SDL` is defined, which it always is in this example (you may want to have some
|
||||
logic which more intelligently enables or disables SDL support).
|
||||
|
||||
To use a custom backend, at a minimum you must set the `custom.onContextInit()` callback in the context config. You do not need to set the
|
||||
other callbacks, but if you don't, you must set them in the implementation of the `onContextInit()` callback which is done via an output
|
||||
parameter. This is the approach taken by this example because it's the simplest way to support multiple custom backends. The idea is that
|
||||
the `onContextInit()` callback is set to a generic "loader", which then calls out to a backend-specific implementation which then sets the
|
||||
remaining callbacks if it is successfully initialized.
|
||||
To use a custom backend you need to plug in a `ma_device_backend_vtable` pointer into the context config. You can plug in multiple
|
||||
custom backends, but for this example we're just using the SDL backend which you can find in the extras folder in the miniaudio
|
||||
repository. If your custom backend requires it, you can also plug in a user data pointer which will be passed to the backend callbacks.
|
||||
|
||||
Custom backends are identified with the `ma_backend_custom` backend type. For the purpose of demonstration, this example only uses the
|
||||
`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`.
|
||||
backends would actually get hit. By default, the `ma_backend_custom` backend is the second-lowest priority backend, sitting just above
|
||||
`ma_backend_null`.
|
||||
*/
|
||||
#define MINIAUDIO_IMPLEMENTATION
|
||||
#include "../miniaudio.h"
|
||||
#include "../miniaudio.c"
|
||||
|
||||
/* We're using SDL for this example. To use this in your own program, you need to include miniaudio_sdl2.h after miniaudio.h. */
|
||||
#include "../extras/backends/sdl2/miniaudio_sdl2.h"
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <emscripten.h>
|
||||
@@ -34,603 +23,6 @@ void main_loop__em()
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Support SDL on everything. */
|
||||
#define MA_SUPPORT_SDL
|
||||
|
||||
/*
|
||||
Only enable SDL if it's hasn't been explicitly disabled (MA_NO_SDL) or enabled (MA_ENABLE_SDL with
|
||||
MA_ENABLE_ONLY_SPECIFIC_BACKENDS) and it's supported at compile time (MA_SUPPORT_SDL).
|
||||
*/
|
||||
#if defined(MA_SUPPORT_SDL) && !defined(MA_NO_SDL) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_SDL))
|
||||
#define MA_HAS_SDL
|
||||
#endif
|
||||
|
||||
|
||||
typedef struct
|
||||
{
|
||||
ma_context context; /* Make this the first member so we can cast between ma_context and ma_context_ex. */
|
||||
#if defined(MA_SUPPORT_SDL)
|
||||
struct
|
||||
{
|
||||
ma_handle hSDL; /* A handle to the SDL2 shared object. We dynamically load function pointers at runtime so we can avoid linking. */
|
||||
ma_proc SDL_InitSubSystem;
|
||||
ma_proc SDL_QuitSubSystem;
|
||||
ma_proc SDL_GetNumAudioDevices;
|
||||
ma_proc SDL_GetAudioDeviceName;
|
||||
ma_proc SDL_CloseAudioDevice;
|
||||
ma_proc SDL_OpenAudioDevice;
|
||||
ma_proc SDL_PauseAudioDevice;
|
||||
} sdl;
|
||||
#endif
|
||||
} ma_context_ex;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
ma_device device; /* Make this the first member so we can cast between ma_device and ma_device_ex. */
|
||||
#if defined(MA_SUPPORT_SDL)
|
||||
struct
|
||||
{
|
||||
int deviceIDPlayback;
|
||||
int deviceIDCapture;
|
||||
} sdl;
|
||||
#endif
|
||||
} ma_device_ex;
|
||||
|
||||
|
||||
|
||||
#if defined(MA_HAS_SDL)
|
||||
/* SDL headers are necessary if using compile-time linking. */
|
||||
#ifdef MA_NO_RUNTIME_LINKING
|
||||
#ifdef __has_include
|
||||
#ifdef MA_EMSCRIPTEN
|
||||
#if !__has_include(<SDL/SDL_audio.h>)
|
||||
#undef MA_HAS_SDL
|
||||
#endif
|
||||
#else
|
||||
#if !__has_include(<SDL2/SDL_audio.h>)
|
||||
#undef MA_HAS_SDL
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
#if defined(MA_HAS_SDL)
|
||||
#define MA_SDL_INIT_AUDIO 0x00000010
|
||||
#define MA_AUDIO_U8 0x0008
|
||||
#define MA_AUDIO_S16 0x8010
|
||||
#define MA_AUDIO_S32 0x8020
|
||||
#define MA_AUDIO_F32 0x8120
|
||||
#define MA_SDL_AUDIO_ALLOW_FREQUENCY_CHANGE 0x00000001
|
||||
#define MA_SDL_AUDIO_ALLOW_FORMAT_CHANGE 0x00000002
|
||||
#define MA_SDL_AUDIO_ALLOW_CHANNELS_CHANGE 0x00000004
|
||||
#define MA_SDL_AUDIO_ALLOW_ANY_CHANGE (MA_SDL_AUDIO_ALLOW_FREQUENCY_CHANGE | MA_SDL_AUDIO_ALLOW_FORMAT_CHANGE | MA_SDL_AUDIO_ALLOW_CHANNELS_CHANGE)
|
||||
|
||||
/* If we are linking at compile time we'll just #include SDL.h. Otherwise we can just redeclare some stuff to avoid the need for development packages to be installed. */
|
||||
#ifdef MA_NO_RUNTIME_LINKING
|
||||
#define SDL_MAIN_HANDLED
|
||||
#ifdef MA_EMSCRIPTEN
|
||||
#include <SDL/SDL.h>
|
||||
#else
|
||||
#include <SDL2/SDL.h>
|
||||
#endif
|
||||
|
||||
typedef SDL_AudioCallback MA_SDL_AudioCallback;
|
||||
typedef SDL_AudioSpec MA_SDL_AudioSpec;
|
||||
typedef SDL_AudioFormat MA_SDL_AudioFormat;
|
||||
typedef SDL_AudioDeviceID MA_SDL_AudioDeviceID;
|
||||
#else
|
||||
typedef void (* MA_SDL_AudioCallback)(void* userdata, ma_uint8* stream, int len);
|
||||
typedef ma_uint16 MA_SDL_AudioFormat;
|
||||
typedef ma_uint32 MA_SDL_AudioDeviceID;
|
||||
|
||||
typedef struct MA_SDL_AudioSpec
|
||||
{
|
||||
int freq;
|
||||
MA_SDL_AudioFormat format;
|
||||
ma_uint8 channels;
|
||||
ma_uint8 silence;
|
||||
ma_uint16 samples;
|
||||
ma_uint16 padding;
|
||||
ma_uint32 size;
|
||||
MA_SDL_AudioCallback callback;
|
||||
void* userdata;
|
||||
} MA_SDL_AudioSpec;
|
||||
#endif
|
||||
|
||||
typedef int (* MA_PFN_SDL_InitSubSystem)(ma_uint32 flags);
|
||||
typedef void (* MA_PFN_SDL_QuitSubSystem)(ma_uint32 flags);
|
||||
typedef int (* MA_PFN_SDL_GetNumAudioDevices)(int iscapture);
|
||||
typedef const char* (* MA_PFN_SDL_GetAudioDeviceName)(int index, int iscapture);
|
||||
typedef void (* MA_PFN_SDL_CloseAudioDevice)(MA_SDL_AudioDeviceID dev);
|
||||
typedef MA_SDL_AudioDeviceID (* MA_PFN_SDL_OpenAudioDevice)(const char* device, int iscapture, const MA_SDL_AudioSpec* desired, MA_SDL_AudioSpec* obtained, int allowed_changes);
|
||||
typedef void (* MA_PFN_SDL_PauseAudioDevice)(MA_SDL_AudioDeviceID dev, int pause_on);
|
||||
|
||||
MA_SDL_AudioFormat ma_format_to_sdl(ma_format format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case ma_format_unknown: return 0;
|
||||
case ma_format_u8: return MA_AUDIO_U8;
|
||||
case ma_format_s16: return MA_AUDIO_S16;
|
||||
case ma_format_s24: return MA_AUDIO_S32; /* Closest match. */
|
||||
case ma_format_s32: return MA_AUDIO_S32;
|
||||
case ma_format_f32: return MA_AUDIO_F32;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
ma_format ma_format_from_sdl(MA_SDL_AudioFormat format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case MA_AUDIO_U8: return ma_format_u8;
|
||||
case MA_AUDIO_S16: return ma_format_s16;
|
||||
case MA_AUDIO_S32: return ma_format_s32;
|
||||
case MA_AUDIO_F32: return ma_format_f32;
|
||||
default: return ma_format_unknown;
|
||||
}
|
||||
}
|
||||
|
||||
static ma_result ma_context_enumerate_devices__sdl(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData)
|
||||
{
|
||||
ma_context_ex* pContextEx = (ma_context_ex*)pContext;
|
||||
ma_bool32 isTerminated = MA_FALSE;
|
||||
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);
|
||||
for (iDevice = 0; iDevice < deviceCount; ++iDevice) {
|
||||
ma_device_info deviceInfo;
|
||||
MA_ZERO_OBJECT(&deviceInfo);
|
||||
|
||||
deviceInfo.id.custom.i = iDevice;
|
||||
ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), ((MA_PFN_SDL_GetAudioDeviceName)pContextEx->sdl.SDL_GetAudioDeviceName)(iDevice, 0), (size_t)-1);
|
||||
|
||||
if (iDevice == 0) {
|
||||
deviceInfo.isDefault = MA_TRUE;
|
||||
}
|
||||
|
||||
cbResult = callback(pContext, ma_device_type_playback, &deviceInfo, pUserData);
|
||||
if (cbResult == MA_FALSE) {
|
||||
isTerminated = MA_TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Capture */
|
||||
if (!isTerminated) {
|
||||
int deviceCount = ((MA_PFN_SDL_GetNumAudioDevices)pContextEx->sdl.SDL_GetNumAudioDevices)(1);
|
||||
for (iDevice = 0; iDevice < deviceCount; ++iDevice) {
|
||||
ma_device_info deviceInfo;
|
||||
MA_ZERO_OBJECT(&deviceInfo);
|
||||
|
||||
deviceInfo.id.custom.i = iDevice;
|
||||
ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), ((MA_PFN_SDL_GetAudioDeviceName)pContextEx->sdl.SDL_GetAudioDeviceName)(iDevice, 1), (size_t)-1);
|
||||
|
||||
if (iDevice == 0) {
|
||||
deviceInfo.isDefault = MA_TRUE;
|
||||
}
|
||||
|
||||
cbResult = callback(pContext, ma_device_type_capture, &deviceInfo, pUserData);
|
||||
if (cbResult == MA_FALSE) {
|
||||
isTerminated = MA_TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
|
||||
static ma_result ma_context_get_device_info__sdl(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_device_info* pDeviceInfo)
|
||||
{
|
||||
ma_context_ex* pContextEx = (ma_context_ex*)pContext;
|
||||
|
||||
#if !defined(__EMSCRIPTEN__)
|
||||
MA_SDL_AudioSpec desiredSpec;
|
||||
MA_SDL_AudioSpec obtainedSpec;
|
||||
MA_SDL_AudioDeviceID tempDeviceID;
|
||||
const char* pDeviceName;
|
||||
#endif
|
||||
|
||||
MA_ASSERT(pContext != NULL);
|
||||
|
||||
if (pDeviceID == NULL) {
|
||||
if (deviceType == ma_device_type_playback) {
|
||||
pDeviceInfo->id.custom.i = 0;
|
||||
ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1);
|
||||
} else {
|
||||
pDeviceInfo->id.custom.i = 0;
|
||||
ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1);
|
||||
}
|
||||
} else {
|
||||
pDeviceInfo->id.custom.i = pDeviceID->custom.i;
|
||||
ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), ((MA_PFN_SDL_GetAudioDeviceName)pContextEx->sdl.SDL_GetAudioDeviceName)(pDeviceID->custom.i, (deviceType == ma_device_type_playback) ? 0 : 1), (size_t)-1);
|
||||
}
|
||||
|
||||
if (pDeviceInfo->id.custom.i == 0) {
|
||||
pDeviceInfo->isDefault = MA_TRUE;
|
||||
}
|
||||
|
||||
/*
|
||||
To get an accurate idea on the backend's native format we need to open the device. Not ideal, but it's the only way. An
|
||||
alternative to this is to report all channel counts, sample rates and formats, but that doesn't offer a good representation
|
||||
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
|
||||
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.
|
||||
*/
|
||||
#if defined(__EMSCRIPTEN__)
|
||||
/* Good practice to prioritize the best format first so that the application can use the first data format as their chosen one if desired. */
|
||||
pDeviceInfo->nativeDataFormatCount = 3;
|
||||
pDeviceInfo->nativeDataFormats[0].format = ma_format_s16;
|
||||
pDeviceInfo->nativeDataFormats[0].channels = 0; /* All channel counts supported. */
|
||||
pDeviceInfo->nativeDataFormats[0].sampleRate = 0; /* All sample rates supported. */
|
||||
pDeviceInfo->nativeDataFormats[0].flags = 0;
|
||||
pDeviceInfo->nativeDataFormats[1].format = ma_format_s32;
|
||||
pDeviceInfo->nativeDataFormats[1].channels = 0; /* All channel counts supported. */
|
||||
pDeviceInfo->nativeDataFormats[1].sampleRate = 0; /* All sample rates supported. */
|
||||
pDeviceInfo->nativeDataFormats[1].flags = 0;
|
||||
pDeviceInfo->nativeDataFormats[2].format = ma_format_u8;
|
||||
pDeviceInfo->nativeDataFormats[2].channels = 0; /* All channel counts supported. */
|
||||
pDeviceInfo->nativeDataFormats[2].sampleRate = 0; /* All sample rates supported. */
|
||||
pDeviceInfo->nativeDataFormats[2].flags = 0;
|
||||
#else
|
||||
MA_ZERO_MEMORY(&desiredSpec, sizeof(desiredSpec));
|
||||
|
||||
pDeviceName = NULL;
|
||||
if (pDeviceID != NULL) {
|
||||
pDeviceName = ((MA_PFN_SDL_GetAudioDeviceName)pContextEx->sdl.SDL_GetAudioDeviceName)(pDeviceID->custom.i, (deviceType == ma_device_type_playback) ? 0 : 1);
|
||||
}
|
||||
|
||||
tempDeviceID = ((MA_PFN_SDL_OpenAudioDevice)pContextEx->sdl.SDL_OpenAudioDevice)(pDeviceName, (deviceType == ma_device_type_playback) ? 0 : 1, &desiredSpec, &obtainedSpec, MA_SDL_AUDIO_ALLOW_ANY_CHANGE);
|
||||
if (tempDeviceID == 0) {
|
||||
ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "Failed to open SDL device.");
|
||||
return MA_FAILED_TO_OPEN_BACKEND_DEVICE;
|
||||
}
|
||||
|
||||
((MA_PFN_SDL_CloseAudioDevice)pContextEx->sdl.SDL_CloseAudioDevice)(tempDeviceID);
|
||||
|
||||
/* Only reporting a single native data format. It'll be whatever SDL decides is the best. */
|
||||
pDeviceInfo->nativeDataFormatCount = 1;
|
||||
pDeviceInfo->nativeDataFormats[0].format = ma_format_from_sdl(obtainedSpec.format);
|
||||
pDeviceInfo->nativeDataFormats[0].channels = obtainedSpec.channels;
|
||||
pDeviceInfo->nativeDataFormats[0].sampleRate = obtainedSpec.freq;
|
||||
pDeviceInfo->nativeDataFormats[0].flags = 0;
|
||||
|
||||
/* If miniaudio does not support the format, just use f32 as the native format (SDL will do the necessary conversions for us). */
|
||||
if (pDeviceInfo->nativeDataFormats[0].format == ma_format_unknown) {
|
||||
pDeviceInfo->nativeDataFormats[0].format = ma_format_f32;
|
||||
}
|
||||
#endif /* __EMSCRIPTEN__ */
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
void ma_audio_callback_capture__sdl(void* pUserData, ma_uint8* pBuffer, int bufferSizeInBytes)
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
||||
void ma_audio_callback_playback__sdl(void* pUserData, ma_uint8* pBuffer, int bufferSizeInBytes)
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
||||
static ma_result ma_device_init_internal__sdl(ma_device_ex* pDeviceEx, const ma_device_config* pConfig, ma_device_descriptor* pDescriptor)
|
||||
{
|
||||
ma_context_ex* pContextEx = (ma_context_ex*)pDeviceEx->device.pContext;
|
||||
MA_SDL_AudioSpec desiredSpec;
|
||||
MA_SDL_AudioSpec obtainedSpec;
|
||||
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
|
||||
the sample rate just set to 0, which indicates that the native sample rate should be used. There's no practical way to calculate this
|
||||
that I can think of right now so I'm just using MA_DEFAULT_SAMPLE_RATE.
|
||||
*/
|
||||
if (pDescriptor->sampleRate == 0) {
|
||||
pDescriptor->sampleRate = MA_DEFAULT_SAMPLE_RATE;
|
||||
}
|
||||
|
||||
/*
|
||||
When determining the period size, you need to take defaults into account. This is how the size of the period should be determined.
|
||||
|
||||
1) If periodSizeInFrames is not 0, use periodSizeInFrames; else
|
||||
2) If periodSizeInMilliseconds is not 0, use periodSizeInMilliseconds; else
|
||||
3) If both periodSizeInFrames and periodSizeInMilliseconds is 0, use the backend's default. If the backend does not allow a default
|
||||
buffer size, use a default value of MA_DEFAULT_PERIOD_SIZE_IN_MILLISECONDS_LOW_LATENCY or
|
||||
MA_DEFAULT_PERIOD_SIZE_IN_MILLISECONDS_CONSERVATIVE depending on the value of pConfig->performanceProfile.
|
||||
|
||||
Note that options 2 and 3 require knowledge of the sample rate in order to convert it to a frame count. You should try to keep the
|
||||
calculation of the period size as accurate as possible, but sometimes it's just not practical so just use whatever you can.
|
||||
|
||||
A helper function called ma_calculate_buffer_size_in_frames_from_descriptor() is available to do all of this for you which is what
|
||||
we'll be using here.
|
||||
*/
|
||||
pDescriptor->periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptor, pDescriptor->sampleRate, pConfig->performanceProfile);
|
||||
|
||||
/* SDL wants the buffer size to be a power of 2 for some reason. */
|
||||
if (pDescriptor->periodSizeInFrames > 32768) {
|
||||
pDescriptor->periodSizeInFrames = 32768;
|
||||
} else {
|
||||
pDescriptor->periodSizeInFrames = ma_next_power_of_2(pDescriptor->periodSizeInFrames);
|
||||
}
|
||||
|
||||
|
||||
/* We now have enough information to set up the device. */
|
||||
MA_ZERO_OBJECT(&desiredSpec);
|
||||
desiredSpec.freq = (int)pDescriptor->sampleRate;
|
||||
desiredSpec.format = ma_format_to_sdl(pDescriptor->format);
|
||||
desiredSpec.channels = (ma_uint8)pDescriptor->channels;
|
||||
desiredSpec.samples = (ma_uint16)pDescriptor->periodSizeInFrames;
|
||||
desiredSpec.callback = (pConfig->deviceType == ma_device_type_capture) ? ma_audio_callback_capture__sdl : ma_audio_callback_playback__sdl;
|
||||
desiredSpec.userdata = pDeviceEx;
|
||||
|
||||
/* We'll fall back to f32 if we don't have an appropriate mapping between SDL and miniaudio. */
|
||||
if (desiredSpec.format == 0) {
|
||||
desiredSpec.format = MA_AUDIO_F32;
|
||||
}
|
||||
|
||||
pDeviceName = NULL;
|
||||
if (pDescriptor->pDeviceID != NULL) {
|
||||
pDeviceName = ((MA_PFN_SDL_GetAudioDeviceName)pContextEx->sdl.SDL_GetAudioDeviceName)(pDescriptor->pDeviceID->custom.i, (pConfig->deviceType == ma_device_type_playback) ? 0 : 1);
|
||||
}
|
||||
|
||||
deviceID = ((MA_PFN_SDL_OpenAudioDevice)pContextEx->sdl.SDL_OpenAudioDevice)(pDeviceName, (pConfig->deviceType == ma_device_type_playback) ? 0 : 1, &desiredSpec, &obtainedSpec, MA_SDL_AUDIO_ALLOW_ANY_CHANGE);
|
||||
if (deviceID == 0) {
|
||||
ma_log_postf(ma_device_get_log((ma_device*)pDeviceEx), MA_LOG_LEVEL_ERROR, "Failed to open SDL2 device.");
|
||||
return MA_FAILED_TO_OPEN_BACKEND_DEVICE;
|
||||
}
|
||||
|
||||
if (pConfig->deviceType == ma_device_type_playback) {
|
||||
pDeviceEx->sdl.deviceIDPlayback = deviceID;
|
||||
} else {
|
||||
pDeviceEx->sdl.deviceIDCapture = deviceID;
|
||||
}
|
||||
|
||||
/* The descriptor needs to be updated with our actual settings. */
|
||||
pDescriptor->format = ma_format_from_sdl(obtainedSpec.format);
|
||||
pDescriptor->channels = obtainedSpec.channels;
|
||||
pDescriptor->sampleRate = (ma_uint32)obtainedSpec.freq;
|
||||
ma_channel_map_init_standard(ma_standard_channel_map_default, pDescriptor->channelMap, ma_countof(pDescriptor->channelMap), pDescriptor->channels);
|
||||
pDescriptor->periodSizeInFrames = obtainedSpec.samples;
|
||||
pDescriptor->periodCount = 1; /* SDL doesn't use the notion of period counts, so just set to 1. */
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
|
||||
static ma_result ma_device_init__sdl(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture)
|
||||
{
|
||||
ma_device_ex* pDeviceEx = (ma_device_ex*)pDevice;
|
||||
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;
|
||||
}
|
||||
|
||||
if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) {
|
||||
result = ma_device_init_internal__sdl(pDeviceEx, pConfig, pDescriptorCapture);
|
||||
if (result != MA_SUCCESS) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) {
|
||||
result = ma_device_init_internal__sdl(pDeviceEx, pConfig, pDescriptorPlayback);
|
||||
if (result != MA_SUCCESS) {
|
||||
if (pConfig->deviceType == ma_device_type_duplex) {
|
||||
((MA_PFN_SDL_CloseAudioDevice)pContextEx->sdl.SDL_CloseAudioDevice)(pDeviceEx->sdl.deviceIDCapture);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) {
|
||||
((MA_PFN_SDL_CloseAudioDevice)pContextEx->sdl.SDL_CloseAudioDevice)(pDeviceEx->sdl.deviceIDCapture);
|
||||
}
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) {
|
||||
((MA_PFN_SDL_PauseAudioDevice)pContextEx->sdl.SDL_PauseAudioDevice)(pDeviceEx->sdl.deviceIDPlayback, 0);
|
||||
}
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) {
|
||||
((MA_PFN_SDL_PauseAudioDevice)pContextEx->sdl.SDL_PauseAudioDevice)(pDeviceEx->sdl.deviceIDPlayback, 1);
|
||||
}
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
|
||||
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. */
|
||||
ma_dlclose(pContext, pContextEx->sdl.hSDL);
|
||||
pContextEx->sdl.hSDL = NULL;
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
|
||||
static ma_result ma_context_init__sdl(ma_context* pContext, const ma_context_config* pConfig, ma_backend_callbacks* pCallbacks)
|
||||
{
|
||||
ma_context_ex* pContextEx = (ma_context_ex*)pContext;
|
||||
int resultSDL;
|
||||
|
||||
#ifndef MA_NO_RUNTIME_LINKING
|
||||
/* We'll use a list of possible shared object names for easier extensibility. */
|
||||
size_t iName;
|
||||
const char* pSDLNames[] = {
|
||||
#if defined(_WIN32)
|
||||
"SDL2.dll"
|
||||
#elif defined(__APPLE__)
|
||||
"SDL2.framework/SDL2"
|
||||
#else
|
||||
"libSDL2-2.0.so.0"
|
||||
#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. */
|
||||
for (iName = 0; iName < ma_countof(pSDLNames); iName += 1) {
|
||||
pContextEx->sdl.hSDL = ma_dlopen(pContext, pSDLNames[iName]);
|
||||
if (pContextEx->sdl.hSDL != NULL) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (pContextEx->sdl.hSDL == NULL) {
|
||||
return MA_NO_BACKEND; /* SDL2 could not be loaded. */
|
||||
}
|
||||
|
||||
/* Now that we have the handle to the shared object we can go ahead and load some function pointers. */
|
||||
pContextEx->sdl.SDL_InitSubSystem = ma_dlsym(pContext, pContextEx->sdl.hSDL, "SDL_InitSubSystem");
|
||||
pContextEx->sdl.SDL_QuitSubSystem = ma_dlsym(pContext, pContextEx->sdl.hSDL, "SDL_QuitSubSystem");
|
||||
pContextEx->sdl.SDL_GetNumAudioDevices = ma_dlsym(pContext, pContextEx->sdl.hSDL, "SDL_GetNumAudioDevices");
|
||||
pContextEx->sdl.SDL_GetAudioDeviceName = ma_dlsym(pContext, pContextEx->sdl.hSDL, "SDL_GetAudioDeviceName");
|
||||
pContextEx->sdl.SDL_CloseAudioDevice = ma_dlsym(pContext, pContextEx->sdl.hSDL, "SDL_CloseAudioDevice");
|
||||
pContextEx->sdl.SDL_OpenAudioDevice = ma_dlsym(pContext, pContextEx->sdl.hSDL, "SDL_OpenAudioDevice");
|
||||
pContextEx->sdl.SDL_PauseAudioDevice = ma_dlsym(pContext, pContextEx->sdl.hSDL, "SDL_PauseAudioDevice");
|
||||
#else
|
||||
pContextEx->sdl.SDL_InitSubSystem = (ma_proc)SDL_InitSubSystem;
|
||||
pContextEx->sdl.SDL_QuitSubSystem = (ma_proc)SDL_QuitSubSystem;
|
||||
pContextEx->sdl.SDL_GetNumAudioDevices = (ma_proc)SDL_GetNumAudioDevices;
|
||||
pContextEx->sdl.SDL_GetAudioDeviceName = (ma_proc)SDL_GetAudioDeviceName;
|
||||
pContextEx->sdl.SDL_CloseAudioDevice = (ma_proc)SDL_CloseAudioDevice;
|
||||
pContextEx->sdl.SDL_OpenAudioDevice = (ma_proc)SDL_OpenAudioDevice;
|
||||
pContextEx->sdl.SDL_PauseAudioDevice = (ma_proc)SDL_PauseAudioDevice;
|
||||
#endif /* MA_NO_RUNTIME_LINKING */
|
||||
|
||||
resultSDL = ((MA_PFN_SDL_InitSubSystem)pContextEx->sdl.SDL_InitSubSystem)(MA_SDL_INIT_AUDIO);
|
||||
if (resultSDL != 0) {
|
||||
ma_dlclose(pContext, pContextEx->sdl.hSDL);
|
||||
return MA_ERROR;
|
||||
}
|
||||
|
||||
/*
|
||||
The last step is to make sure the callbacks are set properly in `pCallbacks`. Internally, miniaudio will copy these callbacks into the
|
||||
context object and then use them for then on for calling into our custom backend.
|
||||
*/
|
||||
pCallbacks->onContextInit = ma_context_init__sdl;
|
||||
pCallbacks->onContextUninit = ma_context_uninit__sdl;
|
||||
pCallbacks->onContextEnumerateDevices = ma_context_enumerate_devices__sdl;
|
||||
pCallbacks->onContextGetDeviceInfo = ma_context_get_device_info__sdl;
|
||||
pCallbacks->onDeviceInit = ma_device_init__sdl;
|
||||
pCallbacks->onDeviceUninit = ma_device_uninit__sdl;
|
||||
pCallbacks->onDeviceStart = ma_device_start__sdl;
|
||||
pCallbacks->onDeviceStop = ma_device_stop__sdl;
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
#endif /* MA_HAS_SDL */
|
||||
|
||||
|
||||
/*
|
||||
This is our custom backend "loader". All this does is attempts to initialize our custom backends in the order they are listed. The first
|
||||
one to successfully initialize is the one that's chosen. In this example we're just listing them statically, but you can use whatever logic
|
||||
you want to handle backend selection.
|
||||
|
||||
This is used as the onContextInit() callback in the context config.
|
||||
*/
|
||||
static ma_result ma_context_init__custom_loader(ma_context* pContext, const ma_context_config* pConfig, ma_backend_callbacks* pCallbacks)
|
||||
{
|
||||
ma_result result = MA_NO_BACKEND;
|
||||
|
||||
/* Silence some unused parameter warnings just in case no custom backends are enabled. */
|
||||
(void)pContext;
|
||||
(void)pCallbacks;
|
||||
|
||||
/* SDL. */
|
||||
#if !defined(MA_NO_SDL)
|
||||
if (result != MA_SUCCESS) {
|
||||
result = ma_context_init__sdl(pContext, pConfig, pCallbacks);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* ... plug in any other custom backends here ... */
|
||||
|
||||
/* If we have a success result we have initialized a backend. Otherwise we need to tell miniaudio about the error so it can skip over our custom backends. */
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Main program starts here.
|
||||
@@ -641,15 +33,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) {
|
||||
@@ -660,36 +45,36 @@ void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uin
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
ma_result result;
|
||||
ma_context_config contextConfig;
|
||||
ma_context_ex context;
|
||||
ma_context context;
|
||||
ma_device_config deviceConfig;
|
||||
ma_device_ex device;
|
||||
ma_device device;
|
||||
ma_waveform_config sineWaveConfig;
|
||||
ma_waveform sineWave;
|
||||
char name[256];
|
||||
|
||||
/*
|
||||
We're just using ma_backend_custom in this example for demonstration purposes, but a more realistic use case would probably want to include
|
||||
other backends as well for robustness.
|
||||
Here is where we would set up the SDL-specific context-level config. The custom SDL backend allows this to be null, but we're
|
||||
defining it here just for the sake of demonstration. Whether or not this is required depends on the backend. If you're not sure,
|
||||
check the documentation for the backend.
|
||||
*/
|
||||
ma_backend backends[] = {
|
||||
ma_backend_custom
|
||||
ma_context_config_sdl2 sdl2ContextConfig = ma_context_config_sdl2_init();
|
||||
sdl2ContextConfig._unused = 0;
|
||||
|
||||
/*
|
||||
You must include an entry for each backend you're using, even if the config is NULL. This is how miniaudio knows about
|
||||
your custom backend.
|
||||
|
||||
For stock backends, you can just leave the config pointer as NULL and fill out any backend-specific config options in
|
||||
the ma_context_config structure. Same with device configs.
|
||||
*/
|
||||
ma_device_backend_config backends[] =
|
||||
{
|
||||
{ ma_device_backend_sdl2, &sdl2ContextConfig },
|
||||
{ ma_device_backend_wasapi, NULL },
|
||||
{ ma_device_backend_pulseaudio, NULL }
|
||||
};
|
||||
|
||||
/*
|
||||
To implement a custom backend you need to implement the callbacks in the "custom" member of the context config. The only mandatory
|
||||
callback required at this point is the onContextInit() callback. If you do not set the other callbacks, you must set them in
|
||||
onContextInit() by setting them on the `pCallbacks` parameter.
|
||||
|
||||
The way we're doing it in this example enables us to easily plug in multiple custom backends. What we do is set the onContextInit()
|
||||
callback to a generic "loader" function (ma_context_init__custom_loader() in this example), which then calls out to backend-specific
|
||||
context initialization routines, one of which will be for SDL. That way, if for example we wanted to add support for another backend,
|
||||
we don't need to touch this part of the code. Instead we add logic to ma_context_init__custom_loader() to choose the most appropriate
|
||||
custom backend. That will then fill out the other callbacks appropriately.
|
||||
*/
|
||||
contextConfig = ma_context_config_init();
|
||||
contextConfig.custom.onContextInit = ma_context_init__custom_loader;
|
||||
|
||||
result = ma_context_init(backends, sizeof(backends)/sizeof(backends[0]), &contextConfig, (ma_context*)&context);
|
||||
result = ma_context_init(backends, sizeof(backends)/sizeof(backends[0]), NULL, &context);
|
||||
if (result != MA_SUCCESS) {
|
||||
return -1;
|
||||
}
|
||||
@@ -698,28 +83,49 @@ int main(int argc, char** argv)
|
||||
sineWaveConfig = ma_waveform_config_init(DEVICE_FORMAT, DEVICE_CHANNELS, DEVICE_SAMPLE_RATE, ma_waveform_type_sine, 0.2, 220);
|
||||
ma_waveform_init(&sineWaveConfig, &sineWave);
|
||||
|
||||
/* The device is created exactly as per normal. */
|
||||
deviceConfig = ma_device_config_init(ma_device_type_playback);
|
||||
deviceConfig.playback.format = DEVICE_FORMAT;
|
||||
deviceConfig.playback.channels = DEVICE_CHANNELS;
|
||||
deviceConfig.capture.format = DEVICE_FORMAT;
|
||||
deviceConfig.capture.channels = DEVICE_CHANNELS;
|
||||
deviceConfig.sampleRate = DEVICE_SAMPLE_RATE;
|
||||
deviceConfig.dataCallback = data_callback;
|
||||
deviceConfig.pUserData = &sineWave;
|
||||
|
||||
result = ma_device_init((ma_context*)&context, &deviceConfig, (ma_device*)&device);
|
||||
|
||||
/*
|
||||
Just like with context configs, we can define some device-level configs as well. It works the same way, except you will pass in
|
||||
a backend-specific device-level config. If the backend doesn't require a device-level config, you can set this to NULL.
|
||||
*/
|
||||
ma_device_config_sdl2 sdl2DeviceConfig = ma_device_config_sdl2_init();
|
||||
sdl2DeviceConfig._unused = 0;
|
||||
|
||||
/*
|
||||
Unlike with contexts, if your backend does not require a device-level config, you can just leave it out of this list entirely.
|
||||
*/
|
||||
ma_device_backend_config pBackendDeviceConfigs[] =
|
||||
{
|
||||
{ ma_device_backend_sdl2, &sdl2DeviceConfig },
|
||||
{ ma_device_backend_wasapi, NULL },
|
||||
{ ma_device_backend_pulseaudio, NULL }
|
||||
};
|
||||
|
||||
deviceConfig = ma_device_config_init(ma_device_type_playback);
|
||||
deviceConfig.playback.format = DEVICE_FORMAT;
|
||||
deviceConfig.playback.channels = DEVICE_CHANNELS;
|
||||
deviceConfig.capture.format = DEVICE_FORMAT;
|
||||
deviceConfig.capture.channels = DEVICE_CHANNELS;
|
||||
deviceConfig.sampleRate = DEVICE_SAMPLE_RATE;
|
||||
deviceConfig.dataCallback = data_callback;
|
||||
deviceConfig.pUserData = &sineWave;
|
||||
deviceConfig.pBackendConfigs = pBackendDeviceConfigs;
|
||||
deviceConfig.backendConfigCount = sizeof(pBackendDeviceConfigs) / sizeof(pBackendDeviceConfigs[0]);
|
||||
|
||||
result = ma_device_init(&context, &deviceConfig, &device);
|
||||
if (result != MA_SUCCESS) {
|
||||
ma_context_uninit((ma_context*)&context);
|
||||
ma_context_uninit(&context);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
printf("Device Name: %s\n", ((ma_device*)&device)->playback.name);
|
||||
ma_device_get_name(&device, ma_device_type_playback, name, sizeof(name), NULL);
|
||||
printf("Device Name: %s\n", name);
|
||||
|
||||
if (ma_device_start((ma_device*)&device) != MA_SUCCESS) {
|
||||
ma_device_uninit((ma_device*)&device);
|
||||
ma_context_uninit((ma_context*)&context);
|
||||
if (ma_device_start(&device) != MA_SUCCESS) {
|
||||
ma_device_uninit(&device);
|
||||
ma_context_uninit(&context);
|
||||
return -5;
|
||||
}
|
||||
|
||||
@@ -729,12 +135,15 @@ int main(int argc, char** argv)
|
||||
printf("Press Enter to quit...\n");
|
||||
getchar();
|
||||
#endif
|
||||
|
||||
ma_device_uninit((ma_device*)&device);
|
||||
ma_context_uninit((ma_context*)&context);
|
||||
|
||||
ma_device_uninit(&device);
|
||||
ma_context_uninit(&context);
|
||||
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* We put the SDL implementation here just to simplify the compilation process. This way you need only compile custom_backend.c. */
|
||||
#include "../extras/backends/sdl2/miniaudio_sdl2.c"
|
||||
|
||||
+30
-169
@@ -1,11 +1,20 @@
|
||||
/*
|
||||
Demonstrates how to implement a custom decoder.
|
||||
Demonstrates how to plug in custom decoders.
|
||||
|
||||
This example implements two custom decoders:
|
||||
|
||||
* Vorbis via libvorbis
|
||||
* Opus via libopus
|
||||
|
||||
The files miniaudio_libvorbis.h and miniaudio_libopus.h are where the custom decoders are implemented.
|
||||
Refer to these files for an example of how you can implement your own custom decoders.
|
||||
|
||||
To wire up your custom decoders to the `ma_decoder` API, you need to set up a `ma_decoder_config`
|
||||
object and fill out the `ppBackendVTables` and `backendCount` members. The `ppBackendVTables` member
|
||||
is an array of pointers to `ma_decoding_backend_vtable` objects. The order of the array defines the
|
||||
order of priority, with the first being the highest priority. The `backendCount` member is the number
|
||||
of items in the `ppBackendVTables` array.
|
||||
|
||||
A custom decoder must implement a data source. In this example, the libvorbis data source is called
|
||||
`ma_libvorbis` and the Opus data source is called `ma_libopus`. These two objects are compatible
|
||||
with the `ma_data_source` APIs and can be taken straight from this example and used in real code.
|
||||
@@ -15,168 +24,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)
|
||||
{
|
||||
ma_data_source* pDataSource = (ma_data_source*)pDevice->pUserData;
|
||||
@@ -201,13 +54,22 @@ int main(int argc, char** argv)
|
||||
ma_uint32 sampleRate;
|
||||
|
||||
/*
|
||||
Add your custom backend vtables here. The order in the array defines the order of priority. The
|
||||
vtables will be passed in via the decoder config.
|
||||
Add your custom backend vtables here. The order in the array defines the order of priority, with the
|
||||
first being the highest priority. The vtables are be passed in via the decoder config. If you want to
|
||||
support stock backends in addition to custom backends, you must add the stock backend vtables here as
|
||||
well. You should list the backends in your preferred order of priority.
|
||||
|
||||
The list below shows how you would set up your array to prioritize the custom decoders over the stock
|
||||
decoders. If you want to prioritize the stock decoders over the custom decoders, you would simply
|
||||
change the order.
|
||||
*/
|
||||
ma_decoding_backend_vtable* pCustomBackendVTables[] =
|
||||
ma_decoding_backend_vtable* pBackendVTables[] =
|
||||
{
|
||||
&g_ma_decoding_backend_vtable_libvorbis,
|
||||
&g_ma_decoding_backend_vtable_libopus
|
||||
ma_decoding_backend_libvorbis,
|
||||
ma_decoding_backend_libopus,
|
||||
ma_decoding_backend_wav,
|
||||
ma_decoding_backend_flac,
|
||||
ma_decoding_backend_mp3
|
||||
};
|
||||
|
||||
|
||||
@@ -219,9 +81,8 @@ 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.ppCustomBackendVTables = pCustomBackendVTables;
|
||||
decoderConfig.customBackendCount = sizeof(pCustomBackendVTables) / sizeof(pCustomBackendVTables[0]);
|
||||
decoderConfig.ppBackendVTables = pBackendVTables;
|
||||
decoderConfig.backendCount = sizeof(pBackendVTables) / sizeof(pBackendVTables[0]);
|
||||
|
||||
result = ma_decoder_init_file(argv[1], &decoderConfig, &decoder);
|
||||
if (result != MA_SUCCESS) {
|
||||
@@ -267,4 +128,4 @@ int main(int argc, char** argv)
|
||||
ma_decoder_uninit(&decoder);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,167 +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)
|
||||
{
|
||||
ma_result result;
|
||||
@@ -175,13 +20,22 @@ int main(int argc, char** argv)
|
||||
ma_engine engine;
|
||||
|
||||
/*
|
||||
Add your custom backend vtables here. The order in the array defines the order of priority. The
|
||||
vtables will be passed in to the resource manager config.
|
||||
Add your custom backend vtables here. The order in the array defines the order of priority, with the
|
||||
first being the highest priority. The vtables are be passed in via the decoder config. If you want to
|
||||
support stock backends in addition to custom backends, you must add the stock backend vtables here as
|
||||
well. You should list the backends in your preferred order of priority.
|
||||
|
||||
The list below shows how you would set up your array to prioritize the custom decoders over the stock
|
||||
decoders. If you want to prioritize the stock decoders over the custom decoders, you would simply
|
||||
change the order.
|
||||
*/
|
||||
ma_decoding_backend_vtable* pCustomBackendVTables[] =
|
||||
ma_decoding_backend_vtable* pBackendVTables[] =
|
||||
{
|
||||
&g_ma_decoding_backend_vtable_libvorbis,
|
||||
&g_ma_decoding_backend_vtable_libopus
|
||||
ma_decoding_backend_libvorbis,
|
||||
ma_decoding_backend_libopus,
|
||||
ma_decoding_backend_wav,
|
||||
ma_decoding_backend_flac,
|
||||
ma_decoding_backend_mp3
|
||||
};
|
||||
|
||||
|
||||
@@ -193,9 +47,8 @@ int main(int argc, char** argv)
|
||||
|
||||
/* Using custom decoding backends requires a resource manager. */
|
||||
resourceManagerConfig = ma_resource_manager_config_init();
|
||||
resourceManagerConfig.ppCustomDecodingBackendVTables = pCustomBackendVTables;
|
||||
resourceManagerConfig.customDecodingBackendCount = sizeof(pCustomBackendVTables) / sizeof(pCustomBackendVTables[0]);
|
||||
resourceManagerConfig.pCustomDecodingBackendUserData = NULL; /* <-- This will be passed in to the pUserData parameter of each function in the decoding backend vtables. */
|
||||
resourceManagerConfig.ppDecodingBackendVTables = pBackendVTables;
|
||||
resourceManagerConfig.decodingBackendCount = sizeof(pBackendVTables) / sizeof(pBackendVTables[0]);
|
||||
|
||||
result = ma_resource_manager_init(&resourceManagerConfig, &resourceManager);
|
||||
if (result != MA_SUCCESS) {
|
||||
@@ -226,5 +79,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;
|
||||
}
|
||||
}
|
||||
|
||||
+21
-14
@@ -6,26 +6,30 @@ 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. */
|
||||
static ma_audio_buffer_ref g_exciteData; /* The underlying data source of the excite node. */
|
||||
static ma_data_source_node g_sourceNode; /* A data source node containing the source data we'll be sending through to the vocoder. This will be routed into the first bus of the vocoder node. */
|
||||
static ma_data_source_node g_exciteNode; /* A data source node containing the excite data we'll be sending through to the vocoder. This will be routed into the second bus of the vocoder node. */
|
||||
static ma_vocoder_node g_vocoderNode; /* The vocoder node. */
|
||||
static ma_node_graph g_nodeGraph;
|
||||
static ma_waveform g_sourceData; /* The underlying data source of the source node. */
|
||||
static ma_audio_ring_buffer g_exciteData; /* The underlying data source of the excite node. */
|
||||
static ma_data_source_node g_sourceNode; /* A data source node containing the source data we'll be sending through to the vocoder. This will be routed into the first bus of the vocoder node. */
|
||||
static ma_data_source_node g_exciteNode; /* A data source node containing the excite data we'll be sending through to the vocoder. This will be routed into the second bus of the vocoder node. */
|
||||
static ma_vocoder_node g_vocoderNode; /* The vocoder node. */
|
||||
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
|
||||
@@ -33,7 +37,7 @@ void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uin
|
||||
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_exciteData, pInput, frameCount);
|
||||
ma_audio_ring_buffer_write_pcm_frames(&g_exciteData, pInput, frameCount, NULL);
|
||||
|
||||
/* 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);
|
||||
@@ -49,6 +53,7 @@ int main(int argc, char** argv)
|
||||
ma_data_source_node_config sourceNodeConfig;
|
||||
ma_data_source_node_config exciteNodeConfig;
|
||||
ma_waveform_config waveformConfig;
|
||||
ma_audio_ring_buffer_config ringBufferConfig;
|
||||
|
||||
deviceConfig = ma_device_config_init(ma_device_type_duplex);
|
||||
deviceConfig.capture.pDeviceID = NULL;
|
||||
@@ -111,7 +116,9 @@ int main(int argc, char** argv)
|
||||
|
||||
|
||||
/* Excite/modulator. Attached to input bus 1 of the vocoder node. */
|
||||
result = ma_audio_buffer_ref_init(device.capture.format, device.capture.channels, NULL, 0, &g_exciteData);
|
||||
ringBufferConfig = ma_audio_ring_buffer_config_init(device.capture.format, device.capture.channels, device.sampleRate, device.capture.internalPeriodSizeInFrames * 3);
|
||||
|
||||
result = ma_audio_ring_buffer_init(&ringBufferConfig, &g_exciteData);
|
||||
if (result != MA_SUCCESS) {
|
||||
printf("Failed to initialize audio buffer for source.");
|
||||
goto done2;
|
||||
@@ -139,7 +146,7 @@ int main(int argc, char** argv)
|
||||
/*done4:*/ ma_data_source_node_uninit(&g_exciteNode, NULL);
|
||||
done3: ma_data_source_node_uninit(&g_sourceNode, NULL);
|
||||
done2: ma_vocoder_node_uninit(&g_vocoderNode, NULL);
|
||||
done1: ma_node_graph_uninit(&g_nodeGraph, NULL);
|
||||
done1: ma_node_graph_uninit(&g_nodeGraph);
|
||||
done0: ma_device_uninit(&device);
|
||||
|
||||
(void)argc;
|
||||
|
||||
@@ -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;
|
||||
@@ -179,7 +183,7 @@ MA_API ma_result ma_steamaudio_binaural_node_init(ma_node_graph* pNodeGraph, con
|
||||
channelsOut = 2; /* Always stereo output. */
|
||||
|
||||
baseConfig = ma_node_config_init();
|
||||
baseConfig.vtable = &g_ma_steamaudio_binaural_node_vtable;
|
||||
baseConfig.pVTable = &g_ma_steamaudio_binaural_node_vtable;
|
||||
baseConfig.pInputChannels = &channelsIn;
|
||||
baseConfig.pOutputChannels = &channelsOut;
|
||||
result = ma_node_init(pNodeGraph, &baseConfig, pAllocationCallbacks, &pBinauralNode->baseNode);
|
||||
@@ -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) {
|
||||
@@ -340,7 +356,7 @@ int main(int argc, char** argv)
|
||||
{
|
||||
ma_sound_config soundConfig;
|
||||
|
||||
soundConfig = ma_sound_config_init();
|
||||
soundConfig = ma_sound_config_init(&g_engine);
|
||||
soundConfig.pFilePath = argv[1];
|
||||
soundConfig.flags = MA_SOUND_FLAG_NO_DEFAULT_ATTACHMENT; /* We'll attach this to the graph later. */
|
||||
|
||||
@@ -429,4 +445,4 @@ int main(int argc, char** argv)
|
||||
ma_engine_uninit(&g_engine);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
+13
-44
@@ -15,50 +15,27 @@ 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_audio_ring_buffer rb;
|
||||
static ma_device device;
|
||||
static ma_engine engine;
|
||||
static ma_sound sound; /* The sound will be the playback of the capture side. */
|
||||
|
||||
void capture_data_callback(ma_device* pDevice, void* pFramesOut, const void* pFramesIn, ma_uint32 frameCount)
|
||||
{
|
||||
ma_result result;
|
||||
ma_uint32 framesWritten;
|
||||
(void)pFramesOut;
|
||||
(void)pDevice;
|
||||
|
||||
/* We need to write to the ring buffer. Need to do this in a loop. */
|
||||
framesWritten = 0;
|
||||
while (framesWritten < frameCount) {
|
||||
void* pMappedBuffer;
|
||||
ma_uint32 framesToWrite = frameCount - framesWritten;
|
||||
|
||||
result = ma_pcm_rb_acquire_write(&rb, &framesToWrite, &pMappedBuffer);
|
||||
if (result != MA_SUCCESS) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (framesToWrite == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* 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);
|
||||
|
||||
result = ma_pcm_rb_commit_write(&rb, framesToWrite);
|
||||
if (result != MA_SUCCESS) {
|
||||
break;
|
||||
}
|
||||
|
||||
framesWritten += framesToWrite;
|
||||
}
|
||||
/* We need to write to the ring buffer. */
|
||||
ma_audio_ring_buffer_write_pcm_frames(&rb, pFramesIn, frameCount, NULL);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
ma_result result;
|
||||
ma_device_config deviceConfig;
|
||||
ma_audio_ring_buffer_config ringBufferConfig;
|
||||
|
||||
/*
|
||||
The first thing we'll do is set up the capture side. There are two parts to this. The first is
|
||||
@@ -82,20 +59,15 @@ int main(int argc, char** argv)
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Initialize the ring buffer. */
|
||||
result = ma_pcm_rb_init(device.capture.format, device.capture.channels, device.capture.internalPeriodSizeInFrames * 5, NULL, NULL, &rb);
|
||||
/* Initialize the ring buffer. Make sure the sample rate is set so the engine can resample it if necessary. */
|
||||
ringBufferConfig = ma_audio_ring_buffer_config_init(device.capture.format, device.capture.channels, device.sampleRate, device.capture.internalPeriodSizeInFrames * 3);
|
||||
|
||||
result = ma_audio_ring_buffer_init(&ringBufferConfig, &rb);
|
||||
if (result != MA_SUCCESS) {
|
||||
printf("Failed to initialize the ring buffer.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
Ring buffers don't require a sample rate for their normal operation, but we can associate it
|
||||
with a sample rate. We'll want to do this so the engine can resample if necessary.
|
||||
*/
|
||||
ma_pcm_rb_set_sample_rate(&rb, device.sampleRate);
|
||||
|
||||
|
||||
|
||||
/*
|
||||
At this point the capture side is set up and we can now set up the playback side. Here we are
|
||||
@@ -119,15 +91,12 @@ int main(int argc, char** argv)
|
||||
ring buffer. The capture side will be writing data into the ring buffer, whereas the sound
|
||||
will be reading from it.
|
||||
*/
|
||||
result = ma_sound_init_from_data_source(&engine, &rb, 0, NULL, &sound);
|
||||
result = ma_sound_init_from_data_source(&engine, &rb, 0, NULL, NULL, &sound);
|
||||
if (result != MA_SUCCESS) {
|
||||
printf("Failed to initialize the sound.");
|
||||
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);
|
||||
@@ -139,7 +108,7 @@ int main(int argc, char** argv)
|
||||
ma_sound_uninit(&sound);
|
||||
ma_engine_uninit(&engine);
|
||||
ma_device_uninit(&device);
|
||||
ma_pcm_rb_uninit(&rb);
|
||||
ma_audio_ring_buffer_uninit(&rb);
|
||||
|
||||
|
||||
(void)argc;
|
||||
|
||||
@@ -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)
|
||||
@@ -243,7 +241,7 @@ cleanup_graph:
|
||||
ma_lpf_node_uninit(&g_lpfNode, NULL);
|
||||
|
||||
/* Node Graph */
|
||||
ma_node_graph_uninit(&g_nodeGraph, NULL);
|
||||
ma_node_graph_uninit(&g_nodeGraph);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -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,16 +154,19 @@ 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;
|
||||
ma_resource_manager_job job;
|
||||
ma_job job;
|
||||
|
||||
/*
|
||||
Retrieve a job from the queue first. This defines what it is you're about to do. By default this will be
|
||||
@@ -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>
|
||||
|
||||
@@ -35,14 +34,14 @@ int main(int argc, char** argv)
|
||||
|
||||
printf("Playback Devices\n");
|
||||
for (iDevice = 0; iDevice < playbackDeviceCount; ++iDevice) {
|
||||
printf(" %u: %s\n", iDevice, pPlaybackDeviceInfos[iDevice].name);
|
||||
printf(" %u: %s%s\n", iDevice, pPlaybackDeviceInfos[iDevice].name, (pPlaybackDeviceInfos[iDevice].isDefault) ? " [Default]" : "");
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
|
||||
printf("Capture Devices\n");
|
||||
for (iDevice = 0; iDevice < captureDeviceCount; ++iDevice) {
|
||||
printf(" %u: %s\n", iDevice, pCaptureDeviceInfos[iDevice].name);
|
||||
printf(" %u: %s%s\n", iDevice, pCaptureDeviceInfos[iDevice].name, (pPlaybackDeviceInfos[iDevice].isDefault) ? " [Default]" : "");
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -35,8 +31,8 @@ int main(int argc, char** argv)
|
||||
ma_device device;
|
||||
|
||||
/* Loopback mode is currently only supported on WASAPI. */
|
||||
ma_backend backends[] = {
|
||||
ma_backend_wasapi
|
||||
ma_device_backend_config backends[] = {
|
||||
{ ma_device_backend_wasapi, NULL }
|
||||
};
|
||||
|
||||
if (argc < 2) {
|
||||
|
||||
@@ -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. */
|
||||
}
|
||||
@@ -64,8 +56,6 @@ int main(int argc, char** argv)
|
||||
return -4;
|
||||
}
|
||||
|
||||
printf("Device Name: %s\n", device.playback.name);
|
||||
|
||||
sineWaveConfig = ma_waveform_config_init(device.playback.format, device.playback.channels, device.sampleRate, ma_waveform_type_sine, 0.2, 220);
|
||||
ma_waveform_init(&sineWaveConfig, &sineWave);
|
||||
|
||||
@@ -83,6 +73,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
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
Demonstrates the use of single-threaded mode.
|
||||
|
||||
By default, miniaudio runs in multi-threaded mode where audio processing is done on a separate
|
||||
thread that's managed internally by miniaudio. Sometimes this is undesireable, such as when trying
|
||||
to get miniaudio working on low-end systems where an extra thread is too costly, or when trying to
|
||||
get it working on extremely old platforms that don't support threading at all, or simply when you
|
||||
want to have more control over threading in your application.
|
||||
|
||||
To enable single-threaded mode, set the `threadingMode` member of the `ma_device_config` structure
|
||||
to `MA_THREADING_MODE_SINGLE_THREADED`. To process audio, you need to regularly call
|
||||
`ma_device_step()`, usually from your main application loop. It is from this function that the
|
||||
data callback will get fired. You should only call `ma_device_step()` when the device is started,
|
||||
so typically you would do this between your `ma_device_start()` and `ma_device_stop()` calls.
|
||||
|
||||
The `ma_device_step()` function lets you control whether or not it should block while waiting for
|
||||
audio to be processed. This is controlled via the `blockingMode` parameter. You would typically
|
||||
use `MA_BLOCKING_MODE_BLOCKING` if you want to relax the CPU. For a game you would probably want to
|
||||
use `MA_BLOCKING_MODE_NON_BLOCKING`.
|
||||
|
||||
You should only ever call `ma_device_step()` in single-threaded mode. In multi-threaded mode (the
|
||||
default), you should never call this function manually. You can query whether or not the device is
|
||||
in single-threaded mode via `ma_device_get_threading_mode()`.
|
||||
|
||||
When in single-threaded mode, you should ensure that all `ma_device` API calls are made from the same
|
||||
thread. This is due to some backends that require it, such as WASAPI.
|
||||
*/
|
||||
#include "../miniaudio.c"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <emscripten.h>
|
||||
|
||||
void main_loop__em(void* pUserData)
|
||||
{
|
||||
ma_device* pDevice = (ma_device*)pUserData;
|
||||
ma_device_step(pDevice, MA_BLOCKING_MODE_NON_BLOCKING);
|
||||
}
|
||||
#endif
|
||||
|
||||
#define DEVICE_FORMAT ma_format_f32
|
||||
#define DEVICE_CHANNELS 2
|
||||
#define DEVICE_SAMPLE_RATE 48000
|
||||
|
||||
void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount)
|
||||
{
|
||||
ma_waveform_read_pcm_frames((ma_waveform*)pDevice->pUserData, pOutput, frameCount, NULL);
|
||||
|
||||
(void)pInput; /* Unused. */
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
ma_waveform sineWave;
|
||||
ma_device_config deviceConfig;
|
||||
ma_device device;
|
||||
ma_waveform_config sineWaveConfig;
|
||||
|
||||
deviceConfig = ma_device_config_init(ma_device_type_playback);
|
||||
deviceConfig.threadingMode = MA_THREADING_MODE_SINGLE_THREADED; /* <-- This is what enables single-threaded mode. */
|
||||
deviceConfig.playback.format = DEVICE_FORMAT;
|
||||
deviceConfig.playback.channels = DEVICE_CHANNELS;
|
||||
deviceConfig.sampleRate = DEVICE_SAMPLE_RATE;
|
||||
deviceConfig.dataCallback = data_callback;
|
||||
deviceConfig.pUserData = &sineWave;
|
||||
|
||||
if (ma_device_init(NULL, &deviceConfig, &device) != MA_SUCCESS) {
|
||||
printf("Failed to open playback device.\n");
|
||||
return -4;
|
||||
}
|
||||
|
||||
sineWaveConfig = ma_waveform_config_init(device.playback.format, device.playback.channels, device.sampleRate, ma_waveform_type_sine, 0.2, 220);
|
||||
ma_waveform_init(&sineWaveConfig, &sineWave);
|
||||
|
||||
if (ma_device_start(&device) != MA_SUCCESS) {
|
||||
printf("Failed to start playback device.\n");
|
||||
ma_device_uninit(&device);
|
||||
return -5;
|
||||
}
|
||||
|
||||
printf("Running in single-threaded mode. Press Ctrl+C to quit.\n");
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
emscripten_set_main_loop_arg(main_loop__em, &device, 0, 1);
|
||||
#else
|
||||
/*
|
||||
We're putting this in an infinite loop for the sake of this example, but in a real application you
|
||||
would probably integrate this into your normal application loop.
|
||||
|
||||
Using blocking mode makes it so the CPU is relaxed. For a game you would probably want to use
|
||||
non-blocking mode which you can do with `MA_BLOCKING_MODE_NON_BLOCKING`.
|
||||
|
||||
If the device is stopped, `ma_device_step()` will return `MA_DEVICE_NOT_STARTED` which means you
|
||||
need not explicitly check if the device is started before calling this function.
|
||||
*/
|
||||
for (;;) {
|
||||
ma_result result = ma_device_step(&device, MA_BLOCKING_MODE_BLOCKING);
|
||||
if (result != MA_SUCCESS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
#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;
|
||||
return 0;
|
||||
}
|
||||
Vendored
+10936
File diff suppressed because it is too large
Load Diff
Vendored
+3757
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,729 @@
|
||||
/*
|
||||
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_SDL2, 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.
|
||||
*/
|
||||
#ifndef miniaudio_backend_sdl2_c
|
||||
#define miniaudio_backend_sdl2_c
|
||||
|
||||
/* Include miniaudio.h if we're not including this file after the implementation. */
|
||||
#if !defined(MINIAUDIO_IMPLEMENTATION) && !defined(MA_IMPLEMENTATION)
|
||||
#include "../../../miniaudio.h"
|
||||
#endif
|
||||
|
||||
#include "miniaudio_sdl2.h"
|
||||
|
||||
#include <string.h> /* memset() */
|
||||
#include <assert.h>
|
||||
|
||||
#ifndef MA_SDL2_ASSERT
|
||||
#define MA_SDL2_ASSERT(cond) assert(cond)
|
||||
#endif
|
||||
|
||||
/* Support SDL on everything. */
|
||||
#define MA_SUPPORT_SDL2
|
||||
|
||||
/*
|
||||
Only enable SDL if it's hasn't been explicitly disabled (MA_NO_SDL2) or enabled (MA_ENABLE_SDL with
|
||||
MA_ENABLE_ONLY_SPECIFIC_BACKENDS) and it's supported at compile time (MA_SUPPORT_SDL).
|
||||
*/
|
||||
#if defined(MA_SUPPORT_SDL2) && !defined(MA_NO_SDL2) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_SDL2))
|
||||
#define MA_HAS_SDL2
|
||||
#endif
|
||||
|
||||
/* SDL headers are necessary if using compile-time linking. Necessary for Emscripten. */
|
||||
#if defined(MA_HAS_SDL2)
|
||||
#if defined(MA_NO_RUNTIME_LINKING) || defined(MA_EMSCRIPTEN)
|
||||
#ifdef __has_include
|
||||
#ifdef MA_EMSCRIPTEN
|
||||
#if !__has_include(<SDL/SDL_audio.h>)
|
||||
#undef MA_HAS_SDL2
|
||||
#endif
|
||||
#else
|
||||
#if !__has_include(<SDL2/SDL_audio.h>)
|
||||
#undef MA_HAS_SDL2
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* Don't compile in the SDL backend if it's been disabled. */
|
||||
#if defined(MA_HAS_SDL2)
|
||||
#define MA_SDL_INIT_AUDIO 0x00000010
|
||||
#define MA_AUDIO_U8 0x0008
|
||||
#define MA_AUDIO_S16 0x8010
|
||||
#define MA_AUDIO_S32 0x8020
|
||||
#define MA_AUDIO_F32 0x8120
|
||||
#define MA_SDL_AUDIO_ALLOW_FREQUENCY_CHANGE 0x00000001
|
||||
#define MA_SDL_AUDIO_ALLOW_FORMAT_CHANGE 0x00000002
|
||||
#define MA_SDL_AUDIO_ALLOW_CHANNELS_CHANGE 0x00000004
|
||||
#define MA_SDL_AUDIO_ALLOW_ANY_CHANGE (MA_SDL_AUDIO_ALLOW_FREQUENCY_CHANGE | MA_SDL_AUDIO_ALLOW_FORMAT_CHANGE | MA_SDL_AUDIO_ALLOW_CHANNELS_CHANGE)
|
||||
|
||||
/* If we are linking at compile time we'll just #include SDL.h. Otherwise we can just redeclare some stuff to avoid the need for development packages to be installed. */
|
||||
#ifdef MA_NO_RUNTIME_LINKING
|
||||
#define SDL_MAIN_HANDLED
|
||||
#ifdef MA_EMSCRIPTEN
|
||||
#include <SDL/SDL.h>
|
||||
#else
|
||||
#include <SDL2/SDL.h>
|
||||
#endif
|
||||
|
||||
typedef SDL_AudioCallback MA_SDL_AudioCallback;
|
||||
typedef SDL_AudioSpec MA_SDL_AudioSpec;
|
||||
typedef SDL_AudioFormat MA_SDL_AudioFormat;
|
||||
typedef SDL_AudioDeviceID MA_SDL_AudioDeviceID;
|
||||
#else
|
||||
typedef void (* MA_SDL_AudioCallback)(void* userdata, ma_uint8* stream, int len);
|
||||
typedef ma_uint16 MA_SDL_AudioFormat;
|
||||
typedef ma_uint32 MA_SDL_AudioDeviceID;
|
||||
|
||||
typedef struct MA_SDL_AudioSpec
|
||||
{
|
||||
int freq;
|
||||
MA_SDL_AudioFormat format;
|
||||
ma_uint8 channels;
|
||||
ma_uint8 silence;
|
||||
ma_uint16 samples;
|
||||
ma_uint16 padding;
|
||||
ma_uint32 size;
|
||||
MA_SDL_AudioCallback callback;
|
||||
void* userdata;
|
||||
} MA_SDL_AudioSpec;
|
||||
#endif
|
||||
|
||||
typedef int (* MA_PFN_SDL_InitSubSystem )(ma_uint32 flags);
|
||||
typedef void (* MA_PFN_SDL_QuitSubSystem )(ma_uint32 flags);
|
||||
typedef int (* MA_PFN_SDL_GetNumAudioDevices )(int iscapture);
|
||||
typedef int (* MA_PFN_SDL_GetDefaultAudioInfo)(char** name, MA_SDL_AudioSpec* spec, int iscapture);
|
||||
typedef int (* MA_PFN_SDL_GetAudioDeviceSpec )(int index, int iscapture, MA_SDL_AudioSpec* spec);
|
||||
typedef const char* (* MA_PFN_SDL_GetAudioDeviceName )(int index, int iscapture);
|
||||
typedef void (* MA_PFN_SDL_CloseAudioDevice )(MA_SDL_AudioDeviceID dev);
|
||||
typedef MA_SDL_AudioDeviceID (* MA_PFN_SDL_OpenAudioDevice )(const char* device, int iscapture, const MA_SDL_AudioSpec* desired, MA_SDL_AudioSpec* obtained, int allowed_changes);
|
||||
typedef void (* MA_PFN_SDL_PauseAudioDevice )(MA_SDL_AudioDeviceID dev, int pause_on);
|
||||
|
||||
typedef struct
|
||||
{
|
||||
ma_handle hSDL; /* A handle to the SDL2 shared object. We dynamically load function pointers at runtime so we can avoid linking. */
|
||||
MA_PFN_SDL_InitSubSystem SDL_InitSubSystem;
|
||||
MA_PFN_SDL_QuitSubSystem SDL_QuitSubSystem;
|
||||
MA_PFN_SDL_GetNumAudioDevices SDL_GetNumAudioDevices;
|
||||
MA_PFN_SDL_GetDefaultAudioInfo SDL_GetDefaultAudioInfo;
|
||||
MA_PFN_SDL_GetAudioDeviceSpec SDL_GetAudioDeviceSpec;
|
||||
MA_PFN_SDL_GetAudioDeviceName SDL_GetAudioDeviceName;
|
||||
MA_PFN_SDL_CloseAudioDevice SDL_CloseAudioDevice;
|
||||
MA_PFN_SDL_OpenAudioDevice SDL_OpenAudioDevice;
|
||||
MA_PFN_SDL_PauseAudioDevice SDL_PauseAudioDevice;
|
||||
} ma_context_state_sdl2;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
ma_device_state_async async;
|
||||
struct
|
||||
{
|
||||
int deviceID;
|
||||
} capture;
|
||||
struct
|
||||
{
|
||||
int deviceID;
|
||||
} playback;
|
||||
} ma_device_state_sdl2;
|
||||
|
||||
|
||||
MA_SDL_AudioFormat ma_format_to_sdl2(ma_format format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case ma_format_unknown: return 0;
|
||||
case ma_format_u8: return MA_AUDIO_U8;
|
||||
case ma_format_s16: return MA_AUDIO_S16;
|
||||
case ma_format_s24: return MA_AUDIO_S32; /* Closest match. */
|
||||
case ma_format_s32: return MA_AUDIO_S32;
|
||||
case ma_format_f32: return MA_AUDIO_F32;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
ma_format ma_format_from_sdl2(MA_SDL_AudioFormat format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case MA_AUDIO_U8: return ma_format_u8;
|
||||
case MA_AUDIO_S16: return ma_format_s16;
|
||||
case MA_AUDIO_S32: return ma_format_s32;
|
||||
case MA_AUDIO_F32: return ma_format_f32;
|
||||
default: return ma_format_unknown;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static ma_context_state_sdl2* ma_context_get_backend_state__sdl2(ma_context* pContext)
|
||||
{
|
||||
return (ma_context_state_sdl2*)ma_context_get_backend_state(pContext);
|
||||
}
|
||||
|
||||
static ma_device_state_sdl2* ma_device_get_backend_state__sdl2(ma_device* pDevice)
|
||||
{
|
||||
return (ma_device_state_sdl2*)ma_device_get_backend_state(pDevice);
|
||||
}
|
||||
|
||||
|
||||
static ma_result ma_device_step__sdl2(ma_device* pDevice, ma_blocking_mode blockingMode);
|
||||
|
||||
|
||||
static void ma_backend_info__sdl2(ma_device_backend_info* pBackendInfo)
|
||||
{
|
||||
MA_SDL2_ASSERT(pBackendInfo != NULL);
|
||||
pBackendInfo->pName = "SDL2";
|
||||
}
|
||||
|
||||
static ma_result ma_context_init__sdl2(ma_context* pContext, const void* pContextBackendConfig, void** ppContextState)
|
||||
{
|
||||
ma_context_state_sdl2* pContextStateSDL;
|
||||
const ma_context_config_sdl2* pContextConfigSDL = (ma_context_config_sdl2*)pContextBackendConfig;
|
||||
ma_log* pLog = ma_context_get_log(pContext);
|
||||
int resultSDL;
|
||||
|
||||
/* The context config is not currently being used for this backend. */
|
||||
(void)pContextConfigSDL;
|
||||
|
||||
|
||||
/* Allocate our SDL-specific context data. */
|
||||
pContextStateSDL = (ma_context_state_sdl2*)ma_calloc(sizeof(*pContextStateSDL), ma_context_get_allocation_callbacks(pContext));
|
||||
if (pContextStateSDL == NULL) {
|
||||
return MA_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
#ifndef MA_NO_RUNTIME_LINKING
|
||||
{
|
||||
/* We'll use a list of possible shared object names for easier extensibility. */
|
||||
size_t iName;
|
||||
const char* pSDLNames[] = {
|
||||
#if defined(_WIN32)
|
||||
"SDL2.dll"
|
||||
#elif defined(__APPLE__)
|
||||
"SDL2.framework/SDL2"
|
||||
#else
|
||||
"libSDL2-2.0.so.0"
|
||||
#endif
|
||||
};
|
||||
|
||||
/* Check if we have SDL2 installed somewhere. If not it's not usable and we need to abort. */
|
||||
for (iName = 0; iName < ma_countof(pSDLNames); iName += 1) {
|
||||
pContextStateSDL->hSDL = ma_dlopen(pLog, pSDLNames[iName]);
|
||||
if (pContextStateSDL->hSDL != NULL) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (pContextStateSDL->hSDL == NULL) {
|
||||
ma_free(pContextStateSDL, ma_context_get_allocation_callbacks(pContext));
|
||||
return MA_NO_BACKEND; /* SDL2 could not be loaded. */
|
||||
}
|
||||
|
||||
/* Now that we have the handle to the shared object we can go ahead and load some function pointers. */
|
||||
pContextStateSDL->SDL_InitSubSystem = (MA_PFN_SDL_InitSubSystem )ma_dlsym(pLog, pContextStateSDL->hSDL, "SDL_InitSubSystem");
|
||||
pContextStateSDL->SDL_QuitSubSystem = (MA_PFN_SDL_QuitSubSystem )ma_dlsym(pLog, pContextStateSDL->hSDL, "SDL_QuitSubSystem");
|
||||
pContextStateSDL->SDL_GetNumAudioDevices = (MA_PFN_SDL_GetNumAudioDevices )ma_dlsym(pLog, pContextStateSDL->hSDL, "SDL_GetNumAudioDevices");
|
||||
pContextStateSDL->SDL_GetDefaultAudioInfo = (MA_PFN_SDL_GetDefaultAudioInfo)ma_dlsym(pLog, pContextStateSDL->hSDL, "SDL_GetDefaultAudioInfo");
|
||||
pContextStateSDL->SDL_GetAudioDeviceSpec = (MA_PFN_SDL_GetAudioDeviceSpec )ma_dlsym(pLog, pContextStateSDL->hSDL, "SDL_GetAudioDeviceSpec");
|
||||
pContextStateSDL->SDL_GetAudioDeviceName = (MA_PFN_SDL_GetAudioDeviceName )ma_dlsym(pLog, pContextStateSDL->hSDL, "SDL_GetAudioDeviceName");
|
||||
pContextStateSDL->SDL_CloseAudioDevice = (MA_PFN_SDL_CloseAudioDevice )ma_dlsym(pLog, pContextStateSDL->hSDL, "SDL_CloseAudioDevice");
|
||||
pContextStateSDL->SDL_OpenAudioDevice = (MA_PFN_SDL_OpenAudioDevice )ma_dlsym(pLog, pContextStateSDL->hSDL, "SDL_OpenAudioDevice");
|
||||
pContextStateSDL->SDL_PauseAudioDevice = (MA_PFN_SDL_PauseAudioDevice )ma_dlsym(pLog, pContextStateSDL->hSDL, "SDL_PauseAudioDevice");
|
||||
}
|
||||
#else
|
||||
{
|
||||
pContextStateSDL->SDL_InitSubSystem = SDL_InitSubSystem;
|
||||
pContextStateSDL->SDL_QuitSubSystem = SDL_QuitSubSystem;
|
||||
pContextStateSDL->SDL_GetNumAudioDevices = SDL_GetNumAudioDevices;
|
||||
#ifndef __EMSCRIPTEN__
|
||||
pContextStateSDL->SDL_GetDefaultAudioInfo = SDL_GetDefaultAudioInfo;
|
||||
pContextStateSDL->SDL_GetAudioDeviceSpec = SDL_GetAudioDeviceSpec;
|
||||
#else
|
||||
pContextStateSDL->SDL_GetDefaultAudioInfo = NULL;
|
||||
pContextStateSDL->SDL_GetAudioDeviceSpec = NULL;
|
||||
#endif
|
||||
pContextStateSDL->SDL_GetAudioDeviceName = SDL_GetAudioDeviceName;
|
||||
pContextStateSDL->SDL_CloseAudioDevice = SDL_CloseAudioDevice;
|
||||
pContextStateSDL->SDL_OpenAudioDevice = SDL_OpenAudioDevice;
|
||||
pContextStateSDL->SDL_PauseAudioDevice = SDL_PauseAudioDevice;
|
||||
}
|
||||
#endif /* MA_NO_RUNTIME_LINKING */
|
||||
|
||||
resultSDL = pContextStateSDL->SDL_InitSubSystem(MA_SDL_INIT_AUDIO);
|
||||
if (resultSDL != 0) {
|
||||
ma_dlclose(pLog, pContextStateSDL->hSDL);
|
||||
ma_free(pContextStateSDL, ma_context_get_allocation_callbacks(pContext));
|
||||
return MA_ERROR;
|
||||
}
|
||||
|
||||
*ppContextState = pContextStateSDL;
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
|
||||
static void ma_context_uninit__sdl2(ma_context* pContext)
|
||||
{
|
||||
ma_context_state_sdl2* pContextStateSDL = ma_context_get_backend_state__sdl2(pContext);
|
||||
|
||||
MA_SDL2_ASSERT(pContextStateSDL != NULL);
|
||||
|
||||
pContextStateSDL->SDL_QuitSubSystem(MA_SDL_INIT_AUDIO);
|
||||
|
||||
/* Close the handle to the SDL shared object last. */
|
||||
ma_dlclose(ma_context_get_log(pContext), pContextStateSDL->hSDL);
|
||||
pContextStateSDL->hSDL = NULL;
|
||||
|
||||
ma_free(pContextStateSDL, ma_context_get_allocation_callbacks(pContext));
|
||||
}
|
||||
|
||||
static void ma_add_native_format_from_AudioSpec__sdl2(ma_device_info* pDeviceInfo, const MA_SDL_AudioSpec* pAudioSpec)
|
||||
{
|
||||
ma_format format = ma_format_from_sdl2(pAudioSpec->format);
|
||||
if (format == ma_format_unknown) {
|
||||
format = ma_format_f32;
|
||||
}
|
||||
|
||||
ma_device_info_add_native_data_format(pDeviceInfo, format, pAudioSpec->channels, pAudioSpec->channels, pAudioSpec->freq, pAudioSpec->freq);
|
||||
}
|
||||
|
||||
static ma_result ma_context_enumerate_devices__sdl2(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pCallbackUserData)
|
||||
{
|
||||
ma_context_state_sdl2* pContextStateSDL = ma_context_get_backend_state__sdl2(pContext);
|
||||
ma_device_enumeration_result cbResult = MA_DEVICE_ENUMERATION_CONTINUE;
|
||||
MA_SDL_AudioSpec defaultAudioSpec;
|
||||
ma_device_info deviceInfo;
|
||||
int deviceCount;
|
||||
int iDevice;
|
||||
|
||||
MA_SDL2_ASSERT(pContextStateSDL != NULL);
|
||||
|
||||
/* Playback */
|
||||
if (cbResult == MA_DEVICE_ENUMERATION_CONTINUE) {
|
||||
ma_bool32 hasDefaultPlaybackDeviceBeenEnumerated = MA_FALSE;
|
||||
ma_bool32 hasDefaultPlaybackDevice;
|
||||
char* pDefaultPlaybackDeviceName = NULL;
|
||||
|
||||
if (pContextStateSDL->SDL_GetDefaultAudioInfo) {
|
||||
hasDefaultPlaybackDevice = pContextStateSDL->SDL_GetDefaultAudioInfo(&pDefaultPlaybackDeviceName, &defaultAudioSpec, 0) == 0;
|
||||
} else {
|
||||
hasDefaultPlaybackDevice = MA_FALSE;
|
||||
}
|
||||
|
||||
deviceCount = pContextStateSDL->SDL_GetNumAudioDevices(0);
|
||||
for (iDevice = 0; iDevice < deviceCount; iDevice += 1) {
|
||||
MA_SDL_AudioSpec audioSpec;
|
||||
|
||||
memset(&deviceInfo, 0, sizeof(deviceInfo));
|
||||
|
||||
/* Default. For SDL2 we'll just use the first device that matches the default device name. */
|
||||
if (!hasDefaultPlaybackDeviceBeenEnumerated && hasDefaultPlaybackDevice) {
|
||||
const char* pDeviceName = pContextStateSDL->SDL_GetAudioDeviceName(iDevice, 0);
|
||||
if (strcmp(pDeviceName, pDefaultPlaybackDeviceName) == 0) {
|
||||
deviceInfo.isDefault = MA_TRUE;
|
||||
hasDefaultPlaybackDeviceBeenEnumerated = MA_TRUE;
|
||||
}
|
||||
} else {
|
||||
deviceInfo.isDefault = MA_FALSE;
|
||||
}
|
||||
|
||||
/* ID. */
|
||||
deviceInfo.id.custom.i = iDevice;
|
||||
|
||||
/* Name. */
|
||||
ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), pContextStateSDL->SDL_GetAudioDeviceName(iDevice, 0), (size_t)-1);
|
||||
|
||||
/* Data Format. */
|
||||
if (pContextStateSDL->SDL_GetAudioDeviceSpec != NULL) {
|
||||
if (pContextStateSDL->SDL_GetAudioDeviceSpec(iDevice, 0, &audioSpec) == 0) {
|
||||
ma_add_native_format_from_AudioSpec__sdl2(&deviceInfo, &audioSpec);
|
||||
}
|
||||
} else {
|
||||
/* No way to retrieve the data format. Just report support for everything. */
|
||||
deviceInfo.nativeDataFormatCount = 1;
|
||||
}
|
||||
|
||||
cbResult = callback(ma_device_type_playback, &deviceInfo, pCallbackUserData);
|
||||
if (cbResult == MA_DEVICE_ENUMERATION_ABORT) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* SDL2 does not flag the default playback device so we'll enumerate an explicit default device here. */
|
||||
if (cbResult == MA_DEVICE_ENUMERATION_CONTINUE && !hasDefaultPlaybackDeviceBeenEnumerated) {
|
||||
memset(&deviceInfo, 0, sizeof(deviceInfo));
|
||||
deviceInfo.isDefault = MA_TRUE;
|
||||
deviceInfo.id.custom.i = -1; /* Special ID for the default device. */
|
||||
|
||||
if (hasDefaultPlaybackDevice) {
|
||||
ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), pDefaultPlaybackDeviceName, (size_t)-1);
|
||||
ma_add_native_format_from_AudioSpec__sdl2(&deviceInfo, &defaultAudioSpec);
|
||||
} else {
|
||||
/* No way to retrieve the data format. Just report support for everything. */
|
||||
ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), "Default Playback Device", (size_t)-1);
|
||||
deviceInfo.nativeDataFormatCount = 1;
|
||||
}
|
||||
|
||||
cbResult = callback(ma_device_type_playback, &deviceInfo, pCallbackUserData);
|
||||
}
|
||||
}
|
||||
|
||||
/* Capture */
|
||||
if (cbResult == MA_DEVICE_ENUMERATION_CONTINUE) {
|
||||
ma_bool32 hasDefaultCaptureDeviceBeenEnumerated = MA_FALSE;
|
||||
ma_bool32 hasDefaultCaptureDevice;
|
||||
char* pDefaultCaptureDeviceName = NULL;
|
||||
|
||||
if (pContextStateSDL->SDL_GetDefaultAudioInfo) {
|
||||
hasDefaultCaptureDevice = pContextStateSDL->SDL_GetDefaultAudioInfo(&pDefaultCaptureDeviceName, &defaultAudioSpec, 1) == 0;
|
||||
} else {
|
||||
hasDefaultCaptureDevice = MA_FALSE;
|
||||
}
|
||||
|
||||
deviceCount = pContextStateSDL->SDL_GetNumAudioDevices(1);
|
||||
for (iDevice = 0; iDevice < deviceCount; iDevice += 1) {
|
||||
MA_SDL_AudioSpec audioSpec;
|
||||
|
||||
memset(&deviceInfo, 0, sizeof(deviceInfo));
|
||||
|
||||
/* Default. For SDL2 we'll just use the first device that matches the default device name. */
|
||||
if (!hasDefaultCaptureDeviceBeenEnumerated && hasDefaultCaptureDevice) {
|
||||
const char* pDeviceName = pContextStateSDL->SDL_GetAudioDeviceName(iDevice, 1);
|
||||
if (strcmp(pDeviceName, pDefaultCaptureDeviceName) == 0) {
|
||||
deviceInfo.isDefault = MA_TRUE;
|
||||
hasDefaultCaptureDeviceBeenEnumerated = MA_TRUE;
|
||||
}
|
||||
} else {
|
||||
deviceInfo.isDefault = MA_FALSE;
|
||||
}
|
||||
|
||||
/* ID. */
|
||||
deviceInfo.id.custom.i = iDevice;
|
||||
|
||||
/* Name. */
|
||||
ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), pContextStateSDL->SDL_GetAudioDeviceName(iDevice, 1), (size_t)-1);
|
||||
|
||||
/* Data Format. */
|
||||
if (pContextStateSDL->SDL_GetAudioDeviceSpec != NULL) {
|
||||
if (pContextStateSDL->SDL_GetAudioDeviceSpec(iDevice, 1, &audioSpec) == 0) {
|
||||
ma_add_native_format_from_AudioSpec__sdl2(&deviceInfo, &audioSpec);
|
||||
}
|
||||
} else {
|
||||
/* No way to retrieve the data format. Just report support for everything. */
|
||||
deviceInfo.nativeDataFormatCount = 1;
|
||||
}
|
||||
|
||||
cbResult = callback(ma_device_type_capture, &deviceInfo, pCallbackUserData);
|
||||
if (cbResult == MA_DEVICE_ENUMERATION_ABORT) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* SDL2 does not flag the default playback device so we'll enumerate an explicit default device here. */
|
||||
if (cbResult == MA_DEVICE_ENUMERATION_CONTINUE && !hasDefaultCaptureDeviceBeenEnumerated) {
|
||||
memset(&deviceInfo, 0, sizeof(deviceInfo));
|
||||
deviceInfo.isDefault = MA_TRUE;
|
||||
deviceInfo.id.custom.i = -1; /* Special ID for the default device. */
|
||||
|
||||
if (hasDefaultCaptureDevice) {
|
||||
ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), pDefaultCaptureDeviceName, (size_t)-1);
|
||||
ma_add_native_format_from_AudioSpec__sdl2(&deviceInfo, &defaultAudioSpec);
|
||||
} else {
|
||||
/* No way to retrieve the data format. Just report support for everything. */
|
||||
ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), "Default Capture Device", (size_t)-1);
|
||||
deviceInfo.nativeDataFormatCount = 1;
|
||||
}
|
||||
|
||||
cbResult = callback(ma_device_type_capture, &deviceInfo, pCallbackUserData);
|
||||
(void)cbResult; /* Silence a static analysis warning. Want to keep this assignment in case we extend this logic later. */
|
||||
}
|
||||
}
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
void ma_audio_callback_capture__sdl2(void* pUserData, ma_uint8* pBuffer, int bufferSizeInBytes)
|
||||
{
|
||||
ma_device* pDevice = (ma_device*)pUserData;
|
||||
ma_device_state_sdl2* pDeviceStateSDL = ma_device_get_backend_state__sdl2(pDevice);
|
||||
ma_device_state_async_process(&pDeviceStateSDL->async, pDevice, NULL, pBuffer, (ma_uint32)bufferSizeInBytes / ma_get_bytes_per_frame(pDeviceStateSDL->async.capture.format, pDeviceStateSDL->async.capture.channels));
|
||||
}
|
||||
|
||||
void ma_audio_callback_playback__sdl2(void* pUserData, ma_uint8* pBuffer, int bufferSizeInBytes)
|
||||
{
|
||||
ma_device* pDevice = (ma_device*)pUserData;
|
||||
ma_device_state_sdl2* pDeviceStateSDL = ma_device_get_backend_state__sdl2(pDevice);
|
||||
ma_device_state_async_process(&pDeviceStateSDL->async, pDevice, pBuffer, NULL, (ma_uint32)bufferSizeInBytes / ma_get_bytes_per_frame(pDeviceStateSDL->async.playback.format, pDeviceStateSDL->async.playback.channels));
|
||||
}
|
||||
|
||||
static ma_result ma_device_init_internal__sdl2(ma_device* pDevice, ma_context_state_sdl2* pContextStateSDL, ma_device_state_sdl2* pDeviceStateSDL, const ma_device_config_sdl2* pDeviceConfigSDL, ma_device_type deviceType, ma_device_descriptor* pDescriptor)
|
||||
{
|
||||
MA_SDL_AudioSpec desiredSpec;
|
||||
MA_SDL_AudioSpec obtainedSpec;
|
||||
const char* pDeviceName;
|
||||
int deviceID;
|
||||
|
||||
(void)pDeviceConfigSDL;
|
||||
|
||||
/*
|
||||
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
|
||||
the sample rate just set to 0, which indicates that the native sample rate should be used. There's no practical way to calculate this
|
||||
that I can think of right now so I'm just using MA_DEFAULT_SAMPLE_RATE.
|
||||
*/
|
||||
if (pDescriptor->sampleRate == 0) {
|
||||
pDescriptor->sampleRate = MA_DEFAULT_SAMPLE_RATE;
|
||||
}
|
||||
|
||||
/*
|
||||
When determining the period size, you need to take defaults into account. This is how the size of the period should be determined.
|
||||
|
||||
1) If periodSizeInFrames is not 0, use periodSizeInFrames; else
|
||||
2) If periodSizeInMilliseconds is not 0, use periodSizeInMilliseconds; else
|
||||
3) If both periodSizeInFrames and periodSizeInMilliseconds is 0, use the backend's default. If the backend does not allow a default
|
||||
buffer size, use a default value of MA_DEFAULT_PERIOD_SIZE_IN_MILLISECONDS.
|
||||
|
||||
Note that options 2 and 3 require knowledge of the sample rate in order to convert it to a frame count. You should try to keep the
|
||||
calculation of the period size as accurate as possible, but sometimes it's just not practical so just use whatever you can.
|
||||
|
||||
A helper function called ma_calculate_buffer_size_in_frames_from_descriptor() is available to do all of this for you which is what
|
||||
we'll be using here.
|
||||
*/
|
||||
pDescriptor->periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptor, pDescriptor->sampleRate);
|
||||
|
||||
/* SDL wants the buffer size to be a power of 2 for some reason. */
|
||||
if (pDescriptor->periodSizeInFrames > 32768) {
|
||||
pDescriptor->periodSizeInFrames = 32768;
|
||||
} else {
|
||||
pDescriptor->periodSizeInFrames = ma_next_power_of_2(pDescriptor->periodSizeInFrames);
|
||||
}
|
||||
|
||||
/*
|
||||
In my experience there is glitching with a period size of anything <= 512. To make this "Just Work" on
|
||||
the Emscripten build we'll set this to 1024.
|
||||
*/
|
||||
#if defined(__EMSCRIPTEN__)
|
||||
{
|
||||
if (pDescriptor->periodSizeInFrames < 1024) {
|
||||
pDescriptor->periodSizeInFrames = 1024;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
/* We now have enough information to set up the device. */
|
||||
memset(&desiredSpec, 0, sizeof(desiredSpec));
|
||||
desiredSpec.freq = (int)pDescriptor->sampleRate;
|
||||
desiredSpec.format = ma_format_to_sdl2(pDescriptor->format);
|
||||
desiredSpec.channels = (ma_uint8)pDescriptor->channels;
|
||||
desiredSpec.samples = (ma_uint16)pDescriptor->periodSizeInFrames;
|
||||
desiredSpec.callback = (deviceType == ma_device_type_capture) ? ma_audio_callback_capture__sdl2 : ma_audio_callback_playback__sdl2;
|
||||
desiredSpec.userdata = pDevice;
|
||||
|
||||
/* We'll fall back to f32 if we don't have an appropriate mapping between SDL and miniaudio. */
|
||||
if (desiredSpec.format == 0) {
|
||||
desiredSpec.format = MA_AUDIO_F32;
|
||||
}
|
||||
|
||||
/* We explicitly want the device name to be NULL for the default device. Otherwise we'll grab the name from the device index. */
|
||||
pDeviceName = NULL;
|
||||
if (pDescriptor->pDeviceID != NULL && pDescriptor->pDeviceID->custom.i != -1) {
|
||||
pDeviceName = pContextStateSDL->SDL_GetAudioDeviceName(pDescriptor->pDeviceID->custom.i, (deviceType == ma_device_type_playback) ? 0 : 1);
|
||||
}
|
||||
|
||||
deviceID = pContextStateSDL->SDL_OpenAudioDevice(pDeviceName, (deviceType == ma_device_type_playback) ? 0 : 1, &desiredSpec, &obtainedSpec, MA_SDL_AUDIO_ALLOW_ANY_CHANGE);
|
||||
if (deviceID == 0) {
|
||||
ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "Failed to open SDL2 device.");
|
||||
return MA_FAILED_TO_OPEN_BACKEND_DEVICE;
|
||||
}
|
||||
|
||||
/* The descriptor needs to be updated with our actual settings. */
|
||||
pDescriptor->format = ma_format_from_sdl2(obtainedSpec.format);
|
||||
pDescriptor->channels = obtainedSpec.channels;
|
||||
pDescriptor->sampleRate = (ma_uint32)obtainedSpec.freq;
|
||||
ma_channel_map_init_standard(ma_standard_channel_map_default, pDescriptor->channelMap, ma_countof(pDescriptor->channelMap), pDescriptor->channels);
|
||||
pDescriptor->periodSizeInFrames = obtainedSpec.samples;
|
||||
pDescriptor->periodCount = 1; /* SDL doesn't use the notion of period counts, so just set to 1. */
|
||||
|
||||
if (deviceType == ma_device_type_playback) {
|
||||
pDeviceStateSDL->playback.deviceID = deviceID;
|
||||
} else {
|
||||
pDeviceStateSDL->capture.deviceID = deviceID;
|
||||
}
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
|
||||
static ma_result ma_device_init__sdl2(ma_device* pDevice, const void* pDeviceBackendConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture, void** ppDeviceState)
|
||||
{
|
||||
ma_device_state_sdl2* pDeviceStateSDL;
|
||||
ma_device_config_sdl2* pDeviceConfigSDL = (ma_device_config_sdl2*)pDeviceBackendConfig;
|
||||
ma_context_state_sdl2* pContextStateSDL = ma_context_get_backend_state__sdl2(ma_device_get_context(pDevice));
|
||||
ma_device_type deviceType = ma_device_get_type(pDevice);
|
||||
ma_result result;
|
||||
|
||||
/* SDL does not support loopback mode, so must return MA_DEVICE_TYPE_NOT_SUPPORTED if it's requested. */
|
||||
if (deviceType == ma_device_type_loopback) {
|
||||
return MA_DEVICE_TYPE_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
/* We need to allocate our backend-specific data. */
|
||||
pDeviceStateSDL = (ma_device_state_sdl2*)ma_calloc(sizeof(*pDeviceStateSDL), ma_device_get_allocation_callbacks(pDevice));
|
||||
if (pDeviceStateSDL == NULL) {
|
||||
return MA_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) {
|
||||
result = ma_device_init_internal__sdl2(pDevice, pContextStateSDL, pDeviceStateSDL, pDeviceConfigSDL, ma_device_type_capture, pDescriptorCapture);
|
||||
if (result != MA_SUCCESS) {
|
||||
ma_free(pDeviceStateSDL, ma_device_get_allocation_callbacks(pDevice));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) {
|
||||
result = ma_device_init_internal__sdl2(pDevice, pContextStateSDL, pDeviceStateSDL, pDeviceConfigSDL, ma_device_type_playback, pDescriptorPlayback);
|
||||
if (result != MA_SUCCESS) {
|
||||
if (deviceType == ma_device_type_duplex) {
|
||||
pContextStateSDL->SDL_CloseAudioDevice(pDeviceStateSDL->capture.deviceID);
|
||||
}
|
||||
|
||||
ma_free(pDeviceStateSDL, ma_device_get_allocation_callbacks(pDevice));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
result = ma_device_state_async_init(deviceType, pDescriptorPlayback, pDescriptorCapture, ma_device_get_allocation_callbacks(pDevice), &pDeviceStateSDL->async);
|
||||
if (result != MA_SUCCESS) {
|
||||
if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) {
|
||||
pContextStateSDL->SDL_CloseAudioDevice(pDeviceStateSDL->capture.deviceID);
|
||||
}
|
||||
if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) {
|
||||
pContextStateSDL->SDL_CloseAudioDevice(pDeviceStateSDL->playback.deviceID);
|
||||
}
|
||||
|
||||
ma_free(pDeviceStateSDL, ma_device_get_allocation_callbacks(pDevice));
|
||||
return result;
|
||||
}
|
||||
|
||||
*ppDeviceState = pDeviceStateSDL;
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
|
||||
static void ma_device_uninit__sdl2(ma_device* pDevice)
|
||||
{
|
||||
ma_device_state_sdl2* pDeviceStateSDL = ma_device_get_backend_state__sdl2(pDevice);
|
||||
ma_context_state_sdl2* pContextStateSDL = ma_context_get_backend_state__sdl2(ma_device_get_context(pDevice));
|
||||
ma_device_type deviceType = ma_device_get_type(pDevice);
|
||||
|
||||
if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) {
|
||||
pContextStateSDL->SDL_CloseAudioDevice(pDeviceStateSDL->capture.deviceID);
|
||||
}
|
||||
|
||||
if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) {
|
||||
pContextStateSDL->SDL_CloseAudioDevice(pDeviceStateSDL->playback.deviceID);
|
||||
}
|
||||
|
||||
ma_device_state_async_uninit(&pDeviceStateSDL->async, ma_device_get_allocation_callbacks(pDevice));
|
||||
|
||||
ma_free(pDeviceStateSDL, ma_device_get_allocation_callbacks(pDevice));
|
||||
}
|
||||
|
||||
static ma_result ma_device_start__sdl2(ma_device* pDevice)
|
||||
{
|
||||
ma_device_state_sdl2* pDeviceStateSDL = ma_device_get_backend_state__sdl2(pDevice);
|
||||
ma_context_state_sdl2* pContextStateSDL = ma_context_get_backend_state__sdl2(ma_device_get_context(pDevice));
|
||||
ma_device_type deviceType = ma_device_get_type(pDevice);
|
||||
|
||||
/* Step the device once to ensure buffers are pre-filled before starting. */
|
||||
ma_device_step__sdl2(pDevice, MA_BLOCKING_MODE_NON_BLOCKING);
|
||||
|
||||
if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) {
|
||||
pContextStateSDL->SDL_PauseAudioDevice(pDeviceStateSDL->capture.deviceID, 0);
|
||||
}
|
||||
|
||||
if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) {
|
||||
pContextStateSDL->SDL_PauseAudioDevice(pDeviceStateSDL->playback.deviceID, 0);
|
||||
}
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
|
||||
static ma_result ma_device_stop__sdl2(ma_device* pDevice)
|
||||
{
|
||||
ma_device_state_sdl2* pDeviceStateSDL = ma_device_get_backend_state__sdl2(pDevice);
|
||||
ma_context_state_sdl2* pContextStateSDL = ma_context_get_backend_state__sdl2(ma_device_get_context(pDevice));
|
||||
ma_device_type deviceType = ma_device_get_type(pDevice);
|
||||
|
||||
if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) {
|
||||
pContextStateSDL->SDL_PauseAudioDevice(pDeviceStateSDL->capture.deviceID, 1);
|
||||
}
|
||||
|
||||
if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) {
|
||||
pContextStateSDL->SDL_PauseAudioDevice(pDeviceStateSDL->playback.deviceID, 1);
|
||||
}
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
|
||||
static ma_result ma_device_step__sdl2(ma_device* pDevice, ma_blocking_mode blockingMode)
|
||||
{
|
||||
ma_device_state_sdl2* pDeviceStateSDL = ma_device_get_backend_state__sdl2(pDevice);
|
||||
return ma_device_state_async_step(&pDeviceStateSDL->async, pDevice, blockingMode, NULL);
|
||||
}
|
||||
|
||||
static void ma_device_wakeup__sdl2(ma_device* pDevice)
|
||||
{
|
||||
ma_device_state_sdl2* pDeviceStateSDL = ma_device_get_backend_state__sdl2(pDevice);
|
||||
ma_device_state_async_release(&pDeviceStateSDL->async);
|
||||
}
|
||||
|
||||
static ma_device_backend_vtable ma_gDeviceBackendVTable_SDL2 =
|
||||
{
|
||||
ma_backend_info__sdl2,
|
||||
ma_context_init__sdl2,
|
||||
ma_context_uninit__sdl2,
|
||||
ma_context_enumerate_devices__sdl2,
|
||||
ma_device_init__sdl2,
|
||||
ma_device_uninit__sdl2,
|
||||
ma_device_start__sdl2,
|
||||
ma_device_stop__sdl2,
|
||||
ma_device_step__sdl2,
|
||||
ma_device_wakeup__sdl2
|
||||
};
|
||||
|
||||
ma_device_backend_vtable* ma_device_backend_sdl2 = &ma_gDeviceBackendVTable_SDL2;
|
||||
#else
|
||||
ma_device_backend_vtable* ma_device_backend_sdl2 = NULL;
|
||||
#endif /* MA_HAS_SDL2 */
|
||||
|
||||
MA_API ma_device_backend_vtable* ma_sdl2_get_vtable(void)
|
||||
{
|
||||
return ma_device_backend_sdl2;
|
||||
}
|
||||
|
||||
|
||||
MA_API ma_context_config_sdl2 ma_context_config_sdl2_init(void)
|
||||
{
|
||||
ma_context_config_sdl2 config;
|
||||
|
||||
memset(&config, 0, sizeof(config));
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
MA_API ma_device_config_sdl2 ma_device_config_sdl2_init(void)
|
||||
{
|
||||
ma_device_config_sdl2 config;
|
||||
|
||||
memset(&config, 0, sizeof(config));
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
|
||||
#endif /* miniaudio_backend_sdl2_c */
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
The SDL2 backend does not require any user data, nor configs. Configs are provided here in case
|
||||
they are needed in the future, however you can safely pass in NULL when setting up your context
|
||||
and device configs.
|
||||
*/
|
||||
#ifndef miniaudio_backend_sdl2_h
|
||||
#define miniaudio_backend_sdl2_h
|
||||
|
||||
#include "../../../miniaudio.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int _unused;
|
||||
} ma_context_config_sdl2;
|
||||
|
||||
MA_API ma_context_config_sdl2 ma_context_config_sdl2_init(void);
|
||||
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int _unused;
|
||||
} ma_device_config_sdl2;
|
||||
|
||||
MA_API ma_device_config_sdl2 ma_device_config_sdl2_init(void);
|
||||
|
||||
|
||||
extern ma_device_backend_vtable* ma_device_backend_sdl2;
|
||||
MA_API ma_device_backend_vtable* ma_sdl2_get_vtable(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* miniaudio_backend_sdl_h */
|
||||
@@ -0,0 +1,362 @@
|
||||
#ifndef miniaudio_backend_template_c
|
||||
#define miniaudio_backend_template_c
|
||||
|
||||
/* Do not include this in your backend. It's only used to validate the template build. Needed for MA_ZERO_OBJECT(). */
|
||||
#include "../../../miniaudio.c"
|
||||
|
||||
#include "miniaudio_backend_template.h"
|
||||
|
||||
/* Wrap this in a #ifdef/#endif depending on the build environment. */
|
||||
#define MA_SUPPORT_TEMPLATE
|
||||
|
||||
#if defined(MA_SUPPORT_TEMPLATE) && !defined(MA_NO_TEMPLATE) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_TEMPLATE))
|
||||
#define MA_HAS_TEMPLATE
|
||||
#endif
|
||||
|
||||
#if defined(MA_HAS_TEMPLATE)
|
||||
typedef struct ma_context_state_template
|
||||
{
|
||||
int _unused;
|
||||
} ma_context_state_template;
|
||||
|
||||
typedef struct ma_device_state_template
|
||||
{
|
||||
int _unused;
|
||||
} ma_device_state_template;
|
||||
|
||||
|
||||
static ma_context_state_template* ma_context_get_backend_state__template(ma_context* pContext)
|
||||
{
|
||||
return (ma_context_state_template*)ma_context_get_backend_state(pContext);
|
||||
}
|
||||
|
||||
static ma_device_state_template* ma_device_get_backend_state__template(ma_device* pDevice)
|
||||
{
|
||||
return (ma_device_state_template*)ma_device_get_backend_state(pDevice);
|
||||
}
|
||||
|
||||
|
||||
static void ma_backend_info__template(ma_device_backend_info* pBackendInfo)
|
||||
{
|
||||
pBackendInfo->pName = "Template";
|
||||
}
|
||||
|
||||
static ma_result ma_context_init__template(ma_context* pContext, const void* pContextBackendConfig, void** ppContextState)
|
||||
{
|
||||
ma_context_state_template* pContextStateTemplate;
|
||||
const ma_context_config_template* pContextConfigTemplate = (ma_context_config_template*)pContextBackendConfig;
|
||||
|
||||
pContextStateTemplate = (ma_context_state_template*)ma_calloc(sizeof(*pContextStateTemplate), ma_context_get_allocation_callbacks(pContext));
|
||||
if (pContextStateTemplate == NULL) {
|
||||
return MA_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
/* Do any context-level initialization here. */
|
||||
(void)pContextConfigTemplate;
|
||||
(void)pContext;
|
||||
|
||||
*ppContextState = pContextStateTemplate;
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
|
||||
static void ma_context_uninit__template(ma_context* pContext)
|
||||
{
|
||||
ma_context_state_template* pContextStateTemplate = ma_context_get_backend_state__template(pContext);
|
||||
|
||||
/* Do any context-level uninitialization here. */
|
||||
|
||||
ma_free(pContextStateTemplate, ma_context_get_allocation_callbacks(pContext));
|
||||
}
|
||||
|
||||
static ma_result ma_context_enumerate_devices__template(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pCallbackUserData)
|
||||
{
|
||||
ma_context_state_template* pContextStateTemplate = ma_context_get_backend_state__template(pContext);
|
||||
ma_device_info deviceInfo;
|
||||
ma_device_enumeration_result enumerationResult;
|
||||
|
||||
/* This example is only outputting a default playback and capture device. Modify this as required. */
|
||||
(void)pContextStateTemplate;
|
||||
|
||||
/* Playback. */
|
||||
MA_ZERO_OBJECT(&deviceInfo);
|
||||
deviceInfo.isDefault = MA_TRUE;
|
||||
deviceInfo.id.custom.i = 0;
|
||||
ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), "Default Playback Device", (size_t)-1);
|
||||
|
||||
/* Add a native format for each natively supported sample format. The channel count and sample rates are ranges. The format is the key. */
|
||||
ma_device_info_add_native_data_format(&deviceInfo, ma_format_s16, 1, 2, 44100, 48000);
|
||||
ma_device_info_add_native_data_format(&deviceInfo, ma_format_f32, 1, 2, 44100, 48000);
|
||||
|
||||
/* If the callback has requested that we abort enumeration we need to respect it. */
|
||||
enumerationResult = callback(ma_device_type_playback, &deviceInfo, pCallbackUserData);
|
||||
if (enumerationResult == MA_DEVICE_ENUMERATION_ABORT) {
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
|
||||
/* Capture. */
|
||||
MA_ZERO_OBJECT(&deviceInfo);
|
||||
deviceInfo.isDefault = MA_TRUE;
|
||||
deviceInfo.id.custom.i = 0;
|
||||
ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), "Default Capture Device", (size_t)-1);
|
||||
|
||||
/* Add a native format for each natively supported sample format. The channel count and sample rates are ranges. The format is the key. */
|
||||
ma_device_info_add_native_data_format(&deviceInfo, ma_format_s16, 1, 2, 44100, 48000);
|
||||
ma_device_info_add_native_data_format(&deviceInfo, ma_format_f32, 1, 2, 44100, 48000);
|
||||
|
||||
/* If the callback has requested that we abort enumeration we need to respect it. */
|
||||
enumerationResult = callback(ma_device_type_capture, &deviceInfo, pCallbackUserData);
|
||||
if (enumerationResult == MA_DEVICE_ENUMERATION_ABORT) {
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
|
||||
/* Enumeration done. */
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
|
||||
static ma_result ma_device_init__template(ma_device* pDevice, const void* pDeviceBackendConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture, void** ppDeviceState)
|
||||
{
|
||||
ma_device_state_template* pDeviceStateTemplate;
|
||||
ma_device_config_template* pDeviceConfigTemplate = (ma_device_config_template*)pDeviceBackendConfig;
|
||||
ma_context_state_template* pContextStateTemplate = ma_context_get_backend_state__template(ma_device_get_context(pDevice));
|
||||
ma_device_config_template defaultConfig;
|
||||
ma_device_type deviceType = ma_device_get_type(pDevice);
|
||||
|
||||
/* Use a default config if one was not provided. This is not mandated by miniaudio, but it's good practice. */
|
||||
if (pDeviceConfigTemplate == NULL) {
|
||||
defaultConfig = ma_device_config_template_init();
|
||||
pDeviceConfigTemplate = &defaultConfig;
|
||||
}
|
||||
|
||||
/* Return an error for any unsupported device types. */
|
||||
if (deviceType == ma_device_type_loopback) {
|
||||
return MA_DEVICE_TYPE_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
pDeviceStateTemplate = (ma_device_state_template*)ma_calloc(sizeof(*pDeviceStateTemplate), ma_device_get_allocation_callbacks(pDevice));
|
||||
if (pDeviceStateTemplate == NULL) {
|
||||
return MA_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
/* Not using the context state for this example. */
|
||||
(void)pContextStateTemplate;
|
||||
|
||||
/* No backend-specific config for this example. */
|
||||
(void)pDeviceConfigTemplate;
|
||||
|
||||
if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex || deviceType == ma_device_type_loopback) {
|
||||
/* Do any backend initialization for the capture side here. Update pDescriptorCapture with the actual internal format, channels, rate and period size. */
|
||||
ma_format format;
|
||||
ma_uint32 channels;
|
||||
ma_uint32 sampleRate;
|
||||
ma_uint32 periodSizeInFrames;
|
||||
|
||||
/*
|
||||
On input pDescriptorCapture contains what miniaudio would like, if possible. On output, you set
|
||||
it to what the backend actually supports. You would typically try to make it as close as possible,
|
||||
but it's ultimately up to the backend to choose what it gives you.
|
||||
*/
|
||||
|
||||
/* Negotiate the format. */
|
||||
format = pDescriptorCapture->format;
|
||||
if (format != ma_format_f32) {
|
||||
format = ma_format_f32;
|
||||
}
|
||||
|
||||
/* Negotiate the channel count. */
|
||||
channels = pDescriptorCapture->channels;
|
||||
if (channels != 1 && channels != 2) {
|
||||
channels = 2;
|
||||
}
|
||||
|
||||
/* Negotiate the sample rate. */
|
||||
sampleRate = pDescriptorCapture->sampleRate;
|
||||
if (sampleRate != 44100 && sampleRate != 48000) {
|
||||
sampleRate = 48000;
|
||||
}
|
||||
|
||||
/*
|
||||
Negotiate the period size. You will want to use ma_calculate_buffer_size_in_frames_from_descriptor() to
|
||||
calculate an appropriate period size based on standardized miniaudio conventions as a base point, and then
|
||||
massage it based on the requirements of the backend. This example is just clamping it between 1024 and
|
||||
16384 just for demonstration purposes.
|
||||
*/
|
||||
periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptorCapture, sampleRate);
|
||||
if (periodSizeInFrames < 1024) {
|
||||
periodSizeInFrames = 1024;
|
||||
}
|
||||
|
||||
/* Initialize any backend-specific stuff here. */
|
||||
|
||||
/* Update the descriptor with the actual internal settings. */
|
||||
pDescriptorCapture->format = format;
|
||||
pDescriptorCapture->channels = channels;
|
||||
pDescriptorCapture->sampleRate = sampleRate;
|
||||
pDescriptorCapture->periodSizeInFrames = periodSizeInFrames;
|
||||
pDescriptorCapture->periodCount = 1; /* Just set this 1 if you're unsure. Set it to 2 if you are using a double-buffering technique. */
|
||||
}
|
||||
|
||||
if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) {
|
||||
/* This works exactly the same as capture, but for playback. */
|
||||
ma_format format;
|
||||
ma_uint32 channels;
|
||||
ma_uint32 sampleRate;
|
||||
ma_uint32 periodSizeInFrames;
|
||||
|
||||
format = pDescriptorPlayback->format;
|
||||
if (format != ma_format_f32) {
|
||||
format = ma_format_f32;
|
||||
}
|
||||
|
||||
channels = pDescriptorPlayback->channels;
|
||||
if (channels != 1 && channels != 2) {
|
||||
channels = 2;
|
||||
}
|
||||
|
||||
sampleRate = pDescriptorPlayback->sampleRate;
|
||||
if (sampleRate != 44100 && sampleRate != 48000) {
|
||||
sampleRate = 48000;
|
||||
}
|
||||
|
||||
periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptorPlayback, sampleRate);
|
||||
if (periodSizeInFrames < 1024) {
|
||||
periodSizeInFrames = 1024;
|
||||
}
|
||||
|
||||
/* Initialize any backend-specific stuff here. */
|
||||
|
||||
/* Update the descriptor with the actual internal settings. */
|
||||
pDescriptorPlayback->format = format;
|
||||
pDescriptorPlayback->channels = channels;
|
||||
pDescriptorPlayback->sampleRate = sampleRate;
|
||||
pDescriptorPlayback->periodSizeInFrames = periodSizeInFrames;
|
||||
pDescriptorPlayback->periodCount = 1;
|
||||
}
|
||||
|
||||
*ppDeviceState = pDeviceStateTemplate;
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
|
||||
static void ma_device_uninit__template(ma_device* pDevice)
|
||||
{
|
||||
ma_device_state_template* pDeviceStateTemplate = ma_device_get_backend_state__template(pDevice);
|
||||
|
||||
/* Do any device-level uninitialization here. */
|
||||
|
||||
ma_free(pDeviceStateTemplate, ma_device_get_allocation_callbacks(pDevice));
|
||||
}
|
||||
|
||||
static ma_result ma_device_start__template(ma_device* pDevice)
|
||||
{
|
||||
ma_device_state_template* pDeviceStateTemplate = ma_device_get_backend_state__template(pDevice);
|
||||
ma_device_type deviceType = ma_device_get_type(pDevice);
|
||||
|
||||
if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex || deviceType == ma_device_type_loopback) {
|
||||
/* Start the capture side. */
|
||||
(void)pDeviceStateTemplate;
|
||||
}
|
||||
|
||||
if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) {
|
||||
/* Start the playback side. */
|
||||
(void)pDeviceStateTemplate;
|
||||
}
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
|
||||
static ma_result ma_device_stop__template(ma_device* pDevice)
|
||||
{
|
||||
ma_device_state_template* pDeviceStateTemplate = ma_device_get_backend_state__template(pDevice);
|
||||
ma_device_type deviceType = ma_device_get_type(pDevice);
|
||||
|
||||
if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex || deviceType == ma_device_type_loopback) {
|
||||
/* Stop the capture side. */
|
||||
(void)pDeviceStateTemplate;
|
||||
}
|
||||
|
||||
if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) {
|
||||
/* Stop the playback side. */
|
||||
(void)pDeviceStateTemplate;
|
||||
}
|
||||
|
||||
return MA_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
static ma_result ma_device_step__template(ma_device* pDevice, ma_blocking_mode blockingMode)
|
||||
{
|
||||
ma_device_state_template* pDeviceStateTemplate = ma_device_get_backend_state__template(pDevice);
|
||||
|
||||
/*
|
||||
When blockingMode is MA_BLOCKING_MODE_BLOCKING, this function should block until data has been processed. Otherwise
|
||||
if it's set to MA_BLOCKING_MODE_NON_BLOCKING it should try processing any data if ready and then immediately return,
|
||||
regardless of whether or not any data was processed. Use ma_device_handle_backend_data_callback() to process data.
|
||||
|
||||
The code below is an example of the general idea. You don't have to follow this exact design.
|
||||
*/
|
||||
if (blockingMode == MA_BLOCKING_MODE_BLOCKING) {
|
||||
/* Wait here. */
|
||||
(void)pDeviceStateTemplate;
|
||||
}
|
||||
|
||||
/* If after waiting the device has been stopped don't process any audio data. */
|
||||
if (!ma_device_is_started(pDevice)) {
|
||||
return MA_DEVICE_NOT_STARTED;
|
||||
}
|
||||
|
||||
/* Call ma_device_handle_backend_data_callback() at some point. */
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
|
||||
static void ma_device_wakeup__template(ma_device* pDevice)
|
||||
{
|
||||
ma_device_state_template* pDeviceStateTemplate = ma_device_get_backend_state__template(pDevice);
|
||||
|
||||
/* Do whatever needs to be done to wakeup the step function. It's OK to just do nothing here so long as your backend will return from a blocking step in a reasonable amount of time. */
|
||||
(void)pDeviceStateTemplate;
|
||||
}
|
||||
|
||||
static ma_device_backend_vtable ma_gDeviceBackendVTable_Template =
|
||||
{
|
||||
ma_backend_info__template,
|
||||
ma_context_init__template,
|
||||
ma_context_uninit__template,
|
||||
ma_context_enumerate_devices__template,
|
||||
ma_device_init__template,
|
||||
ma_device_uninit__template,
|
||||
ma_device_start__template,
|
||||
ma_device_stop__template,
|
||||
ma_device_step__template,
|
||||
ma_device_wakeup__template
|
||||
};
|
||||
|
||||
ma_device_backend_vtable* ma_device_backend_template = &ma_gDeviceBackendVTable_Template;
|
||||
#else
|
||||
ma_device_backend_vtable* ma_device_backend_template = NULL;
|
||||
#endif /* MA_HAS_TEMPLATE */
|
||||
|
||||
MA_API ma_device_backend_vtable* ma_template_get_vtable(void)
|
||||
{
|
||||
return ma_device_backend_template;
|
||||
}
|
||||
|
||||
MA_API ma_context_config_template ma_context_config_template_init(void)
|
||||
{
|
||||
ma_context_config_template config;
|
||||
|
||||
MA_ZERO_OBJECT(&config);
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
MA_API ma_device_config_template ma_device_config_template_init(void)
|
||||
{
|
||||
ma_device_config_template config;
|
||||
|
||||
MA_ZERO_OBJECT(&config);
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
#endif /* miniaudio_backend_template_c */
|
||||
@@ -0,0 +1,33 @@
|
||||
#ifndef miniaudio_backend_template_h
|
||||
#define miniaudio_backend_template_h
|
||||
|
||||
#include "../../../miniaudio.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern ma_device_backend_vtable* ma_device_backend_template;
|
||||
MA_API ma_device_backend_vtable* ma_template_get_vtable(void);
|
||||
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int _unused;
|
||||
} ma_context_config_template;
|
||||
|
||||
MA_API ma_context_config_template ma_context_config_template_init(void);
|
||||
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int _unused;
|
||||
} ma_device_config_template;
|
||||
|
||||
MA_API ma_device_config_template ma_device_config_template_init(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* miniaudio_backend_template_h */
|
||||
|
||||
@@ -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,508 @@
|
||||
#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 size_t ma_libopus_ds_sizeof(void)
|
||||
{
|
||||
return sizeof(ma_libopus);
|
||||
}
|
||||
|
||||
static void ma_libopus_ds_uninit(ma_data_source* pDataSource)
|
||||
{
|
||||
ma_libopus_uninit((ma_libopus*)pDataSource);
|
||||
}
|
||||
|
||||
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 ma_gDataSourceVTable_libopus =
|
||||
{
|
||||
ma_libopus_ds_sizeof,
|
||||
ma_libopus_ds_uninit,
|
||||
NULL, /* onCopy. Copying is not supported. */
|
||||
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 */
|
||||
};
|
||||
|
||||
|
||||
#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_SET;
|
||||
} else if (whence == SEEK_END) {
|
||||
origin = MA_SEEK_END;
|
||||
} else {
|
||||
origin = MA_SEEK_CUR;
|
||||
}
|
||||
|
||||
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, const ma_allocation_callbacks* pAllocationCallbacks, ma_libopus* pOpus)
|
||||
{
|
||||
ma_result result;
|
||||
ma_data_source_config dataSourceConfig;
|
||||
|
||||
if (pOpus == NULL) {
|
||||
return MA_INVALID_ARGS;
|
||||
}
|
||||
|
||||
memset(pOpus, 0, sizeof(*pOpus));
|
||||
pOpus->allocationCallbacks = ma_allocation_callbacks_init_copy(pAllocationCallbacks);
|
||||
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.pVTable = &ma_gDataSourceVTable_libopus;
|
||||
|
||||
result = ma_data_source_base_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, pAllocationCallbacks, 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. */
|
||||
(void)ma_gDataSourceVTable_libopus;
|
||||
return MA_NOT_IMPLEMENTED;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
MA_API void ma_libopus_uninit(ma_libopus* pOpus)
|
||||
{
|
||||
if (pOpus == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
#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_base_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 void ma_decoding_backend_info__libopus(void* pUserData, ma_decoding_backend_info* pInfo)
|
||||
{
|
||||
(void)pUserData;
|
||||
|
||||
pInfo->pName = "Opus";
|
||||
pInfo->pLibraryName = "libopus";
|
||||
pInfo->pVendor = "Xiph.Org";
|
||||
pInfo->encodingFormat = ma_encoding_format_opus;
|
||||
}
|
||||
|
||||
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 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);
|
||||
ma_free(pOpus, pAllocationCallbacks);
|
||||
}
|
||||
|
||||
static ma_decoding_backend_vtable ma_gDecodingBackendVTable_libopus =
|
||||
{
|
||||
ma_decoding_backend_info__libopus,
|
||||
ma_decoding_backend_init__libopus,
|
||||
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
|
||||
|
||||
MA_API ma_decoding_backend_vtable* ma_libopus_get_vtable(void)
|
||||
{
|
||||
return ma_decoding_backend_libopus;
|
||||
}
|
||||
|
||||
#endif /* miniaudio_libopus_c */
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
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.
|
||||
|
||||
This does not support copying.
|
||||
*/
|
||||
#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_allocation_callbacks allocationCallbacks;
|
||||
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 void ma_libopus_uninit(ma_libopus* pOpus);
|
||||
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;
|
||||
MA_API ma_decoding_backend_vtable* ma_libopus_get_vtable(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* miniaudio_libopus_h */
|
||||
|
||||
@@ -0,0 +1,560 @@
|
||||
#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 size_t ma_libvorbis_ds_sizeof(void)
|
||||
{
|
||||
return sizeof(ma_libvorbis);
|
||||
}
|
||||
|
||||
static void ma_libvorbis_ds_uninit(ma_data_source* pDataSource)
|
||||
{
|
||||
ma_libvorbis_uninit((ma_libvorbis*)pDataSource);
|
||||
}
|
||||
|
||||
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 ma_gDataSourceVTable_libvorbis =
|
||||
{
|
||||
ma_libvorbis_ds_sizeof,
|
||||
ma_libvorbis_ds_uninit,
|
||||
NULL, /* onCopy. Copying is not supported. */
|
||||
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 */
|
||||
};
|
||||
|
||||
|
||||
#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_SET;
|
||||
} else if (whence == SEEK_END) {
|
||||
origin = MA_SEEK_END;
|
||||
} else {
|
||||
origin = MA_SEEK_CUR;
|
||||
}
|
||||
|
||||
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->allocationCallbacks = ma_allocation_callbacks_init_copy(pAllocationCallbacks);
|
||||
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.pVTable = &ma_gDataSourceVTable_libvorbis;
|
||||
|
||||
result = ma_data_source_base_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_base_uninit(&pVorbis->ds);
|
||||
return MA_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
#else
|
||||
{
|
||||
/* libvorbis is disabled. */
|
||||
(void)ma_gDataSourceVTable_libvorbis;
|
||||
(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_base_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 void ma_libvorbis_uninit(ma_libvorbis* pVorbis)
|
||||
{
|
||||
if (pVorbis == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
#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_base_uninit(&pVorbis->ds);
|
||||
ma_free(pVorbis->vf, &pVorbis->allocationCallbacks);
|
||||
}
|
||||
|
||||
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 void ma_decoding_backend_info__libvorbis(void* pUserData, ma_decoding_backend_info* pInfo)
|
||||
{
|
||||
(void)pUserData;
|
||||
|
||||
pInfo->pName = "Vorbis";
|
||||
pInfo->pLibraryName = "libvorbis";
|
||||
pInfo->pVendor = "Xiph.Org";
|
||||
pInfo->encodingFormat = ma_encoding_format_vorbis;
|
||||
}
|
||||
|
||||
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 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);
|
||||
ma_free(pVorbis, pAllocationCallbacks);
|
||||
}
|
||||
|
||||
static ma_decoding_backend_vtable ma_gDecodingBackendVTable_libvorbis =
|
||||
{
|
||||
ma_decoding_backend_info__libvorbis,
|
||||
ma_decoding_backend_init__libvorbis,
|
||||
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
|
||||
|
||||
MA_API ma_decoding_backend_vtable* ma_libvorbis_get_vtable(void)
|
||||
{
|
||||
return ma_decoding_backend_libvorbis;
|
||||
}
|
||||
|
||||
#endif /* miniaudio_libvorbis_c */
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
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.
|
||||
|
||||
This does not support copying.
|
||||
*/
|
||||
#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_allocation_callbacks allocationCallbacks;
|
||||
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 void ma_libvorbis_uninit(ma_libvorbis* pVorbis);
|
||||
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;
|
||||
MA_API ma_decoding_backend_vtable* ma_libvorbis_get_vtable(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* miniaudio_libvorbis_h */
|
||||
+102
-2
@@ -1,8 +1,13 @@
|
||||
/* 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
|
||||
|
||||
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.
|
||||
The `ma_libopus` object can be plugged into any `ma_data_source_*()` API.
|
||||
|
||||
This can also be used as a custom decoding backend for `ma_decoder`. Use `ma_decoding_backend_libopus`
|
||||
to wire up the custom decoding backend to `ma_decoder`. See the custom_decoder example for an example
|
||||
of how to do this.
|
||||
|
||||
You need to include this file after miniaudio.h.
|
||||
*/
|
||||
@@ -39,6 +44,9 @@ MA_API ma_result ma_libopus_get_data_format(ma_libopus* pOpus, ma_format* pForma
|
||||
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 const ma_decoding_backend_vtable* ma_decoding_backend_libopus;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -493,4 +501,96 @@ MA_API ma_result ma_libopus_get_length_in_pcm_frames(ma_libopus* pOpus, ma_uint6
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
The code below defines the vtable that you'll plug into your `ma_decoder_config` object.
|
||||
*/
|
||||
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_encoding_format ma_decoding_backend_get_encoding_format__libopus(void* pUserData, ma_data_source* pBackend)
|
||||
{
|
||||
(void)pUserData;
|
||||
(void)pBackend;
|
||||
|
||||
/*
|
||||
When pBackend is null, return ma_encoding_format_unknown if the backend supports multiple
|
||||
formats. An example might be an FFmpeg backend. If the backend only supports a single format,
|
||||
like this one, return the format directly (if it's not recognized by miniaudio, return
|
||||
ma_encoding_format_unknown).
|
||||
|
||||
When pBackend is non-null, return the encoded format of the data source. If the format is not
|
||||
recognized by miniaudio, return ma_encoding_format_unknown.
|
||||
|
||||
Since this backend only operates on Opus streams, we can just return ma_encoding_format_opus
|
||||
in all cases.
|
||||
*/
|
||||
return ma_encoding_format_opus;
|
||||
}
|
||||
|
||||
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,
|
||||
ma_decoding_backend_get_encoding_format__libopus
|
||||
};
|
||||
const ma_decoding_backend_vtable* ma_decoding_backend_libopus = &g_ma_decoding_backend_vtable_libopus;
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
/* 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
|
||||
|
||||
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.
|
||||
The `ma_libvorbis` object can be plugged into any `ma_data_source_*()` API.
|
||||
|
||||
This can also be used as a custom decoding backend for `ma_decoder`. Use `ma_decoding_backend_libvorbis`
|
||||
to wire up the custom decoding backend to `ma_decoder`. See the custom_decoder example for an example
|
||||
of how to do this.
|
||||
|
||||
You need to include this file after miniaudio.h.
|
||||
*/
|
||||
@@ -42,6 +47,9 @@ MA_API ma_result ma_libvorbis_get_data_format(ma_libvorbis* pVorbis, ma_format*
|
||||
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 const ma_decoding_backend_vtable* ma_decoding_backend_libvorbis;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -326,7 +334,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;
|
||||
@@ -513,4 +521,96 @@ MA_API ma_result ma_libvorbis_get_length_in_pcm_frames(ma_libvorbis* pVorbis, ma
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
The code below defines the vtable that you'll plug into your `ma_decoder_config` object.
|
||||
*/
|
||||
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_encoding_format ma_decoding_backend_get_encoding_format__libvorbis(void* pUserData, ma_data_source* pBackend)
|
||||
{
|
||||
(void)pUserData;
|
||||
(void)pBackend;
|
||||
|
||||
/*
|
||||
When pBackend is null, return ma_encoding_format_unknown if the backend supports multiple
|
||||
formats. An example might be an FFmpeg backend. If the backend only supports a single format,
|
||||
like this one, return the format directly (if it's not recognized by miniaudio, return
|
||||
ma_encoding_format_unknown).
|
||||
|
||||
When pBackend is non-null, return the encoded format of the data source. If the format is not
|
||||
recognized by miniaudio, return ma_encoding_format_unknown.
|
||||
|
||||
Since this backend only operates on Vorbis streams, we can just return ma_encoding_format_vorbis
|
||||
in all cases.
|
||||
*/
|
||||
return ma_encoding_format_vorbis;
|
||||
}
|
||||
|
||||
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,
|
||||
ma_decoding_backend_get_encoding_format__libvorbis
|
||||
};
|
||||
const ma_decoding_backend_vtable* ma_decoding_backend_libvorbis = &g_ma_decoding_backend_vtable_libvorbis;
|
||||
|
||||
#endif
|
||||
|
||||
+15304
-13027
File diff suppressed because it is too large
Load Diff
+311
-150
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
@@ -57,7 +61,7 @@ MA_API ma_result ma_channel_combiner_node_init(ma_node_graph* pNodeGraph, const
|
||||
outputChannels[0] = pConfig->channels;
|
||||
|
||||
baseConfig = pConfig->nodeConfig;
|
||||
baseConfig.vtable = &g_ma_channel_combiner_node_vtable;
|
||||
baseConfig.pVTable = &g_ma_channel_combiner_node_vtable;
|
||||
baseConfig.inputBusCount = pConfig->channels; /* The vtable has an unknown channel count, so must specify it here. */
|
||||
baseConfig.pInputChannels = inputChannels;
|
||||
baseConfig.pOutputChannels = outputChannels;
|
||||
@@ -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;
|
||||
@@ -61,7 +65,7 @@ MA_API ma_result ma_channel_separator_node_init(ma_node_graph* pNodeGraph, const
|
||||
}
|
||||
|
||||
baseConfig = pConfig->nodeConfig;
|
||||
baseConfig.vtable = &g_ma_channel_separator_node_vtable;
|
||||
baseConfig.pVTable = &g_ma_channel_separator_node_vtable;
|
||||
baseConfig.outputBusCount = pConfig->channels; /* The vtable has an unknown channel count, so must specify it here. */
|
||||
baseConfig.pInputChannels = inputChannels;
|
||||
baseConfig.pOutputChannels = outputChannels;
|
||||
@@ -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"
|
||||
|
||||
@@ -139,11 +138,11 @@ int main(int argc, char** argv)
|
||||
/*done4:*/ ma_data_source_node_uninit(&g_dataSupplyNode, NULL);
|
||||
done3: ma_channel_separator_node_uninit(&g_separatorNode, NULL);
|
||||
done2: ma_channel_combiner_node_uninit(&g_combinerNode, NULL);
|
||||
done1: ma_node_graph_uninit(&g_nodeGraph, NULL);
|
||||
done1: ma_node_graph_uninit(&g_nodeGraph);
|
||||
done0: ma_device_uninit(&device);
|
||||
|
||||
(void)argc;
|
||||
(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;
|
||||
@@ -83,7 +91,7 @@ MA_API ma_result ma_ltrim_node_init(ma_node_graph* pNodeGraph, const ma_ltrim_no
|
||||
pTrimNode->foundStart = MA_FALSE;
|
||||
|
||||
baseConfig = pConfig->nodeConfig;
|
||||
baseConfig.vtable = &g_ma_ltrim_node_vtable;
|
||||
baseConfig.pVTable = &g_ma_ltrim_node_vtable;
|
||||
baseConfig.pInputChannels = &pConfig->channels;
|
||||
baseConfig.pOutputChannels = &pConfig->channels;
|
||||
|
||||
@@ -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>
|
||||
@@ -108,8 +107,8 @@ int main(int argc, char** argv)
|
||||
|
||||
/*done3:*/ ma_data_source_node_uninit(&g_dataSupplyNode, NULL);
|
||||
done2: ma_ltrim_node_uninit(&g_trimNode, NULL);
|
||||
done1: ma_node_graph_uninit(&g_nodeGraph, NULL);
|
||||
done1: ma_node_graph_uninit(&g_nodeGraph);
|
||||
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;
|
||||
@@ -59,7 +63,7 @@ MA_API ma_result ma_reverb_node_init(ma_node_graph* pNodeGraph, const ma_reverb_
|
||||
}
|
||||
|
||||
baseConfig = pConfig->nodeConfig;
|
||||
baseConfig.vtable = &g_ma_reverb_node_vtable;
|
||||
baseConfig.pVTable = &g_ma_reverb_node_vtable;
|
||||
baseConfig.pInputChannels = &pConfig->channels;
|
||||
baseConfig.pOutputChannels = &pConfig->channels;
|
||||
|
||||
@@ -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>
|
||||
@@ -8,15 +7,20 @@
|
||||
#define DEVICE_CHANNELS 1 /* For this example, always set to 1. */
|
||||
#define DEVICE_SAMPLE_RATE 48000 /* Cannot be less than 22050 for this example. */
|
||||
|
||||
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_reverb_node g_reverbNode; /* The reverb node. */
|
||||
static ma_node_graph g_nodeGraph; /* The main node graph that we'll be feeding data through. */
|
||||
static ma_audio_ring_buffer 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_reverb_node g_reverbNode; /* The reverb 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);
|
||||
/*
|
||||
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
|
||||
@@ -24,7 +28,7 @@ void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uin
|
||||
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);
|
||||
ma_audio_ring_buffer_write_pcm_frames(&g_dataSupply, pInput, frameCount, NULL);
|
||||
|
||||
/* 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);
|
||||
@@ -38,6 +42,7 @@ int main(int argc, char** argv)
|
||||
ma_node_graph_config nodeGraphConfig;
|
||||
ma_reverb_node_config reverbNodeConfig;
|
||||
ma_data_source_node_config dataSupplyNodeConfig;
|
||||
ma_audio_ring_buffer_config ringBufferConfig;
|
||||
|
||||
deviceConfig = ma_device_config_init(ma_device_type_duplex);
|
||||
deviceConfig.capture.pDeviceID = NULL;
|
||||
@@ -78,7 +83,9 @@ int main(int argc, char** argv)
|
||||
|
||||
|
||||
/* Data supply. Attached to input bus 0 of the reverb node. */
|
||||
result = ma_audio_buffer_ref_init(device.capture.format, device.capture.channels, NULL, 0, &g_dataSupply);
|
||||
ringBufferConfig = ma_audio_ring_buffer_config_init(device.capture.format, device.capture.channels, device.sampleRate, device.capture.internalPeriodSizeInFrames * 3);
|
||||
|
||||
result = ma_audio_ring_buffer_init(&ringBufferConfig, &g_dataSupply);
|
||||
if (result != MA_SUCCESS) {
|
||||
printf("Failed to initialize audio buffer for source.");
|
||||
goto done2;
|
||||
@@ -89,7 +96,7 @@ int main(int argc, char** argv)
|
||||
result = ma_data_source_node_init(&g_nodeGraph, &dataSupplyNodeConfig, NULL, &g_dataSupplyNode);
|
||||
if (result != MA_SUCCESS) {
|
||||
printf("Failed to initialize source node.");
|
||||
goto done2;
|
||||
goto done3;
|
||||
}
|
||||
|
||||
ma_node_attach_output_bus(&g_dataSupplyNode, 0, &g_reverbNode, 0);
|
||||
@@ -106,13 +113,14 @@ int main(int argc, char** argv)
|
||||
ma_device_stop(&device);
|
||||
|
||||
|
||||
/*done3:*/ ma_data_source_node_uninit(&g_dataSupplyNode, NULL);
|
||||
/*done4:*/ ma_data_source_node_uninit(&g_dataSupplyNode, NULL);
|
||||
done3: ma_audio_ring_buffer_uninit(&g_dataSupply);
|
||||
done2: ma_reverb_node_uninit(&g_reverbNode, NULL);
|
||||
done1: ma_node_graph_uninit(&g_nodeGraph, NULL);
|
||||
done1: ma_node_graph_uninit(&g_nodeGraph);
|
||||
done0: ma_device_uninit(&device);
|
||||
|
||||
(void)argc;
|
||||
(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;
|
||||
@@ -61,7 +65,7 @@ MA_API ma_result ma_vocoder_node_init(ma_node_graph* pNodeGraph, const ma_vocode
|
||||
outputChannels[0] = pConfig->channels; /* Output channels is always the same as the source/carrier. */
|
||||
|
||||
baseConfig = pConfig->nodeConfig;
|
||||
baseConfig.vtable = &g_ma_vocoder_node_vtable;
|
||||
baseConfig.pVTable = &g_ma_vocoder_node_vtable;
|
||||
baseConfig.pInputChannels = inputChannels;
|
||||
baseConfig.pOutputChannels = outputChannels;
|
||||
|
||||
@@ -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,26 +6,30 @@ 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>
|
||||
|
||||
#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. */
|
||||
#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 excite node. */
|
||||
static ma_audio_buffer_ref g_exciteData; /* The underlying data source of the source node. */
|
||||
static ma_data_source_node g_sourceNode; /* A data source node containing the source data we'll be sending through to the vocoder. This will be routed into the first bus of the vocoder node. */
|
||||
static ma_data_source_node g_exciteNode; /* A data source node containing the excite data we'll be sending through to the vocoder. This will be routed into the second bus of the vocoder node. */
|
||||
static ma_vocoder_node g_vocoderNode; /* The vocoder node. */
|
||||
static ma_node_graph g_nodeGraph;
|
||||
static ma_waveform g_sourceData; /* The underlying data source of the excite node. */
|
||||
static ma_audio_ring_buffer g_exciteData; /* The underlying data source of the source node. */
|
||||
static ma_data_source_node g_sourceNode; /* A data source node containing the source data we'll be sending through to the vocoder. This will be routed into the first bus of the vocoder node. */
|
||||
static ma_data_source_node g_exciteNode; /* A data source node containing the excite data we'll be sending through to the vocoder. This will be routed into the second bus of the vocoder node. */
|
||||
static ma_vocoder_node g_vocoderNode; /* The vocoder node. */
|
||||
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
|
||||
@@ -33,7 +37,7 @@ void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uin
|
||||
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_exciteData, pInput, frameCount);
|
||||
ma_audio_ring_buffer_write_pcm_frames(&g_exciteData, pInput, frameCount, NULL);
|
||||
|
||||
/* 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);
|
||||
@@ -49,6 +53,7 @@ int main(int argc, char** argv)
|
||||
ma_data_source_node_config sourceNodeConfig;
|
||||
ma_data_source_node_config exciteNodeConfig;
|
||||
ma_waveform_config waveformConfig;
|
||||
ma_audio_ring_buffer_config ringBufferConfig;
|
||||
|
||||
deviceConfig = ma_device_config_init(ma_device_type_duplex);
|
||||
deviceConfig.capture.pDeviceID = NULL;
|
||||
@@ -96,7 +101,7 @@ int main(int argc, char** argv)
|
||||
result = ma_waveform_init(&waveformConfig, &g_sourceData);
|
||||
if (result != MA_SUCCESS) {
|
||||
printf("Failed to initialize waveform for excite node.");
|
||||
goto done3;
|
||||
goto done2;
|
||||
}
|
||||
|
||||
sourceNodeConfig = ma_data_source_node_config_init(&g_sourceData);
|
||||
@@ -111,10 +116,12 @@ int main(int argc, char** argv)
|
||||
|
||||
|
||||
/* Excite/modulator. Attached to input bus 1 of the vocoder node. */
|
||||
result = ma_audio_buffer_ref_init(device.capture.format, device.capture.channels, NULL, 0, &g_exciteData);
|
||||
ringBufferConfig = ma_audio_ring_buffer_config_init(device.capture.format, device.capture.channels, device.sampleRate, device.capture.internalPeriodSizeInFrames * 3);
|
||||
|
||||
result = ma_audio_ring_buffer_init(&ringBufferConfig, &g_exciteData);
|
||||
if (result != MA_SUCCESS) {
|
||||
printf("Failed to initialize audio buffer for source.");
|
||||
goto done2;
|
||||
goto done4;
|
||||
}
|
||||
|
||||
exciteNodeConfig = ma_data_source_node_config_init(&g_exciteData);
|
||||
@@ -122,7 +129,7 @@ int main(int argc, char** argv)
|
||||
result = ma_data_source_node_init(&g_nodeGraph, &exciteNodeConfig, NULL, &g_exciteNode);
|
||||
if (result != MA_SUCCESS) {
|
||||
printf("Failed to initialize source node.");
|
||||
goto done2;
|
||||
goto done5;
|
||||
}
|
||||
|
||||
ma_node_attach_output_bus(&g_exciteNode, 0, &g_vocoderNode, 1);
|
||||
@@ -136,10 +143,12 @@ int main(int argc, char** argv)
|
||||
/* It's important that we stop the device first or else we'll uninitialize the graph from under the device. */
|
||||
ma_device_stop(&device);
|
||||
|
||||
/*done4:*/ ma_data_source_node_uninit(&g_exciteNode, NULL);
|
||||
done3: ma_data_source_node_uninit(&g_sourceNode, NULL);
|
||||
/*done6:*/ ma_data_source_node_uninit(&g_exciteNode, NULL);
|
||||
done5: ma_audio_ring_buffer_uninit(&g_exciteData);
|
||||
done4: ma_data_source_node_uninit(&g_sourceNode, NULL);
|
||||
done3: ma_waveform_uninit(&g_sourceData);
|
||||
done2: ma_vocoder_node_uninit(&g_vocoderNode, NULL);
|
||||
done1: ma_node_graph_uninit(&g_nodeGraph, NULL);
|
||||
done1: ma_node_graph_uninit(&g_nodeGraph);
|
||||
done0: ma_device_uninit(&device);
|
||||
|
||||
(void)argc;
|
||||
|
||||
@@ -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,110 @@
|
||||
#ifndef miniaudio_vfs_debugging_c
|
||||
#define miniaudio_vfs_debugging_c
|
||||
|
||||
#include "miniaudio_vfs_debugging.h"
|
||||
|
||||
MA_API ma_vfs_debugging_config ma_vfs_debugging_config_init(ma_vfs* pUnderlyingVFS, ma_uint32 latencyInMilliseconds)
|
||||
{
|
||||
ma_vfs_debugging_config config;
|
||||
|
||||
MA_ZERO_OBJECT(&config);
|
||||
config.pUnderlyingVFS = pUnderlyingVFS;
|
||||
config.latencyInMilliseconds = latencyInMilliseconds;
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
|
||||
static ma_vfs_debugging* ma_vfs_debugging_cast(ma_vfs* pVFS)
|
||||
{
|
||||
return (ma_vfs_debugging*)pVFS;
|
||||
}
|
||||
|
||||
static ma_vfs* ma_vfs_debugging_get_underlying_vfs(ma_vfs* pVFS)
|
||||
{
|
||||
return ma_vfs_debugging_cast(pVFS)->config.pUnderlyingVFS;
|
||||
}
|
||||
|
||||
|
||||
static ma_result ma_vfs_debugging_open(ma_vfs* pVFS, const char* pFilePath, ma_uint32 openMode, ma_vfs_file* pFile)
|
||||
{
|
||||
return ma_vfs_open(ma_vfs_debugging_get_underlying_vfs(pVFS), pFilePath, openMode, pFile);
|
||||
}
|
||||
|
||||
static ma_result ma_vfs_debugging_open_w(ma_vfs* pVFS, const wchar_t* pFilePath, ma_uint32 openMode, ma_vfs_file* pFile)
|
||||
{
|
||||
return ma_vfs_open_w(ma_vfs_debugging_get_underlying_vfs(pVFS), pFilePath, openMode, pFile);
|
||||
}
|
||||
|
||||
static ma_result ma_vfs_debugging_close(ma_vfs* pVFS, ma_vfs_file file)
|
||||
{
|
||||
return ma_vfs_close(ma_vfs_debugging_get_underlying_vfs(pVFS), file);
|
||||
}
|
||||
|
||||
static ma_result ma_vfs_debugging_read(ma_vfs* pVFS, ma_vfs_file file, void* pDst, size_t sizeInBytes, size_t* pBytesRead)
|
||||
{
|
||||
ma_vfs_debugging* pDebuggingVFS = ma_vfs_debugging_cast(pVFS);
|
||||
ma_result result;
|
||||
|
||||
/* Introduce artificial latency if requested. Ignored on Emscripten. */
|
||||
#ifndef __EMSCRIPTEN__
|
||||
{
|
||||
if (pDebuggingVFS->config.latencyInMilliseconds > 0) {
|
||||
ma_sleep(pDebuggingVFS->config.latencyInMilliseconds);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/*printf("READING\n");*/
|
||||
|
||||
result = ma_vfs_read(ma_vfs_debugging_get_underlying_vfs(pVFS), file, pDst, sizeInBytes, pBytesRead);
|
||||
return result;
|
||||
}
|
||||
|
||||
static ma_result ma_vfs_debugging_write(ma_vfs* pVFS, ma_vfs_file file, const void* pSrc, size_t sizeInBytes, size_t* pBytesWritten)
|
||||
{
|
||||
return ma_vfs_write(ma_vfs_debugging_get_underlying_vfs(pVFS), file, pSrc, sizeInBytes, pBytesWritten);
|
||||
}
|
||||
|
||||
static ma_result ma_vfs_debugging_seek(ma_vfs* pVFS, ma_vfs_file file, ma_int64 offset, ma_seek_origin origin)
|
||||
{
|
||||
return ma_vfs_seek(ma_vfs_debugging_get_underlying_vfs(pVFS), file, offset, origin);
|
||||
}
|
||||
|
||||
static ma_result ma_vfs_debugging_tell(ma_vfs* pVFS, ma_vfs_file file, ma_int64* pCursor)
|
||||
{
|
||||
return ma_vfs_tell(ma_vfs_debugging_get_underlying_vfs(pVFS), file, pCursor);
|
||||
}
|
||||
|
||||
static ma_result ma_vfs_debugging_info(ma_vfs* pVFS, ma_vfs_file file, ma_file_info* pInfo)
|
||||
{
|
||||
return ma_vfs_info(ma_vfs_debugging_get_underlying_vfs(pVFS), file, pInfo);
|
||||
}
|
||||
|
||||
MA_API ma_result ma_vfs_debugging_init(const ma_vfs_debugging_config* pConfig, ma_vfs_debugging* pVFS)
|
||||
{
|
||||
if (pVFS == NULL) {
|
||||
return MA_INVALID_ARGS;
|
||||
}
|
||||
|
||||
MA_ZERO_OBJECT(pVFS);
|
||||
|
||||
if (pConfig == NULL) {
|
||||
pVFS->config = ma_vfs_debugging_config_init(NULL, 0);
|
||||
} else {
|
||||
pVFS->config = *pConfig;
|
||||
}
|
||||
|
||||
pVFS->cb.onOpen = ma_vfs_debugging_open;
|
||||
pVFS->cb.onOpenW = ma_vfs_debugging_open_w;
|
||||
pVFS->cb.onClose = ma_vfs_debugging_close;
|
||||
pVFS->cb.onRead = ma_vfs_debugging_read;
|
||||
pVFS->cb.onWrite = ma_vfs_debugging_write;
|
||||
pVFS->cb.onSeek = ma_vfs_debugging_seek;
|
||||
pVFS->cb.onTell = ma_vfs_debugging_tell;
|
||||
pVFS->cb.onInfo = ma_vfs_debugging_info;
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
|
||||
#endif /* miniaudio_vfs_debugging_c */
|
||||
@@ -0,0 +1,27 @@
|
||||
#ifndef miniaudio_vfs_debugging_h
|
||||
#define miniaudio_vfs_debugging_h
|
||||
|
||||
#include "../../../miniaudio.h"
|
||||
|
||||
/*
|
||||
This is a VFS for debugging purposes. I use it for things like artificial latency.
|
||||
*/
|
||||
typedef struct ma_vfs_debugging_config
|
||||
{
|
||||
ma_vfs* pUnderlyingVFS; /* The underlying VFS to which all calls are forwarded. */
|
||||
ma_uint32 latencyInMilliseconds; /* The amount of latency to introduce in milliseconds. This will be done with a sleep every read. */
|
||||
} ma_vfs_debugging_config;
|
||||
|
||||
MA_API ma_vfs_debugging_config ma_vfs_debugging_config_init(ma_vfs* pUnderlyingVFS, ma_uint32 latencyInMilliseconds);
|
||||
|
||||
|
||||
typedef struct ma_vfs_debugging
|
||||
{
|
||||
ma_vfs_callbacks cb; /* Must be first. */
|
||||
ma_vfs_debugging_config config;
|
||||
} ma_vfs_debugging;
|
||||
|
||||
MA_API ma_result ma_vfs_debugging_init(const ma_vfs_debugging_config* pConfig, ma_vfs_debugging* pVFS);
|
||||
|
||||
|
||||
#endif /* miniaudio_vfs_debugging_h */
|
||||
@@ -0,0 +1,2 @@
|
||||
#define MINIAUDIO_IMPLEMENTATION
|
||||
#include "miniaudio.h"
|
||||
+39228
-27793
File diff suppressed because it is too large
Load Diff
@@ -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@
|
||||
+19
-5
@@ -2,22 +2,36 @@ Building
|
||||
========
|
||||
Build and run from this directory. Example:
|
||||
|
||||
gcc ../test_deviceio/ma_test_deviceio.c -o bin/test_deviceio -ldl -lm -lpthread -Wall -Wextra -Wpedantic -std=c89
|
||||
./bin/test_deviceio
|
||||
gcc ../deviceio/deviceio.c -o bin/deviceio -ldl -lm -lpthread -Wall -Wextra -Wpedantic -std=c89
|
||||
./bin/deviceio
|
||||
|
||||
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:
|
||||
|
||||
emcc ../test_emscripten/ma_test_emscripten.c -o bin/test_emscripten.html -sAUDIO_WORKLET=1 -sWASM_WORKERS=1 -sASYNCIFY -DMA_ENABLE_AUDIO_WORKLETS -Wall -Wextra
|
||||
emcc ../emscripten/emscripten.c -o bin/emscripten.html -sAUDIO_WORKLET=1 -sWASM_WORKERS=1 -DMA_ENABLE_AUDIO_WORKLETS -Wall -Wextra
|
||||
|
||||
If you output WASM it may not work when running the web page locally. To test you can run with something
|
||||
like this:
|
||||
|
||||
emrun ./bin/test_emscripten.html
|
||||
emrun ./bin/emscripten.html
|
||||
|
||||
If you want to see stdout on the command line when running from emrun, add `--emrun` to your emcc command.
|
||||
If you want to see stdout on the command line when running from emrun, add `--emrun` to your emcc command.
|
||||
|
||||
To use with CMake, you can do something like this:
|
||||
|
||||
emcmake cmake -S ../../ -B cmake-emcc -DCMAKE_BUILD_TYPE=Debug -DMINIAUDIO_NO_LIBVORBIS=Yes -DMINIAUDIO_NO_LIBOPUS=Yes -DMINIAUDIO_BUILD_TESTS=Yes -DMINIAUDIO_BUILD_EXAMPLES=No
|
||||
|
||||
Then to compile with CMake:
|
||||
|
||||
cmake --build cmake-emcc -j
|
||||
|
||||
To do a clean rebuild:
|
||||
|
||||
cmake --build cmake-emcc -j --clean-first
|
||||
|
||||
@@ -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,62 @@
|
||||
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"
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
cppFlags += "-Wall -Wextra -pedantic"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
+24
@@ -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,183 @@
|
||||
#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);
|
||||
|
||||
(void)pFramesIn;
|
||||
|
||||
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 = "";
|
||||
|
||||
(void)env;
|
||||
|
||||
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_device_backend_config pBackends[1];
|
||||
size_t backendCount;
|
||||
|
||||
if (backend == BACKEND_AUTO) {
|
||||
backendCount = 0;
|
||||
} else {
|
||||
backendCount = 1;
|
||||
if (backend == BACKEND_AAUDIO) {
|
||||
pBackends[0] = ma_device_backend_config_init(ma_device_backend_aaudio, nullptr);
|
||||
} else if (backend == BACKEND_OPENSL) {
|
||||
pBackends[0] = ma_device_backend_config_init(ma_device_backend_opensl, nullptr);
|
||||
} 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());
|
||||
}
|
||||
|
||||
(void)env;
|
||||
|
||||
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.");
|
||||
}
|
||||
}
|
||||
|
||||
(void)env;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
(void)env;
|
||||
|
||||
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;
|
||||
}
|
||||
+79
@@ -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>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.8 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 982 B |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user