Compare commits
675 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 27239f7844 | |||
| 75e5b00ef7 | |||
| 430467664c | |||
| ef45405eed | |||
| 425e5b5615 | |||
| cd9f8bd49d | |||
| d9f5668968 | |||
| 5ff5fd8ecc | |||
| 5729e1004d | |||
| 87dbeed853 | |||
| fb6585cf18 | |||
| 19adb622ef | |||
| db4de2b56a | |||
| 20f5e7c1fa | |||
| 92c653e5f0 | |||
| 1c649943f7 | |||
| e1e0809253 | |||
| 32e0d92c51 | |||
| 13698362f1 | |||
| c3380d4201 | |||
| 8ebf146c72 | |||
| 229bf45e24 | |||
| 855a8c0cd9 | |||
| 8c4adfd752 | |||
| 1f01311ee3 | |||
| 3348d3ce15 | |||
| a30d3ad803 | |||
| b99adb623d | |||
| 89906453ea | |||
| 537fd6d0a1 | |||
| 1f24f5caa1 | |||
| 948ae3bc7e | |||
| a9ac165c39 | |||
| 292de73c75 | |||
| 04cd0bc576 | |||
| 1f51de9983 | |||
| d6495b35df | |||
| 1327aca9d7 | |||
| 87a862199d | |||
| 20ef8bf390 | |||
| 002c506296 | |||
| f2b7a063ef | |||
| 9d354f721c | |||
| 78f3d8fcd8 | |||
| ec7e7c9174 | |||
| 96942d7968 | |||
| aae115c35a | |||
| 15e05fcef8 | |||
| 7fe9d8d20c | |||
| 62fa6a2277 | |||
| ffa7d1b1a7 | |||
| 43d053cb32 | |||
| 04358890d9 | |||
| adb5467720 | |||
| 4c0f77c13e | |||
| 7833cfa8de | |||
| cf7f201c92 | |||
| cd2ebb9c3a | |||
| e20143fcc4 | |||
| 507cbfa027 | |||
| 54719de132 | |||
| 8850a3f730 | |||
| 8a86ec8ac5 | |||
| 5f233a2c24 | |||
| 223f93e13d | |||
| f6e29ce5bb | |||
| 78f73a4277 | |||
| eaf209d2f1 | |||
| 204f7d8811 | |||
| 20820fd01b | |||
| fe9ec157fd | |||
| 4bc686d89b | |||
| 0d3b041aa2 | |||
| eedc42890f | |||
| 63ea62bea1 | |||
| 8c4c14ea73 | |||
| 6ae0115bf7 | |||
| 5edd5a11fc | |||
| 7d2e21334c | |||
| 4d1ac95ae2 | |||
| d578bc6cb1 | |||
| f0fe57dc5a | |||
| efc9c146c3 | |||
| 9710a859fa | |||
| 41594a07bc | |||
| 02be0149f1 | |||
| c96b50fb65 | |||
| 5ad28f8cba | |||
| f38919eecc | |||
| 5b51a58ff2 | |||
| 304ab8d890 | |||
| a08f68f213 | |||
| daf9d46478 | |||
| 03358a0d80 | |||
| 9435641c0d | |||
| 56af137ba0 | |||
| 783904e414 | |||
| 225d42538d | |||
| bb597546e1 | |||
| e179bf0a12 | |||
| 14df534114 | |||
| f57801102a | |||
| 1ad944555c | |||
| 176bc8a835 | |||
| 191fe888b4 | |||
| 1bb7f575f0 | |||
| bd08b9a5b5 | |||
| 4ee95dc296 | |||
| 2fc10a2bc0 | |||
| 1679ab937e | |||
| 16c7d1816d | |||
| 7485e402c1 | |||
| 8a352d25f9 | |||
| 2b919eac78 | |||
| 16ec07275f | |||
| 50e33ce8f4 | |||
| c4132b80ed | |||
| 3a3094c887 | |||
| 69d55d7e03 | |||
| 84d47f7d4c | |||
| 36c1589c11 | |||
| aa05feec4a | |||
| b1c2671e9d | |||
| 75f469fa71 | |||
| 630a3b8cbd | |||
| e3179aa058 | |||
| 8750810f5c | |||
| eb9b9fb4e5 | |||
| dbfdd35545 | |||
| 7e798df7b8 | |||
| b0919240ab | |||
| d86046d522 | |||
| 32b9a46b39 | |||
| 3970252da9 | |||
| 28278a9c15 | |||
| f16692ecf8 | |||
| ba39c622ef | |||
| a913aa6ba8 | |||
| 7632dfb51a | |||
| d08d6b18c4 | |||
| d062c16629 | |||
| 31c7eb5c55 | |||
| 5d82058da7 | |||
| 534c487f14 | |||
| 822224312d | |||
| 21174047df | |||
| 54afcbe113 | |||
| 2cb2ffef72 | |||
| c3cb09aa0c | |||
| f85e76285f | |||
| bee9917598 | |||
| 0024ea229b | |||
| 03f9700071 | |||
| 537510afc8 | |||
| ee9f0fe306 | |||
| 645d354ffa | |||
| 88cb398a83 | |||
| 1869dc14d4 | |||
| a494a89c5a | |||
| e64c52ce8c | |||
| 417d8da6f4 | |||
| 1a05dda8db | |||
| 7e76770278 | |||
| 3a58bac7bd | |||
| 54e4346f50 | |||
| db6d6d68b4 | |||
| 0498ca200d | |||
| 4e760bbecc | |||
| 120776bec5 | |||
| 26b03f534c | |||
| aeb7170a8b | |||
| 4e50ce9d30 | |||
| 40c92c4643 | |||
| c0cbe602ce | |||
| ce52ecaa87 | |||
| 03389f390c | |||
| 52d58a4023 | |||
| 6e05adf955 | |||
| 267d9caac4 | |||
| dfb6d70902 | |||
| be0798b56d | |||
| 9e7c816498 | |||
| 36ecea458a | |||
| 0311f6431c | |||
| 88b9eb1560 | |||
| 6de861a75c | |||
| 966c4498d4 | |||
| 556321f18d | |||
| 7cb1cc8ab5 | |||
| 7bb37deb51 | |||
| 3e62600ecc | |||
| b8f5e1f424 | |||
| a3ea46f773 | |||
| 1acfd11635 | |||
| bd9d5d7bf0 | |||
| 95ea5951b1 | |||
| a3903b84f9 | |||
| ada92b058a | |||
| cdc15a4783 | |||
| d6686bceb0 | |||
| 20cc3882b4 | |||
| 24c7ddbac2 | |||
| 3728f74e10 | |||
| f8af992b2b | |||
| 17e88bdef3 | |||
| 8e09ae8ee6 | |||
| 2beb095eb1 | |||
| 5f8fe0e5bb | |||
| 7b43cb8266 | |||
| b0e025946e | |||
| 303ca3af72 | |||
| 45ff10fb4d | |||
| 0dc9ae0998 | |||
| af9c990277 | |||
| 5fbd02e60e | |||
| 7fb6d64ca8 | |||
| e7a44d3b70 | |||
| 7361e108a3 | |||
| e0097c18b8 | |||
| b5eccf424b | |||
| f02bc1354d | |||
| da29489ead | |||
| 39f4ea61c5 | |||
| cef82153b6 | |||
| 04fd50bcdf | |||
| d2970443dd | |||
| c993ade9bf | |||
| 844e6c881b | |||
| d912b92a60 | |||
| 191150d9a8 | |||
| 8ef242bce0 | |||
| 07989a34dc | |||
| e7d8e7eb3f | |||
| d86d882d8b | |||
| 0974b6b47c | |||
| 9a883f5046 | |||
| 1d4b355622 | |||
| bb90bfd9b3 | |||
| cf298075b5 | |||
| 33d91ceeec | |||
| a4bfba4cb5 | |||
| 21162cf5fc | |||
| bb63d5aab3 | |||
| ce0a45cbc7 | |||
| 68a099ba35 | |||
| f4a55cc8c2 | |||
| 29566b995c | |||
| 7eb5dfb3f8 | |||
| f5163ca988 | |||
| 800e2b6125 | |||
| 13710b474e | |||
| c52bf8c401 | |||
| 9975aeb37f | |||
| 7851f7d4ce | |||
| c1ad5d6055 | |||
| 7e3fed42ed | |||
| 73f5331305 | |||
| 88f7617807 | |||
| afdd1c67ad | |||
| 9e862900fe | |||
| fe329c2af2 | |||
| 1968136c98 | |||
| eeba1a385c | |||
| a6aefeccd8 | |||
| 02e51c0525 | |||
| af97a92952 | |||
| 88425becb2 | |||
| 82873486b7 | |||
| ebe0765ce3 | |||
| 8724c1ce4b | |||
| 3fdee6c814 | |||
| ecfb0d4f5b | |||
| 7266cd8b6c | |||
| 52531d8b72 | |||
| 986a58d5b4 | |||
| 1f486f820e | |||
| ecf30de36f | |||
| a9ebcdcb7f | |||
| 0ad7bb577a | |||
| 3c09f17421 | |||
| 2a01940041 | |||
| 97ecf5810f | |||
| 577a8234f2 | |||
| 9dab2db33b | |||
| 309be1c6f6 | |||
| 801c7a9226 | |||
| e98a058595 | |||
| a6bd53c4e1 | |||
| 1a9fbc1b2f | |||
| b955479e18 | |||
| 9e5a388dfe | |||
| 9d61484e4b | |||
| 73ab9d46c8 | |||
| f9d6aa2f48 | |||
| 54a0972bfe | |||
| ff5c9d1c15 | |||
| 01f76f2b67 | |||
| de060ce09a | |||
| 4eba5c34e9 | |||
| 34b29a48a4 | |||
| 23259b48dd | |||
| 2fa842a04c | |||
| 3796e407b5 | |||
| aafcb1f6fc | |||
| 8e7199cbb6 | |||
| fd52c828a9 | |||
| 476e9d7522 | |||
| b12cd77d32 | |||
| ed1135f7ae | |||
| 57d6df29b4 | |||
| 529049dc59 | |||
| 475ed0f039 | |||
| c549797af0 | |||
| e8b3e82f96 | |||
| 84354c792e | |||
| 02f8419663 | |||
| 58a2a5e2e3 | |||
| 816510fee7 | |||
| 064710cdf7 | |||
| 7ddd49e16a | |||
| f138369808 | |||
| 516de157d8 | |||
| 2e1645f32a | |||
| 7cac5304c8 | |||
| 76c57990be | |||
| 39a2f0626a | |||
| ac1fc1b60a | |||
| 91df03f7eb | |||
| 4817d82185 | |||
| d3ffaeb8d7 | |||
| f409cb4039 | |||
| c1383e3a48 | |||
| da25557ed6 | |||
| b0e72266b6 | |||
| 775187788a | |||
| 88ad84edf6 | |||
| f81558c948 | |||
| 31791c9997 | |||
| c4cf84c549 | |||
| a0eb532100 | |||
| 967ea42359 | |||
| 24393ab192 | |||
| a01dbeb09f | |||
| bc91e4d302 | |||
| 8780976979 | |||
| 4cd0e333c8 | |||
| 3f26ce4a82 | |||
| beba71a6f9 | |||
| 4cfd63a1c7 | |||
| cd6bb65d49 | |||
| 8ea7d5183b | |||
| b0b720c609 | |||
| bd0144065a | |||
| c142b49981 | |||
| 6db6d1510a | |||
| 401a9bacd1 | |||
| 540315b07c | |||
| c951190d3a | |||
| 6ce9bf6c94 | |||
| 26eccd159f | |||
| ad9fc31bfd | |||
| df1bdc7bc4 | |||
| 8fee1da426 | |||
| 3702dcb1d3 | |||
| 15437e3140 | |||
| c50250c978 | |||
| b2b3ad570b | |||
| 46eb353b84 | |||
| b7b52ec28b | |||
| 9322c86e2f | |||
| ab3ccb513a | |||
| 2a324d34be | |||
| b4a52321c4 | |||
| c8f67cffc5 | |||
| 03eb6a01b9 | |||
| 4acfbfc37a | |||
| 5cffe7e50a | |||
| 70d345bbf2 | |||
| 7a95a870be | |||
| f1612ce228 | |||
| 9b63e2460c | |||
| 7b642330c5 | |||
| 8d77c59da4 | |||
| 4e82feffef | |||
| 5eb1ae5a14 | |||
| 4d9d406966 | |||
| 070ed0d5ac | |||
| c262d0146e | |||
| 1a287f434a | |||
| 978f32acf7 | |||
| 109875942c | |||
| 94105409f8 | |||
| da3dfa2fb0 | |||
| e98017b475 | |||
| 5bbbe8341e | |||
| e880c04f32 | |||
| 200c164d78 | |||
| f38b813ba8 | |||
| 19baf646d2 | |||
| c5e90ed6c7 | |||
| 825206bfa2 | |||
| a512eae7bb | |||
| 463591663c | |||
| 400a108ead | |||
| c75c96ce2d | |||
| 36aebbb6ae | |||
| 0f81fafc62 | |||
| d086e78a38 | |||
| 91b8387c0a | |||
| f5bf7dc76e | |||
| ad95cb6312 | |||
| 2f8f829772 | |||
| 3ce8a9914c | |||
| 2574b98f8a | |||
| 0406e3bef7 | |||
| 2fb4ac1621 | |||
| 52a5dc23a3 | |||
| 73569dfeca | |||
| 1c320c3532 | |||
| 12bb5a47cd | |||
| 48fbad9836 | |||
| 301f8f5db5 | |||
| 0cfb8fee44 | |||
| 7ed37a8282 | |||
| faec0c78eb | |||
| 4630a535b2 | |||
| ca851298ad | |||
| bef25361f1 | |||
| f82fca2c6a | |||
| ee82db3966 | |||
| deac36a1eb | |||
| fcec76663f | |||
| 58d953f6aa | |||
| 15fd3da870 | |||
| fe7a74b09a | |||
| b52fde7c0e | |||
| b051f2e33d | |||
| 6823fa2de2 | |||
| 3898b77dd6 | |||
| 8acf16fc82 | |||
| ae09e1f60c | |||
| 6bc3ae178e | |||
| 972869f76f | |||
| 054071998c | |||
| a25dea6cd9 | |||
| 047f1666d5 | |||
| 4094135ed7 | |||
| 065775d814 | |||
| 45cb3e4956 | |||
| cf64dc1103 | |||
| 2520d492a5 | |||
| c47f4822dc | |||
| 1120f52556 | |||
| b828cf81a9 | |||
| 5441fba419 | |||
| 187477db03 | |||
| b4435cd053 | |||
| 0a69adf845 | |||
| 950e4f3cc4 | |||
| ff1562c916 | |||
| e3f6afafde | |||
| be476c7935 | |||
| f49238c792 | |||
| 8739833770 | |||
| e89978195f | |||
| aff410fa6d | |||
| 2deb5e47d1 | |||
| a97e6e6a12 | |||
| 552d2b1918 | |||
| 947826af1c | |||
| b692440725 | |||
| 3ef6f22760 | |||
| 61cf6d3bb6 | |||
| 9b9a65a0bf | |||
| 3902337e01 | |||
| 07726ad63a | |||
| 1ca7f22145 | |||
| 0a0478f670 | |||
| 12d71a0ad1 | |||
| 253773b316 | |||
| 643b192fd5 | |||
| a387a610f8 | |||
| 0390828de0 | |||
| bd9eae8edd | |||
| 67e54a2669 | |||
| a1e5f4eb83 | |||
| cd87b9c66b | |||
| 93e677edb3 | |||
| 3824b92704 | |||
| 27ac637a66 | |||
| cf4a848fd0 | |||
| 3cb12da765 | |||
| dc41f276d8 | |||
| 5214a7210a | |||
| 3a5a9e90d0 | |||
| 8ef8b5e215 | |||
| cd6908fd19 | |||
| 930cda39cd | |||
| ac54ab8a3e | |||
| 78e789bf7b | |||
| 6ac5816354 | |||
| 7f81eec1a6 | |||
| 02d7fbfa0c | |||
| 1d8ac4d61f | |||
| 13153bab60 | |||
| f7380a44d4 | |||
| 611a0155cd | |||
| fc140db781 | |||
| 7548c235bd | |||
| e123fa0ec3 | |||
| 8bddf3e6ab | |||
| 8713bb8afb | |||
| ae173ba863 | |||
| 0dc9b0392d | |||
| 35a2b0e5f8 | |||
| c83609277b | |||
| 97c11577cd | |||
| d67cba99bd | |||
| 8a5153ce0d | |||
| 55539787fb | |||
| 024112a53a | |||
| 0077a01667 | |||
| f628ab2151 | |||
| efed431237 | |||
| 2fcc3f3887 | |||
| 7a324231a3 | |||
| 6ff5339112 | |||
| baf14aad2a | |||
| ab6afd6fa0 | |||
| 192b1a1f65 | |||
| de2599b70b | |||
| 25464b2210 | |||
| 8084d08cf9 | |||
| 9b11029af9 | |||
| 431d01cc7f | |||
| a0b7638d63 | |||
| fc906c97f5 | |||
| 902f5a0dd2 | |||
| bd50601f15 | |||
| 8f47013e43 | |||
| 1130597562 | |||
| d9225083b4 | |||
| 236564b54a | |||
| 8adbde90f5 | |||
| 367d4af07c | |||
| ebe30fb0bd | |||
| ee89ca1bdd | |||
| 57109902cd | |||
| 8853519d89 | |||
| 6fb59f1182 | |||
| bf48dcd375 | |||
| b9f19c6ca2 | |||
| 662d77159b | |||
| f2ddf971df | |||
| 167bdce48d | |||
| 75479b58e3 | |||
| cc009b8aa0 | |||
| f8a5ea2d74 | |||
| 9b237790b2 | |||
| d70912c034 | |||
| fed80e0c10 | |||
| fb433cd918 | |||
| 5dfa24d5e2 | |||
| a5b8167280 | |||
| 963d9b761b | |||
| fb07612545 | |||
| f1f9a14172 | |||
| 504745abec | |||
| e78cb9b77f | |||
| 3fce1c4217 | |||
| a3a2305d7a | |||
| 6cfb4ec462 | |||
| 2a3c7ccd81 | |||
| 8731accd3f | |||
| e73c0979a1 | |||
| d344c5a228 | |||
| e8149ec509 | |||
| a27096dd63 | |||
| cae78e79e8 | |||
| 586bc94d51 | |||
| c24d127229 | |||
| cc27bbb680 | |||
| 62f17d8763 | |||
| eed8407599 | |||
| 7effad45c2 | |||
| 6a3b3b180b | |||
| b53294e291 | |||
| 5d0f90fed0 | |||
| 41b4c06697 | |||
| 132a69c03d | |||
| 46234171d1 | |||
| dc8ccc06ce | |||
| 813d4ed566 | |||
| 49b2d703e7 | |||
| 3c631149e3 | |||
| be1584ab47 | |||
| ef663f13c0 | |||
| 3d09898d40 | |||
| 2acdcba8d1 | |||
| f3ff28fb2a | |||
| d30d9a2c9f | |||
| 002d0eb6a0 | |||
| 9ce182228d | |||
| 040309b9f4 | |||
| f6a0b004e0 | |||
| adf28d87aa | |||
| 12775513ae | |||
| 0d11de3e8d | |||
| f56ef433d8 | |||
| ad98895876 | |||
| 7805179313 | |||
| 54e2eafcc9 | |||
| 9f077d5f41 | |||
| bf50f227b9 | |||
| 78251fd7e6 | |||
| 48b9b541ec | |||
| 971862a80c | |||
| e7405f7801 | |||
| 08dc831e0e | |||
| accc1ec1c8 | |||
| 3e1a2c0f77 | |||
| a01617904c | |||
| b42d241da5 | |||
| eb49290dab | |||
| 4555b74bd7 | |||
| 872096958b | |||
| 814f80ec59 | |||
| 8618571866 | |||
| 795c4fcecc | |||
| 9ddfcd6a1b | |||
| af6584b8fc | |||
| 7144cf405a | |||
| fd0ed9f33f | |||
| 52d86ad51f | |||
| 90d45984dc | |||
| 8554e6cde2 | |||
| a37932a0d0 | |||
| 6044e0d7ca | |||
| 4660819f6a | |||
| a8162a77c2 | |||
| 6e6a30679e | |||
| 0694df9014 | |||
| 9887c6971d | |||
| 9b801227d5 | |||
| 9b43327f56 | |||
| bdb8ef22b0 | |||
| acf7f6f74e | |||
| 642ef4edc9 | |||
| 3aad01d3ba | |||
| 4277c05b0c | |||
| d4e01a1818 | |||
| 04e5b23286 | |||
| d6e49b503b | |||
| 3122c3b4a1 | |||
| c8dfd24818 | |||
| a221f8671e | |||
| ccc6da0a5b | |||
| 3156b17057 | |||
| 8665f404a5 | |||
| be89684f3c | |||
| 1d91e282c8 | |||
| 3acc667c28 | |||
| 6ebdf3ab62 | |||
| f2bc24f761 | |||
| 85395b23a4 | |||
| 3ee0906c81 | |||
| e72a02c5e2 | |||
| 7e319ad99b | |||
| ebb1aaa06d | |||
| e960276a47 | |||
| bd7937e5b5 | |||
| f63880c2ff | |||
| 3cd4af1b8e | |||
| 3001d56d4b | |||
| 79bce84900 |
+8
-4
@@ -1,5 +1,9 @@
|
||||
build*/
|
||||
winbuild/
|
||||
win64build/
|
||||
openal-soft.kdev4
|
||||
.kdev4/
|
||||
winbuild
|
||||
win64build
|
||||
|
||||
## kdevelop
|
||||
*.kdev4
|
||||
|
||||
## qt-creator
|
||||
CMakeLists.txt.user*
|
||||
|
||||
-68
@@ -1,68 +0,0 @@
|
||||
language: cpp
|
||||
matrix:
|
||||
include:
|
||||
- os: linux
|
||||
dist: xenial
|
||||
- os: linux
|
||||
dist: trusty
|
||||
env:
|
||||
- BUILD_ANDROID=true
|
||||
- os: osx
|
||||
sudo: required
|
||||
install:
|
||||
- >
|
||||
if [[ "${TRAVIS_OS_NAME}" == "linux" && -z "${BUILD_ANDROID}" ]]; then
|
||||
# Install pulseaudio, portaudio, ALSA, JACK dependencies for
|
||||
# corresponding backends.
|
||||
# Install Qt5 dependency for alsoft-config.
|
||||
sudo apt-get install -qq \
|
||||
libpulse-dev \
|
||||
portaudio19-dev \
|
||||
libasound2-dev \
|
||||
libjack-dev \
|
||||
qtbase5-dev
|
||||
fi
|
||||
- >
|
||||
if [[ "${TRAVIS_OS_NAME}" == "linux" && "${BUILD_ANDROID}" == "true" ]]; then
|
||||
curl -o ~/android-ndk.zip https://dl.google.com/android/repository/android-ndk-r16b-linux-x86_64.zip
|
||||
unzip -q ~/android-ndk.zip -d ~ \
|
||||
'android-ndk-r16b/build/cmake/*' \
|
||||
'android-ndk-r16b/build/core/toolchains/arm-linux-androideabi-*/*' \
|
||||
'android-ndk-r16b/platforms/android-14/arch-arm/*' \
|
||||
'android-ndk-r16b/source.properties' \
|
||||
'android-ndk-r16b/sources/android/support/include/*' \
|
||||
'android-ndk-r16b/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/*' \
|
||||
'android-ndk-r16b/sources/cxx-stl/llvm-libc++/include/*' \
|
||||
'android-ndk-r16b/sysroot/*' \
|
||||
'android-ndk-r16b/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/*' \
|
||||
'android-ndk-r16b/toolchains/llvm/prebuilt/linux-x86_64/*'
|
||||
fi
|
||||
script:
|
||||
- >
|
||||
if [[ "${TRAVIS_OS_NAME}" == "linux" && -z "${BUILD_ANDROID}" ]]; then
|
||||
cmake \
|
||||
-DALSOFT_REQUIRE_ALSA=ON \
|
||||
-DALSOFT_REQUIRE_OSS=ON \
|
||||
-DALSOFT_REQUIRE_PORTAUDIO=ON \
|
||||
-DALSOFT_REQUIRE_PULSEAUDIO=ON \
|
||||
-DALSOFT_REQUIRE_JACK=ON \
|
||||
-DALSOFT_EMBED_HRTF_DATA=YES \
|
||||
.
|
||||
fi
|
||||
- >
|
||||
if [[ "${TRAVIS_OS_NAME}" == "linux" && "${BUILD_ANDROID}" == "true" ]]; then
|
||||
cmake \
|
||||
-DANDROID_STL=c++_shared \
|
||||
-DCMAKE_TOOLCHAIN_FILE=~/android-ndk-r16b/build/cmake/android.toolchain.cmake \
|
||||
-DALSOFT_REQUIRE_OPENSL=ON \
|
||||
-DALSOFT_EMBED_HRTF_DATA=YES \
|
||||
.
|
||||
fi
|
||||
- >
|
||||
if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then
|
||||
cmake \
|
||||
-DALSOFT_REQUIRE_COREAUDIO=ON \
|
||||
-DALSOFT_EMBED_HRTF_DATA=YES \
|
||||
.
|
||||
fi
|
||||
- make -j2
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
Portions of this software are licensed under the BSD 3-Clause license.
|
||||
|
||||
Copyright (c) 2015, Archontis Politis
|
||||
Copyright (c) 2019, Christopher Robinson
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of Spherical-Harmonic-Transform nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
-1575
File diff suppressed because it is too large
Load Diff
@@ -1,437 +0,0 @@
|
||||
GNU LIBRARY GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1991 Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
[This is the first released version of the library GPL. It is
|
||||
numbered 2 because it goes with version 2 of the ordinary GPL.]
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
Licenses are intended to guarantee your freedom to share and change
|
||||
free software--to make sure the software is free for all its users.
|
||||
|
||||
This license, the Library General Public License, applies to some
|
||||
specially designated Free Software Foundation software, and to any
|
||||
other libraries whose authors decide to use it. You can use it for
|
||||
your libraries, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if
|
||||
you distribute copies of the library, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of the library, whether gratis
|
||||
or for a fee, you must give the recipients all the rights that we gave
|
||||
you. You must make sure that they, too, receive or can get the source
|
||||
code. If you link a program with the library, you must provide
|
||||
complete object files to the recipients so that they can relink them
|
||||
with the library, after making changes to the library and recompiling
|
||||
it. And you must show them these terms so they know their rights.
|
||||
|
||||
Our method of protecting your rights has two steps: (1) copyright
|
||||
the library, and (2) offer you this license which gives you legal
|
||||
permission to copy, distribute and/or modify the library.
|
||||
|
||||
Also, for each distributor's protection, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
library. If the library is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original
|
||||
version, so that any problems introduced by others will not reflect on
|
||||
the original authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that companies distributing free
|
||||
software will individually obtain patent licenses, thus in effect
|
||||
transforming the program into proprietary software. To prevent this,
|
||||
we have made it clear that any patent must be licensed for everyone's
|
||||
free use or not licensed at all.
|
||||
|
||||
Most GNU software, including some libraries, is covered by the ordinary
|
||||
GNU General Public License, which was designed for utility programs. This
|
||||
license, the GNU Library General Public License, applies to certain
|
||||
designated libraries. This license is quite different from the ordinary
|
||||
one; be sure to read it in full, and don't assume that anything in it is
|
||||
the same as in the ordinary license.
|
||||
|
||||
The reason we have a separate public license for some libraries is that
|
||||
they blur the distinction we usually make between modifying or adding to a
|
||||
program and simply using it. Linking a program with a library, without
|
||||
changing the library, is in some sense simply using the library, and is
|
||||
analogous to running a utility program or application program. However, in
|
||||
a textual and legal sense, the linked executable is a combined work, a
|
||||
derivative of the original library, and the ordinary General Public License
|
||||
treats it as such.
|
||||
|
||||
Because of this blurred distinction, using the ordinary General
|
||||
Public License for libraries did not effectively promote software
|
||||
sharing, because most developers did not use the libraries. We
|
||||
concluded that weaker conditions might promote sharing better.
|
||||
|
||||
However, unrestricted linking of non-free programs would deprive the
|
||||
users of those programs of all benefit from the free status of the
|
||||
libraries themselves. This Library General Public License is intended to
|
||||
permit developers of non-free programs to use free libraries, while
|
||||
preserving your freedom as a user of such programs to change the free
|
||||
libraries that are incorporated in them. (We have not seen how to achieve
|
||||
this as regards changes in header files, but we have achieved it as regards
|
||||
changes in the actual functions of the Library.) The hope is that this
|
||||
will lead to faster development of free libraries.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow. Pay close attention to the difference between a
|
||||
"work based on the library" and a "work that uses the library". The
|
||||
former contains code derived from the library, while the latter only
|
||||
works together with the library.
|
||||
|
||||
Note that it is possible for a library to be covered by the ordinary
|
||||
General Public License rather than by this special one.
|
||||
|
||||
GNU LIBRARY GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License Agreement applies to any software library which
|
||||
contains a notice placed by the copyright holder or other authorized
|
||||
party saying it may be distributed under the terms of this Library
|
||||
General Public License (also called "this License"). Each licensee is
|
||||
addressed as "you".
|
||||
|
||||
A "library" means a collection of software functions and/or data
|
||||
prepared so as to be conveniently linked with application programs
|
||||
(which use some of those functions and data) to form executables.
|
||||
|
||||
The "Library", below, refers to any such software library or work
|
||||
which has been distributed under these terms. A "work based on the
|
||||
Library" means either the Library or any derivative work under
|
||||
copyright law: that is to say, a work containing the Library or a
|
||||
portion of it, either verbatim or with modifications and/or translated
|
||||
straightforwardly into another language. (Hereinafter, translation is
|
||||
included without limitation in the term "modification".)
|
||||
|
||||
"Source code" for a work means the preferred form of the work for
|
||||
making modifications to it. For a library, complete source code means
|
||||
all the source code for all modules it contains, plus any associated
|
||||
interface definition files, plus the scripts used to control compilation
|
||||
and installation of the library.
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running a program using the Library is not restricted, and output from
|
||||
such a program is covered only if its contents constitute a work based
|
||||
on the Library (independent of the use of the Library in a tool for
|
||||
writing it). Whether that is true depends on what the Library does
|
||||
and what the program that uses the Library does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Library's
|
||||
complete source code as you receive it, in any medium, provided that
|
||||
you conspicuously and appropriately publish on each copy an
|
||||
appropriate copyright notice and disclaimer of warranty; keep intact
|
||||
all the notices that refer to this License and to the absence of any
|
||||
warranty; and distribute a copy of this License along with the
|
||||
Library.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy,
|
||||
and you may at your option offer warranty protection in exchange for a
|
||||
fee.
|
||||
|
||||
2. You may modify your copy or copies of the Library or any portion
|
||||
of it, thus forming a work based on the Library, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) The modified work must itself be a software library.
|
||||
|
||||
b) You must cause the files modified to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
c) You must cause the whole of the work to be licensed at no
|
||||
charge to all third parties under the terms of this License.
|
||||
|
||||
d) If a facility in the modified Library refers to a function or a
|
||||
table of data to be supplied by an application program that uses
|
||||
the facility, other than as an argument passed when the facility
|
||||
is invoked, then you must make a good faith effort to ensure that,
|
||||
in the event an application does not supply such function or
|
||||
table, the facility still operates, and performs whatever part of
|
||||
its purpose remains meaningful.
|
||||
|
||||
(For example, a function in a library to compute square roots has
|
||||
a purpose that is entirely well-defined independent of the
|
||||
application. Therefore, Subsection 2d requires that any
|
||||
application-supplied function or table used by this function must
|
||||
be optional: if the application does not supply it, the square
|
||||
root function must still compute square roots.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Library,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Library, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote
|
||||
it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Library.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Library
|
||||
with the Library (or with a work based on the Library) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may opt to apply the terms of the ordinary GNU General Public
|
||||
License instead of this License to a given copy of the Library. To do
|
||||
this, you must alter all the notices that refer to this License, so
|
||||
that they refer to the ordinary GNU General Public License, version 2,
|
||||
instead of to this License. (If a newer version than version 2 of the
|
||||
ordinary GNU General Public License has appeared, then you can specify
|
||||
that version instead if you wish.) Do not make any other change in
|
||||
these notices.
|
||||
|
||||
Once this change is made in a given copy, it is irreversible for
|
||||
that copy, so the ordinary GNU General Public License applies to all
|
||||
subsequent copies and derivative works made from that copy.
|
||||
|
||||
This option is useful when you wish to copy part of the code of
|
||||
the Library into a program that is not a library.
|
||||
|
||||
4. You may copy and distribute the Library (or a portion or
|
||||
derivative of it, under Section 2) in object code or executable form
|
||||
under the terms of Sections 1 and 2 above provided that you accompany
|
||||
it with the complete corresponding machine-readable source code, which
|
||||
must be distributed under the terms of Sections 1 and 2 above on a
|
||||
medium customarily used for software interchange.
|
||||
|
||||
If distribution of object code is made by offering access to copy
|
||||
from a designated place, then offering equivalent access to copy the
|
||||
source code from the same place satisfies the requirement to
|
||||
distribute the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
5. A program that contains no derivative of any portion of the
|
||||
Library, but is designed to work with the Library by being compiled or
|
||||
linked with it, is called a "work that uses the Library". Such a
|
||||
work, in isolation, is not a derivative work of the Library, and
|
||||
therefore falls outside the scope of this License.
|
||||
|
||||
However, linking a "work that uses the Library" with the Library
|
||||
creates an executable that is a derivative of the Library (because it
|
||||
contains portions of the Library), rather than a "work that uses the
|
||||
library". The executable is therefore covered by this License.
|
||||
Section 6 states terms for distribution of such executables.
|
||||
|
||||
When a "work that uses the Library" uses material from a header file
|
||||
that is part of the Library, the object code for the work may be a
|
||||
derivative work of the Library even though the source code is not.
|
||||
Whether this is true is especially significant if the work can be
|
||||
linked without the Library, or if the work is itself a library. The
|
||||
threshold for this to be true is not precisely defined by law.
|
||||
|
||||
If such an object file uses only numerical parameters, data
|
||||
structure layouts and accessors, and small macros and small inline
|
||||
functions (ten lines or less in length), then the use of the object
|
||||
file is unrestricted, regardless of whether it is legally a derivative
|
||||
work. (Executables containing this object code plus portions of the
|
||||
Library will still fall under Section 6.)
|
||||
|
||||
Otherwise, if the work is a derivative of the Library, you may
|
||||
distribute the object code for the work under the terms of Section 6.
|
||||
Any executables containing that work also fall under Section 6,
|
||||
whether or not they are linked directly with the Library itself.
|
||||
|
||||
6. As an exception to the Sections above, you may also compile or
|
||||
link a "work that uses the Library" with the Library to produce a
|
||||
work containing portions of the Library, and distribute that work
|
||||
under terms of your choice, provided that the terms permit
|
||||
modification of the work for the customer's own use and reverse
|
||||
engineering for debugging such modifications.
|
||||
|
||||
You must give prominent notice with each copy of the work that the
|
||||
Library is used in it and that the Library and its use are covered by
|
||||
this License. You must supply a copy of this License. If the work
|
||||
during execution displays copyright notices, you must include the
|
||||
copyright notice for the Library among them, as well as a reference
|
||||
directing the user to the copy of this License. Also, you must do one
|
||||
of these things:
|
||||
|
||||
a) Accompany the work with the complete corresponding
|
||||
machine-readable source code for the Library including whatever
|
||||
changes were used in the work (which must be distributed under
|
||||
Sections 1 and 2 above); and, if the work is an executable linked
|
||||
with the Library, with the complete machine-readable "work that
|
||||
uses the Library", as object code and/or source code, so that the
|
||||
user can modify the Library and then relink to produce a modified
|
||||
executable containing the modified Library. (It is understood
|
||||
that the user who changes the contents of definitions files in the
|
||||
Library will not necessarily be able to recompile the application
|
||||
to use the modified definitions.)
|
||||
|
||||
b) Accompany the work with a written offer, valid for at
|
||||
least three years, to give the same user the materials
|
||||
specified in Subsection 6a, above, for a charge no more
|
||||
than the cost of performing this distribution.
|
||||
|
||||
c) If distribution of the work is made by offering access to copy
|
||||
from a designated place, offer equivalent access to copy the above
|
||||
specified materials from the same place.
|
||||
|
||||
d) Verify that the user has already received a copy of these
|
||||
materials or that you have already sent this user a copy.
|
||||
|
||||
For an executable, the required form of the "work that uses the
|
||||
Library" must include any data and utility programs needed for
|
||||
reproducing the executable from it. However, as a special exception,
|
||||
the source code distributed need not include anything that is normally
|
||||
distributed (in either source or binary form) with the major
|
||||
components (compiler, kernel, and so on) of the operating system on
|
||||
which the executable runs, unless that component itself accompanies
|
||||
the executable.
|
||||
|
||||
It may happen that this requirement contradicts the license
|
||||
restrictions of other proprietary libraries that do not normally
|
||||
accompany the operating system. Such a contradiction means you cannot
|
||||
use both them and the Library together in an executable that you
|
||||
distribute.
|
||||
|
||||
7. You may place library facilities that are a work based on the
|
||||
Library side-by-side in a single library together with other library
|
||||
facilities not covered by this License, and distribute such a combined
|
||||
library, provided that the separate distribution of the work based on
|
||||
the Library and of the other library facilities is otherwise
|
||||
permitted, and provided that you do these two things:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work
|
||||
based on the Library, uncombined with any other library
|
||||
facilities. This must be distributed under the terms of the
|
||||
Sections above.
|
||||
|
||||
b) Give prominent notice with the combined library of the fact
|
||||
that part of it is a work based on the Library, and explaining
|
||||
where to find the accompanying uncombined form of the same work.
|
||||
|
||||
8. You may not copy, modify, sublicense, link with, or distribute
|
||||
the Library except as expressly provided under this License. Any
|
||||
attempt otherwise to copy, modify, sublicense, link with, or
|
||||
distribute the Library is void, and will automatically terminate your
|
||||
rights under this License. However, parties who have received copies,
|
||||
or rights, from you under this License will not have their licenses
|
||||
terminated so long as such parties remain in full compliance.
|
||||
|
||||
9. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Library or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Library (or any work based on the
|
||||
Library), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Library or works based on it.
|
||||
|
||||
10. Each time you redistribute the Library (or any work based on the
|
||||
Library), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute, link with or modify the Library
|
||||
subject to these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
11. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Library at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Library by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Library.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under any
|
||||
particular circumstance, the balance of the section is intended to apply,
|
||||
and the section as a whole is intended to apply in other circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
12. If the distribution and/or use of the Library is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Library under this License may add
|
||||
an explicit geographical distribution limitation excluding those countries,
|
||||
so that distribution is permitted only in or among countries not thus
|
||||
excluded. In such case, this License incorporates the limitation as if
|
||||
written in the body of this License.
|
||||
|
||||
13. The Free Software Foundation may publish revised and/or new
|
||||
versions of the Library General Public License from time to time.
|
||||
Such new versions will be similar in spirit to the present version,
|
||||
but may differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Library
|
||||
specifies a version number of this License which applies to it and
|
||||
"any later version", you have the option of following the terms and
|
||||
conditions either of that version or of any later version published by
|
||||
the Free Software Foundation. If the Library does not specify a
|
||||
license version number, you may choose any version ever published by
|
||||
the Free Software Foundation.
|
||||
|
||||
14. If you wish to incorporate parts of the Library into other free
|
||||
programs whose distribution conditions are incompatible with these,
|
||||
write to the author to ask for permission. For software which is
|
||||
copyrighted by the Free Software Foundation, write to the Free
|
||||
Software Foundation; we sometimes make exceptions for this. Our
|
||||
decision will be guided by the two goals of preserving the free status
|
||||
of all derivatives of our free software and of promoting the sharing
|
||||
and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
|
||||
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
|
||||
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
|
||||
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
|
||||
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
|
||||
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
|
||||
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
|
||||
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
|
||||
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
|
||||
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
|
||||
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
|
||||
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
|
||||
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
|
||||
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
|
||||
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
@@ -1,461 +0,0 @@
|
||||
openal-soft-1.20.1:
|
||||
|
||||
Implemented the AL_SOFT_direct_channels_remix extension. This extends
|
||||
AL_DIRECT_CHANNELS_SOFT to optionally remix input channels that don't have
|
||||
a matching output channel.
|
||||
|
||||
Implemented the AL_SOFT_bformat_ex extension. This extends B-Format buffer
|
||||
support for N3D or SN3D scaling, or ACN channel ordering.
|
||||
|
||||
Fixed a potential voice leak when a source is started and stopped or
|
||||
restarted in quick succession.
|
||||
|
||||
Fixed a potential device reset failure with JACK.
|
||||
|
||||
Improved handling of unsupported channel configurations with WASAPI. Such
|
||||
setups will now try to output at least a stereo mix.
|
||||
|
||||
Improved clarity a bit for the HRTF second-order ambisonic decoder.
|
||||
|
||||
Improved detection of compatible layouts for SOFA files in makemhr and
|
||||
sofa-info.
|
||||
|
||||
Added the ability to resample HRTFs on load. MHR files no longer need to
|
||||
match the device sample rate to be usable.
|
||||
|
||||
Added an option to limit the HRTF's filter length.
|
||||
|
||||
openal-soft-1.20.0:
|
||||
|
||||
Converted the library codebase to C++11. A lot of hacks and custom
|
||||
structures have been replaced with standard or cleaner implementations.
|
||||
|
||||
Partially implemented the Vocal Morpher effect.
|
||||
|
||||
Fixed the bsinc SSE resamplers on non-GCC compilers.
|
||||
|
||||
Fixed OpenSL capture.
|
||||
|
||||
Fixed support for extended capture formats with OpenSL.
|
||||
|
||||
Fixed handling of WASAPI not reporting a default device.
|
||||
|
||||
Fixed performance problems relating to semaphores on macOS.
|
||||
|
||||
Modified the bsinc12 resampler's transition band to better avoid aliasing
|
||||
noise.
|
||||
|
||||
Modified alcResetDeviceSOFT to attempt recovery of disconnected devices.
|
||||
|
||||
Modified the virtual speaker layout for HRTF B-Format decoding.
|
||||
|
||||
Modified the PulseAudio backend to use a custom processing loop.
|
||||
|
||||
Renamed the makehrtf utility to makemhr.
|
||||
|
||||
Improved the efficiency of the bsinc resamplers when up-sampling.
|
||||
|
||||
Improved the quality of the bsinc resamplers slightly.
|
||||
|
||||
Improved the efficiency of the HRTF filters.
|
||||
|
||||
Improved the HRTF B-Format decoder coefficient generation.
|
||||
|
||||
Improved reverb feedback fading to be more consistent with pan fading.
|
||||
|
||||
Improved handling of sources that end prematurely, avoiding loud clicks.
|
||||
|
||||
Improved the performance of some reverb processing loops.
|
||||
|
||||
Added fast_bsinc12 and 24 resamplers that improve efficiency at the cost of
|
||||
some quality. Notably, down-sampling has less smooth pitch ramping.
|
||||
|
||||
Added support for SOFA input files with makemhr.
|
||||
|
||||
Added a build option to use pre-built native tools. For cross-compiling,
|
||||
use with caution and ensure the native tools' binaries are kept up-to-date.
|
||||
|
||||
Added an adjust-latency config option for the PulseAudio backend.
|
||||
|
||||
Added basic support for multi-field HRTFs.
|
||||
|
||||
Added an option for mixing first- or second-order B-Format with HRTF
|
||||
output. This can improve HRTF performance given a number of sources.
|
||||
|
||||
Added an RC file for proper DLL version information.
|
||||
|
||||
Disabled some old KDE workarounds by default. Specifically, PulseAudio
|
||||
streams can now be moved (KDE may try to move them after opening).
|
||||
|
||||
openal-soft-1.19.1:
|
||||
|
||||
Implemented capture support for the SoundIO backend.
|
||||
|
||||
Fixed source buffer queues potentially not playing properly when a queue
|
||||
entry completes.
|
||||
|
||||
Fixed possible unexpected failures when generating auxiliary effect slots.
|
||||
|
||||
Fixed a crash with certain reverb or device settings.
|
||||
|
||||
Fixed OpenSL capture.
|
||||
|
||||
Improved output limiter response, better ensuring the sample amplitude is
|
||||
clamped for output.
|
||||
|
||||
openal-soft-1.19.0:
|
||||
|
||||
Implemented the ALC_SOFT_device_clock extension.
|
||||
|
||||
Implemented the Pitch Shifter, Frequency Shifter, and Autowah effects.
|
||||
|
||||
Fixed compiling on FreeBSD systems that use freebsd-lib 9.1.
|
||||
|
||||
Fixed compiling on NetBSD.
|
||||
|
||||
Fixed the reverb effect's density scale and panning parameters.
|
||||
|
||||
Fixed use of the WASAPI backend with certain games, which caused odd COM
|
||||
initialization errors.
|
||||
|
||||
Increased the number of virtual channels for decoding Ambisonics to HRTF
|
||||
output.
|
||||
|
||||
Changed 32-bit x86 builds to use SSE2 math by default for performance.
|
||||
Build-time options are available to use just SSE1 or x87 instead.
|
||||
|
||||
Replaced the 4-point Sinc resampler with a more efficient cubic resampler.
|
||||
|
||||
Renamed the MMDevAPI backend to WASAPI.
|
||||
|
||||
Added support for 24-bit, dual-ear HRTF data sets. The built-in data set
|
||||
has been updated to 24-bit.
|
||||
|
||||
Added a 24- to 48-point band-limited Sinc resampler.
|
||||
|
||||
Added an SDL2 playback backend. Disabled by default to avoid a dependency
|
||||
on SDL2.
|
||||
|
||||
Improved the performance and quality of the Chorus and Flanger effects.
|
||||
|
||||
Improved the efficiency of the band-limited Sinc resampler.
|
||||
|
||||
Improved the Sinc resampler's transition band to avoid over-attenuating
|
||||
higher frequencies.
|
||||
|
||||
Improved the performance of some filter operations.
|
||||
|
||||
Improved the efficiency of object ID lookups.
|
||||
|
||||
Improved the efficienty of internal voice/source synchronization.
|
||||
|
||||
Improved AL call error logging with contextualized messages.
|
||||
|
||||
Removed the reverb effect's modulation stage. Due to the lack of reference
|
||||
for its intended behavior and strength.
|
||||
|
||||
openal-soft-1.18.2:
|
||||
|
||||
Fixed resetting the FPU rounding mode after certain function calls on
|
||||
Windows.
|
||||
|
||||
Fixed use of SSE intrinsics when building with Clang on Windows.
|
||||
|
||||
Fixed a crash with the JACK backend when using JACK1.
|
||||
|
||||
Fixed use of pthread_setnane_np on NetBSD.
|
||||
|
||||
Fixed building on FreeBSD with an older freebsd-lib.
|
||||
|
||||
OSS now links with libossaudio if found at build time (for NetBSD).
|
||||
|
||||
openal-soft-1.18.1:
|
||||
|
||||
Fixed an issue where resuming a source might not restart playing it.
|
||||
|
||||
Fixed PulseAudio playback when the configured stream length is much less
|
||||
than the requested length.
|
||||
|
||||
Fixed MMDevAPI capture with sample rates not matching the backing device.
|
||||
|
||||
Fixed int32 output for the Wave Writer.
|
||||
|
||||
Fixed enumeration of OSS devices that are missing device files.
|
||||
|
||||
Added correct retrieval of the executable's path on FreeBSD.
|
||||
|
||||
Added a config option to specify the dithering depth.
|
||||
|
||||
Added a 5.1 decoder preset that excludes front-center output.
|
||||
|
||||
openal-soft-1.18.0:
|
||||
|
||||
Implemented the AL_EXT_STEREO_ANGLES and AL_EXT_SOURCE_RADIUS extensions.
|
||||
|
||||
Implemented the AL_SOFT_gain_clamp_ex, AL_SOFT_source_resampler,
|
||||
AL_SOFT_source_spatialize, and ALC_SOFT_output_limiter extensions.
|
||||
|
||||
Implemented 3D processing for some effects. Currently implemented for
|
||||
Reverb, Compressor, Equalizer, and Ring Modulator.
|
||||
|
||||
Implemented 2-channel UHJ output encoding. This needs to be enabled with a
|
||||
config option to be used.
|
||||
|
||||
Implemented dual-band processing for high-quality ambisonic decoding.
|
||||
|
||||
Implemented distance-compensation for surround sound output.
|
||||
|
||||
Implemented near-field emulation and compensation with ambisonic rendering.
|
||||
Currently only applies when using the high-quality ambisonic decoder or
|
||||
ambisonic output, with appropriate config options.
|
||||
|
||||
Implemented an output limiter to reduce the amount of distortion from
|
||||
clipping.
|
||||
|
||||
Implemented dithering for 8-bit and 16-bit output.
|
||||
|
||||
Implemented a config option to select a preferred HRTF.
|
||||
|
||||
Implemented a run-time check for NEON extensions using /proc/cpuinfo.
|
||||
|
||||
Implemented experimental capture support for the OpenSL backend.
|
||||
|
||||
Fixed building on compilers with NEON support but don't default to having
|
||||
NEON enabled.
|
||||
|
||||
Fixed support for JACK on Windows.
|
||||
|
||||
Fixed starting a source while alcSuspendContext is in effect.
|
||||
|
||||
Fixed detection of headsets as headphones, with MMDevAPI.
|
||||
|
||||
Added support for AmbDec config files, for custom ambisonic decoder
|
||||
configurations. Version 3 files only.
|
||||
|
||||
Added backend-specific options to alsoft-config.
|
||||
|
||||
Added first-, second-, and third-order ambisonic output formats. Currently
|
||||
only works with backends that don't rely on channel labels, like JACK,
|
||||
ALSA, and OSS.
|
||||
|
||||
Added a build option to embed the default HRTFs into the lib.
|
||||
|
||||
Added AmbDec presets to enable high-quality ambisonic decoding.
|
||||
|
||||
Added an AmbDec preset for 3D7.1 speaker setups.
|
||||
|
||||
Added documentation regarding Ambisonics, 3D7.1, AmbDec config files, and
|
||||
the provided ambdec presets.
|
||||
|
||||
Added the ability for MMDevAPI to open devices given a Device ID or GUID
|
||||
string.
|
||||
|
||||
Added an option to the example apps to open a specific device.
|
||||
|
||||
Increased the maximum auxiliary send limit to 16 (up from 4). Requires
|
||||
requesting them with the ALC_MAX_AUXILIARY_SENDS context creation
|
||||
attribute.
|
||||
|
||||
Increased the default auxiliary effect slot count to 64 (up from 4).
|
||||
|
||||
Reduced the default period count to 3 (down from 4).
|
||||
|
||||
Slightly improved automatic naming for enumerated HRTFs.
|
||||
|
||||
Improved B-Format decoding with HRTF output.
|
||||
|
||||
Improved internal property handling for better batching behavior.
|
||||
|
||||
Improved performance of certain filter uses.
|
||||
|
||||
Removed support for the AL_SOFT_buffer_samples and AL_SOFT_buffer_sub_data
|
||||
extensions. Due to conflicts with AL_EXT_SOURCE_RADIUS.
|
||||
|
||||
openal-soft-1.17.2:
|
||||
|
||||
Implemented device enumeration for OSSv4.
|
||||
|
||||
Fixed building on OSX.
|
||||
|
||||
Fixed building on non-Windows systems without POSIX-2008.
|
||||
|
||||
Fixed Dedicated Dialog and Dedicated LFE effect output.
|
||||
|
||||
Added a build option to override the share install dir.
|
||||
|
||||
Added a build option to static-link libgcc for MinGW.
|
||||
|
||||
openal-soft-1.17.1:
|
||||
|
||||
Fixed building with JACK and without PulseAudio.
|
||||
|
||||
Fixed building on FreeBSD.
|
||||
|
||||
Fixed the ALSA backend's allow-resampler option.
|
||||
|
||||
Fixed handling of inexact ALSA period counts.
|
||||
|
||||
Altered device naming scheme on Windows backends to better match other
|
||||
drivers.
|
||||
|
||||
Updated the CoreAudio backend to use the AudioComponent API. This clears up
|
||||
deprecation warnings for OSX 10.11, although requires OSX 10.6 or newer.
|
||||
|
||||
openal-soft-1.17.0:
|
||||
|
||||
Implemented a JACK playback backend.
|
||||
|
||||
Implemented the AL_EXT_BFORMAT and AL_EXT_MULAW_BFORMAT extensions.
|
||||
|
||||
Implemented the ALC_SOFT_HRTF extension.
|
||||
|
||||
Implemented C, SSE3, and SSE4.1 based 4- and 8-point Sinc resamplers.
|
||||
|
||||
Implemented a C and SSE based band-limited Sinc resampler. This does 12- to
|
||||
24-point Sinc resampling, and performs anti-aliasing.
|
||||
|
||||
Implemented B-Format output support for the wave file writer. This creates
|
||||
FuMa-style first-order Ambisonics wave files (AMB format).
|
||||
|
||||
Implemented a stereo-mode config option for treating stereo modes as either
|
||||
speakers or headphones.
|
||||
|
||||
Implemented per-device configuration options.
|
||||
|
||||
Fixed handling of PulseAudio and MMDevAPI devices that have identical
|
||||
descriptions.
|
||||
|
||||
Fixed a potential lockup when stopping playback of suspended PulseAudio devices.
|
||||
|
||||
Fixed logging of Unicode characters on Windows.
|
||||
|
||||
Fixed 5.1 surround sound channels. By default it will now use the side
|
||||
channels for the surround output. A configuration using rear channels is
|
||||
still available.
|
||||
|
||||
Fixed the QSA backend potentially altering the capture format.
|
||||
|
||||
Fixed detecting MMDevAPI's default device.
|
||||
|
||||
Fixed returning the default capture device name.
|
||||
|
||||
Fixed mixing property calculations when deferring context updates.
|
||||
|
||||
Altered the behavior of alcSuspendContext and alcProcessContext to better
|
||||
match certain Windows drivers.
|
||||
|
||||
Altered the panning algorithm, utilizing Ambisonics for better side and
|
||||
back positioning cues with surround sound output.
|
||||
|
||||
Improved support for certain older Windows apps.
|
||||
|
||||
Improved the alffplay example to support surround sound streams.
|
||||
|
||||
Improved support for building as a sub-project.
|
||||
|
||||
Added an HRTF playback example.
|
||||
|
||||
Added a tone generator output test.
|
||||
|
||||
Added a toolchain to help with cross-compiling to Android.
|
||||
|
||||
openal-soft-1.16.0:
|
||||
|
||||
Implemented EFX Chorus, Flanger, Distortion, Equalizer, and Compressor
|
||||
effects.
|
||||
|
||||
Implemented high-pass and band-pass EFX filters.
|
||||
|
||||
Implemented the high-pass filter for the EAXReverb effect.
|
||||
|
||||
Implemented SSE2 and SSE4.1 linear resamplers.
|
||||
|
||||
Implemented Neon-enhanced non-HRTF mixers.
|
||||
|
||||
Implemented a QSA backend, for QNX.
|
||||
|
||||
Implemented the ALC_SOFT_pause_device, AL_SOFT_deferred_updates,
|
||||
AL_SOFT_block_alignment, AL_SOFT_MSADPCM, and AL_SOFT_source_length
|
||||
extensions.
|
||||
|
||||
Fixed resetting mmdevapi backend devices.
|
||||
|
||||
Fixed clamping when converting 32-bit float samples to integer.
|
||||
|
||||
Fixed modulation range in the Modulator effect.
|
||||
|
||||
Several fixes for the OpenSL playback backend.
|
||||
|
||||
Fixed device specifier names that have Unicode characters on Windows.
|
||||
|
||||
Added support for filenames and paths with Unicode (UTF-8) characters on
|
||||
Windows.
|
||||
|
||||
Added support for alsoft.conf config files found in XDG Base Directory
|
||||
Specification locations (XDG_CONFIG_DIRS and XDG_CONFIG_HOME, or their
|
||||
defaults) on non-Windows systems.
|
||||
|
||||
Added a GUI configuration utility (requires Qt 4.8).
|
||||
|
||||
Added support for environment variable expansion in config options (not
|
||||
keys or section names).
|
||||
|
||||
Added an example that uses SDL2 and ffmpeg.
|
||||
|
||||
Modified examples to use SDL_sound.
|
||||
|
||||
Modified CMake config option names for better sorting.
|
||||
|
||||
HRTF data sets specified in the hrtf_tables config option may now be
|
||||
relative or absolute filenames.
|
||||
|
||||
Made the default HRTF data set an external file, and added a data set for
|
||||
48khz playback in addition to 44.1khz.
|
||||
|
||||
Added support for C11 atomic methods.
|
||||
|
||||
Improved support for some non-GNU build systems.
|
||||
|
||||
openal-soft-1.15.1:
|
||||
|
||||
Fixed a regression with retrieving the source's AL_GAIN property.
|
||||
|
||||
openal-soft-1.15:
|
||||
|
||||
Fixed device enumeration with the OSS backend.
|
||||
|
||||
Reorganized internal mixing logic, so unneeded steps can potentially be
|
||||
skipped for better performance.
|
||||
|
||||
Removed the lookup table for calculating the mixing pans. The panning is
|
||||
now calculated directly for better precision.
|
||||
|
||||
Improved the panning of stereo source channels when using stereo output.
|
||||
|
||||
Improved source filter quality on send paths.
|
||||
|
||||
Added a config option to allow PulseAudio to move streams between devices.
|
||||
|
||||
The PulseAudio backend will now attempt to spawn a server by default.
|
||||
|
||||
Added a workaround for a DirectSound bug relating to float32 output.
|
||||
|
||||
Added SSE-based mixers, for HRTF and non-HRTF mixing.
|
||||
|
||||
Added support for the new AL_SOFT_source_latency extension.
|
||||
|
||||
Improved ALSA capture by avoiding an extra buffer when using sizes
|
||||
supported by the underlying device.
|
||||
|
||||
Improved the makehrtf utility to support new options and input formats.
|
||||
|
||||
Modified the CFLAGS declared in the pkg-config file so the "AL/" portion of
|
||||
the header includes can optionally be omitted.
|
||||
|
||||
Added a couple example code programs to show how to apply reverb, and
|
||||
retrieve latency.
|
||||
|
||||
The configuration sample is now installed into the share/openal/ directory
|
||||
instead of /etc/openal.
|
||||
|
||||
The configuration sample now gets installed by default.
|
||||
@@ -1,61 +1,16 @@
|
||||
OpenAL soft
|
||||
===========
|
||||
# openal-soft-vita
|
||||
|
||||
`master` branch CI status : [](https://travis-ci.org/kcat/openal-soft) [](https://ci.appveyor.com/api/projects/status/github/kcat/openal-soft?branch=master&svg=true)
|
||||
This repo contains patchsets for vita [openal-soft](https://openal-soft.org/) port.
|
||||
|
||||
OpenAL Soft is an LGPL-licensed, cross-platform, software implementation of the OpenAL 3D audio API. It's forked from the open-sourced Windows version available originally from openal.org's SVN repository (now defunct).
|
||||
OpenAL provides capabilities for playing audio in a virtual 3D environment. Distance attenuation, doppler shift, and directional sound emitters are among the features handled by the API. More advanced effects, including air absorption, occlusion, and environmental reverb, are available through the EFX extension. It also facilitates streaming audio, multi-channel buffers, and audio capture.
|
||||
Format is openal-soft-<openal_version>-vita-<patchset_version>.patch
|
||||
|
||||
More information is available on the [official website](http://openal-soft.org/)
|
||||
For full source see vita-* branches
|
||||
|
||||
Source Install
|
||||
-------------
|
||||
To install OpenAL Soft, use your favorite shell to go into the build/
|
||||
directory, and run:
|
||||
|
||||
```bash
|
||||
cmake ..
|
||||
# building
|
||||
|
||||
```
|
||||
mkdir build && cd build
|
||||
cmake -DCMAKE_TOOLCHAIN_FILE=${VITASDK}/share/vita.toolchain.cmake -DCMAKE_BUILD_TYPE=Release ..
|
||||
make install
|
||||
```
|
||||
|
||||
Assuming configuration went well, you can then build it, typically using GNU
|
||||
Make (KDevelop, MSVC, and others are possible depending on your system setup
|
||||
and CMake configuration).
|
||||
|
||||
Please Note: Double check that the appropriate backends were detected. Often,
|
||||
complaints of no sound, crashing, and missing devices can be solved by making
|
||||
sure the correct backends are being used. CMake's output will identify which
|
||||
backends were enabled.
|
||||
|
||||
For most systems, you will likely want to make sure ALSA, OSS, and PulseAudio
|
||||
were detected (if your target system uses them). For Windows, make sure
|
||||
DirectSound was detected.
|
||||
|
||||
|
||||
Utilities
|
||||
---------
|
||||
The source package comes with an informational utility, openal-info, and is
|
||||
built by default. It prints out information provided by the ALC and AL sub-
|
||||
systems, including discovered devices, version information, and extensions.
|
||||
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
OpenAL Soft can be configured on a per-user and per-system basis. This allows
|
||||
users and sysadmins to control information provided to applications, as well
|
||||
as application-agnostic behavior of the library. See alsoftrc.sample for
|
||||
available settings.
|
||||
|
||||
|
||||
Acknowledgements
|
||||
----------------
|
||||
|
||||
Special thanks go to:
|
||||
|
||||
- Creative Labs for the original source code this is based off of.
|
||||
- Christopher Fitzgerald for the current reverb effect implementation, and
|
||||
helping with the low-pass and HRTF filters.
|
||||
- Christian Borss for the 3D panning code previous versions used as a base.
|
||||
- Ben Davis for the idea behind a previous version of the click-removal code.
|
||||
- Richard Furse for helping with my understanding of Ambisonics that is used by
|
||||
the various parts of the library.
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
# Cross-compiling requires CMake 2.6 or newer. Example:
|
||||
# cmake .. -DCMAKE_TOOLCHAIN_FILE=../XCompile-Android.txt -DHOST=arm-linux-androideabi
|
||||
# Where 'arm-linux-androideabi' is the host prefix for the cross-compiler. If
|
||||
# you already have a toolchain file setup, you may use that instead of this
|
||||
# file. Make sure to set CMAKE_FIND_ROOT_PATH to where the NDK toolchain was
|
||||
# installed (e.g. "$ENV{HOME}/toolchains/arm-linux-androideabi-r10c-21").
|
||||
|
||||
# the name of the target operating system
|
||||
SET(CMAKE_SYSTEM_NAME Linux)
|
||||
|
||||
# which compilers to use for C and C++
|
||||
SET(CMAKE_C_COMPILER "${HOST}-gcc")
|
||||
SET(CMAKE_CXX_COMPILER "${HOST}-g++")
|
||||
SET(CMAKE_RC_COMPILER "${HOST}-windres")
|
||||
|
||||
# here is the target environment located
|
||||
SET(CMAKE_FIND_ROOT_PATH "SET THIS TO THE NDK TOOLCHAIN'S INSTALL PATH")
|
||||
|
||||
# here is where stuff gets installed to
|
||||
SET(CMAKE_INSTALL_PREFIX "${CMAKE_FIND_ROOT_PATH}" CACHE STRING "Install path prefix, prepended onto install directories." FORCE)
|
||||
|
||||
# adjust the default behaviour of the FIND_XXX() commands:
|
||||
# search headers and libraries in the target environment, search
|
||||
# programs in the host environment
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||
|
||||
# set env vars so that pkg-config will look in the appropriate directory for
|
||||
# .pc files (as there seems to be no way to force using ${HOST}-pkg-config)
|
||||
set(ENV{PKG_CONFIG_LIBDIR} "${CMAKE_INSTALL_PREFIX}/lib/pkgconfig")
|
||||
set(ENV{PKG_CONFIG_PATH} "")
|
||||
|
||||
# Qt4 tools
|
||||
SET(QT_QMAKE_EXECUTABLE ${HOST}-qmake)
|
||||
SET(QT_MOC_EXECUTABLE ${HOST}-moc)
|
||||
SET(QT_RCC_EXECUTABLE ${HOST}-rcc)
|
||||
SET(QT_UIC_EXECUTABLE ${HOST}-uic)
|
||||
SET(QT_LRELEASE_EXECUTABLE ${HOST}-lrelease)
|
||||
@@ -1,37 +0,0 @@
|
||||
# Cross-compiling requires CMake 2.6 or newer. Example:
|
||||
# cmake .. -DCMAKE_TOOLCHAIN_FILE=../XCompile.txt -DHOST=i686-w64-mingw32
|
||||
# Where 'i686-w64-mingw32' is the host prefix for your cross-compiler. If you
|
||||
# already have a toolchain file setup, you may use that instead of this file.
|
||||
|
||||
# the name of the target operating system
|
||||
SET(CMAKE_SYSTEM_NAME Windows)
|
||||
|
||||
# which compilers to use for C and C++
|
||||
SET(CMAKE_C_COMPILER "${HOST}-gcc")
|
||||
SET(CMAKE_CXX_COMPILER "${HOST}-g++")
|
||||
SET(CMAKE_RC_COMPILER "${HOST}-windres")
|
||||
|
||||
# here is the target environment located
|
||||
SET(CMAKE_FIND_ROOT_PATH "/usr/${HOST}")
|
||||
|
||||
# here is where stuff gets installed to
|
||||
SET(CMAKE_INSTALL_PREFIX "${CMAKE_FIND_ROOT_PATH}" CACHE STRING "Install path prefix, prepended onto install directories." FORCE)
|
||||
|
||||
# adjust the default behaviour of the FIND_XXX() commands:
|
||||
# search headers and libraries in the target environment, search
|
||||
# programs in the host environment
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||
|
||||
# set env vars so that pkg-config will look in the appropriate directory for
|
||||
# .pc files (as there seems to be no way to force using ${HOST}-pkg-config)
|
||||
set(ENV{PKG_CONFIG_LIBDIR} "${CMAKE_INSTALL_PREFIX}/lib/pkgconfig")
|
||||
set(ENV{PKG_CONFIG_PATH} "")
|
||||
|
||||
# Qt4 tools
|
||||
SET(QT_QMAKE_EXECUTABLE ${HOST}-qmake)
|
||||
SET(QT_MOC_EXECUTABLE ${HOST}-moc)
|
||||
SET(QT_RCC_EXECUTABLE ${HOST}-rcc)
|
||||
SET(QT_UIC_EXECUTABLE ${HOST}-uic)
|
||||
SET(QT_LRELEASE_EXECUTABLE ${HOST}-lrelease)
|
||||
@@ -1,796 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 1999-2007 by authors.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "auxeffectslot.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <numeric>
|
||||
#include <thread>
|
||||
|
||||
#include "AL/al.h"
|
||||
#include "AL/alc.h"
|
||||
#include "AL/efx.h"
|
||||
|
||||
#include "alcmain.h"
|
||||
#include "alcontext.h"
|
||||
#include "alexcpt.h"
|
||||
#include "almalloc.h"
|
||||
#include "alnumeric.h"
|
||||
#include "alspan.h"
|
||||
#include "alu.h"
|
||||
#include "effect.h"
|
||||
#include "fpu_modes.h"
|
||||
#include "inprogext.h"
|
||||
#include "logging.h"
|
||||
#include "opthelpers.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
inline ALeffectslot *LookupEffectSlot(ALCcontext *context, ALuint id) noexcept
|
||||
{
|
||||
const size_t lidx{(id-1) >> 6};
|
||||
const ALuint slidx{(id-1) & 0x3f};
|
||||
|
||||
if UNLIKELY(lidx >= context->mEffectSlotList.size())
|
||||
return nullptr;
|
||||
EffectSlotSubList &sublist{context->mEffectSlotList[lidx]};
|
||||
if UNLIKELY(sublist.FreeMask & (1_u64 << slidx))
|
||||
return nullptr;
|
||||
return sublist.EffectSlots + slidx;
|
||||
}
|
||||
|
||||
inline ALeffect *LookupEffect(ALCdevice *device, ALuint id) noexcept
|
||||
{
|
||||
const size_t lidx{(id-1) >> 6};
|
||||
const ALuint slidx{(id-1) & 0x3f};
|
||||
|
||||
if UNLIKELY(lidx >= device->EffectList.size())
|
||||
return nullptr;
|
||||
EffectSubList &sublist = device->EffectList[lidx];
|
||||
if UNLIKELY(sublist.FreeMask & (1_u64 << slidx))
|
||||
return nullptr;
|
||||
return sublist.Effects + slidx;
|
||||
}
|
||||
|
||||
|
||||
void AddActiveEffectSlots(const ALuint *slotids, size_t count, ALCcontext *context)
|
||||
{
|
||||
if(count < 1) return;
|
||||
ALeffectslotArray *curarray{context->mActiveAuxSlots.load(std::memory_order_acquire)};
|
||||
size_t newcount{curarray->size() + count};
|
||||
|
||||
/* Insert the new effect slots into the head of the array, followed by the
|
||||
* existing ones.
|
||||
*/
|
||||
ALeffectslotArray *newarray = ALeffectslot::CreatePtrArray(newcount);
|
||||
auto slotiter = std::transform(slotids, slotids+count, newarray->begin(),
|
||||
[context](ALuint id) noexcept -> ALeffectslot*
|
||||
{ return LookupEffectSlot(context, id); }
|
||||
);
|
||||
std::copy(curarray->begin(), curarray->end(), slotiter);
|
||||
|
||||
/* Remove any duplicates (first instance of each will be kept). */
|
||||
auto last = newarray->end();
|
||||
for(auto start=newarray->begin()+1;;)
|
||||
{
|
||||
last = std::remove(start, last, *(start-1));
|
||||
if(start == last) break;
|
||||
++start;
|
||||
}
|
||||
newcount = static_cast<size_t>(std::distance(newarray->begin(), last));
|
||||
|
||||
/* Reallocate newarray if the new size ended up smaller from duplicate
|
||||
* removal.
|
||||
*/
|
||||
if UNLIKELY(newcount < newarray->size())
|
||||
{
|
||||
curarray = newarray;
|
||||
newarray = ALeffectslot::CreatePtrArray(newcount);
|
||||
std::copy_n(curarray->begin(), newcount, newarray->begin());
|
||||
delete curarray;
|
||||
curarray = nullptr;
|
||||
}
|
||||
std::uninitialized_fill_n(newarray->end(), newcount, nullptr);
|
||||
|
||||
curarray = context->mActiveAuxSlots.exchange(newarray, std::memory_order_acq_rel);
|
||||
ALCdevice *device{context->mDevice.get()};
|
||||
while((device->MixCount.load(std::memory_order_acquire)&1))
|
||||
std::this_thread::yield();
|
||||
al::destroy_n(curarray->end(), curarray->size());
|
||||
delete curarray;
|
||||
}
|
||||
|
||||
void RemoveActiveEffectSlots(const ALuint *slotids, size_t count, ALCcontext *context)
|
||||
{
|
||||
if(count < 1) return;
|
||||
ALeffectslotArray *curarray{context->mActiveAuxSlots.load(std::memory_order_acquire)};
|
||||
|
||||
/* Don't shrink the allocated array size since we don't know how many (if
|
||||
* any) of the effect slots to remove are in the array.
|
||||
*/
|
||||
ALeffectslotArray *newarray = ALeffectslot::CreatePtrArray(curarray->size());
|
||||
|
||||
/* Copy each element in curarray to newarray whose ID is not in slotids. */
|
||||
const ALuint *slotids_end{slotids + count};
|
||||
auto slotiter = std::copy_if(curarray->begin(), curarray->end(), newarray->begin(),
|
||||
[slotids, slotids_end](const ALeffectslot *slot) -> bool
|
||||
{ return std::find(slotids, slotids_end, slot->id) == slotids_end; }
|
||||
);
|
||||
|
||||
/* Reallocate with the new size. */
|
||||
auto newsize = static_cast<size_t>(std::distance(newarray->begin(), slotiter));
|
||||
if LIKELY(newsize != newarray->size())
|
||||
{
|
||||
curarray = newarray;
|
||||
newarray = ALeffectslot::CreatePtrArray(newsize);
|
||||
std::copy_n(curarray->begin(), newsize, newarray->begin());
|
||||
|
||||
delete curarray;
|
||||
curarray = nullptr;
|
||||
}
|
||||
std::uninitialized_fill_n(newarray->end(), newsize, nullptr);
|
||||
|
||||
curarray = context->mActiveAuxSlots.exchange(newarray, std::memory_order_acq_rel);
|
||||
ALCdevice *device{context->mDevice.get()};
|
||||
while((device->MixCount.load(std::memory_order_acquire)&1))
|
||||
std::this_thread::yield();
|
||||
al::destroy_n(curarray->end(), curarray->size());
|
||||
delete curarray;
|
||||
}
|
||||
|
||||
|
||||
bool EnsureEffectSlots(ALCcontext *context, size_t needed)
|
||||
{
|
||||
size_t count{std::accumulate(context->mEffectSlotList.cbegin(),
|
||||
context->mEffectSlotList.cend(), size_t{0},
|
||||
[](size_t cur, const EffectSlotSubList &sublist) noexcept -> size_t
|
||||
{ return cur + static_cast<ALuint>(POPCNT64(sublist.FreeMask)); }
|
||||
)};
|
||||
|
||||
while(needed > count)
|
||||
{
|
||||
if UNLIKELY(context->mEffectSlotList.size() >= 1<<25)
|
||||
return false;
|
||||
|
||||
context->mEffectSlotList.emplace_back();
|
||||
auto sublist = context->mEffectSlotList.end() - 1;
|
||||
sublist->FreeMask = ~0_u64;
|
||||
sublist->EffectSlots = static_cast<ALeffectslot*>(
|
||||
al_calloc(alignof(ALeffectslot), sizeof(ALeffectslot)*64));
|
||||
if UNLIKELY(!sublist->EffectSlots)
|
||||
{
|
||||
context->mEffectSlotList.pop_back();
|
||||
return false;
|
||||
}
|
||||
count += 64;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
ALeffectslot *AllocEffectSlot(ALCcontext *context)
|
||||
{
|
||||
auto sublist = std::find_if(context->mEffectSlotList.begin(), context->mEffectSlotList.end(),
|
||||
[](const EffectSlotSubList &entry) noexcept -> bool
|
||||
{ return entry.FreeMask != 0; }
|
||||
);
|
||||
auto lidx = static_cast<ALuint>(std::distance(context->mEffectSlotList.begin(), sublist));
|
||||
auto slidx = static_cast<ALuint>(CTZ64(sublist->FreeMask));
|
||||
|
||||
ALeffectslot *slot{::new (sublist->EffectSlots + slidx) ALeffectslot{}};
|
||||
if(ALenum err{InitEffectSlot(slot)})
|
||||
{
|
||||
al::destroy_at(slot);
|
||||
context->setError(err, "Effect slot object initialization failed");
|
||||
return nullptr;
|
||||
}
|
||||
aluInitEffectPanning(slot, context->mDevice.get());
|
||||
|
||||
/* Add 1 to avoid source ID 0. */
|
||||
slot->id = ((lidx<<6) | slidx) + 1;
|
||||
|
||||
context->mNumEffectSlots += 1;
|
||||
sublist->FreeMask &= ~(1_u64 << slidx);
|
||||
|
||||
return slot;
|
||||
}
|
||||
|
||||
void FreeEffectSlot(ALCcontext *context, ALeffectslot *slot)
|
||||
{
|
||||
const ALuint id{slot->id - 1};
|
||||
const size_t lidx{id >> 6};
|
||||
const ALuint slidx{id & 0x3f};
|
||||
|
||||
al::destroy_at(slot);
|
||||
|
||||
context->mEffectSlotList[lidx].FreeMask |= 1_u64 << slidx;
|
||||
context->mNumEffectSlots--;
|
||||
}
|
||||
|
||||
|
||||
#define DO_UPDATEPROPS() do { \
|
||||
if(!context->mDeferUpdates.load(std::memory_order_acquire)) \
|
||||
UpdateEffectSlotProps(slot, context.get()); \
|
||||
else \
|
||||
slot->PropsClean.clear(std::memory_order_release); \
|
||||
} while(0)
|
||||
|
||||
} // namespace
|
||||
|
||||
ALeffectslotArray *ALeffectslot::CreatePtrArray(size_t count) noexcept
|
||||
{
|
||||
/* Allocate space for twice as many pointers, so the mixer has scratch
|
||||
* space to store a sorted list during mixing.
|
||||
*/
|
||||
void *ptr{al_calloc(alignof(ALeffectslotArray), ALeffectslotArray::Sizeof(count*2))};
|
||||
return new (ptr) ALeffectslotArray{count};
|
||||
}
|
||||
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alGenAuxiliaryEffectSlots(ALsizei n, ALuint *effectslots)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
if UNLIKELY(n < 0)
|
||||
context->setError(AL_INVALID_VALUE, "Generating %d effect slots", n);
|
||||
if UNLIKELY(n <= 0) return;
|
||||
|
||||
std::unique_lock<std::mutex> slotlock{context->mEffectSlotLock};
|
||||
ALCdevice *device{context->mDevice.get()};
|
||||
if(static_cast<ALuint>(n) > device->AuxiliaryEffectSlotMax-context->mNumEffectSlots)
|
||||
{
|
||||
context->setError(AL_OUT_OF_MEMORY, "Exceeding %u effect slot limit (%u + %d)",
|
||||
device->AuxiliaryEffectSlotMax, context->mNumEffectSlots, n);
|
||||
return;
|
||||
}
|
||||
if(!EnsureEffectSlots(context.get(), static_cast<ALuint>(n)))
|
||||
{
|
||||
context->setError(AL_OUT_OF_MEMORY, "Failed to allocate %d effectslot%s", n,
|
||||
(n==1) ? "" : "s");
|
||||
return;
|
||||
}
|
||||
|
||||
if(n == 1)
|
||||
{
|
||||
ALeffectslot *slot{AllocEffectSlot(context.get())};
|
||||
if(!slot) return;
|
||||
effectslots[0] = slot->id;
|
||||
}
|
||||
else
|
||||
{
|
||||
al::vector<ALuint> ids;
|
||||
ALsizei count{n};
|
||||
ids.reserve(static_cast<ALuint>(count));
|
||||
do {
|
||||
ALeffectslot *slot{AllocEffectSlot(context.get())};
|
||||
if(!slot)
|
||||
{
|
||||
slotlock.unlock();
|
||||
alDeleteAuxiliaryEffectSlots(static_cast<ALsizei>(ids.size()), ids.data());
|
||||
return;
|
||||
}
|
||||
ids.emplace_back(slot->id);
|
||||
} while(--count);
|
||||
std::copy(ids.cbegin(), ids.cend(), effectslots);
|
||||
}
|
||||
|
||||
AddActiveEffectSlots(effectslots, static_cast<ALuint>(n), context.get());
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alDeleteAuxiliaryEffectSlots(ALsizei n, const ALuint *effectslots)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
if UNLIKELY(n < 0)
|
||||
context->setError(AL_INVALID_VALUE, "Deleting %d effect slots", n);
|
||||
if UNLIKELY(n <= 0) return;
|
||||
|
||||
std::lock_guard<std::mutex> _{context->mEffectSlotLock};
|
||||
auto validate_slot = [&context](const ALuint id) -> bool
|
||||
{
|
||||
ALeffectslot *slot{LookupEffectSlot(context.get(), id)};
|
||||
if UNLIKELY(!slot)
|
||||
{
|
||||
context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", id);
|
||||
return false;
|
||||
}
|
||||
if UNLIKELY(ReadRef(slot->ref) != 0)
|
||||
{
|
||||
context->setError(AL_INVALID_OPERATION, "Deleting in-use effect slot %u", id);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
auto effectslots_end = effectslots + n;
|
||||
auto bad_slot = std::find_if_not(effectslots, effectslots_end, validate_slot);
|
||||
if UNLIKELY(bad_slot != effectslots_end) return;
|
||||
|
||||
// All effectslots are valid, remove and delete them
|
||||
RemoveActiveEffectSlots(effectslots, static_cast<ALuint>(n), context.get());
|
||||
auto delete_slot = [&context](const ALuint sid) -> void
|
||||
{
|
||||
ALeffectslot *slot{LookupEffectSlot(context.get(), sid)};
|
||||
if(slot) FreeEffectSlot(context.get(), slot);
|
||||
};
|
||||
std::for_each(effectslots, effectslots_end, delete_slot);
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API ALboolean AL_APIENTRY alIsAuxiliaryEffectSlot(ALuint effectslot)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if LIKELY(context)
|
||||
{
|
||||
std::lock_guard<std::mutex> _{context->mEffectSlotLock};
|
||||
if(LookupEffectSlot(context.get(), effectslot) != nullptr)
|
||||
return AL_TRUE;
|
||||
}
|
||||
return AL_FALSE;
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSloti(ALuint effectslot, ALenum param, ALint value)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
std::lock_guard<std::mutex> _{context->mPropLock};
|
||||
std::lock_guard<std::mutex> __{context->mEffectSlotLock};
|
||||
ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot);
|
||||
if UNLIKELY(!slot)
|
||||
SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid effect slot ID %u", effectslot);
|
||||
|
||||
ALeffectslot *target{};
|
||||
ALCdevice *device{};
|
||||
ALenum err{};
|
||||
switch(param)
|
||||
{
|
||||
case AL_EFFECTSLOT_EFFECT:
|
||||
device = context->mDevice.get();
|
||||
|
||||
{ std::lock_guard<std::mutex> ___{device->EffectLock};
|
||||
ALeffect *effect{value ? LookupEffect(device, static_cast<ALuint>(value)) : nullptr};
|
||||
if(!(value == 0 || effect != nullptr))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Invalid effect ID %u", value);
|
||||
err = InitializeEffect(context.get(), slot, effect);
|
||||
}
|
||||
if(err != AL_NO_ERROR)
|
||||
{
|
||||
context->setError(err, "Effect initialization failed");
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO:
|
||||
if(!(value == AL_TRUE || value == AL_FALSE))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,,
|
||||
"Effect slot auxiliary send auto out of range");
|
||||
slot->AuxSendAuto = static_cast<ALboolean>(value);
|
||||
break;
|
||||
|
||||
case AL_EFFECTSLOT_TARGET_SOFT:
|
||||
target = LookupEffectSlot(context.get(), static_cast<ALuint>(value));
|
||||
if(value && !target)
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Invalid effect slot target ID");
|
||||
if(target)
|
||||
{
|
||||
ALeffectslot *checker{target};
|
||||
while(checker && checker != slot)
|
||||
checker = checker->Target;
|
||||
if(checker)
|
||||
SETERR_RETURN(context, AL_INVALID_OPERATION,,
|
||||
"Setting target of effect slot ID %u to %u creates circular chain", slot->id,
|
||||
target->id);
|
||||
}
|
||||
|
||||
if(ALeffectslot *oldtarget{slot->Target})
|
||||
{
|
||||
/* We must force an update if there was an existing effect slot
|
||||
* target, in case it's about to be deleted.
|
||||
*/
|
||||
if(target) IncrementRef(target->ref);
|
||||
DecrementRef(oldtarget->ref);
|
||||
slot->Target = target;
|
||||
UpdateEffectSlotProps(slot, context.get());
|
||||
return;
|
||||
}
|
||||
|
||||
if(target) IncrementRef(target->ref);
|
||||
slot->Target = target;
|
||||
break;
|
||||
|
||||
default:
|
||||
SETERR_RETURN(context, AL_INVALID_ENUM,, "Invalid effect slot integer property 0x%04x",
|
||||
param);
|
||||
}
|
||||
DO_UPDATEPROPS();
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSlotiv(ALuint effectslot, ALenum param, const ALint *values)
|
||||
START_API_FUNC
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_EFFECTSLOT_EFFECT:
|
||||
case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO:
|
||||
case AL_EFFECTSLOT_TARGET_SOFT:
|
||||
alAuxiliaryEffectSloti(effectslot, param, values[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
std::lock_guard<std::mutex> _{context->mEffectSlotLock};
|
||||
ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot);
|
||||
if UNLIKELY(!slot)
|
||||
SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid effect slot ID %u", effectslot);
|
||||
|
||||
switch(param)
|
||||
{
|
||||
default:
|
||||
SETERR_RETURN(context, AL_INVALID_ENUM,,
|
||||
"Invalid effect slot integer-vector property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSlotf(ALuint effectslot, ALenum param, ALfloat value)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
std::lock_guard<std::mutex> _{context->mPropLock};
|
||||
std::lock_guard<std::mutex> __{context->mEffectSlotLock};
|
||||
ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot);
|
||||
if UNLIKELY(!slot)
|
||||
SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid effect slot ID %u", effectslot);
|
||||
|
||||
switch(param)
|
||||
{
|
||||
case AL_EFFECTSLOT_GAIN:
|
||||
if(!(value >= 0.0f && value <= 1.0f))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Effect slot gain out of range");
|
||||
slot->Gain = value;
|
||||
break;
|
||||
|
||||
default:
|
||||
SETERR_RETURN(context, AL_INVALID_ENUM,, "Invalid effect slot float property 0x%04x",
|
||||
param);
|
||||
}
|
||||
DO_UPDATEPROPS();
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSlotfv(ALuint effectslot, ALenum param, const ALfloat *values)
|
||||
START_API_FUNC
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_EFFECTSLOT_GAIN:
|
||||
alAuxiliaryEffectSlotf(effectslot, param, values[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
std::lock_guard<std::mutex> _{context->mEffectSlotLock};
|
||||
ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot);
|
||||
if UNLIKELY(!slot)
|
||||
SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid effect slot ID %u", effectslot);
|
||||
|
||||
switch(param)
|
||||
{
|
||||
default:
|
||||
SETERR_RETURN(context, AL_INVALID_ENUM,,
|
||||
"Invalid effect slot float-vector property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSloti(ALuint effectslot, ALenum param, ALint *value)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
std::lock_guard<std::mutex> _{context->mEffectSlotLock};
|
||||
ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot);
|
||||
if UNLIKELY(!slot)
|
||||
SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid effect slot ID %u", effectslot);
|
||||
|
||||
switch(param)
|
||||
{
|
||||
case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO:
|
||||
*value = slot->AuxSendAuto;
|
||||
break;
|
||||
|
||||
case AL_EFFECTSLOT_TARGET_SOFT:
|
||||
if(auto *target = slot->Target)
|
||||
*value = static_cast<ALint>(target->id);
|
||||
else
|
||||
*value = 0;
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid effect slot integer property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSlotiv(ALuint effectslot, ALenum param, ALint *values)
|
||||
START_API_FUNC
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_EFFECTSLOT_EFFECT:
|
||||
case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO:
|
||||
case AL_EFFECTSLOT_TARGET_SOFT:
|
||||
alGetAuxiliaryEffectSloti(effectslot, param, values);
|
||||
return;
|
||||
}
|
||||
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
std::lock_guard<std::mutex> _{context->mEffectSlotLock};
|
||||
ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot);
|
||||
if UNLIKELY(!slot)
|
||||
SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid effect slot ID %u", effectslot);
|
||||
|
||||
switch(param)
|
||||
{
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid effect slot integer-vector property 0x%04x",
|
||||
param);
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSlotf(ALuint effectslot, ALenum param, ALfloat *value)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
std::lock_guard<std::mutex> _{context->mEffectSlotLock};
|
||||
ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot);
|
||||
if UNLIKELY(!slot)
|
||||
SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid effect slot ID %u", effectslot);
|
||||
|
||||
switch(param)
|
||||
{
|
||||
case AL_EFFECTSLOT_GAIN:
|
||||
*value = slot->Gain;
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid effect slot float property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSlotfv(ALuint effectslot, ALenum param, ALfloat *values)
|
||||
START_API_FUNC
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_EFFECTSLOT_GAIN:
|
||||
alGetAuxiliaryEffectSlotf(effectslot, param, values);
|
||||
return;
|
||||
}
|
||||
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
std::lock_guard<std::mutex> _{context->mEffectSlotLock};
|
||||
ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot);
|
||||
if UNLIKELY(!slot)
|
||||
SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid effect slot ID %u", effectslot);
|
||||
|
||||
switch(param)
|
||||
{
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid effect slot float-vector property 0x%04x",
|
||||
param);
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
|
||||
ALenum InitializeEffect(ALCcontext *Context, ALeffectslot *EffectSlot, ALeffect *effect)
|
||||
{
|
||||
ALenum newtype{effect ? effect->type : AL_EFFECT_NULL};
|
||||
if(newtype != EffectSlot->Effect.Type)
|
||||
{
|
||||
EffectStateFactory *factory{getFactoryByType(newtype)};
|
||||
if(!factory)
|
||||
{
|
||||
ERR("Failed to find factory for effect type 0x%04x\n", newtype);
|
||||
return AL_INVALID_ENUM;
|
||||
}
|
||||
EffectState *State{factory->create()};
|
||||
if(!State) return AL_OUT_OF_MEMORY;
|
||||
|
||||
FPUCtl mixer_mode{};
|
||||
ALCdevice *Device{Context->mDevice.get()};
|
||||
std::unique_lock<std::mutex> statelock{Device->StateLock};
|
||||
State->mOutTarget = Device->Dry.Buffer;
|
||||
if(State->deviceUpdate(Device) == AL_FALSE)
|
||||
{
|
||||
statelock.unlock();
|
||||
mixer_mode.leave();
|
||||
State->release();
|
||||
return AL_OUT_OF_MEMORY;
|
||||
}
|
||||
mixer_mode.leave();
|
||||
|
||||
if(!effect)
|
||||
{
|
||||
EffectSlot->Effect.Type = AL_EFFECT_NULL;
|
||||
EffectSlot->Effect.Props = EffectProps {};
|
||||
}
|
||||
else
|
||||
{
|
||||
EffectSlot->Effect.Type = effect->type;
|
||||
EffectSlot->Effect.Props = effect->Props;
|
||||
}
|
||||
|
||||
EffectSlot->Effect.State->release();
|
||||
EffectSlot->Effect.State = State;
|
||||
}
|
||||
else if(effect)
|
||||
EffectSlot->Effect.Props = effect->Props;
|
||||
|
||||
/* Remove state references from old effect slot property updates. */
|
||||
ALeffectslotProps *props{Context->mFreeEffectslotProps.load()};
|
||||
while(props)
|
||||
{
|
||||
if(props->State)
|
||||
props->State->release();
|
||||
props->State = nullptr;
|
||||
props = props->next.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
return AL_NO_ERROR;
|
||||
}
|
||||
|
||||
|
||||
ALenum InitEffectSlot(ALeffectslot *slot)
|
||||
{
|
||||
EffectStateFactory *factory{getFactoryByType(slot->Effect.Type)};
|
||||
if(!factory) return AL_INVALID_VALUE;
|
||||
slot->Effect.State = factory->create();
|
||||
if(!slot->Effect.State) return AL_OUT_OF_MEMORY;
|
||||
|
||||
slot->Effect.State->add_ref();
|
||||
slot->Params.mEffectState = slot->Effect.State;
|
||||
return AL_NO_ERROR;
|
||||
}
|
||||
|
||||
ALeffectslot::~ALeffectslot()
|
||||
{
|
||||
if(Target)
|
||||
DecrementRef(Target->ref);
|
||||
Target = nullptr;
|
||||
|
||||
ALeffectslotProps *props{Params.Update.load()};
|
||||
if(props)
|
||||
{
|
||||
if(props->State) props->State->release();
|
||||
TRACE("Freed unapplied AuxiliaryEffectSlot update %p\n",
|
||||
decltype(std::declval<void*>()){props});
|
||||
delete props;
|
||||
}
|
||||
|
||||
if(Effect.State)
|
||||
Effect.State->release();
|
||||
if(Params.mEffectState)
|
||||
Params.mEffectState->release();
|
||||
}
|
||||
|
||||
void UpdateEffectSlotProps(ALeffectslot *slot, ALCcontext *context)
|
||||
{
|
||||
/* Get an unused property container, or allocate a new one as needed. */
|
||||
ALeffectslotProps *props{context->mFreeEffectslotProps.load(std::memory_order_relaxed)};
|
||||
if(!props)
|
||||
props = new ALeffectslotProps{};
|
||||
else
|
||||
{
|
||||
ALeffectslotProps *next;
|
||||
do {
|
||||
next = props->next.load(std::memory_order_relaxed);
|
||||
} while(context->mFreeEffectslotProps.compare_exchange_weak(props, next,
|
||||
std::memory_order_seq_cst, std::memory_order_acquire) == 0);
|
||||
}
|
||||
|
||||
/* Copy in current property values. */
|
||||
props->Gain = slot->Gain;
|
||||
props->AuxSendAuto = slot->AuxSendAuto;
|
||||
props->Target = slot->Target;
|
||||
|
||||
props->Type = slot->Effect.Type;
|
||||
props->Props = slot->Effect.Props;
|
||||
/* Swap out any stale effect state object there may be in the container, to
|
||||
* delete it.
|
||||
*/
|
||||
EffectState *oldstate{props->State};
|
||||
slot->Effect.State->add_ref();
|
||||
props->State = slot->Effect.State;
|
||||
|
||||
/* Set the new container for updating internal parameters. */
|
||||
props = slot->Params.Update.exchange(props, std::memory_order_acq_rel);
|
||||
if(props)
|
||||
{
|
||||
/* If there was an unused update container, put it back in the
|
||||
* freelist.
|
||||
*/
|
||||
if(props->State)
|
||||
props->State->release();
|
||||
props->State = nullptr;
|
||||
AtomicReplaceHead(context->mFreeEffectslotProps, props);
|
||||
}
|
||||
|
||||
if(oldstate)
|
||||
oldstate->release();
|
||||
}
|
||||
|
||||
void UpdateAllEffectSlotProps(ALCcontext *context)
|
||||
{
|
||||
std::lock_guard<std::mutex> _{context->mEffectSlotLock};
|
||||
ALeffectslotArray *auxslots{context->mActiveAuxSlots.load(std::memory_order_acquire)};
|
||||
for(ALeffectslot *slot : *auxslots)
|
||||
{
|
||||
if(!slot->PropsClean.test_and_set(std::memory_order_acq_rel))
|
||||
UpdateEffectSlotProps(slot, context);
|
||||
}
|
||||
}
|
||||
|
||||
EffectSlotSubList::~EffectSlotSubList()
|
||||
{
|
||||
uint64_t usemask{~FreeMask};
|
||||
while(usemask)
|
||||
{
|
||||
ALsizei idx{CTZ64(usemask)};
|
||||
al::destroy_at(EffectSlots+idx);
|
||||
usemask &= ~(1_u64 << idx);
|
||||
}
|
||||
FreeMask = ~usemask;
|
||||
al_free(EffectSlots);
|
||||
EffectSlots = nullptr;
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
#ifndef AL_AUXEFFECTSLOT_H
|
||||
#define AL_AUXEFFECTSLOT_H
|
||||
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
|
||||
#include "AL/al.h"
|
||||
#include "AL/alc.h"
|
||||
#include "AL/efx.h"
|
||||
|
||||
#include "alcmain.h"
|
||||
#include "almalloc.h"
|
||||
#include "atomic.h"
|
||||
#include "effects/base.h"
|
||||
#include "vector.h"
|
||||
|
||||
struct ALeffect;
|
||||
struct ALeffectslot;
|
||||
|
||||
|
||||
using ALeffectslotArray = al::FlexArray<ALeffectslot*>;
|
||||
|
||||
|
||||
struct ALeffectslotProps {
|
||||
ALfloat Gain;
|
||||
ALboolean AuxSendAuto;
|
||||
ALeffectslot *Target;
|
||||
|
||||
ALenum Type;
|
||||
EffectProps Props;
|
||||
|
||||
EffectState *State;
|
||||
|
||||
std::atomic<ALeffectslotProps*> next;
|
||||
|
||||
DEF_NEWDEL(ALeffectslotProps)
|
||||
};
|
||||
|
||||
|
||||
struct ALeffectslot {
|
||||
ALfloat Gain{1.0f};
|
||||
ALboolean AuxSendAuto{AL_TRUE};
|
||||
ALeffectslot *Target{nullptr};
|
||||
|
||||
struct {
|
||||
ALenum Type{AL_EFFECT_NULL};
|
||||
EffectProps Props{};
|
||||
|
||||
EffectState *State{nullptr};
|
||||
} Effect;
|
||||
|
||||
std::atomic_flag PropsClean;
|
||||
|
||||
RefCount ref{0u};
|
||||
|
||||
struct {
|
||||
std::atomic<ALeffectslotProps*> Update{nullptr};
|
||||
|
||||
ALfloat Gain{1.0f};
|
||||
ALboolean AuxSendAuto{AL_TRUE};
|
||||
ALeffectslot *Target{nullptr};
|
||||
|
||||
ALenum EffectType{AL_EFFECT_NULL};
|
||||
EffectProps mEffectProps{};
|
||||
EffectState *mEffectState{nullptr};
|
||||
|
||||
ALfloat RoomRolloff{0.0f}; /* Added to the source's room rolloff, not multiplied. */
|
||||
ALfloat DecayTime{0.0f};
|
||||
ALfloat DecayLFRatio{0.0f};
|
||||
ALfloat DecayHFRatio{0.0f};
|
||||
ALboolean DecayHFLimit{AL_FALSE};
|
||||
ALfloat AirAbsorptionGainHF{1.0f};
|
||||
} Params;
|
||||
|
||||
/* Self ID */
|
||||
ALuint id{};
|
||||
|
||||
/* Mixing buffer used by the Wet mix. */
|
||||
al::vector<FloatBufferLine, 16> MixBuffer;
|
||||
|
||||
/* Wet buffer configuration is ACN channel order with N3D scaling.
|
||||
* Consequently, effects that only want to work with mono input can use
|
||||
* channel 0 by itself. Effects that want multichannel can process the
|
||||
* ambisonics signal and make a B-Format source pan.
|
||||
*/
|
||||
MixParams Wet;
|
||||
|
||||
ALeffectslot() { PropsClean.test_and_set(std::memory_order_relaxed); }
|
||||
ALeffectslot(const ALeffectslot&) = delete;
|
||||
ALeffectslot& operator=(const ALeffectslot&) = delete;
|
||||
~ALeffectslot();
|
||||
|
||||
static ALeffectslotArray *CreatePtrArray(size_t count) noexcept;
|
||||
|
||||
DEF_NEWDEL(ALeffectslot)
|
||||
};
|
||||
|
||||
ALenum InitEffectSlot(ALeffectslot *slot);
|
||||
void UpdateEffectSlotProps(ALeffectslot *slot, ALCcontext *context);
|
||||
void UpdateAllEffectSlotProps(ALCcontext *context);
|
||||
|
||||
|
||||
ALenum InitializeEffect(ALCcontext *Context, ALeffectslot *EffectSlot, ALeffect *effect);
|
||||
|
||||
#endif
|
||||
-1422
File diff suppressed because it is too large
Load Diff
-104
@@ -1,104 +0,0 @@
|
||||
#ifndef AL_BUFFER_H
|
||||
#define AL_BUFFER_H
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include "AL/al.h"
|
||||
|
||||
#include "albyte.h"
|
||||
#include "almalloc.h"
|
||||
#include "atomic.h"
|
||||
#include "inprogext.h"
|
||||
#include "vector.h"
|
||||
|
||||
|
||||
/* User formats */
|
||||
enum UserFmtType : unsigned char {
|
||||
UserFmtUByte,
|
||||
UserFmtShort,
|
||||
UserFmtFloat,
|
||||
UserFmtDouble,
|
||||
UserFmtMulaw,
|
||||
UserFmtAlaw,
|
||||
UserFmtIMA4,
|
||||
UserFmtMSADPCM,
|
||||
};
|
||||
enum UserFmtChannels : unsigned char {
|
||||
UserFmtMono,
|
||||
UserFmtStereo,
|
||||
UserFmtRear,
|
||||
UserFmtQuad,
|
||||
UserFmtX51, /* (WFX order) */
|
||||
UserFmtX61, /* (WFX order) */
|
||||
UserFmtX71, /* (WFX order) */
|
||||
UserFmtBFormat2D, /* WXY */
|
||||
UserFmtBFormat3D, /* WXYZ */
|
||||
};
|
||||
|
||||
|
||||
/* Storable formats */
|
||||
enum FmtType : unsigned char {
|
||||
FmtUByte = UserFmtUByte,
|
||||
FmtShort = UserFmtShort,
|
||||
FmtFloat = UserFmtFloat,
|
||||
FmtDouble = UserFmtDouble,
|
||||
FmtMulaw = UserFmtMulaw,
|
||||
FmtAlaw = UserFmtAlaw,
|
||||
};
|
||||
enum FmtChannels : unsigned char {
|
||||
FmtMono = UserFmtMono,
|
||||
FmtStereo = UserFmtStereo,
|
||||
FmtRear = UserFmtRear,
|
||||
FmtQuad = UserFmtQuad,
|
||||
FmtX51 = UserFmtX51,
|
||||
FmtX61 = UserFmtX61,
|
||||
FmtX71 = UserFmtX71,
|
||||
FmtBFormat2D = UserFmtBFormat2D,
|
||||
FmtBFormat3D = UserFmtBFormat3D,
|
||||
};
|
||||
#define MAX_INPUT_CHANNELS (8)
|
||||
|
||||
|
||||
ALuint BytesFromFmt(FmtType type) noexcept;
|
||||
ALuint ChannelsFromFmt(FmtChannels chans) noexcept;
|
||||
|
||||
|
||||
struct ALbuffer {
|
||||
al::vector<al::byte,16> mData;
|
||||
|
||||
ALuint Frequency{0u};
|
||||
ALbitfieldSOFT Access{0u};
|
||||
ALuint SampleLen{0u};
|
||||
|
||||
FmtChannels mFmtChannels{};
|
||||
FmtType mFmtType{};
|
||||
|
||||
UserFmtType OriginalType{};
|
||||
ALuint OriginalSize{0};
|
||||
ALuint OriginalAlign{0};
|
||||
|
||||
ALenum AmbiLayout{AL_FUMA_SOFT};
|
||||
ALenum AmbiScaling{AL_FUMA_SOFT};
|
||||
|
||||
ALuint LoopStart{0u};
|
||||
ALuint LoopEnd{0u};
|
||||
|
||||
ALuint UnpackAlign{0};
|
||||
ALuint PackAlign{0};
|
||||
|
||||
ALbitfieldSOFT MappedAccess{0u};
|
||||
ALsizei MappedOffset{0};
|
||||
ALsizei MappedSize{0};
|
||||
|
||||
/* Number of times buffer was attached to a source (deletion can only occur when 0) */
|
||||
RefCount ref{0u};
|
||||
|
||||
/* Self ID */
|
||||
ALuint id{0};
|
||||
|
||||
inline ALuint bytesFromFmt() const noexcept { return BytesFromFmt(mFmtType); }
|
||||
inline ALuint channelsFromFmt() const noexcept { return ChannelsFromFmt(mFmtChannels); }
|
||||
inline ALuint frameSizeFromFmt() const noexcept { return channelsFromFmt() * bytesFromFmt(); }
|
||||
};
|
||||
|
||||
#endif
|
||||
-725
@@ -1,725 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 1999-2007 by authors.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "effect.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <new>
|
||||
#include <numeric>
|
||||
#include <utility>
|
||||
|
||||
#include "AL/al.h"
|
||||
#include "AL/alc.h"
|
||||
#include "AL/alext.h"
|
||||
#include "AL/efx-presets.h"
|
||||
#include "AL/efx.h"
|
||||
|
||||
#include "alcmain.h"
|
||||
#include "alcontext.h"
|
||||
#include "alexcpt.h"
|
||||
#include "almalloc.h"
|
||||
#include "alnumeric.h"
|
||||
#include "alstring.h"
|
||||
#include "effects/base.h"
|
||||
#include "logging.h"
|
||||
#include "opthelpers.h"
|
||||
#include "vector.h"
|
||||
|
||||
|
||||
const EffectList gEffectList[15]{
|
||||
{ "eaxreverb", EAXREVERB_EFFECT, AL_EFFECT_EAXREVERB },
|
||||
{ "reverb", REVERB_EFFECT, AL_EFFECT_REVERB },
|
||||
{ "autowah", AUTOWAH_EFFECT, AL_EFFECT_AUTOWAH },
|
||||
{ "chorus", CHORUS_EFFECT, AL_EFFECT_CHORUS },
|
||||
{ "compressor", COMPRESSOR_EFFECT, AL_EFFECT_COMPRESSOR },
|
||||
{ "distortion", DISTORTION_EFFECT, AL_EFFECT_DISTORTION },
|
||||
{ "echo", ECHO_EFFECT, AL_EFFECT_ECHO },
|
||||
{ "equalizer", EQUALIZER_EFFECT, AL_EFFECT_EQUALIZER },
|
||||
{ "flanger", FLANGER_EFFECT, AL_EFFECT_FLANGER },
|
||||
{ "fshifter", FSHIFTER_EFFECT, AL_EFFECT_FREQUENCY_SHIFTER },
|
||||
{ "modulator", MODULATOR_EFFECT, AL_EFFECT_RING_MODULATOR },
|
||||
{ "pshifter", PSHIFTER_EFFECT, AL_EFFECT_PITCH_SHIFTER },
|
||||
{ "vmorpher", VMORPHER_EFFECT, AL_EFFECT_VOCAL_MORPHER },
|
||||
{ "dedicated", DEDICATED_EFFECT, AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT },
|
||||
{ "dedicated", DEDICATED_EFFECT, AL_EFFECT_DEDICATED_DIALOGUE },
|
||||
};
|
||||
|
||||
ALboolean DisabledEffects[MAX_EFFECTS];
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr struct FactoryItem {
|
||||
ALenum Type;
|
||||
EffectStateFactory* (&GetFactory)(void);
|
||||
} FactoryList[] = {
|
||||
{ AL_EFFECT_NULL, NullStateFactory_getFactory },
|
||||
{ AL_EFFECT_EAXREVERB, ReverbStateFactory_getFactory },
|
||||
{ AL_EFFECT_REVERB, StdReverbStateFactory_getFactory },
|
||||
{ AL_EFFECT_AUTOWAH, AutowahStateFactory_getFactory },
|
||||
{ AL_EFFECT_CHORUS, ChorusStateFactory_getFactory },
|
||||
{ AL_EFFECT_COMPRESSOR, CompressorStateFactory_getFactory },
|
||||
{ AL_EFFECT_DISTORTION, DistortionStateFactory_getFactory },
|
||||
{ AL_EFFECT_ECHO, EchoStateFactory_getFactory },
|
||||
{ AL_EFFECT_EQUALIZER, EqualizerStateFactory_getFactory },
|
||||
{ AL_EFFECT_FLANGER, FlangerStateFactory_getFactory },
|
||||
{ AL_EFFECT_FREQUENCY_SHIFTER, FshifterStateFactory_getFactory },
|
||||
{ AL_EFFECT_RING_MODULATOR, ModulatorStateFactory_getFactory },
|
||||
{ AL_EFFECT_PITCH_SHIFTER, PshifterStateFactory_getFactory},
|
||||
{ AL_EFFECT_VOCAL_MORPHER, VmorpherStateFactory_getFactory},
|
||||
{ AL_EFFECT_DEDICATED_DIALOGUE, DedicatedStateFactory_getFactory },
|
||||
{ AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT, DedicatedStateFactory_getFactory }
|
||||
};
|
||||
|
||||
|
||||
template<typename... T>
|
||||
void ALeffect_setParami(ALeffect *effect, T&& ...args)
|
||||
{ effect->vtab->setParami(&effect->Props, std::forward<T>(args)...); }
|
||||
template<typename... T>
|
||||
void ALeffect_setParamiv(ALeffect *effect, T&& ...args)
|
||||
{ effect->vtab->setParamiv(&effect->Props, std::forward<T>(args)...); }
|
||||
template<typename... T>
|
||||
void ALeffect_setParamf(ALeffect *effect, T&& ...args)
|
||||
{ effect->vtab->setParamf(&effect->Props, std::forward<T>(args)...); }
|
||||
template<typename... T>
|
||||
void ALeffect_setParamfv(ALeffect *effect, T&& ...args)
|
||||
{ effect->vtab->setParamfv(&effect->Props, std::forward<T>(args)...); }
|
||||
|
||||
template<typename... T>
|
||||
void ALeffect_getParami(const ALeffect *effect, T&& ...args)
|
||||
{ effect->vtab->getParami(&effect->Props, std::forward<T>(args)...); }
|
||||
template<typename... T>
|
||||
void ALeffect_getParamiv(const ALeffect *effect, T&& ...args)
|
||||
{ effect->vtab->getParamiv(&effect->Props, std::forward<T>(args)...); }
|
||||
template<typename... T>
|
||||
void ALeffect_getParamf(const ALeffect *effect, T&& ...args)
|
||||
{ effect->vtab->getParamf(&effect->Props, std::forward<T>(args)...); }
|
||||
template<typename... T>
|
||||
void ALeffect_getParamfv(const ALeffect *effect, T&& ...args)
|
||||
{ effect->vtab->getParamfv(&effect->Props, std::forward<T>(args)...); }
|
||||
|
||||
|
||||
void InitEffectParams(ALeffect *effect, ALenum type)
|
||||
{
|
||||
EffectStateFactory *factory = getFactoryByType(type);
|
||||
if(factory)
|
||||
{
|
||||
effect->Props = factory->getDefaultProps();
|
||||
effect->vtab = factory->getEffectVtable();
|
||||
}
|
||||
else
|
||||
{
|
||||
effect->Props = EffectProps{};
|
||||
effect->vtab = nullptr;
|
||||
}
|
||||
effect->type = type;
|
||||
}
|
||||
|
||||
bool EnsureEffects(ALCdevice *device, size_t needed)
|
||||
{
|
||||
size_t count{std::accumulate(device->EffectList.cbegin(), device->EffectList.cend(), size_t{0},
|
||||
[](size_t cur, const EffectSubList &sublist) noexcept -> size_t
|
||||
{ return cur + static_cast<ALuint>(POPCNT64(sublist.FreeMask)); }
|
||||
)};
|
||||
|
||||
while(needed > count)
|
||||
{
|
||||
if UNLIKELY(device->EffectList.size() >= 1<<25)
|
||||
return false;
|
||||
|
||||
device->EffectList.emplace_back();
|
||||
auto sublist = device->EffectList.end() - 1;
|
||||
sublist->FreeMask = ~0_u64;
|
||||
sublist->Effects = static_cast<ALeffect*>(al_calloc(alignof(ALeffect), sizeof(ALeffect)*64));
|
||||
if UNLIKELY(!sublist->Effects)
|
||||
{
|
||||
device->EffectList.pop_back();
|
||||
return false;
|
||||
}
|
||||
count += 64;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
ALeffect *AllocEffect(ALCdevice *device)
|
||||
{
|
||||
auto sublist = std::find_if(device->EffectList.begin(), device->EffectList.end(),
|
||||
[](const EffectSubList &entry) noexcept -> bool
|
||||
{ return entry.FreeMask != 0; }
|
||||
);
|
||||
auto lidx = static_cast<ALuint>(std::distance(device->EffectList.begin(), sublist));
|
||||
auto slidx = static_cast<ALuint>(CTZ64(sublist->FreeMask));
|
||||
|
||||
ALeffect *effect{::new (sublist->Effects + slidx) ALeffect{}};
|
||||
InitEffectParams(effect, AL_EFFECT_NULL);
|
||||
|
||||
/* Add 1 to avoid effect ID 0. */
|
||||
effect->id = ((lidx<<6) | slidx) + 1;
|
||||
|
||||
sublist->FreeMask &= ~(1_u64 << slidx);
|
||||
|
||||
return effect;
|
||||
}
|
||||
|
||||
void FreeEffect(ALCdevice *device, ALeffect *effect)
|
||||
{
|
||||
const ALuint id{effect->id - 1};
|
||||
const size_t lidx{id >> 6};
|
||||
const ALuint slidx{id & 0x3f};
|
||||
|
||||
al::destroy_at(effect);
|
||||
|
||||
device->EffectList[lidx].FreeMask |= 1_u64 << slidx;
|
||||
}
|
||||
|
||||
inline ALeffect *LookupEffect(ALCdevice *device, ALuint id)
|
||||
{
|
||||
const size_t lidx{(id-1) >> 6};
|
||||
const ALuint slidx{(id-1) & 0x3f};
|
||||
|
||||
if UNLIKELY(lidx >= device->EffectList.size())
|
||||
return nullptr;
|
||||
EffectSubList &sublist = device->EffectList[lidx];
|
||||
if UNLIKELY(sublist.FreeMask & (1_u64 << slidx))
|
||||
return nullptr;
|
||||
return sublist.Effects + slidx;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alGenEffects(ALsizei n, ALuint *effects)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
if UNLIKELY(n < 0)
|
||||
context->setError(AL_INVALID_VALUE, "Generating %d effects", n);
|
||||
if UNLIKELY(n <= 0) return;
|
||||
|
||||
ALCdevice *device{context->mDevice.get()};
|
||||
std::lock_guard<std::mutex> _{device->EffectLock};
|
||||
if(!EnsureEffects(device, static_cast<ALuint>(n)))
|
||||
{
|
||||
context->setError(AL_OUT_OF_MEMORY, "Failed to allocate %d effect%s", n, (n==1)?"":"s");
|
||||
return;
|
||||
}
|
||||
|
||||
if LIKELY(n == 1)
|
||||
{
|
||||
/* Special handling for the easy and normal case. */
|
||||
ALeffect *effect{AllocEffect(device)};
|
||||
effects[0] = effect->id;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Store the allocated buffer IDs in a separate local list, to avoid
|
||||
* modifying the user storage in case of failure.
|
||||
*/
|
||||
al::vector<ALuint> ids;
|
||||
ids.reserve(static_cast<ALuint>(n));
|
||||
do {
|
||||
ALeffect *effect{AllocEffect(device)};
|
||||
ids.emplace_back(effect->id);
|
||||
} while(--n);
|
||||
std::copy(ids.cbegin(), ids.cend(), effects);
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alDeleteEffects(ALsizei n, const ALuint *effects)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
if UNLIKELY(n < 0)
|
||||
context->setError(AL_INVALID_VALUE, "Deleting %d effects", n);
|
||||
if UNLIKELY(n <= 0) return;
|
||||
|
||||
ALCdevice *device{context->mDevice.get()};
|
||||
std::lock_guard<std::mutex> _{device->EffectLock};
|
||||
|
||||
/* First try to find any effects that are invalid. */
|
||||
auto validate_effect = [device](const ALuint eid) -> bool
|
||||
{ return !eid || LookupEffect(device, eid) != nullptr; };
|
||||
|
||||
const ALuint *effects_end = effects + n;
|
||||
auto inveffect = std::find_if_not(effects, effects_end, validate_effect);
|
||||
if UNLIKELY(inveffect != effects_end)
|
||||
{
|
||||
context->setError(AL_INVALID_NAME, "Invalid effect ID %u", *inveffect);
|
||||
return;
|
||||
}
|
||||
|
||||
/* All good. Delete non-0 effect IDs. */
|
||||
auto delete_effect = [device](ALuint eid) -> void
|
||||
{
|
||||
ALeffect *effect{eid ? LookupEffect(device, eid) : nullptr};
|
||||
if(effect) FreeEffect(device, effect);
|
||||
};
|
||||
std::for_each(effects, effects_end, delete_effect);
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API ALboolean AL_APIENTRY alIsEffect(ALuint effect)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if LIKELY(context)
|
||||
{
|
||||
ALCdevice *device{context->mDevice.get()};
|
||||
std::lock_guard<std::mutex> _{device->EffectLock};
|
||||
if(!effect || LookupEffect(device, effect))
|
||||
return AL_TRUE;
|
||||
}
|
||||
return AL_FALSE;
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alEffecti(ALuint effect, ALenum param, ALint value)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
ALCdevice *device{context->mDevice.get()};
|
||||
std::lock_guard<std::mutex> _{device->EffectLock};
|
||||
|
||||
ALeffect *aleffect{LookupEffect(device, effect)};
|
||||
if UNLIKELY(!aleffect)
|
||||
context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
|
||||
else
|
||||
{
|
||||
if(param == AL_EFFECT_TYPE)
|
||||
{
|
||||
ALboolean isOk{value == AL_EFFECT_NULL};
|
||||
if(!isOk)
|
||||
{
|
||||
for(const EffectList &effectitem : gEffectList)
|
||||
{
|
||||
if(value == effectitem.val && !DisabledEffects[effectitem.type])
|
||||
{
|
||||
isOk = AL_TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(isOk)
|
||||
InitEffectParams(aleffect, value);
|
||||
else
|
||||
context->setError(AL_INVALID_VALUE, "Effect type 0x%04x not supported", value);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Call the appropriate handler */
|
||||
ALeffect_setParami(aleffect, context.get(), param, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alEffectiv(ALuint effect, ALenum param, const ALint *values)
|
||||
START_API_FUNC
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_EFFECT_TYPE:
|
||||
alEffecti(effect, param, values[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
ALCdevice *device{context->mDevice.get()};
|
||||
std::lock_guard<std::mutex> _{device->EffectLock};
|
||||
|
||||
ALeffect *aleffect{LookupEffect(device, effect)};
|
||||
if UNLIKELY(!aleffect)
|
||||
context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
|
||||
else
|
||||
{
|
||||
/* Call the appropriate handler */
|
||||
ALeffect_setParamiv(aleffect, context.get(), param, values);
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alEffectf(ALuint effect, ALenum param, ALfloat value)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
ALCdevice *device{context->mDevice.get()};
|
||||
std::lock_guard<std::mutex> _{device->EffectLock};
|
||||
|
||||
ALeffect *aleffect{LookupEffect(device, effect)};
|
||||
if UNLIKELY(!aleffect)
|
||||
context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
|
||||
else
|
||||
{
|
||||
/* Call the appropriate handler */
|
||||
ALeffect_setParamf(aleffect, context.get(), param, value);
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alEffectfv(ALuint effect, ALenum param, const ALfloat *values)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
ALCdevice *device{context->mDevice.get()};
|
||||
std::lock_guard<std::mutex> _{device->EffectLock};
|
||||
|
||||
ALeffect *aleffect{LookupEffect(device, effect)};
|
||||
if UNLIKELY(!aleffect)
|
||||
context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
|
||||
else
|
||||
{
|
||||
/* Call the appropriate handler */
|
||||
ALeffect_setParamfv(aleffect, context.get(), param, values);
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alGetEffecti(ALuint effect, ALenum param, ALint *value)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
ALCdevice *device{context->mDevice.get()};
|
||||
std::lock_guard<std::mutex> _{device->EffectLock};
|
||||
|
||||
const ALeffect *aleffect{LookupEffect(device, effect)};
|
||||
if UNLIKELY(!aleffect)
|
||||
context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
|
||||
else
|
||||
{
|
||||
if(param == AL_EFFECT_TYPE)
|
||||
*value = aleffect->type;
|
||||
else
|
||||
{
|
||||
/* Call the appropriate handler */
|
||||
ALeffect_getParami(aleffect, context.get(), param, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alGetEffectiv(ALuint effect, ALenum param, ALint *values)
|
||||
START_API_FUNC
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_EFFECT_TYPE:
|
||||
alGetEffecti(effect, param, values);
|
||||
return;
|
||||
}
|
||||
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
ALCdevice *device{context->mDevice.get()};
|
||||
std::lock_guard<std::mutex> _{device->EffectLock};
|
||||
|
||||
const ALeffect *aleffect{LookupEffect(device, effect)};
|
||||
if UNLIKELY(!aleffect)
|
||||
context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
|
||||
else
|
||||
{
|
||||
/* Call the appropriate handler */
|
||||
ALeffect_getParamiv(aleffect, context.get(), param, values);
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alGetEffectf(ALuint effect, ALenum param, ALfloat *value)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
ALCdevice *device{context->mDevice.get()};
|
||||
std::lock_guard<std::mutex> _{device->EffectLock};
|
||||
|
||||
const ALeffect *aleffect{LookupEffect(device, effect)};
|
||||
if UNLIKELY(!aleffect)
|
||||
context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
|
||||
else
|
||||
{
|
||||
/* Call the appropriate handler */
|
||||
ALeffect_getParamf(aleffect, context.get(), param, value);
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alGetEffectfv(ALuint effect, ALenum param, ALfloat *values)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
ALCdevice *device{context->mDevice.get()};
|
||||
std::lock_guard<std::mutex> _{device->EffectLock};
|
||||
|
||||
const ALeffect *aleffect{LookupEffect(device, effect)};
|
||||
if UNLIKELY(!aleffect)
|
||||
context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
|
||||
else
|
||||
{
|
||||
/* Call the appropriate handler */
|
||||
ALeffect_getParamfv(aleffect, context.get(), param, values);
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
|
||||
void InitEffect(ALeffect *effect)
|
||||
{
|
||||
InitEffectParams(effect, AL_EFFECT_NULL);
|
||||
}
|
||||
|
||||
EffectSubList::~EffectSubList()
|
||||
{
|
||||
uint64_t usemask{~FreeMask};
|
||||
while(usemask)
|
||||
{
|
||||
ALsizei idx = CTZ64(usemask);
|
||||
al::destroy_at(Effects+idx);
|
||||
usemask &= ~(1_u64 << idx);
|
||||
}
|
||||
FreeMask = ~usemask;
|
||||
al_free(Effects);
|
||||
Effects = nullptr;
|
||||
}
|
||||
|
||||
|
||||
EffectStateFactory *getFactoryByType(ALenum type)
|
||||
{
|
||||
auto iter = std::find_if(std::begin(FactoryList), std::end(FactoryList),
|
||||
[type](const FactoryItem &item) noexcept -> bool
|
||||
{ return item.Type == type; }
|
||||
);
|
||||
return (iter != std::end(FactoryList)) ? iter->GetFactory() : nullptr;
|
||||
}
|
||||
|
||||
|
||||
#define DECL(x) { #x, EFX_REVERB_PRESET_##x }
|
||||
static const struct {
|
||||
const char name[32];
|
||||
EFXEAXREVERBPROPERTIES props;
|
||||
} reverblist[] = {
|
||||
DECL(GENERIC),
|
||||
DECL(PADDEDCELL),
|
||||
DECL(ROOM),
|
||||
DECL(BATHROOM),
|
||||
DECL(LIVINGROOM),
|
||||
DECL(STONEROOM),
|
||||
DECL(AUDITORIUM),
|
||||
DECL(CONCERTHALL),
|
||||
DECL(CAVE),
|
||||
DECL(ARENA),
|
||||
DECL(HANGAR),
|
||||
DECL(CARPETEDHALLWAY),
|
||||
DECL(HALLWAY),
|
||||
DECL(STONECORRIDOR),
|
||||
DECL(ALLEY),
|
||||
DECL(FOREST),
|
||||
DECL(CITY),
|
||||
DECL(MOUNTAINS),
|
||||
DECL(QUARRY),
|
||||
DECL(PLAIN),
|
||||
DECL(PARKINGLOT),
|
||||
DECL(SEWERPIPE),
|
||||
DECL(UNDERWATER),
|
||||
DECL(DRUGGED),
|
||||
DECL(DIZZY),
|
||||
DECL(PSYCHOTIC),
|
||||
|
||||
DECL(CASTLE_SMALLROOM),
|
||||
DECL(CASTLE_SHORTPASSAGE),
|
||||
DECL(CASTLE_MEDIUMROOM),
|
||||
DECL(CASTLE_LARGEROOM),
|
||||
DECL(CASTLE_LONGPASSAGE),
|
||||
DECL(CASTLE_HALL),
|
||||
DECL(CASTLE_CUPBOARD),
|
||||
DECL(CASTLE_COURTYARD),
|
||||
DECL(CASTLE_ALCOVE),
|
||||
|
||||
DECL(FACTORY_SMALLROOM),
|
||||
DECL(FACTORY_SHORTPASSAGE),
|
||||
DECL(FACTORY_MEDIUMROOM),
|
||||
DECL(FACTORY_LARGEROOM),
|
||||
DECL(FACTORY_LONGPASSAGE),
|
||||
DECL(FACTORY_HALL),
|
||||
DECL(FACTORY_CUPBOARD),
|
||||
DECL(FACTORY_COURTYARD),
|
||||
DECL(FACTORY_ALCOVE),
|
||||
|
||||
DECL(ICEPALACE_SMALLROOM),
|
||||
DECL(ICEPALACE_SHORTPASSAGE),
|
||||
DECL(ICEPALACE_MEDIUMROOM),
|
||||
DECL(ICEPALACE_LARGEROOM),
|
||||
DECL(ICEPALACE_LONGPASSAGE),
|
||||
DECL(ICEPALACE_HALL),
|
||||
DECL(ICEPALACE_CUPBOARD),
|
||||
DECL(ICEPALACE_COURTYARD),
|
||||
DECL(ICEPALACE_ALCOVE),
|
||||
|
||||
DECL(SPACESTATION_SMALLROOM),
|
||||
DECL(SPACESTATION_SHORTPASSAGE),
|
||||
DECL(SPACESTATION_MEDIUMROOM),
|
||||
DECL(SPACESTATION_LARGEROOM),
|
||||
DECL(SPACESTATION_LONGPASSAGE),
|
||||
DECL(SPACESTATION_HALL),
|
||||
DECL(SPACESTATION_CUPBOARD),
|
||||
DECL(SPACESTATION_ALCOVE),
|
||||
|
||||
DECL(WOODEN_SMALLROOM),
|
||||
DECL(WOODEN_SHORTPASSAGE),
|
||||
DECL(WOODEN_MEDIUMROOM),
|
||||
DECL(WOODEN_LARGEROOM),
|
||||
DECL(WOODEN_LONGPASSAGE),
|
||||
DECL(WOODEN_HALL),
|
||||
DECL(WOODEN_CUPBOARD),
|
||||
DECL(WOODEN_COURTYARD),
|
||||
DECL(WOODEN_ALCOVE),
|
||||
|
||||
DECL(SPORT_EMPTYSTADIUM),
|
||||
DECL(SPORT_SQUASHCOURT),
|
||||
DECL(SPORT_SMALLSWIMMINGPOOL),
|
||||
DECL(SPORT_LARGESWIMMINGPOOL),
|
||||
DECL(SPORT_GYMNASIUM),
|
||||
DECL(SPORT_FULLSTADIUM),
|
||||
DECL(SPORT_STADIUMTANNOY),
|
||||
|
||||
DECL(PREFAB_WORKSHOP),
|
||||
DECL(PREFAB_SCHOOLROOM),
|
||||
DECL(PREFAB_PRACTISEROOM),
|
||||
DECL(PREFAB_OUTHOUSE),
|
||||
DECL(PREFAB_CARAVAN),
|
||||
|
||||
DECL(DOME_TOMB),
|
||||
DECL(PIPE_SMALL),
|
||||
DECL(DOME_SAINTPAULS),
|
||||
DECL(PIPE_LONGTHIN),
|
||||
DECL(PIPE_LARGE),
|
||||
DECL(PIPE_RESONANT),
|
||||
|
||||
DECL(OUTDOORS_BACKYARD),
|
||||
DECL(OUTDOORS_ROLLINGPLAINS),
|
||||
DECL(OUTDOORS_DEEPCANYON),
|
||||
DECL(OUTDOORS_CREEK),
|
||||
DECL(OUTDOORS_VALLEY),
|
||||
|
||||
DECL(MOOD_HEAVEN),
|
||||
DECL(MOOD_HELL),
|
||||
DECL(MOOD_MEMORY),
|
||||
|
||||
DECL(DRIVING_COMMENTATOR),
|
||||
DECL(DRIVING_PITGARAGE),
|
||||
DECL(DRIVING_INCAR_RACER),
|
||||
DECL(DRIVING_INCAR_SPORTS),
|
||||
DECL(DRIVING_INCAR_LUXURY),
|
||||
DECL(DRIVING_FULLGRANDSTAND),
|
||||
DECL(DRIVING_EMPTYGRANDSTAND),
|
||||
DECL(DRIVING_TUNNEL),
|
||||
|
||||
DECL(CITY_STREETS),
|
||||
DECL(CITY_SUBWAY),
|
||||
DECL(CITY_MUSEUM),
|
||||
DECL(CITY_LIBRARY),
|
||||
DECL(CITY_UNDERPASS),
|
||||
DECL(CITY_ABANDONED),
|
||||
|
||||
DECL(DUSTYROOM),
|
||||
DECL(CHAPEL),
|
||||
DECL(SMALLWATERROOM),
|
||||
};
|
||||
#undef DECL
|
||||
|
||||
void LoadReverbPreset(const char *name, ALeffect *effect)
|
||||
{
|
||||
if(al::strcasecmp(name, "NONE") == 0)
|
||||
{
|
||||
InitEffectParams(effect, AL_EFFECT_NULL);
|
||||
TRACE("Loading reverb '%s'\n", "NONE");
|
||||
return;
|
||||
}
|
||||
|
||||
if(!DisabledEffects[EAXREVERB_EFFECT])
|
||||
InitEffectParams(effect, AL_EFFECT_EAXREVERB);
|
||||
else if(!DisabledEffects[REVERB_EFFECT])
|
||||
InitEffectParams(effect, AL_EFFECT_REVERB);
|
||||
else
|
||||
InitEffectParams(effect, AL_EFFECT_NULL);
|
||||
for(const auto &reverbitem : reverblist)
|
||||
{
|
||||
const EFXEAXREVERBPROPERTIES *props;
|
||||
|
||||
if(al::strcasecmp(name, reverbitem.name) != 0)
|
||||
continue;
|
||||
|
||||
TRACE("Loading reverb '%s'\n", reverbitem.name);
|
||||
props = &reverbitem.props;
|
||||
effect->Props.Reverb.Density = props->flDensity;
|
||||
effect->Props.Reverb.Diffusion = props->flDiffusion;
|
||||
effect->Props.Reverb.Gain = props->flGain;
|
||||
effect->Props.Reverb.GainHF = props->flGainHF;
|
||||
effect->Props.Reverb.GainLF = props->flGainLF;
|
||||
effect->Props.Reverb.DecayTime = props->flDecayTime;
|
||||
effect->Props.Reverb.DecayHFRatio = props->flDecayHFRatio;
|
||||
effect->Props.Reverb.DecayLFRatio = props->flDecayLFRatio;
|
||||
effect->Props.Reverb.ReflectionsGain = props->flReflectionsGain;
|
||||
effect->Props.Reverb.ReflectionsDelay = props->flReflectionsDelay;
|
||||
effect->Props.Reverb.ReflectionsPan[0] = props->flReflectionsPan[0];
|
||||
effect->Props.Reverb.ReflectionsPan[1] = props->flReflectionsPan[1];
|
||||
effect->Props.Reverb.ReflectionsPan[2] = props->flReflectionsPan[2];
|
||||
effect->Props.Reverb.LateReverbGain = props->flLateReverbGain;
|
||||
effect->Props.Reverb.LateReverbDelay = props->flLateReverbDelay;
|
||||
effect->Props.Reverb.LateReverbPan[0] = props->flLateReverbPan[0];
|
||||
effect->Props.Reverb.LateReverbPan[1] = props->flLateReverbPan[1];
|
||||
effect->Props.Reverb.LateReverbPan[2] = props->flLateReverbPan[2];
|
||||
effect->Props.Reverb.EchoTime = props->flEchoTime;
|
||||
effect->Props.Reverb.EchoDepth = props->flEchoDepth;
|
||||
effect->Props.Reverb.ModulationTime = props->flModulationTime;
|
||||
effect->Props.Reverb.ModulationDepth = props->flModulationDepth;
|
||||
effect->Props.Reverb.AirAbsorptionGainHF = props->flAirAbsorptionGainHF;
|
||||
effect->Props.Reverb.HFReference = props->flHFReference;
|
||||
effect->Props.Reverb.LFReference = props->flLFReference;
|
||||
effect->Props.Reverb.RoomRolloffFactor = props->flRoomRolloffFactor;
|
||||
effect->Props.Reverb.DecayHFLimit = props->iDecayHFLimit ? AL_TRUE : AL_FALSE;
|
||||
return;
|
||||
}
|
||||
|
||||
WARN("Reverb preset '%s' not found\n", name);
|
||||
}
|
||||
-61
@@ -1,61 +0,0 @@
|
||||
#ifndef AL_EFFECT_H
|
||||
#define AL_EFFECT_H
|
||||
|
||||
#include "AL/al.h"
|
||||
#include "AL/efx.h"
|
||||
|
||||
#include "effects/base.h"
|
||||
|
||||
|
||||
enum {
|
||||
EAXREVERB_EFFECT = 0,
|
||||
REVERB_EFFECT,
|
||||
AUTOWAH_EFFECT,
|
||||
CHORUS_EFFECT,
|
||||
COMPRESSOR_EFFECT,
|
||||
DISTORTION_EFFECT,
|
||||
ECHO_EFFECT,
|
||||
EQUALIZER_EFFECT,
|
||||
FLANGER_EFFECT,
|
||||
FSHIFTER_EFFECT,
|
||||
MODULATOR_EFFECT,
|
||||
PSHIFTER_EFFECT,
|
||||
VMORPHER_EFFECT,
|
||||
DEDICATED_EFFECT,
|
||||
|
||||
MAX_EFFECTS
|
||||
};
|
||||
extern ALboolean DisabledEffects[MAX_EFFECTS];
|
||||
|
||||
extern ALfloat ReverbBoost;
|
||||
|
||||
struct EffectList {
|
||||
const char name[16];
|
||||
int type;
|
||||
ALenum val;
|
||||
};
|
||||
extern const EffectList gEffectList[15];
|
||||
|
||||
|
||||
struct ALeffect {
|
||||
// Effect type (AL_EFFECT_NULL, ...)
|
||||
ALenum type{AL_EFFECT_NULL};
|
||||
|
||||
EffectProps Props{};
|
||||
|
||||
const EffectVtable *vtab{nullptr};
|
||||
|
||||
/* Self ID */
|
||||
ALuint id{0u};
|
||||
};
|
||||
|
||||
inline ALboolean IsReverbEffect(ALenum type)
|
||||
{ return type == AL_EFFECT_REVERB || type == AL_EFFECT_EAXREVERB; }
|
||||
|
||||
EffectStateFactory *getFactoryByType(ALenum type);
|
||||
|
||||
void InitEffect(ALeffect *effect);
|
||||
|
||||
void LoadReverbPreset(const char *name, ALeffect *effect);
|
||||
|
||||
#endif
|
||||
-117
@@ -1,117 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 1999-2000 by authors.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#include <atomic>
|
||||
#include <csignal>
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
|
||||
#include "AL/al.h"
|
||||
#include "AL/alc.h"
|
||||
|
||||
#include "alcontext.h"
|
||||
#include "alexcpt.h"
|
||||
#include "almalloc.h"
|
||||
#include "event.h"
|
||||
#include "inprogext.h"
|
||||
#include "logging.h"
|
||||
#include "opthelpers.h"
|
||||
#include "vector.h"
|
||||
|
||||
|
||||
bool TrapALError{false};
|
||||
|
||||
void ALCcontext::setError(ALenum errorCode, const char *msg, ...)
|
||||
{
|
||||
auto message = al::vector<char>(256);
|
||||
|
||||
va_list args, args2;
|
||||
va_start(args, msg);
|
||||
va_copy(args2, args);
|
||||
int msglen{std::vsnprintf(message.data(), message.size(), msg, args)};
|
||||
if(msglen >= 0 && static_cast<size_t>(msglen) >= message.size())
|
||||
{
|
||||
message.resize(static_cast<size_t>(msglen) + 1u);
|
||||
msglen = std::vsnprintf(message.data(), message.size(), msg, args2);
|
||||
}
|
||||
va_end(args2);
|
||||
va_end(args);
|
||||
|
||||
if(msglen >= 0) msg = message.data();
|
||||
else msg = "<internal error constructing message>";
|
||||
msglen = static_cast<int>(strlen(msg));
|
||||
|
||||
WARN("Error generated on context %p, code 0x%04x, \"%s\"\n",
|
||||
decltype(std::declval<void*>()){this}, errorCode, msg);
|
||||
if(TrapALError)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
/* DebugBreak will cause an exception if there is no debugger */
|
||||
if(IsDebuggerPresent())
|
||||
DebugBreak();
|
||||
#elif defined(SIGTRAP)
|
||||
raise(SIGTRAP);
|
||||
#endif
|
||||
}
|
||||
|
||||
ALenum curerr{AL_NO_ERROR};
|
||||
mLastError.compare_exchange_strong(curerr, errorCode);
|
||||
if((mEnabledEvts.load(std::memory_order_relaxed)&EventType_Error))
|
||||
{
|
||||
std::lock_guard<std::mutex> _{mEventCbLock};
|
||||
ALbitfieldSOFT enabledevts{mEnabledEvts.load(std::memory_order_relaxed)};
|
||||
if((enabledevts&EventType_Error) && mEventCb)
|
||||
(*mEventCb)(AL_EVENT_TYPE_ERROR_SOFT, 0, static_cast<ALuint>(errorCode), msglen, msg,
|
||||
mEventParam);
|
||||
}
|
||||
}
|
||||
|
||||
AL_API ALenum AL_APIENTRY alGetError(void)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context)
|
||||
{
|
||||
constexpr ALenum deferror{AL_INVALID_OPERATION};
|
||||
WARN("Querying error state on null context (implicitly 0x%04x)\n", deferror);
|
||||
if(TrapALError)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if(IsDebuggerPresent())
|
||||
DebugBreak();
|
||||
#elif defined(SIGTRAP)
|
||||
raise(SIGTRAP);
|
||||
#endif
|
||||
}
|
||||
return deferror;
|
||||
}
|
||||
|
||||
return context->mLastError.exchange(AL_NO_ERROR);
|
||||
}
|
||||
END_API_FUNC
|
||||
-205
@@ -1,205 +0,0 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "event.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <new>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
#include "AL/al.h"
|
||||
#include "AL/alc.h"
|
||||
|
||||
#include "albyte.h"
|
||||
#include "alcontext.h"
|
||||
#include "alexcpt.h"
|
||||
#include "almalloc.h"
|
||||
#include "effects/base.h"
|
||||
#include "inprogext.h"
|
||||
#include "logging.h"
|
||||
#include "opthelpers.h"
|
||||
#include "ringbuffer.h"
|
||||
#include "threads.h"
|
||||
|
||||
|
||||
static int EventThread(ALCcontext *context)
|
||||
{
|
||||
RingBuffer *ring{context->mAsyncEvents.get()};
|
||||
bool quitnow{false};
|
||||
while LIKELY(!quitnow)
|
||||
{
|
||||
auto evt_data = ring->getReadVector().first;
|
||||
if(evt_data.len == 0)
|
||||
{
|
||||
context->mEventSem.wait();
|
||||
continue;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> _{context->mEventCbLock};
|
||||
do {
|
||||
auto *evt_ptr = reinterpret_cast<AsyncEvent*>(evt_data.buf);
|
||||
evt_data.buf += sizeof(AsyncEvent);
|
||||
evt_data.len -= 1;
|
||||
|
||||
AsyncEvent evt{*evt_ptr};
|
||||
al::destroy_at(evt_ptr);
|
||||
ring->readAdvance(1);
|
||||
|
||||
quitnow = evt.EnumType == EventType_KillThread;
|
||||
if UNLIKELY(quitnow) break;
|
||||
|
||||
if(evt.EnumType == EventType_ReleaseEffectState)
|
||||
{
|
||||
evt.u.mEffectState->release();
|
||||
continue;
|
||||
}
|
||||
|
||||
ALbitfieldSOFT enabledevts{context->mEnabledEvts.load(std::memory_order_acquire)};
|
||||
if(!context->mEventCb) continue;
|
||||
|
||||
if(evt.EnumType == EventType_SourceStateChange)
|
||||
{
|
||||
if(!(enabledevts&EventType_SourceStateChange))
|
||||
continue;
|
||||
std::string msg{"Source ID " + std::to_string(evt.u.srcstate.id)};
|
||||
msg += " state has changed to ";
|
||||
msg += (evt.u.srcstate.state==AL_INITIAL) ? "AL_INITIAL" :
|
||||
(evt.u.srcstate.state==AL_PLAYING) ? "AL_PLAYING" :
|
||||
(evt.u.srcstate.state==AL_PAUSED) ? "AL_PAUSED" :
|
||||
(evt.u.srcstate.state==AL_STOPPED) ? "AL_STOPPED" : "<unknown>";
|
||||
context->mEventCb(AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT, evt.u.srcstate.id,
|
||||
static_cast<ALuint>(evt.u.srcstate.state), static_cast<ALsizei>(msg.length()),
|
||||
msg.c_str(), context->mEventParam);
|
||||
}
|
||||
else if(evt.EnumType == EventType_BufferCompleted)
|
||||
{
|
||||
if(!(enabledevts&EventType_BufferCompleted))
|
||||
continue;
|
||||
std::string msg{std::to_string(evt.u.bufcomp.count)};
|
||||
if(evt.u.bufcomp.count == 1) msg += " buffer completed";
|
||||
else msg += " buffers completed";
|
||||
context->mEventCb(AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT, evt.u.bufcomp.id,
|
||||
evt.u.bufcomp.count, static_cast<ALsizei>(msg.length()), msg.c_str(),
|
||||
context->mEventParam);
|
||||
}
|
||||
else if((enabledevts&evt.EnumType) == evt.EnumType)
|
||||
context->mEventCb(evt.u.user.type, evt.u.user.id, evt.u.user.param,
|
||||
static_cast<ALsizei>(strlen(evt.u.user.msg)), evt.u.user.msg,
|
||||
context->mEventParam);
|
||||
} while(evt_data.len != 0);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void StartEventThrd(ALCcontext *ctx)
|
||||
{
|
||||
try {
|
||||
ctx->mEventThread = std::thread{EventThread, ctx};
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
ERR("Failed to start event thread: %s\n", e.what());
|
||||
}
|
||||
catch(...) {
|
||||
ERR("Failed to start event thread! Expect problems.\n");
|
||||
}
|
||||
}
|
||||
|
||||
void StopEventThrd(ALCcontext *ctx)
|
||||
{
|
||||
RingBuffer *ring{ctx->mAsyncEvents.get()};
|
||||
auto evt_data = ring->getWriteVector().first;
|
||||
if(evt_data.len == 0)
|
||||
{
|
||||
do {
|
||||
std::this_thread::yield();
|
||||
evt_data = ring->getWriteVector().first;
|
||||
} while(evt_data.len == 0);
|
||||
}
|
||||
new (evt_data.buf) AsyncEvent{EventType_KillThread};
|
||||
ring->writeAdvance(1);
|
||||
|
||||
ctx->mEventSem.post();
|
||||
if(ctx->mEventThread.joinable())
|
||||
ctx->mEventThread.join();
|
||||
}
|
||||
|
||||
AL_API void AL_APIENTRY alEventControlSOFT(ALsizei count, const ALenum *types, ALboolean enable)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
if(count < 0) context->setError(AL_INVALID_VALUE, "Controlling %d events", count);
|
||||
if(count <= 0) return;
|
||||
if(!types) SETERR_RETURN(context, AL_INVALID_VALUE,, "NULL pointer");
|
||||
|
||||
ALbitfieldSOFT flags{0};
|
||||
const ALenum *types_end = types+count;
|
||||
auto bad_type = std::find_if_not(types, types_end,
|
||||
[&flags](ALenum type) noexcept -> bool
|
||||
{
|
||||
if(type == AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT)
|
||||
flags |= EventType_BufferCompleted;
|
||||
else if(type == AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT)
|
||||
flags |= EventType_SourceStateChange;
|
||||
else if(type == AL_EVENT_TYPE_ERROR_SOFT)
|
||||
flags |= EventType_Error;
|
||||
else if(type == AL_EVENT_TYPE_PERFORMANCE_SOFT)
|
||||
flags |= EventType_Performance;
|
||||
else if(type == AL_EVENT_TYPE_DEPRECATED_SOFT)
|
||||
flags |= EventType_Deprecated;
|
||||
else if(type == AL_EVENT_TYPE_DISCONNECTED_SOFT)
|
||||
flags |= EventType_Disconnected;
|
||||
else
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
);
|
||||
if(bad_type != types_end)
|
||||
SETERR_RETURN(context, AL_INVALID_ENUM,, "Invalid event type 0x%04x", *bad_type);
|
||||
|
||||
if(enable)
|
||||
{
|
||||
ALbitfieldSOFT enabledevts{context->mEnabledEvts.load(std::memory_order_relaxed)};
|
||||
while(context->mEnabledEvts.compare_exchange_weak(enabledevts, enabledevts|flags,
|
||||
std::memory_order_acq_rel, std::memory_order_acquire) == 0)
|
||||
{
|
||||
/* enabledevts is (re-)filled with the current value on failure, so
|
||||
* just try again.
|
||||
*/
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ALbitfieldSOFT enabledevts{context->mEnabledEvts.load(std::memory_order_relaxed)};
|
||||
while(context->mEnabledEvts.compare_exchange_weak(enabledevts, enabledevts&~flags,
|
||||
std::memory_order_acq_rel, std::memory_order_acquire) == 0)
|
||||
{
|
||||
}
|
||||
/* Wait to ensure the event handler sees the changed flags before
|
||||
* returning.
|
||||
*/
|
||||
std::lock_guard<std::mutex>{context->mEventCbLock};
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API void AL_APIENTRY alEventCallbackSOFT(ALEVENTPROCSOFT callback, void *userParam)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
std::lock_guard<std::mutex> _{context->mPropLock};
|
||||
std::lock_guard<std::mutex> __{context->mEventCbLock};
|
||||
context->mEventCb = callback;
|
||||
context->mEventParam = userParam;
|
||||
}
|
||||
END_API_FUNC
|
||||
-55
@@ -1,55 +0,0 @@
|
||||
#ifndef AL_EVENT_H
|
||||
#define AL_EVENT_H
|
||||
|
||||
#include "AL/al.h"
|
||||
#include "AL/alc.h"
|
||||
|
||||
struct EffectState;
|
||||
|
||||
|
||||
enum {
|
||||
/* End event thread processing. */
|
||||
EventType_KillThread = 0,
|
||||
|
||||
/* User event types. */
|
||||
EventType_SourceStateChange = 1<<0,
|
||||
EventType_BufferCompleted = 1<<1,
|
||||
EventType_Error = 1<<2,
|
||||
EventType_Performance = 1<<3,
|
||||
EventType_Deprecated = 1<<4,
|
||||
EventType_Disconnected = 1<<5,
|
||||
|
||||
/* Internal events. */
|
||||
EventType_ReleaseEffectState = 65536,
|
||||
};
|
||||
|
||||
struct AsyncEvent {
|
||||
unsigned int EnumType{0u};
|
||||
union {
|
||||
char dummy;
|
||||
struct {
|
||||
ALuint id;
|
||||
ALenum state;
|
||||
} srcstate;
|
||||
struct {
|
||||
ALuint id;
|
||||
ALuint count;
|
||||
} bufcomp;
|
||||
struct {
|
||||
ALenum type;
|
||||
ALuint id;
|
||||
ALuint param;
|
||||
ALchar msg[232];
|
||||
} user;
|
||||
EffectState *mEffectState;
|
||||
} u{};
|
||||
|
||||
AsyncEvent() noexcept = default;
|
||||
constexpr AsyncEvent(unsigned int type) noexcept : EnumType{type} { }
|
||||
};
|
||||
|
||||
|
||||
void StartEventThrd(ALCcontext *ctx);
|
||||
void StopEventThrd(ALCcontext *ctx);
|
||||
|
||||
#endif
|
||||
@@ -1,79 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 1999-2007 by authors.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <cctype>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#include "AL/al.h"
|
||||
#include "AL/alc.h"
|
||||
|
||||
#include "alcontext.h"
|
||||
#include "alexcpt.h"
|
||||
#include "alstring.h"
|
||||
#include "opthelpers.h"
|
||||
|
||||
|
||||
AL_API ALboolean AL_APIENTRY alIsExtensionPresent(const ALchar *extName)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return AL_FALSE;
|
||||
|
||||
if(!extName)
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE, AL_FALSE, "NULL pointer");
|
||||
|
||||
size_t len{strlen(extName)};
|
||||
const char *ptr{context->mExtensionList};
|
||||
while(ptr && *ptr)
|
||||
{
|
||||
if(al::strncasecmp(ptr, extName, len) == 0 && (ptr[len] == '\0' || isspace(ptr[len])))
|
||||
return AL_TRUE;
|
||||
|
||||
if((ptr=strchr(ptr, ' ')) != nullptr)
|
||||
{
|
||||
do {
|
||||
++ptr;
|
||||
} while(isspace(*ptr));
|
||||
}
|
||||
}
|
||||
|
||||
return AL_FALSE;
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
|
||||
AL_API ALvoid* AL_APIENTRY alGetProcAddress(const ALchar *funcName)
|
||||
START_API_FUNC
|
||||
{
|
||||
if(!funcName) return nullptr;
|
||||
return alcGetProcAddress(nullptr, funcName);
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API ALenum AL_APIENTRY alGetEnumValue(const ALchar *enumName)
|
||||
START_API_FUNC
|
||||
{
|
||||
if(!enumName) return static_cast<ALenum>(0);
|
||||
return alcGetEnumValue(nullptr, enumName);
|
||||
}
|
||||
END_API_FUNC
|
||||
-650
@@ -1,650 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 1999-2007 by authors.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "filter.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <new>
|
||||
#include <numeric>
|
||||
|
||||
#include "AL/al.h"
|
||||
#include "AL/alc.h"
|
||||
#include "AL/efx.h"
|
||||
|
||||
#include "alcmain.h"
|
||||
#include "alcontext.h"
|
||||
#include "alexcpt.h"
|
||||
#include "almalloc.h"
|
||||
#include "alnumeric.h"
|
||||
#include "opthelpers.h"
|
||||
#include "vector.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
#define FILTER_MIN_GAIN 0.0f
|
||||
#define FILTER_MAX_GAIN 4.0f /* +12dB */
|
||||
|
||||
void ALlowpass_setParami(ALfilter*, ALCcontext *context, ALenum param, ALint)
|
||||
{ context->setError(AL_INVALID_ENUM, "Invalid low-pass integer property 0x%04x", param); }
|
||||
void ALlowpass_setParamiv(ALfilter*, ALCcontext *context, ALenum param, const ALint*)
|
||||
{ context->setError(AL_INVALID_ENUM, "Invalid low-pass integer-vector property 0x%04x", param); }
|
||||
void ALlowpass_setParamf(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat val)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_LOWPASS_GAIN:
|
||||
if(!(val >= FILTER_MIN_GAIN && val <= FILTER_MAX_GAIN))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Low-pass gain %f out of range", val);
|
||||
filter->Gain = val;
|
||||
break;
|
||||
|
||||
case AL_LOWPASS_GAINHF:
|
||||
if(!(val >= AL_LOWPASS_MIN_GAINHF && val <= AL_LOWPASS_MAX_GAINHF))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Low-pass gainhf %f out of range", val);
|
||||
filter->GainHF = val;
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid low-pass float property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
void ALlowpass_setParamfv(ALfilter *filter, ALCcontext *context, ALenum param, const ALfloat *vals)
|
||||
{ ALlowpass_setParamf(filter, context, param, vals[0]); }
|
||||
|
||||
void ALlowpass_getParami(ALfilter*, ALCcontext *context, ALenum param, ALint*)
|
||||
{ context->setError(AL_INVALID_ENUM, "Invalid low-pass integer property 0x%04x", param); }
|
||||
void ALlowpass_getParamiv(ALfilter*, ALCcontext *context, ALenum param, ALint*)
|
||||
{ context->setError(AL_INVALID_ENUM, "Invalid low-pass integer-vector property 0x%04x", param); }
|
||||
void ALlowpass_getParamf(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat *val)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_LOWPASS_GAIN:
|
||||
*val = filter->Gain;
|
||||
break;
|
||||
|
||||
case AL_LOWPASS_GAINHF:
|
||||
*val = filter->GainHF;
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid low-pass float property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
void ALlowpass_getParamfv(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat *vals)
|
||||
{ ALlowpass_getParamf(filter, context, param, vals); }
|
||||
|
||||
DEFINE_ALFILTER_VTABLE(ALlowpass);
|
||||
|
||||
|
||||
void ALhighpass_setParami(ALfilter*, ALCcontext *context, ALenum param, ALint)
|
||||
{ context->setError(AL_INVALID_ENUM, "Invalid high-pass integer property 0x%04x", param); }
|
||||
void ALhighpass_setParamiv(ALfilter*, ALCcontext *context, ALenum param, const ALint*)
|
||||
{ context->setError(AL_INVALID_ENUM, "Invalid high-pass integer-vector property 0x%04x", param); }
|
||||
void ALhighpass_setParamf(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat val)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_HIGHPASS_GAIN:
|
||||
if(!(val >= FILTER_MIN_GAIN && val <= FILTER_MAX_GAIN))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "High-pass gain out of range");
|
||||
filter->Gain = val;
|
||||
break;
|
||||
|
||||
case AL_HIGHPASS_GAINLF:
|
||||
if(!(val >= AL_HIGHPASS_MIN_GAINLF && val <= AL_HIGHPASS_MAX_GAINLF))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "High-pass gainlf out of range");
|
||||
filter->GainLF = val;
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid high-pass float property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
void ALhighpass_setParamfv(ALfilter *filter, ALCcontext *context, ALenum param, const ALfloat *vals)
|
||||
{ ALhighpass_setParamf(filter, context, param, vals[0]); }
|
||||
|
||||
void ALhighpass_getParami(ALfilter*, ALCcontext *context, ALenum param, ALint*)
|
||||
{ context->setError(AL_INVALID_ENUM, "Invalid high-pass integer property 0x%04x", param); }
|
||||
void ALhighpass_getParamiv(ALfilter*, ALCcontext *context, ALenum param, ALint*)
|
||||
{ context->setError(AL_INVALID_ENUM, "Invalid high-pass integer-vector property 0x%04x", param); }
|
||||
void ALhighpass_getParamf(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat *val)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_HIGHPASS_GAIN:
|
||||
*val = filter->Gain;
|
||||
break;
|
||||
|
||||
case AL_HIGHPASS_GAINLF:
|
||||
*val = filter->GainLF;
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid high-pass float property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
void ALhighpass_getParamfv(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat *vals)
|
||||
{ ALhighpass_getParamf(filter, context, param, vals); }
|
||||
|
||||
DEFINE_ALFILTER_VTABLE(ALhighpass);
|
||||
|
||||
|
||||
void ALbandpass_setParami(ALfilter*, ALCcontext *context, ALenum param, ALint)
|
||||
{ context->setError(AL_INVALID_ENUM, "Invalid band-pass integer property 0x%04x", param); }
|
||||
void ALbandpass_setParamiv(ALfilter*, ALCcontext *context, ALenum param, const ALint*)
|
||||
{ context->setError(AL_INVALID_ENUM, "Invalid band-pass integer-vector property 0x%04x", param); }
|
||||
void ALbandpass_setParamf(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat val)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_BANDPASS_GAIN:
|
||||
if(!(val >= FILTER_MIN_GAIN && val <= FILTER_MAX_GAIN))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Band-pass gain out of range");
|
||||
filter->Gain = val;
|
||||
break;
|
||||
|
||||
case AL_BANDPASS_GAINHF:
|
||||
if(!(val >= AL_BANDPASS_MIN_GAINHF && val <= AL_BANDPASS_MAX_GAINHF))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Band-pass gainhf out of range");
|
||||
filter->GainHF = val;
|
||||
break;
|
||||
|
||||
case AL_BANDPASS_GAINLF:
|
||||
if(!(val >= AL_BANDPASS_MIN_GAINLF && val <= AL_BANDPASS_MAX_GAINLF))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Band-pass gainlf out of range");
|
||||
filter->GainLF = val;
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid band-pass float property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
void ALbandpass_setParamfv(ALfilter *filter, ALCcontext *context, ALenum param, const ALfloat *vals)
|
||||
{ ALbandpass_setParamf(filter, context, param, vals[0]); }
|
||||
|
||||
void ALbandpass_getParami(ALfilter*, ALCcontext *context, ALenum param, ALint*)
|
||||
{ context->setError(AL_INVALID_ENUM, "Invalid band-pass integer property 0x%04x", param); }
|
||||
void ALbandpass_getParamiv(ALfilter*, ALCcontext *context, ALenum param, ALint*)
|
||||
{ context->setError(AL_INVALID_ENUM, "Invalid band-pass integer-vector property 0x%04x", param); }
|
||||
void ALbandpass_getParamf(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat *val)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_BANDPASS_GAIN:
|
||||
*val = filter->Gain;
|
||||
break;
|
||||
|
||||
case AL_BANDPASS_GAINHF:
|
||||
*val = filter->GainHF;
|
||||
break;
|
||||
|
||||
case AL_BANDPASS_GAINLF:
|
||||
*val = filter->GainLF;
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid band-pass float property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
void ALbandpass_getParamfv(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat *vals)
|
||||
{ ALbandpass_getParamf(filter, context, param, vals); }
|
||||
|
||||
DEFINE_ALFILTER_VTABLE(ALbandpass);
|
||||
|
||||
|
||||
void ALnullfilter_setParami(ALfilter*, ALCcontext *context, ALenum param, ALint)
|
||||
{ context->setError(AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param); }
|
||||
void ALnullfilter_setParamiv(ALfilter*, ALCcontext *context, ALenum param, const ALint*)
|
||||
{ context->setError(AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param); }
|
||||
void ALnullfilter_setParamf(ALfilter*, ALCcontext *context, ALenum param, ALfloat)
|
||||
{ context->setError(AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param); }
|
||||
void ALnullfilter_setParamfv(ALfilter*, ALCcontext *context, ALenum param, const ALfloat*)
|
||||
{ context->setError(AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param); }
|
||||
|
||||
void ALnullfilter_getParami(ALfilter*, ALCcontext *context, ALenum param, ALint*)
|
||||
{ context->setError(AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param); }
|
||||
void ALnullfilter_getParamiv(ALfilter*, ALCcontext *context, ALenum param, ALint*)
|
||||
{ context->setError(AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param); }
|
||||
void ALnullfilter_getParamf(ALfilter*, ALCcontext *context, ALenum param, ALfloat*)
|
||||
{ context->setError(AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param); }
|
||||
void ALnullfilter_getParamfv(ALfilter*, ALCcontext *context, ALenum param, ALfloat*)
|
||||
{ context->setError(AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param); }
|
||||
|
||||
DEFINE_ALFILTER_VTABLE(ALnullfilter);
|
||||
|
||||
|
||||
void InitFilterParams(ALfilter *filter, ALenum type)
|
||||
{
|
||||
if(type == AL_FILTER_LOWPASS)
|
||||
{
|
||||
filter->Gain = AL_LOWPASS_DEFAULT_GAIN;
|
||||
filter->GainHF = AL_LOWPASS_DEFAULT_GAINHF;
|
||||
filter->HFReference = LOWPASSFREQREF;
|
||||
filter->GainLF = 1.0f;
|
||||
filter->LFReference = HIGHPASSFREQREF;
|
||||
filter->vtab = &ALlowpass_vtable;
|
||||
}
|
||||
else if(type == AL_FILTER_HIGHPASS)
|
||||
{
|
||||
filter->Gain = AL_HIGHPASS_DEFAULT_GAIN;
|
||||
filter->GainHF = 1.0f;
|
||||
filter->HFReference = LOWPASSFREQREF;
|
||||
filter->GainLF = AL_HIGHPASS_DEFAULT_GAINLF;
|
||||
filter->LFReference = HIGHPASSFREQREF;
|
||||
filter->vtab = &ALhighpass_vtable;
|
||||
}
|
||||
else if(type == AL_FILTER_BANDPASS)
|
||||
{
|
||||
filter->Gain = AL_BANDPASS_DEFAULT_GAIN;
|
||||
filter->GainHF = AL_BANDPASS_DEFAULT_GAINHF;
|
||||
filter->HFReference = LOWPASSFREQREF;
|
||||
filter->GainLF = AL_BANDPASS_DEFAULT_GAINLF;
|
||||
filter->LFReference = HIGHPASSFREQREF;
|
||||
filter->vtab = &ALbandpass_vtable;
|
||||
}
|
||||
else
|
||||
{
|
||||
filter->Gain = 1.0f;
|
||||
filter->GainHF = 1.0f;
|
||||
filter->HFReference = LOWPASSFREQREF;
|
||||
filter->GainLF = 1.0f;
|
||||
filter->LFReference = HIGHPASSFREQREF;
|
||||
filter->vtab = &ALnullfilter_vtable;
|
||||
}
|
||||
filter->type = type;
|
||||
}
|
||||
|
||||
bool EnsureFilters(ALCdevice *device, size_t needed)
|
||||
{
|
||||
size_t count{std::accumulate(device->FilterList.cbegin(), device->FilterList.cend(), size_t{0},
|
||||
[](size_t cur, const FilterSubList &sublist) noexcept -> size_t
|
||||
{ return cur + static_cast<ALuint>(POPCNT64(sublist.FreeMask)); }
|
||||
)};
|
||||
|
||||
while(needed > count)
|
||||
{
|
||||
if UNLIKELY(device->FilterList.size() >= 1<<25)
|
||||
return false;
|
||||
|
||||
device->FilterList.emplace_back();
|
||||
auto sublist = device->FilterList.end() - 1;
|
||||
sublist->FreeMask = ~0_u64;
|
||||
sublist->Filters = static_cast<ALfilter*>(al_calloc(alignof(ALfilter), sizeof(ALfilter)*64));
|
||||
if UNLIKELY(!sublist->Filters)
|
||||
{
|
||||
device->FilterList.pop_back();
|
||||
return false;
|
||||
}
|
||||
count += 64;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
ALfilter *AllocFilter(ALCdevice *device)
|
||||
{
|
||||
auto sublist = std::find_if(device->FilterList.begin(), device->FilterList.end(),
|
||||
[](const FilterSubList &entry) noexcept -> bool
|
||||
{ return entry.FreeMask != 0; }
|
||||
);
|
||||
auto lidx = static_cast<ALuint>(std::distance(device->FilterList.begin(), sublist));
|
||||
auto slidx = static_cast<ALuint>(CTZ64(sublist->FreeMask));
|
||||
|
||||
ALfilter *filter{::new (sublist->Filters + slidx) ALfilter{}};
|
||||
InitFilterParams(filter, AL_FILTER_NULL);
|
||||
|
||||
/* Add 1 to avoid filter ID 0. */
|
||||
filter->id = ((lidx<<6) | slidx) + 1;
|
||||
|
||||
sublist->FreeMask &= ~(1_u64 << slidx);
|
||||
|
||||
return filter;
|
||||
}
|
||||
|
||||
void FreeFilter(ALCdevice *device, ALfilter *filter)
|
||||
{
|
||||
const ALuint id{filter->id - 1};
|
||||
const size_t lidx{id >> 6};
|
||||
const ALuint slidx{id & 0x3f};
|
||||
|
||||
al::destroy_at(filter);
|
||||
|
||||
device->FilterList[lidx].FreeMask |= 1_u64 << slidx;
|
||||
}
|
||||
|
||||
|
||||
inline ALfilter *LookupFilter(ALCdevice *device, ALuint id)
|
||||
{
|
||||
const size_t lidx{(id-1) >> 6};
|
||||
const ALuint slidx{(id-1) & 0x3f};
|
||||
|
||||
if UNLIKELY(lidx >= device->FilterList.size())
|
||||
return nullptr;
|
||||
FilterSubList &sublist = device->FilterList[lidx];
|
||||
if UNLIKELY(sublist.FreeMask & (1_u64 << slidx))
|
||||
return nullptr;
|
||||
return sublist.Filters + slidx;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alGenFilters(ALsizei n, ALuint *filters)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
if UNLIKELY(n < 0)
|
||||
context->setError(AL_INVALID_VALUE, "Generating %d filters", n);
|
||||
if UNLIKELY(n <= 0) return;
|
||||
|
||||
ALCdevice *device{context->mDevice.get()};
|
||||
std::lock_guard<std::mutex> _{device->EffectLock};
|
||||
if(!EnsureFilters(device, static_cast<ALuint>(n)))
|
||||
{
|
||||
context->setError(AL_OUT_OF_MEMORY, "Failed to allocate %d filter%s", n, (n==1)?"":"s");
|
||||
return;
|
||||
}
|
||||
|
||||
if LIKELY(n == 1)
|
||||
{
|
||||
/* Special handling for the easy and normal case. */
|
||||
ALfilter *filter{AllocFilter(device)};
|
||||
if(filter) filters[0] = filter->id;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Store the allocated buffer IDs in a separate local list, to avoid
|
||||
* modifying the user storage in case of failure.
|
||||
*/
|
||||
al::vector<ALuint> ids;
|
||||
ids.reserve(static_cast<ALuint>(n));
|
||||
do {
|
||||
ALfilter *filter{AllocFilter(device)};
|
||||
ids.emplace_back(filter->id);
|
||||
} while(--n);
|
||||
std::copy(ids.begin(), ids.end(), filters);
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alDeleteFilters(ALsizei n, const ALuint *filters)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
if UNLIKELY(n < 0)
|
||||
context->setError(AL_INVALID_VALUE, "Deleting %d filters", n);
|
||||
if UNLIKELY(n <= 0) return;
|
||||
|
||||
ALCdevice *device{context->mDevice.get()};
|
||||
std::lock_guard<std::mutex> _{device->FilterLock};
|
||||
|
||||
/* First try to find any filters that are invalid. */
|
||||
auto validate_filter = [device](const ALuint fid) -> bool
|
||||
{ return !fid || LookupFilter(device, fid) != nullptr; };
|
||||
|
||||
const ALuint *filters_end = filters + n;
|
||||
auto invflt = std::find_if_not(filters, filters_end, validate_filter);
|
||||
if UNLIKELY(invflt != filters_end)
|
||||
{
|
||||
context->setError(AL_INVALID_NAME, "Invalid filter ID %u", *invflt);
|
||||
return;
|
||||
}
|
||||
|
||||
/* All good. Delete non-0 filter IDs. */
|
||||
auto delete_filter = [device](const ALuint fid) -> void
|
||||
{
|
||||
ALfilter *filter{fid ? LookupFilter(device, fid) : nullptr};
|
||||
if(filter) FreeFilter(device, filter);
|
||||
};
|
||||
std::for_each(filters, filters_end, delete_filter);
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API ALboolean AL_APIENTRY alIsFilter(ALuint filter)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if LIKELY(context)
|
||||
{
|
||||
ALCdevice *device{context->mDevice.get()};
|
||||
std::lock_guard<std::mutex> _{device->FilterLock};
|
||||
if(!filter || LookupFilter(device, filter))
|
||||
return AL_TRUE;
|
||||
}
|
||||
return AL_FALSE;
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alFilteri(ALuint filter, ALenum param, ALint value)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
ALCdevice *device{context->mDevice.get()};
|
||||
std::lock_guard<std::mutex> _{device->FilterLock};
|
||||
|
||||
ALfilter *alfilt{LookupFilter(device, filter)};
|
||||
if UNLIKELY(!alfilt)
|
||||
context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter);
|
||||
else
|
||||
{
|
||||
if(param == AL_FILTER_TYPE)
|
||||
{
|
||||
if(value == AL_FILTER_NULL || value == AL_FILTER_LOWPASS ||
|
||||
value == AL_FILTER_HIGHPASS || value == AL_FILTER_BANDPASS)
|
||||
InitFilterParams(alfilt, value);
|
||||
else
|
||||
context->setError(AL_INVALID_VALUE, "Invalid filter type 0x%04x", value);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Call the appropriate handler */
|
||||
ALfilter_setParami(alfilt, context.get(), param, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alFilteriv(ALuint filter, ALenum param, const ALint *values)
|
||||
START_API_FUNC
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_FILTER_TYPE:
|
||||
alFilteri(filter, param, values[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
ALCdevice *device{context->mDevice.get()};
|
||||
std::lock_guard<std::mutex> _{device->FilterLock};
|
||||
|
||||
ALfilter *alfilt{LookupFilter(device, filter)};
|
||||
if UNLIKELY(!alfilt)
|
||||
context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter);
|
||||
else
|
||||
{
|
||||
/* Call the appropriate handler */
|
||||
ALfilter_setParamiv(alfilt, context.get(), param, values);
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alFilterf(ALuint filter, ALenum param, ALfloat value)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
ALCdevice *device{context->mDevice.get()};
|
||||
std::lock_guard<std::mutex> _{device->FilterLock};
|
||||
|
||||
ALfilter *alfilt{LookupFilter(device, filter)};
|
||||
if UNLIKELY(!alfilt)
|
||||
context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter);
|
||||
else
|
||||
{
|
||||
/* Call the appropriate handler */
|
||||
ALfilter_setParamf(alfilt, context.get(), param, value);
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alFilterfv(ALuint filter, ALenum param, const ALfloat *values)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
ALCdevice *device{context->mDevice.get()};
|
||||
std::lock_guard<std::mutex> _{device->FilterLock};
|
||||
|
||||
ALfilter *alfilt{LookupFilter(device, filter)};
|
||||
if UNLIKELY(!alfilt)
|
||||
context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter);
|
||||
else
|
||||
{
|
||||
/* Call the appropriate handler */
|
||||
ALfilter_setParamfv(alfilt, context.get(), param, values);
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alGetFilteri(ALuint filter, ALenum param, ALint *value)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
ALCdevice *device{context->mDevice.get()};
|
||||
std::lock_guard<std::mutex> _{device->FilterLock};
|
||||
|
||||
ALfilter *alfilt{LookupFilter(device, filter)};
|
||||
if UNLIKELY(!alfilt)
|
||||
context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter);
|
||||
else
|
||||
{
|
||||
if(param == AL_FILTER_TYPE)
|
||||
*value = alfilt->type;
|
||||
else
|
||||
{
|
||||
/* Call the appropriate handler */
|
||||
ALfilter_getParami(alfilt, context.get(), param, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alGetFilteriv(ALuint filter, ALenum param, ALint *values)
|
||||
START_API_FUNC
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_FILTER_TYPE:
|
||||
alGetFilteri(filter, param, values);
|
||||
return;
|
||||
}
|
||||
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
ALCdevice *device{context->mDevice.get()};
|
||||
std::lock_guard<std::mutex> _{device->FilterLock};
|
||||
|
||||
ALfilter *alfilt{LookupFilter(device, filter)};
|
||||
if UNLIKELY(!alfilt)
|
||||
context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter);
|
||||
else
|
||||
{
|
||||
/* Call the appropriate handler */
|
||||
ALfilter_getParamiv(alfilt, context.get(), param, values);
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alGetFilterf(ALuint filter, ALenum param, ALfloat *value)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
ALCdevice *device{context->mDevice.get()};
|
||||
std::lock_guard<std::mutex> _{device->FilterLock};
|
||||
|
||||
ALfilter *alfilt{LookupFilter(device, filter)};
|
||||
if UNLIKELY(!alfilt)
|
||||
context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter);
|
||||
else
|
||||
{
|
||||
/* Call the appropriate handler */
|
||||
ALfilter_getParamf(alfilt, context.get(), param, value);
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alGetFilterfv(ALuint filter, ALenum param, ALfloat *values)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
ALCdevice *device{context->mDevice.get()};
|
||||
std::lock_guard<std::mutex> _{device->FilterLock};
|
||||
|
||||
ALfilter *alfilt{LookupFilter(device, filter)};
|
||||
if UNLIKELY(!alfilt)
|
||||
context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter);
|
||||
else
|
||||
{
|
||||
/* Call the appropriate handler */
|
||||
ALfilter_getParamfv(alfilt, context.get(), param, values);
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
|
||||
FilterSubList::~FilterSubList()
|
||||
{
|
||||
uint64_t usemask{~FreeMask};
|
||||
while(usemask)
|
||||
{
|
||||
ALsizei idx = CTZ64(usemask);
|
||||
al::destroy_at(Filters+idx);
|
||||
usemask &= ~(1_u64 << idx);
|
||||
}
|
||||
FreeMask = ~usemask;
|
||||
al_free(Filters);
|
||||
Filters = nullptr;
|
||||
}
|
||||
-56
@@ -1,56 +0,0 @@
|
||||
#ifndef AL_FILTER_H
|
||||
#define AL_FILTER_H
|
||||
|
||||
#include "AL/al.h"
|
||||
#include "AL/alc.h"
|
||||
|
||||
|
||||
#define LOWPASSFREQREF (5000.0f)
|
||||
#define HIGHPASSFREQREF (250.0f)
|
||||
|
||||
|
||||
struct ALfilter;
|
||||
|
||||
struct ALfilterVtable {
|
||||
void (*const setParami)(ALfilter *filter, ALCcontext *context, ALenum param, ALint val);
|
||||
void (*const setParamiv)(ALfilter *filter, ALCcontext *context, ALenum param, const ALint *vals);
|
||||
void (*const setParamf)(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat val);
|
||||
void (*const setParamfv)(ALfilter *filter, ALCcontext *context, ALenum param, const ALfloat *vals);
|
||||
|
||||
void (*const getParami)(ALfilter *filter, ALCcontext *context, ALenum param, ALint *val);
|
||||
void (*const getParamiv)(ALfilter *filter, ALCcontext *context, ALenum param, ALint *vals);
|
||||
void (*const getParamf)(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat *val);
|
||||
void (*const getParamfv)(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat *vals);
|
||||
};
|
||||
|
||||
#define DEFINE_ALFILTER_VTABLE(T) \
|
||||
const ALfilterVtable T##_vtable = { \
|
||||
T##_setParami, T##_setParamiv, T##_setParamf, T##_setParamfv, \
|
||||
T##_getParami, T##_getParamiv, T##_getParamf, T##_getParamfv, \
|
||||
}
|
||||
|
||||
struct ALfilter {
|
||||
// Filter type (AL_FILTER_NULL, ...)
|
||||
ALenum type;
|
||||
|
||||
ALfloat Gain;
|
||||
ALfloat GainHF;
|
||||
ALfloat HFReference;
|
||||
ALfloat GainLF;
|
||||
ALfloat LFReference;
|
||||
|
||||
const ALfilterVtable *vtab;
|
||||
|
||||
/* Self ID */
|
||||
ALuint id;
|
||||
};
|
||||
#define ALfilter_setParami(o, c, p, v) ((o)->vtab->setParami(o, c, p, v))
|
||||
#define ALfilter_setParamf(o, c, p, v) ((o)->vtab->setParamf(o, c, p, v))
|
||||
#define ALfilter_setParamiv(o, c, p, v) ((o)->vtab->setParamiv(o, c, p, v))
|
||||
#define ALfilter_setParamfv(o, c, p, v) ((o)->vtab->setParamfv(o, c, p, v))
|
||||
#define ALfilter_getParami(o, c, p, v) ((o)->vtab->getParami(o, c, p, v))
|
||||
#define ALfilter_getParamf(o, c, p, v) ((o)->vtab->getParamf(o, c, p, v))
|
||||
#define ALfilter_getParamiv(o, c, p, v) ((o)->vtab->getParamiv(o, c, p, v))
|
||||
#define ALfilter_getParamfv(o, c, p, v) ((o)->vtab->getParamfv(o, c, p, v))
|
||||
|
||||
#endif
|
||||
-452
@@ -1,452 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 1999-2000 by authors.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "listener.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <mutex>
|
||||
|
||||
#include "AL/al.h"
|
||||
#include "AL/alc.h"
|
||||
#include "AL/efx.h"
|
||||
|
||||
#include "alcontext.h"
|
||||
#include "alexcpt.h"
|
||||
#include "almalloc.h"
|
||||
#include "atomic.h"
|
||||
#include "opthelpers.h"
|
||||
|
||||
|
||||
#define DO_UPDATEPROPS() do { \
|
||||
if(!context->mDeferUpdates.load(std::memory_order_acquire)) \
|
||||
UpdateListenerProps(context.get()); \
|
||||
else \
|
||||
listener.PropsClean.clear(std::memory_order_release); \
|
||||
} while(0)
|
||||
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alListenerf(ALenum param, ALfloat value)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
ALlistener &listener = context->mListener;
|
||||
std::lock_guard<std::mutex> _{context->mPropLock};
|
||||
switch(param)
|
||||
{
|
||||
case AL_GAIN:
|
||||
if(!(value >= 0.0f && std::isfinite(value)))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Listener gain out of range");
|
||||
listener.Gain = value;
|
||||
DO_UPDATEPROPS();
|
||||
break;
|
||||
|
||||
case AL_METERS_PER_UNIT:
|
||||
if(!(value >= AL_MIN_METERS_PER_UNIT && value <= AL_MAX_METERS_PER_UNIT))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Listener meters per unit out of range");
|
||||
listener.mMetersPerUnit = value;
|
||||
DO_UPDATEPROPS();
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid listener float property");
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alListener3f(ALenum param, ALfloat value1, ALfloat value2, ALfloat value3)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
ALlistener &listener = context->mListener;
|
||||
std::lock_guard<std::mutex> _{context->mPropLock};
|
||||
switch(param)
|
||||
{
|
||||
case AL_POSITION:
|
||||
if(!(std::isfinite(value1) && std::isfinite(value2) && std::isfinite(value3)))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Listener position out of range");
|
||||
listener.Position[0] = value1;
|
||||
listener.Position[1] = value2;
|
||||
listener.Position[2] = value3;
|
||||
DO_UPDATEPROPS();
|
||||
break;
|
||||
|
||||
case AL_VELOCITY:
|
||||
if(!(std::isfinite(value1) && std::isfinite(value2) && std::isfinite(value3)))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Listener velocity out of range");
|
||||
listener.Velocity[0] = value1;
|
||||
listener.Velocity[1] = value2;
|
||||
listener.Velocity[2] = value3;
|
||||
DO_UPDATEPROPS();
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid listener 3-float property");
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alListenerfv(ALenum param, const ALfloat *values)
|
||||
START_API_FUNC
|
||||
{
|
||||
if(values)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_GAIN:
|
||||
case AL_METERS_PER_UNIT:
|
||||
alListenerf(param, values[0]);
|
||||
return;
|
||||
|
||||
case AL_POSITION:
|
||||
case AL_VELOCITY:
|
||||
alListener3f(param, values[0], values[1], values[2]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
ALlistener &listener = context->mListener;
|
||||
std::lock_guard<std::mutex> _{context->mPropLock};
|
||||
if(!values) SETERR_RETURN(context, AL_INVALID_VALUE,, "NULL pointer");
|
||||
switch(param)
|
||||
{
|
||||
case AL_ORIENTATION:
|
||||
if(!(std::isfinite(values[0]) && std::isfinite(values[1]) && std::isfinite(values[2]) &&
|
||||
std::isfinite(values[3]) && std::isfinite(values[4]) && std::isfinite(values[5])))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Listener orientation out of range");
|
||||
/* AT then UP */
|
||||
listener.OrientAt[0] = values[0];
|
||||
listener.OrientAt[1] = values[1];
|
||||
listener.OrientAt[2] = values[2];
|
||||
listener.OrientUp[0] = values[3];
|
||||
listener.OrientUp[1] = values[4];
|
||||
listener.OrientUp[2] = values[5];
|
||||
DO_UPDATEPROPS();
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid listener float-vector property");
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alListeneri(ALenum param, ALint /*value*/)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
std::lock_guard<std::mutex> _{context->mPropLock};
|
||||
switch(param)
|
||||
{
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid listener integer property");
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API void AL_APIENTRY alListener3i(ALenum param, ALint value1, ALint value2, ALint value3)
|
||||
START_API_FUNC
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_POSITION:
|
||||
case AL_VELOCITY:
|
||||
alListener3f(param, static_cast<ALfloat>(value1), static_cast<ALfloat>(value2), static_cast<ALfloat>(value3));
|
||||
return;
|
||||
}
|
||||
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
std::lock_guard<std::mutex> _{context->mPropLock};
|
||||
switch(param)
|
||||
{
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid listener 3-integer property");
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API void AL_APIENTRY alListeneriv(ALenum param, const ALint *values)
|
||||
START_API_FUNC
|
||||
{
|
||||
if(values)
|
||||
{
|
||||
ALfloat fvals[6];
|
||||
switch(param)
|
||||
{
|
||||
case AL_POSITION:
|
||||
case AL_VELOCITY:
|
||||
alListener3f(param, static_cast<ALfloat>(values[0]), static_cast<ALfloat>(values[1]), static_cast<ALfloat>(values[2]));
|
||||
return;
|
||||
|
||||
case AL_ORIENTATION:
|
||||
fvals[0] = static_cast<ALfloat>(values[0]);
|
||||
fvals[1] = static_cast<ALfloat>(values[1]);
|
||||
fvals[2] = static_cast<ALfloat>(values[2]);
|
||||
fvals[3] = static_cast<ALfloat>(values[3]);
|
||||
fvals[4] = static_cast<ALfloat>(values[4]);
|
||||
fvals[5] = static_cast<ALfloat>(values[5]);
|
||||
alListenerfv(param, fvals);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
std::lock_guard<std::mutex> _{context->mPropLock};
|
||||
if(!values)
|
||||
context->setError(AL_INVALID_VALUE, "NULL pointer");
|
||||
else switch(param)
|
||||
{
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid listener integer-vector property");
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alGetListenerf(ALenum param, ALfloat *value)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
ALlistener &listener = context->mListener;
|
||||
std::lock_guard<std::mutex> _{context->mPropLock};
|
||||
if(!value)
|
||||
context->setError(AL_INVALID_VALUE, "NULL pointer");
|
||||
else switch(param)
|
||||
{
|
||||
case AL_GAIN:
|
||||
*value = listener.Gain;
|
||||
break;
|
||||
|
||||
case AL_METERS_PER_UNIT:
|
||||
*value = listener.mMetersPerUnit;
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid listener float property");
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alGetListener3f(ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
ALlistener &listener = context->mListener;
|
||||
std::lock_guard<std::mutex> _{context->mPropLock};
|
||||
if(!value1 || !value2 || !value3)
|
||||
context->setError(AL_INVALID_VALUE, "NULL pointer");
|
||||
else switch(param)
|
||||
{
|
||||
case AL_POSITION:
|
||||
*value1 = listener.Position[0];
|
||||
*value2 = listener.Position[1];
|
||||
*value3 = listener.Position[2];
|
||||
break;
|
||||
|
||||
case AL_VELOCITY:
|
||||
*value1 = listener.Velocity[0];
|
||||
*value2 = listener.Velocity[1];
|
||||
*value3 = listener.Velocity[2];
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid listener 3-float property");
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alGetListenerfv(ALenum param, ALfloat *values)
|
||||
START_API_FUNC
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_GAIN:
|
||||
case AL_METERS_PER_UNIT:
|
||||
alGetListenerf(param, values);
|
||||
return;
|
||||
|
||||
case AL_POSITION:
|
||||
case AL_VELOCITY:
|
||||
alGetListener3f(param, values+0, values+1, values+2);
|
||||
return;
|
||||
}
|
||||
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
ALlistener &listener = context->mListener;
|
||||
std::lock_guard<std::mutex> _{context->mPropLock};
|
||||
if(!values)
|
||||
context->setError(AL_INVALID_VALUE, "NULL pointer");
|
||||
else switch(param)
|
||||
{
|
||||
case AL_ORIENTATION:
|
||||
// AT then UP
|
||||
values[0] = listener.OrientAt[0];
|
||||
values[1] = listener.OrientAt[1];
|
||||
values[2] = listener.OrientAt[2];
|
||||
values[3] = listener.OrientUp[0];
|
||||
values[4] = listener.OrientUp[1];
|
||||
values[5] = listener.OrientUp[2];
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid listener float-vector property");
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alGetListeneri(ALenum param, ALint *value)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
std::lock_guard<std::mutex> _{context->mPropLock};
|
||||
if(!value)
|
||||
context->setError(AL_INVALID_VALUE, "NULL pointer");
|
||||
else switch(param)
|
||||
{
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid listener integer property");
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API void AL_APIENTRY alGetListener3i(ALenum param, ALint *value1, ALint *value2, ALint *value3)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
ALlistener &listener = context->mListener;
|
||||
std::lock_guard<std::mutex> _{context->mPropLock};
|
||||
if(!value1 || !value2 || !value3)
|
||||
context->setError(AL_INVALID_VALUE, "NULL pointer");
|
||||
else switch(param)
|
||||
{
|
||||
case AL_POSITION:
|
||||
*value1 = static_cast<ALint>(listener.Position[0]);
|
||||
*value2 = static_cast<ALint>(listener.Position[1]);
|
||||
*value3 = static_cast<ALint>(listener.Position[2]);
|
||||
break;
|
||||
|
||||
case AL_VELOCITY:
|
||||
*value1 = static_cast<ALint>(listener.Velocity[0]);
|
||||
*value2 = static_cast<ALint>(listener.Velocity[1]);
|
||||
*value3 = static_cast<ALint>(listener.Velocity[2]);
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid listener 3-integer property");
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API void AL_APIENTRY alGetListeneriv(ALenum param, ALint* values)
|
||||
START_API_FUNC
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_POSITION:
|
||||
case AL_VELOCITY:
|
||||
alGetListener3i(param, values+0, values+1, values+2);
|
||||
return;
|
||||
}
|
||||
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
ALlistener &listener = context->mListener;
|
||||
std::lock_guard<std::mutex> _{context->mPropLock};
|
||||
if(!values)
|
||||
context->setError(AL_INVALID_VALUE, "NULL pointer");
|
||||
else switch(param)
|
||||
{
|
||||
case AL_ORIENTATION:
|
||||
// AT then UP
|
||||
values[0] = static_cast<ALint>(listener.OrientAt[0]);
|
||||
values[1] = static_cast<ALint>(listener.OrientAt[1]);
|
||||
values[2] = static_cast<ALint>(listener.OrientAt[2]);
|
||||
values[3] = static_cast<ALint>(listener.OrientUp[0]);
|
||||
values[4] = static_cast<ALint>(listener.OrientUp[1]);
|
||||
values[5] = static_cast<ALint>(listener.OrientUp[2]);
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid listener integer-vector property");
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
|
||||
void UpdateListenerProps(ALCcontext *context)
|
||||
{
|
||||
/* Get an unused proprty container, or allocate a new one as needed. */
|
||||
ALlistenerProps *props{context->mFreeListenerProps.load(std::memory_order_acquire)};
|
||||
if(!props)
|
||||
props = new ALlistenerProps{};
|
||||
else
|
||||
{
|
||||
ALlistenerProps *next;
|
||||
do {
|
||||
next = props->next.load(std::memory_order_relaxed);
|
||||
} while(context->mFreeListenerProps.compare_exchange_weak(props, next,
|
||||
std::memory_order_seq_cst, std::memory_order_acquire) == 0);
|
||||
}
|
||||
|
||||
/* Copy in current property values. */
|
||||
ALlistener &listener = context->mListener;
|
||||
props->Position = listener.Position;
|
||||
props->Velocity = listener.Velocity;
|
||||
props->OrientAt = listener.OrientAt;
|
||||
props->OrientUp = listener.OrientUp;
|
||||
props->Gain = listener.Gain;
|
||||
props->MetersPerUnit = listener.mMetersPerUnit;
|
||||
|
||||
/* Set the new container for updating internal parameters. */
|
||||
props = listener.Params.Update.exchange(props, std::memory_order_acq_rel);
|
||||
if(props)
|
||||
{
|
||||
/* If there was an unused update container, put it back in the
|
||||
* freelist.
|
||||
*/
|
||||
AtomicReplaceHead(context->mFreeListenerProps, props);
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
#ifndef AL_LISTENER_H
|
||||
#define AL_LISTENER_H
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
|
||||
#include "AL/al.h"
|
||||
#include "AL/alc.h"
|
||||
#include "AL/efx.h"
|
||||
|
||||
#include "almalloc.h"
|
||||
#include "vecmat.h"
|
||||
|
||||
enum class DistanceModel;
|
||||
|
||||
|
||||
struct ALlistenerProps {
|
||||
std::array<ALfloat,3> Position;
|
||||
std::array<ALfloat,3> Velocity;
|
||||
std::array<ALfloat,3> OrientAt;
|
||||
std::array<ALfloat,3> OrientUp;
|
||||
ALfloat Gain;
|
||||
ALfloat MetersPerUnit;
|
||||
|
||||
std::atomic<ALlistenerProps*> next;
|
||||
|
||||
DEF_NEWDEL(ALlistenerProps)
|
||||
};
|
||||
|
||||
struct ALlistener {
|
||||
std::array<ALfloat,3> Position{{0.0f, 0.0f, 0.0f}};
|
||||
std::array<ALfloat,3> Velocity{{0.0f, 0.0f, 0.0f}};
|
||||
std::array<ALfloat,3> OrientAt{{0.0f, 0.0f, -1.0f}};
|
||||
std::array<ALfloat,3> OrientUp{{0.0f, 1.0f, 0.0f}};
|
||||
ALfloat Gain{1.0f};
|
||||
ALfloat mMetersPerUnit{AL_DEFAULT_METERS_PER_UNIT};
|
||||
|
||||
std::atomic_flag PropsClean;
|
||||
|
||||
struct {
|
||||
/* Pointer to the most recent property values that are awaiting an
|
||||
* update.
|
||||
*/
|
||||
std::atomic<ALlistenerProps*> Update{nullptr};
|
||||
|
||||
alu::Matrix Matrix;
|
||||
alu::Vector Velocity;
|
||||
|
||||
ALfloat Gain;
|
||||
ALfloat MetersPerUnit;
|
||||
|
||||
ALfloat DopplerFactor;
|
||||
ALfloat SpeedOfSound; /* in units per sec! */
|
||||
|
||||
ALboolean SourceDistanceModel;
|
||||
DistanceModel mDistanceModel;
|
||||
} Params;
|
||||
|
||||
ALlistener() { PropsClean.test_and_set(std::memory_order_relaxed); }
|
||||
};
|
||||
|
||||
void UpdateListenerProps(ALCcontext *context);
|
||||
|
||||
#endif
|
||||
-3345
File diff suppressed because it is too large
Load Diff
-129
@@ -1,129 +0,0 @@
|
||||
#ifndef AL_SOURCE_H
|
||||
#define AL_SOURCE_H
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <iterator>
|
||||
|
||||
#include "AL/al.h"
|
||||
#include "AL/alc.h"
|
||||
|
||||
#include "alcontext.h"
|
||||
#include "almalloc.h"
|
||||
#include "alnumeric.h"
|
||||
#include "alu.h"
|
||||
#include "vector.h"
|
||||
|
||||
struct ALbuffer;
|
||||
struct ALeffectslot;
|
||||
|
||||
|
||||
#define DEFAULT_SENDS 2
|
||||
|
||||
#define INVALID_VOICE_IDX static_cast<ALuint>(-1)
|
||||
|
||||
struct ALbufferlistitem {
|
||||
std::atomic<ALbufferlistitem*> mNext{nullptr};
|
||||
ALuint mSampleLen{0u};
|
||||
ALbuffer *mBuffer{nullptr};
|
||||
|
||||
DEF_NEWDEL(ALbufferlistitem)
|
||||
};
|
||||
|
||||
|
||||
struct ALsource {
|
||||
/** Source properties. */
|
||||
ALfloat Pitch;
|
||||
ALfloat Gain;
|
||||
ALfloat OuterGain;
|
||||
ALfloat MinGain;
|
||||
ALfloat MaxGain;
|
||||
ALfloat InnerAngle;
|
||||
ALfloat OuterAngle;
|
||||
ALfloat RefDistance;
|
||||
ALfloat MaxDistance;
|
||||
ALfloat RolloffFactor;
|
||||
std::array<ALfloat,3> Position;
|
||||
std::array<ALfloat,3> Velocity;
|
||||
std::array<ALfloat,3> Direction;
|
||||
std::array<ALfloat,3> OrientAt;
|
||||
std::array<ALfloat,3> OrientUp;
|
||||
bool HeadRelative;
|
||||
bool Looping;
|
||||
DistanceModel mDistanceModel;
|
||||
Resampler mResampler;
|
||||
DirectMode DirectChannels;
|
||||
SpatializeMode mSpatialize;
|
||||
|
||||
bool DryGainHFAuto;
|
||||
bool WetGainAuto;
|
||||
bool WetGainHFAuto;
|
||||
ALfloat OuterGainHF;
|
||||
|
||||
ALfloat AirAbsorptionFactor;
|
||||
ALfloat RoomRolloffFactor;
|
||||
ALfloat DopplerFactor;
|
||||
|
||||
/* NOTE: Stereo pan angles are specified in radians, counter-clockwise
|
||||
* rather than clockwise.
|
||||
*/
|
||||
std::array<ALfloat,2> StereoPan;
|
||||
|
||||
ALfloat Radius;
|
||||
|
||||
/** Direct filter and auxiliary send info. */
|
||||
struct {
|
||||
ALfloat Gain;
|
||||
ALfloat GainHF;
|
||||
ALfloat HFReference;
|
||||
ALfloat GainLF;
|
||||
ALfloat LFReference;
|
||||
} Direct;
|
||||
struct SendData {
|
||||
ALeffectslot *Slot;
|
||||
ALfloat Gain;
|
||||
ALfloat GainHF;
|
||||
ALfloat HFReference;
|
||||
ALfloat GainLF;
|
||||
ALfloat LFReference;
|
||||
};
|
||||
al::vector<SendData> Send;
|
||||
|
||||
/**
|
||||
* Last user-specified offset, and the offset type (bytes, samples, or
|
||||
* seconds).
|
||||
*/
|
||||
ALdouble Offset{0.0};
|
||||
ALenum OffsetType{AL_NONE};
|
||||
|
||||
/** Source type (static, streaming, or undetermined) */
|
||||
ALenum SourceType{AL_UNDETERMINED};
|
||||
|
||||
/** Source state (initial, playing, paused, or stopped) */
|
||||
ALenum state{AL_INITIAL};
|
||||
|
||||
/** Source Buffer Queue head. */
|
||||
ALbufferlistitem *queue{nullptr};
|
||||
|
||||
std::atomic_flag PropsClean;
|
||||
|
||||
/* Index into the context's Voices array. Lazily updated, only checked and
|
||||
* reset when looking up the voice.
|
||||
*/
|
||||
ALuint VoiceIdx{INVALID_VOICE_IDX};
|
||||
|
||||
/** Self ID */
|
||||
ALuint id{0};
|
||||
|
||||
|
||||
ALsource(ALuint num_sends);
|
||||
~ALsource();
|
||||
|
||||
ALsource(const ALsource&) = delete;
|
||||
ALsource& operator=(const ALsource&) = delete;
|
||||
};
|
||||
|
||||
void UpdateAllSourceProps(ALCcontext *context);
|
||||
|
||||
#endif
|
||||
-880
@@ -1,880 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 1999-2000 by authors.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "version.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
|
||||
#include "AL/al.h"
|
||||
#include "AL/alc.h"
|
||||
#include "AL/alext.h"
|
||||
|
||||
#include "alcontext.h"
|
||||
#include "alexcpt.h"
|
||||
#include "almalloc.h"
|
||||
#include "alnumeric.h"
|
||||
#include "alspan.h"
|
||||
#include "alu.h"
|
||||
#include "atomic.h"
|
||||
#include "event.h"
|
||||
#include "inprogext.h"
|
||||
#include "opthelpers.h"
|
||||
#include "strutils.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr ALchar alVendor[] = "OpenAL Community";
|
||||
constexpr ALchar alVersion[] = "1.1 ALSOFT " ALSOFT_VERSION;
|
||||
constexpr ALchar alRenderer[] = "OpenAL Soft";
|
||||
|
||||
// Error Messages
|
||||
constexpr ALchar alNoError[] = "No Error";
|
||||
constexpr ALchar alErrInvalidName[] = "Invalid Name";
|
||||
constexpr ALchar alErrInvalidEnum[] = "Invalid Enum";
|
||||
constexpr ALchar alErrInvalidValue[] = "Invalid Value";
|
||||
constexpr ALchar alErrInvalidOp[] = "Invalid Operation";
|
||||
constexpr ALchar alErrOutOfMemory[] = "Out of Memory";
|
||||
|
||||
/* Resampler strings */
|
||||
template<Resampler rtype> struct ResamplerName { };
|
||||
template<> struct ResamplerName<Resampler::Point>
|
||||
{ static constexpr const ALchar *Get() noexcept { return "Nearest"; } };
|
||||
template<> struct ResamplerName<Resampler::Linear>
|
||||
{ static constexpr const ALchar *Get() noexcept { return "Linear"; } };
|
||||
template<> struct ResamplerName<Resampler::Cubic>
|
||||
{ static constexpr const ALchar *Get() noexcept { return "Cubic"; } };
|
||||
template<> struct ResamplerName<Resampler::FastBSinc12>
|
||||
{ static constexpr const ALchar *Get() noexcept { return "11th order Sinc (fast)"; } };
|
||||
template<> struct ResamplerName<Resampler::BSinc12>
|
||||
{ static constexpr const ALchar *Get() noexcept { return "11th order Sinc"; } };
|
||||
template<> struct ResamplerName<Resampler::FastBSinc24>
|
||||
{ static constexpr const ALchar *Get() noexcept { return "23rd order Sinc (fast)"; } };
|
||||
template<> struct ResamplerName<Resampler::BSinc24>
|
||||
{ static constexpr const ALchar *Get() noexcept { return "23rd order Sinc"; } };
|
||||
|
||||
const ALchar *GetResamplerName(const Resampler rtype)
|
||||
{
|
||||
#define HANDLE_RESAMPLER(r) case r: return ResamplerName<r>::Get()
|
||||
switch(rtype)
|
||||
{
|
||||
HANDLE_RESAMPLER(Resampler::Point);
|
||||
HANDLE_RESAMPLER(Resampler::Linear);
|
||||
HANDLE_RESAMPLER(Resampler::Cubic);
|
||||
HANDLE_RESAMPLER(Resampler::FastBSinc12);
|
||||
HANDLE_RESAMPLER(Resampler::BSinc12);
|
||||
HANDLE_RESAMPLER(Resampler::FastBSinc24);
|
||||
HANDLE_RESAMPLER(Resampler::BSinc24);
|
||||
}
|
||||
#undef HANDLE_RESAMPLER
|
||||
/* Should never get here. */
|
||||
throw std::runtime_error{"Unexpected resampler index"};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/* WARNING: Non-standard export! Not part of any extension, or exposed in the
|
||||
* alcFunctions list.
|
||||
*/
|
||||
extern "C" AL_API const ALchar* AL_APIENTRY alsoft_get_version(void)
|
||||
START_API_FUNC
|
||||
{
|
||||
static const auto spoof = al::getenv("ALSOFT_SPOOF_VERSION");
|
||||
if(spoof) return spoof->c_str();
|
||||
return ALSOFT_VERSION;
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
#define DO_UPDATEPROPS() do { \
|
||||
if(!context->mDeferUpdates.load(std::memory_order_acquire)) \
|
||||
UpdateContextProps(context.get()); \
|
||||
else \
|
||||
context->mPropsClean.clear(std::memory_order_release); \
|
||||
} while(0)
|
||||
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alEnable(ALenum capability)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
std::lock_guard<std::mutex> _{context->mPropLock};
|
||||
switch(capability)
|
||||
{
|
||||
case AL_SOURCE_DISTANCE_MODEL:
|
||||
context->mSourceDistanceModel = AL_TRUE;
|
||||
DO_UPDATEPROPS();
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_VALUE, "Invalid enable property 0x%04x", capability);
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alDisable(ALenum capability)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
std::lock_guard<std::mutex> _{context->mPropLock};
|
||||
switch(capability)
|
||||
{
|
||||
case AL_SOURCE_DISTANCE_MODEL:
|
||||
context->mSourceDistanceModel = AL_FALSE;
|
||||
DO_UPDATEPROPS();
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_VALUE, "Invalid disable property 0x%04x", capability);
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API ALboolean AL_APIENTRY alIsEnabled(ALenum capability)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return AL_FALSE;
|
||||
|
||||
std::lock_guard<std::mutex> _{context->mPropLock};
|
||||
ALboolean value{AL_FALSE};
|
||||
switch(capability)
|
||||
{
|
||||
case AL_SOURCE_DISTANCE_MODEL:
|
||||
value = context->mSourceDistanceModel;
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_VALUE, "Invalid is enabled property 0x%04x", capability);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API ALboolean AL_APIENTRY alGetBoolean(ALenum pname)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return AL_FALSE;
|
||||
|
||||
std::lock_guard<std::mutex> _{context->mPropLock};
|
||||
ALboolean value{AL_FALSE};
|
||||
switch(pname)
|
||||
{
|
||||
case AL_DOPPLER_FACTOR:
|
||||
if(context->mDopplerFactor != 0.0f)
|
||||
value = AL_TRUE;
|
||||
break;
|
||||
|
||||
case AL_DOPPLER_VELOCITY:
|
||||
if(context->mDopplerVelocity != 0.0f)
|
||||
value = AL_TRUE;
|
||||
break;
|
||||
|
||||
case AL_DISTANCE_MODEL:
|
||||
if(context->mDistanceModel == DistanceModel::Default)
|
||||
value = AL_TRUE;
|
||||
break;
|
||||
|
||||
case AL_SPEED_OF_SOUND:
|
||||
if(context->mSpeedOfSound != 0.0f)
|
||||
value = AL_TRUE;
|
||||
break;
|
||||
|
||||
case AL_DEFERRED_UPDATES_SOFT:
|
||||
if(context->mDeferUpdates.load(std::memory_order_acquire))
|
||||
value = AL_TRUE;
|
||||
break;
|
||||
|
||||
case AL_GAIN_LIMIT_SOFT:
|
||||
if(GAIN_MIX_MAX/context->mGainBoost != 0.0f)
|
||||
value = AL_TRUE;
|
||||
break;
|
||||
|
||||
case AL_NUM_RESAMPLERS_SOFT:
|
||||
/* Always non-0. */
|
||||
value = AL_TRUE;
|
||||
break;
|
||||
|
||||
case AL_DEFAULT_RESAMPLER_SOFT:
|
||||
value = static_cast<int>(ResamplerDefault) ? AL_TRUE : AL_FALSE;
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_VALUE, "Invalid boolean property 0x%04x", pname);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API ALdouble AL_APIENTRY alGetDouble(ALenum pname)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return 0.0;
|
||||
|
||||
std::lock_guard<std::mutex> _{context->mPropLock};
|
||||
ALdouble value{0.0};
|
||||
switch(pname)
|
||||
{
|
||||
case AL_DOPPLER_FACTOR:
|
||||
value = context->mDopplerFactor;
|
||||
break;
|
||||
|
||||
case AL_DOPPLER_VELOCITY:
|
||||
value = context->mDopplerVelocity;
|
||||
break;
|
||||
|
||||
case AL_DISTANCE_MODEL:
|
||||
value = static_cast<ALdouble>(context->mDistanceModel);
|
||||
break;
|
||||
|
||||
case AL_SPEED_OF_SOUND:
|
||||
value = context->mSpeedOfSound;
|
||||
break;
|
||||
|
||||
case AL_DEFERRED_UPDATES_SOFT:
|
||||
if(context->mDeferUpdates.load(std::memory_order_acquire))
|
||||
value = static_cast<ALdouble>(AL_TRUE);
|
||||
break;
|
||||
|
||||
case AL_GAIN_LIMIT_SOFT:
|
||||
value = ALdouble{GAIN_MIX_MAX}/context->mGainBoost;
|
||||
break;
|
||||
|
||||
case AL_NUM_RESAMPLERS_SOFT:
|
||||
value = static_cast<ALdouble>(Resampler::Max) + 1.0;
|
||||
break;
|
||||
|
||||
case AL_DEFAULT_RESAMPLER_SOFT:
|
||||
value = static_cast<ALdouble>(ResamplerDefault);
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_VALUE, "Invalid double property 0x%04x", pname);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API ALfloat AL_APIENTRY alGetFloat(ALenum pname)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return 0.0f;
|
||||
|
||||
std::lock_guard<std::mutex> _{context->mPropLock};
|
||||
ALfloat value{0.0f};
|
||||
switch(pname)
|
||||
{
|
||||
case AL_DOPPLER_FACTOR:
|
||||
value = context->mDopplerFactor;
|
||||
break;
|
||||
|
||||
case AL_DOPPLER_VELOCITY:
|
||||
value = context->mDopplerVelocity;
|
||||
break;
|
||||
|
||||
case AL_DISTANCE_MODEL:
|
||||
value = static_cast<ALfloat>(context->mDistanceModel);
|
||||
break;
|
||||
|
||||
case AL_SPEED_OF_SOUND:
|
||||
value = context->mSpeedOfSound;
|
||||
break;
|
||||
|
||||
case AL_DEFERRED_UPDATES_SOFT:
|
||||
if(context->mDeferUpdates.load(std::memory_order_acquire))
|
||||
value = static_cast<ALfloat>(AL_TRUE);
|
||||
break;
|
||||
|
||||
case AL_GAIN_LIMIT_SOFT:
|
||||
value = GAIN_MIX_MAX/context->mGainBoost;
|
||||
break;
|
||||
|
||||
case AL_NUM_RESAMPLERS_SOFT:
|
||||
value = static_cast<ALfloat>(Resampler::Max) + 1.0f;
|
||||
break;
|
||||
|
||||
case AL_DEFAULT_RESAMPLER_SOFT:
|
||||
value = static_cast<ALfloat>(ResamplerDefault);
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_VALUE, "Invalid float property 0x%04x", pname);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API ALint AL_APIENTRY alGetInteger(ALenum pname)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return 0;
|
||||
|
||||
std::lock_guard<std::mutex> _{context->mPropLock};
|
||||
ALint value{0};
|
||||
switch(pname)
|
||||
{
|
||||
case AL_DOPPLER_FACTOR:
|
||||
value = static_cast<ALint>(context->mDopplerFactor);
|
||||
break;
|
||||
|
||||
case AL_DOPPLER_VELOCITY:
|
||||
value = static_cast<ALint>(context->mDopplerVelocity);
|
||||
break;
|
||||
|
||||
case AL_DISTANCE_MODEL:
|
||||
value = static_cast<ALint>(context->mDistanceModel);
|
||||
break;
|
||||
|
||||
case AL_SPEED_OF_SOUND:
|
||||
value = static_cast<ALint>(context->mSpeedOfSound);
|
||||
break;
|
||||
|
||||
case AL_DEFERRED_UPDATES_SOFT:
|
||||
if(context->mDeferUpdates.load(std::memory_order_acquire))
|
||||
value = AL_TRUE;
|
||||
break;
|
||||
|
||||
case AL_GAIN_LIMIT_SOFT:
|
||||
value = static_cast<ALint>(GAIN_MIX_MAX/context->mGainBoost);
|
||||
break;
|
||||
|
||||
case AL_NUM_RESAMPLERS_SOFT:
|
||||
value = static_cast<int>(Resampler::Max) + 1;
|
||||
break;
|
||||
|
||||
case AL_DEFAULT_RESAMPLER_SOFT:
|
||||
value = static_cast<int>(ResamplerDefault);
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_VALUE, "Invalid integer property 0x%04x", pname);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
extern "C" AL_API ALint64SOFT AL_APIENTRY alGetInteger64SOFT(ALenum pname)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return 0_i64;
|
||||
|
||||
std::lock_guard<std::mutex> _{context->mPropLock};
|
||||
ALint64SOFT value{0};
|
||||
switch(pname)
|
||||
{
|
||||
case AL_DOPPLER_FACTOR:
|
||||
value = static_cast<ALint64SOFT>(context->mDopplerFactor);
|
||||
break;
|
||||
|
||||
case AL_DOPPLER_VELOCITY:
|
||||
value = static_cast<ALint64SOFT>(context->mDopplerVelocity);
|
||||
break;
|
||||
|
||||
case AL_DISTANCE_MODEL:
|
||||
value = static_cast<ALint64SOFT>(context->mDistanceModel);
|
||||
break;
|
||||
|
||||
case AL_SPEED_OF_SOUND:
|
||||
value = static_cast<ALint64SOFT>(context->mSpeedOfSound);
|
||||
break;
|
||||
|
||||
case AL_DEFERRED_UPDATES_SOFT:
|
||||
if(context->mDeferUpdates.load(std::memory_order_acquire))
|
||||
value = AL_TRUE;
|
||||
break;
|
||||
|
||||
case AL_GAIN_LIMIT_SOFT:
|
||||
value = static_cast<ALint64SOFT>(GAIN_MIX_MAX/context->mGainBoost);
|
||||
break;
|
||||
|
||||
case AL_NUM_RESAMPLERS_SOFT:
|
||||
value = static_cast<ALint64SOFT>(Resampler::Max) + 1;
|
||||
break;
|
||||
|
||||
case AL_DEFAULT_RESAMPLER_SOFT:
|
||||
value = static_cast<ALint64SOFT>(ResamplerDefault);
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_VALUE, "Invalid integer64 property 0x%04x", pname);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API void* AL_APIENTRY alGetPointerSOFT(ALenum pname)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return nullptr;
|
||||
|
||||
std::lock_guard<std::mutex> _{context->mPropLock};
|
||||
void *value{nullptr};
|
||||
switch(pname)
|
||||
{
|
||||
case AL_EVENT_CALLBACK_FUNCTION_SOFT:
|
||||
value = reinterpret_cast<void*>(context->mEventCb);
|
||||
break;
|
||||
|
||||
case AL_EVENT_CALLBACK_USER_PARAM_SOFT:
|
||||
value = context->mEventParam;
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_VALUE, "Invalid pointer property 0x%04x", pname);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alGetBooleanv(ALenum pname, ALboolean *values)
|
||||
START_API_FUNC
|
||||
{
|
||||
if(values)
|
||||
{
|
||||
switch(pname)
|
||||
{
|
||||
case AL_DOPPLER_FACTOR:
|
||||
case AL_DOPPLER_VELOCITY:
|
||||
case AL_DISTANCE_MODEL:
|
||||
case AL_SPEED_OF_SOUND:
|
||||
case AL_DEFERRED_UPDATES_SOFT:
|
||||
case AL_GAIN_LIMIT_SOFT:
|
||||
case AL_NUM_RESAMPLERS_SOFT:
|
||||
case AL_DEFAULT_RESAMPLER_SOFT:
|
||||
values[0] = alGetBoolean(pname);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
if(!values)
|
||||
context->setError(AL_INVALID_VALUE, "NULL pointer");
|
||||
else switch(pname)
|
||||
{
|
||||
default:
|
||||
context->setError(AL_INVALID_VALUE, "Invalid boolean-vector property 0x%04x", pname);
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alGetDoublev(ALenum pname, ALdouble *values)
|
||||
START_API_FUNC
|
||||
{
|
||||
if(values)
|
||||
{
|
||||
switch(pname)
|
||||
{
|
||||
case AL_DOPPLER_FACTOR:
|
||||
case AL_DOPPLER_VELOCITY:
|
||||
case AL_DISTANCE_MODEL:
|
||||
case AL_SPEED_OF_SOUND:
|
||||
case AL_DEFERRED_UPDATES_SOFT:
|
||||
case AL_GAIN_LIMIT_SOFT:
|
||||
case AL_NUM_RESAMPLERS_SOFT:
|
||||
case AL_DEFAULT_RESAMPLER_SOFT:
|
||||
values[0] = alGetDouble(pname);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
if(!values)
|
||||
context->setError(AL_INVALID_VALUE, "NULL pointer");
|
||||
else switch(pname)
|
||||
{
|
||||
default:
|
||||
context->setError(AL_INVALID_VALUE, "Invalid double-vector property 0x%04x", pname);
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alGetFloatv(ALenum pname, ALfloat *values)
|
||||
START_API_FUNC
|
||||
{
|
||||
if(values)
|
||||
{
|
||||
switch(pname)
|
||||
{
|
||||
case AL_DOPPLER_FACTOR:
|
||||
case AL_DOPPLER_VELOCITY:
|
||||
case AL_DISTANCE_MODEL:
|
||||
case AL_SPEED_OF_SOUND:
|
||||
case AL_DEFERRED_UPDATES_SOFT:
|
||||
case AL_GAIN_LIMIT_SOFT:
|
||||
case AL_NUM_RESAMPLERS_SOFT:
|
||||
case AL_DEFAULT_RESAMPLER_SOFT:
|
||||
values[0] = alGetFloat(pname);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
if(!values)
|
||||
context->setError(AL_INVALID_VALUE, "NULL pointer");
|
||||
else switch(pname)
|
||||
{
|
||||
default:
|
||||
context->setError(AL_INVALID_VALUE, "Invalid float-vector property 0x%04x", pname);
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alGetIntegerv(ALenum pname, ALint *values)
|
||||
START_API_FUNC
|
||||
{
|
||||
if(values)
|
||||
{
|
||||
switch(pname)
|
||||
{
|
||||
case AL_DOPPLER_FACTOR:
|
||||
case AL_DOPPLER_VELOCITY:
|
||||
case AL_DISTANCE_MODEL:
|
||||
case AL_SPEED_OF_SOUND:
|
||||
case AL_DEFERRED_UPDATES_SOFT:
|
||||
case AL_GAIN_LIMIT_SOFT:
|
||||
case AL_NUM_RESAMPLERS_SOFT:
|
||||
case AL_DEFAULT_RESAMPLER_SOFT:
|
||||
values[0] = alGetInteger(pname);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
if(!values)
|
||||
context->setError(AL_INVALID_VALUE, "NULL pointer");
|
||||
else switch(pname)
|
||||
{
|
||||
default:
|
||||
context->setError(AL_INVALID_VALUE, "Invalid integer-vector property 0x%04x", pname);
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
extern "C" AL_API void AL_APIENTRY alGetInteger64vSOFT(ALenum pname, ALint64SOFT *values)
|
||||
START_API_FUNC
|
||||
{
|
||||
if(values)
|
||||
{
|
||||
switch(pname)
|
||||
{
|
||||
case AL_DOPPLER_FACTOR:
|
||||
case AL_DOPPLER_VELOCITY:
|
||||
case AL_DISTANCE_MODEL:
|
||||
case AL_SPEED_OF_SOUND:
|
||||
case AL_DEFERRED_UPDATES_SOFT:
|
||||
case AL_GAIN_LIMIT_SOFT:
|
||||
case AL_NUM_RESAMPLERS_SOFT:
|
||||
case AL_DEFAULT_RESAMPLER_SOFT:
|
||||
values[0] = alGetInteger64SOFT(pname);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
if(!values)
|
||||
context->setError(AL_INVALID_VALUE, "NULL pointer");
|
||||
else switch(pname)
|
||||
{
|
||||
default:
|
||||
context->setError(AL_INVALID_VALUE, "Invalid integer64-vector property 0x%04x", pname);
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API void AL_APIENTRY alGetPointervSOFT(ALenum pname, void **values)
|
||||
START_API_FUNC
|
||||
{
|
||||
if(values)
|
||||
{
|
||||
switch(pname)
|
||||
{
|
||||
case AL_EVENT_CALLBACK_FUNCTION_SOFT:
|
||||
case AL_EVENT_CALLBACK_USER_PARAM_SOFT:
|
||||
values[0] = alGetPointerSOFT(pname);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
if(!values)
|
||||
context->setError(AL_INVALID_VALUE, "NULL pointer");
|
||||
else switch(pname)
|
||||
{
|
||||
default:
|
||||
context->setError(AL_INVALID_VALUE, "Invalid pointer-vector property 0x%04x", pname);
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API const ALchar* AL_APIENTRY alGetString(ALenum pname)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return nullptr;
|
||||
|
||||
const ALchar *value{nullptr};
|
||||
switch(pname)
|
||||
{
|
||||
case AL_VENDOR:
|
||||
value = alVendor;
|
||||
break;
|
||||
|
||||
case AL_VERSION:
|
||||
value = alVersion;
|
||||
break;
|
||||
|
||||
case AL_RENDERER:
|
||||
value = alRenderer;
|
||||
break;
|
||||
|
||||
case AL_EXTENSIONS:
|
||||
value = context->mExtensionList;
|
||||
break;
|
||||
|
||||
case AL_NO_ERROR:
|
||||
value = alNoError;
|
||||
break;
|
||||
|
||||
case AL_INVALID_NAME:
|
||||
value = alErrInvalidName;
|
||||
break;
|
||||
|
||||
case AL_INVALID_ENUM:
|
||||
value = alErrInvalidEnum;
|
||||
break;
|
||||
|
||||
case AL_INVALID_VALUE:
|
||||
value = alErrInvalidValue;
|
||||
break;
|
||||
|
||||
case AL_INVALID_OPERATION:
|
||||
value = alErrInvalidOp;
|
||||
break;
|
||||
|
||||
case AL_OUT_OF_MEMORY:
|
||||
value = alErrOutOfMemory;
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_VALUE, "Invalid string property 0x%04x", pname);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alDopplerFactor(ALfloat value)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
if(!(value >= 0.0f && std::isfinite(value)))
|
||||
context->setError(AL_INVALID_VALUE, "Doppler factor %f out of range", value);
|
||||
else
|
||||
{
|
||||
std::lock_guard<std::mutex> _{context->mPropLock};
|
||||
context->mDopplerFactor = value;
|
||||
DO_UPDATEPROPS();
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alDopplerVelocity(ALfloat value)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
if((context->mEnabledEvts.load(std::memory_order_relaxed)&EventType_Deprecated))
|
||||
{
|
||||
std::lock_guard<std::mutex> _{context->mEventCbLock};
|
||||
ALbitfieldSOFT enabledevts{context->mEnabledEvts.load(std::memory_order_relaxed)};
|
||||
if((enabledevts&EventType_Deprecated) && context->mEventCb)
|
||||
{
|
||||
static const char msg[] =
|
||||
"alDopplerVelocity is deprecated in AL1.1, use alSpeedOfSound";
|
||||
const ALsizei msglen{sizeof(msg)-1};
|
||||
(*context->mEventCb)(AL_EVENT_TYPE_DEPRECATED_SOFT, 0, 0, msglen, msg,
|
||||
context->mEventParam);
|
||||
}
|
||||
}
|
||||
|
||||
if(!(value >= 0.0f && std::isfinite(value)))
|
||||
context->setError(AL_INVALID_VALUE, "Doppler velocity %f out of range", value);
|
||||
else
|
||||
{
|
||||
std::lock_guard<std::mutex> _{context->mPropLock};
|
||||
context->mDopplerVelocity = value;
|
||||
DO_UPDATEPROPS();
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alSpeedOfSound(ALfloat value)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
if(!(value > 0.0f && std::isfinite(value)))
|
||||
context->setError(AL_INVALID_VALUE, "Speed of sound %f out of range", value);
|
||||
else
|
||||
{
|
||||
std::lock_guard<std::mutex> _{context->mPropLock};
|
||||
context->mSpeedOfSound = value;
|
||||
DO_UPDATEPROPS();
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alDistanceModel(ALenum value)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
if(!(value == AL_INVERSE_DISTANCE || value == AL_INVERSE_DISTANCE_CLAMPED ||
|
||||
value == AL_LINEAR_DISTANCE || value == AL_LINEAR_DISTANCE_CLAMPED ||
|
||||
value == AL_EXPONENT_DISTANCE || value == AL_EXPONENT_DISTANCE_CLAMPED ||
|
||||
value == AL_NONE))
|
||||
context->setError(AL_INVALID_VALUE, "Distance model 0x%04x out of range", value);
|
||||
else
|
||||
{
|
||||
std::lock_guard<std::mutex> _{context->mPropLock};
|
||||
context->mDistanceModel = static_cast<DistanceModel>(value);
|
||||
if(!context->mSourceDistanceModel)
|
||||
DO_UPDATEPROPS();
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alDeferUpdatesSOFT(void)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
context->deferUpdates();
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alProcessUpdatesSOFT(void)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
context->processUpdates();
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
|
||||
AL_API const ALchar* AL_APIENTRY alGetStringiSOFT(ALenum pname, ALsizei index)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return nullptr;
|
||||
|
||||
const ALchar *value{nullptr};
|
||||
switch(pname)
|
||||
{
|
||||
case AL_RESAMPLER_NAME_SOFT:
|
||||
if(index < 0 || index > static_cast<ALint>(Resampler::Max))
|
||||
context->setError(AL_INVALID_VALUE, "Resampler name index %d out of range", index);
|
||||
else
|
||||
value = GetResamplerName(static_cast<Resampler>(index));
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_VALUE, "Invalid string indexed property");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
|
||||
void UpdateContextProps(ALCcontext *context)
|
||||
{
|
||||
/* Get an unused proprty container, or allocate a new one as needed. */
|
||||
ALcontextProps *props{context->mFreeContextProps.load(std::memory_order_acquire)};
|
||||
if(!props)
|
||||
props = new ALcontextProps{};
|
||||
else
|
||||
{
|
||||
ALcontextProps *next;
|
||||
do {
|
||||
next = props->next.load(std::memory_order_relaxed);
|
||||
} while(context->mFreeContextProps.compare_exchange_weak(props, next,
|
||||
std::memory_order_seq_cst, std::memory_order_acquire) == 0);
|
||||
}
|
||||
|
||||
/* Copy in current property values. */
|
||||
props->DopplerFactor = context->mDopplerFactor;
|
||||
props->DopplerVelocity = context->mDopplerVelocity;
|
||||
props->SpeedOfSound = context->mSpeedOfSound;
|
||||
|
||||
props->SourceDistanceModel = context->mSourceDistanceModel;
|
||||
props->mDistanceModel = context->mDistanceModel;
|
||||
|
||||
/* Set the new container for updating internal parameters. */
|
||||
props = context->mUpdate.exchange(props, std::memory_order_acq_rel);
|
||||
if(props)
|
||||
{
|
||||
/* If there was an unused update container, put it back in the
|
||||
* freelist.
|
||||
*/
|
||||
AtomicReplaceHead(context->mFreeContextProps, props);
|
||||
}
|
||||
}
|
||||
-4300
File diff suppressed because it is too large
Load Diff
-399
@@ -1,399 +0,0 @@
|
||||
#ifndef ALC_MAIN_H
|
||||
#define ALC_MAIN_H
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "AL/al.h"
|
||||
#include "AL/alc.h"
|
||||
#include "AL/alext.h"
|
||||
|
||||
#include "albyte.h"
|
||||
#include "almalloc.h"
|
||||
#include "alnumeric.h"
|
||||
#include "alspan.h"
|
||||
#include "ambidefs.h"
|
||||
#include "atomic.h"
|
||||
#include "devformat.h"
|
||||
#include "filters/splitter.h"
|
||||
#include "hrtf.h"
|
||||
#include "inprogext.h"
|
||||
#include "intrusive_ptr.h"
|
||||
#include "vector.h"
|
||||
|
||||
class BFormatDec;
|
||||
struct ALbuffer;
|
||||
struct ALeffect;
|
||||
struct ALfilter;
|
||||
struct BackendBase;
|
||||
struct Compressor;
|
||||
struct EffectState;
|
||||
struct Uhj2Encoder;
|
||||
struct bs2b;
|
||||
|
||||
|
||||
#define MIN_OUTPUT_RATE 8000
|
||||
#define DEFAULT_OUTPUT_RATE 44100
|
||||
#define DEFAULT_UPDATE_SIZE 882 /* 20ms */
|
||||
#define DEFAULT_NUM_UPDATES 3
|
||||
|
||||
|
||||
enum DeviceType {
|
||||
Playback,
|
||||
Capture,
|
||||
Loopback
|
||||
};
|
||||
|
||||
|
||||
enum RenderMode {
|
||||
NormalRender,
|
||||
StereoPair,
|
||||
HrtfRender
|
||||
};
|
||||
|
||||
|
||||
struct InputRemixMap {
|
||||
struct TargetMix { Channel channel; float mix; };
|
||||
|
||||
Channel channel;
|
||||
std::array<TargetMix,2> targets;
|
||||
};
|
||||
|
||||
|
||||
struct BufferSubList {
|
||||
uint64_t FreeMask{~0_u64};
|
||||
ALbuffer *Buffers{nullptr}; /* 64 */
|
||||
|
||||
BufferSubList() noexcept = default;
|
||||
BufferSubList(const BufferSubList&) = delete;
|
||||
BufferSubList(BufferSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Buffers{rhs.Buffers}
|
||||
{ rhs.FreeMask = ~0_u64; rhs.Buffers = nullptr; }
|
||||
~BufferSubList();
|
||||
|
||||
BufferSubList& operator=(const BufferSubList&) = delete;
|
||||
BufferSubList& operator=(BufferSubList&& rhs) noexcept
|
||||
{ std::swap(FreeMask, rhs.FreeMask); std::swap(Buffers, rhs.Buffers); return *this; }
|
||||
};
|
||||
|
||||
struct EffectSubList {
|
||||
uint64_t FreeMask{~0_u64};
|
||||
ALeffect *Effects{nullptr}; /* 64 */
|
||||
|
||||
EffectSubList() noexcept = default;
|
||||
EffectSubList(const EffectSubList&) = delete;
|
||||
EffectSubList(EffectSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Effects{rhs.Effects}
|
||||
{ rhs.FreeMask = ~0_u64; rhs.Effects = nullptr; }
|
||||
~EffectSubList();
|
||||
|
||||
EffectSubList& operator=(const EffectSubList&) = delete;
|
||||
EffectSubList& operator=(EffectSubList&& rhs) noexcept
|
||||
{ std::swap(FreeMask, rhs.FreeMask); std::swap(Effects, rhs.Effects); return *this; }
|
||||
};
|
||||
|
||||
struct FilterSubList {
|
||||
uint64_t FreeMask{~0_u64};
|
||||
ALfilter *Filters{nullptr}; /* 64 */
|
||||
|
||||
FilterSubList() noexcept = default;
|
||||
FilterSubList(const FilterSubList&) = delete;
|
||||
FilterSubList(FilterSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Filters{rhs.Filters}
|
||||
{ rhs.FreeMask = ~0_u64; rhs.Filters = nullptr; }
|
||||
~FilterSubList();
|
||||
|
||||
FilterSubList& operator=(const FilterSubList&) = delete;
|
||||
FilterSubList& operator=(FilterSubList&& rhs) noexcept
|
||||
{ std::swap(FreeMask, rhs.FreeMask); std::swap(Filters, rhs.Filters); return *this; }
|
||||
};
|
||||
|
||||
|
||||
/* Maximum delay in samples for speaker distance compensation. */
|
||||
#define MAX_DELAY_LENGTH 1024
|
||||
|
||||
class DistanceComp {
|
||||
public:
|
||||
struct DistData {
|
||||
ALfloat Gain{1.0f};
|
||||
ALuint Length{0u}; /* Valid range is [0...MAX_DELAY_LENGTH). */
|
||||
ALfloat *Buffer{nullptr};
|
||||
};
|
||||
|
||||
private:
|
||||
std::array<DistData,MAX_OUTPUT_CHANNELS> mChannels;
|
||||
al::vector<ALfloat,16> mSamples;
|
||||
|
||||
public:
|
||||
void setSampleCount(size_t new_size) { mSamples.resize(new_size); }
|
||||
void clear() noexcept
|
||||
{
|
||||
for(auto &chan : mChannels)
|
||||
{
|
||||
chan.Gain = 1.0f;
|
||||
chan.Length = 0;
|
||||
chan.Buffer = nullptr;
|
||||
}
|
||||
using SampleVecT = decltype(mSamples);
|
||||
SampleVecT{}.swap(mSamples);
|
||||
}
|
||||
|
||||
ALfloat *getSamples() noexcept { return mSamples.data(); }
|
||||
|
||||
al::span<DistData,MAX_OUTPUT_CHANNELS> as_span() { return mChannels; }
|
||||
};
|
||||
|
||||
struct BFChannelConfig {
|
||||
ALfloat Scale;
|
||||
ALuint Index;
|
||||
};
|
||||
|
||||
/* Size for temporary storage of buffer data, in ALfloats. Larger values need
|
||||
* more memory, while smaller values may need more iterations. The value needs
|
||||
* to be a sensible size, however, as it constrains the max stepping value used
|
||||
* for mixing, as well as the maximum number of samples per mixing iteration.
|
||||
*/
|
||||
#define BUFFERSIZE 1024
|
||||
|
||||
using FloatBufferLine = std::array<float,BUFFERSIZE>;
|
||||
|
||||
/* Maximum number of samples to pad on the ends of a buffer for resampling.
|
||||
* Note that the padding is symmetric (half at the beginning and half at the
|
||||
* end)!
|
||||
*/
|
||||
#define MAX_RESAMPLER_PADDING 48
|
||||
|
||||
|
||||
struct FrontStablizer {
|
||||
static constexpr size_t DelayLength{256u};
|
||||
|
||||
alignas(16) float DelayBuf[MAX_OUTPUT_CHANNELS][DelayLength];
|
||||
|
||||
BandSplitter LFilter, RFilter;
|
||||
alignas(16) float LSplit[2][BUFFERSIZE];
|
||||
alignas(16) float RSplit[2][BUFFERSIZE];
|
||||
|
||||
alignas(16) float TempBuf[BUFFERSIZE + DelayLength];
|
||||
|
||||
DEF_NEWDEL(FrontStablizer)
|
||||
};
|
||||
|
||||
|
||||
struct MixParams {
|
||||
/* Coefficient channel mapping for mixing to the buffer. */
|
||||
std::array<BFChannelConfig,MAX_OUTPUT_CHANNELS> AmbiMap{};
|
||||
|
||||
al::span<FloatBufferLine> Buffer;
|
||||
};
|
||||
|
||||
struct RealMixParams {
|
||||
al::span<const InputRemixMap> RemixMap;
|
||||
std::array<ALuint,MaxChannels> ChannelIndex{};
|
||||
|
||||
al::span<FloatBufferLine> Buffer;
|
||||
};
|
||||
|
||||
enum {
|
||||
// Frequency was requested by the app or config file
|
||||
FrequencyRequest,
|
||||
// Channel configuration was requested by the config file
|
||||
ChannelsRequest,
|
||||
// Sample type was requested by the config file
|
||||
SampleTypeRequest,
|
||||
|
||||
// Specifies if the DSP is paused at user request
|
||||
DevicePaused,
|
||||
// Specifies if the device is currently running
|
||||
DeviceRunning,
|
||||
|
||||
DeviceFlagsCount
|
||||
};
|
||||
|
||||
struct ALCdevice : public al::intrusive_ref<ALCdevice> {
|
||||
std::atomic<bool> Connected{true};
|
||||
const DeviceType Type{};
|
||||
|
||||
ALuint Frequency{};
|
||||
ALuint UpdateSize{};
|
||||
ALuint BufferSize{};
|
||||
|
||||
DevFmtChannels FmtChans{};
|
||||
DevFmtType FmtType{};
|
||||
ALboolean IsHeadphones{AL_FALSE};
|
||||
ALuint mAmbiOrder{0};
|
||||
/* For DevFmtAmbi* output only, specifies the channel order and
|
||||
* normalization.
|
||||
*/
|
||||
AmbiLayout mAmbiLayout{AmbiLayout::Default};
|
||||
AmbiNorm mAmbiScale{AmbiNorm::Default};
|
||||
|
||||
ALCenum LimiterState{ALC_DONT_CARE_SOFT};
|
||||
|
||||
std::string DeviceName;
|
||||
|
||||
// Device flags
|
||||
al::bitfield<DeviceFlagsCount> Flags{};
|
||||
|
||||
std::string HrtfName;
|
||||
al::vector<std::string> HrtfList;
|
||||
ALCenum HrtfStatus{ALC_FALSE};
|
||||
|
||||
std::atomic<ALCenum> LastError{ALC_NO_ERROR};
|
||||
|
||||
// Maximum number of sources that can be created
|
||||
ALuint SourcesMax{};
|
||||
// Maximum number of slots that can be created
|
||||
ALuint AuxiliaryEffectSlotMax{};
|
||||
|
||||
ALCuint NumMonoSources{};
|
||||
ALCuint NumStereoSources{};
|
||||
ALCuint NumAuxSends{};
|
||||
|
||||
// Map of Buffers for this device
|
||||
std::mutex BufferLock;
|
||||
al::vector<BufferSubList> BufferList;
|
||||
|
||||
// Map of Effects for this device
|
||||
std::mutex EffectLock;
|
||||
al::vector<EffectSubList> EffectList;
|
||||
|
||||
// Map of Filters for this device
|
||||
std::mutex FilterLock;
|
||||
al::vector<FilterSubList> FilterList;
|
||||
|
||||
/* Rendering mode. */
|
||||
RenderMode mRenderMode{NormalRender};
|
||||
|
||||
/* The average speaker distance as determined by the ambdec configuration,
|
||||
* HRTF data set, or the NFC-HOA reference delay. Only used for NFC.
|
||||
*/
|
||||
ALfloat AvgSpeakerDist{0.0f};
|
||||
|
||||
ALuint SamplesDone{0u};
|
||||
std::chrono::nanoseconds ClockBase{0};
|
||||
std::chrono::nanoseconds FixedLatency{0};
|
||||
|
||||
/* Temp storage used for mixer processing. */
|
||||
alignas(16) ALfloat SourceData[BUFFERSIZE + MAX_RESAMPLER_PADDING];
|
||||
alignas(16) ALfloat ResampledData[BUFFERSIZE];
|
||||
alignas(16) ALfloat FilteredData[BUFFERSIZE];
|
||||
union {
|
||||
alignas(16) ALfloat HrtfSourceData[BUFFERSIZE + HRTF_HISTORY_LENGTH];
|
||||
alignas(16) ALfloat NfcSampleData[BUFFERSIZE];
|
||||
};
|
||||
|
||||
/* Persistent storage for HRTF mixing. */
|
||||
alignas(16) float2 HrtfAccumData[BUFFERSIZE + HRIR_LENGTH];
|
||||
|
||||
/* Mixing buffer used by the Dry mix and Real output. */
|
||||
al::vector<FloatBufferLine, 16> MixBuffer;
|
||||
|
||||
/* The "dry" path corresponds to the main output. */
|
||||
MixParams Dry;
|
||||
ALuint NumChannelsPerOrder[MAX_AMBI_ORDER+1]{};
|
||||
|
||||
/* "Real" output, which will be written to the device buffer. May alias the
|
||||
* dry buffer.
|
||||
*/
|
||||
RealMixParams RealOut;
|
||||
|
||||
/* HRTF state and info */
|
||||
std::unique_ptr<DirectHrtfState> mHrtfState;
|
||||
HrtfStore *mHrtf{nullptr};
|
||||
|
||||
/* Ambisonic-to-UHJ encoder */
|
||||
std::unique_ptr<Uhj2Encoder> Uhj_Encoder;
|
||||
|
||||
/* Ambisonic decoder for speakers */
|
||||
std::unique_ptr<BFormatDec> AmbiDecoder;
|
||||
|
||||
/* Stereo-to-binaural filter */
|
||||
std::unique_ptr<bs2b> Bs2b;
|
||||
|
||||
using PostProc = void(ALCdevice::*)(const size_t SamplesToDo);
|
||||
PostProc PostProcess{nullptr};
|
||||
|
||||
std::unique_ptr<FrontStablizer> Stablizer;
|
||||
|
||||
std::unique_ptr<Compressor> Limiter;
|
||||
|
||||
/* Delay buffers used to compensate for speaker distances. */
|
||||
DistanceComp ChannelDelay;
|
||||
|
||||
/* Dithering control. */
|
||||
ALfloat DitherDepth{0.0f};
|
||||
ALuint DitherSeed{0u};
|
||||
|
||||
/* Running count of the mixer invocations, in 31.1 fixed point. This
|
||||
* actually increments *twice* when mixing, first at the start and then at
|
||||
* the end, so the bottom bit indicates if the device is currently mixing
|
||||
* and the upper bits indicates how many mixes have been done.
|
||||
*/
|
||||
RefCount MixCount{0u};
|
||||
|
||||
// Contexts created on this device
|
||||
std::atomic<al::FlexArray<ALCcontext*>*> mContexts{nullptr};
|
||||
|
||||
/* This lock protects the device state (format, update size, etc) from
|
||||
* being from being changed in multiple threads, or being accessed while
|
||||
* being changed. It's also used to serialize calls to the backend.
|
||||
*/
|
||||
std::mutex StateLock;
|
||||
std::unique_ptr<BackendBase> Backend;
|
||||
|
||||
|
||||
ALCdevice(DeviceType type);
|
||||
ALCdevice(const ALCdevice&) = delete;
|
||||
ALCdevice& operator=(const ALCdevice&) = delete;
|
||||
~ALCdevice();
|
||||
|
||||
ALuint bytesFromFmt() const noexcept { return BytesFromDevFmt(FmtType); }
|
||||
ALuint channelsFromFmt() const noexcept { return ChannelsFromDevFmt(FmtChans, mAmbiOrder); }
|
||||
ALuint frameSizeFromFmt() const noexcept { return bytesFromFmt() * channelsFromFmt(); }
|
||||
|
||||
void ProcessHrtf(const size_t SamplesToDo);
|
||||
void ProcessAmbiDec(const size_t SamplesToDo);
|
||||
void ProcessUhj(const size_t SamplesToDo);
|
||||
void ProcessBs2b(const size_t SamplesToDo);
|
||||
|
||||
inline void postProcess(const size_t SamplesToDo)
|
||||
{ if LIKELY(PostProcess) (this->*PostProcess)(SamplesToDo); }
|
||||
|
||||
DEF_NEWDEL(ALCdevice)
|
||||
};
|
||||
|
||||
/* Must be less than 15 characters (16 including terminating null) for
|
||||
* compatibility with pthread_setname_np limitations. */
|
||||
#define MIXER_THREAD_NAME "alsoft-mixer"
|
||||
|
||||
#define RECORD_THREAD_NAME "alsoft-record"
|
||||
|
||||
|
||||
extern ALint RTPrioLevel;
|
||||
void SetRTPriority(void);
|
||||
|
||||
void SetDefaultChannelOrder(ALCdevice *device);
|
||||
void SetDefaultWFXChannelOrder(ALCdevice *device);
|
||||
|
||||
const ALCchar *DevFmtTypeString(DevFmtType type) noexcept;
|
||||
const ALCchar *DevFmtChannelsString(DevFmtChannels chans) noexcept;
|
||||
|
||||
/**
|
||||
* GetChannelIdxByName
|
||||
*
|
||||
* Returns the index for the given channel name (e.g. FrontCenter), or
|
||||
* INVALID_CHANNEL_INDEX if it doesn't exist.
|
||||
*/
|
||||
inline ALuint GetChannelIdxByName(const RealMixParams &real, Channel chan) noexcept
|
||||
{ return real.ChannelIndex[chan]; }
|
||||
#define INVALID_CHANNEL_INDEX ~0u
|
||||
|
||||
|
||||
al::vector<std::string> SearchDataFiles(const char *match, const char *subdir);
|
||||
|
||||
#endif
|
||||
@@ -1,550 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 1999-2007 by authors.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#ifdef _WIN32
|
||||
#ifdef __MINGW32__
|
||||
#define _WIN32_IE 0x501
|
||||
#else
|
||||
#define _WIN32_IE 0x400
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "alconfig.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
#ifdef _WIN32_IE
|
||||
#include <windows.h>
|
||||
#include <shlobj.h>
|
||||
#endif
|
||||
#ifdef __APPLE__
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "alfstream.h"
|
||||
#include "alstring.h"
|
||||
#include "compat.h"
|
||||
#include "logging.h"
|
||||
#include "strutils.h"
|
||||
#include "vector.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
struct ConfigEntry {
|
||||
std::string key;
|
||||
std::string value;
|
||||
};
|
||||
al::vector<ConfigEntry> ConfOpts;
|
||||
|
||||
|
||||
std::string &lstrip(std::string &line)
|
||||
{
|
||||
size_t pos{0};
|
||||
while(pos < line.length() && std::isspace(line[pos]))
|
||||
++pos;
|
||||
line.erase(0, pos);
|
||||
return line;
|
||||
}
|
||||
|
||||
bool readline(std::istream &f, std::string &output)
|
||||
{
|
||||
while(f.good() && f.peek() == '\n')
|
||||
f.ignore();
|
||||
|
||||
return std::getline(f, output) && !output.empty();
|
||||
}
|
||||
|
||||
std::string expdup(const char *str)
|
||||
{
|
||||
std::string output;
|
||||
|
||||
std::string envval;
|
||||
while(*str != '\0')
|
||||
{
|
||||
const char *addstr;
|
||||
size_t addstrlen;
|
||||
|
||||
if(str[0] != '$')
|
||||
{
|
||||
const char *next = std::strchr(str, '$');
|
||||
addstr = str;
|
||||
addstrlen = next ? static_cast<size_t>(next-str) : std::strlen(str);
|
||||
|
||||
str += addstrlen;
|
||||
}
|
||||
else
|
||||
{
|
||||
str++;
|
||||
if(*str == '$')
|
||||
{
|
||||
const char *next = std::strchr(str+1, '$');
|
||||
addstr = str;
|
||||
addstrlen = next ? static_cast<size_t>(next-str) : std::strlen(str);
|
||||
|
||||
str += addstrlen;
|
||||
}
|
||||
else
|
||||
{
|
||||
const bool hasbraces{(*str == '{')};
|
||||
|
||||
if(hasbraces) str++;
|
||||
const char *envstart = str;
|
||||
while(std::isalnum(*str) || *str == '_')
|
||||
++str;
|
||||
if(hasbraces && *str != '}')
|
||||
continue;
|
||||
const std::string envname{envstart, str};
|
||||
if(hasbraces) str++;
|
||||
|
||||
envval = al::getenv(envname.c_str()).value_or(std::string{});
|
||||
addstr = envval.data();
|
||||
addstrlen = envval.length();
|
||||
}
|
||||
}
|
||||
if(addstrlen == 0)
|
||||
continue;
|
||||
|
||||
output.append(addstr, addstrlen);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
void LoadConfigFromFile(std::istream &f)
|
||||
{
|
||||
std::string curSection;
|
||||
std::string buffer;
|
||||
|
||||
while(readline(f, buffer))
|
||||
{
|
||||
if(lstrip(buffer).empty())
|
||||
continue;
|
||||
|
||||
if(buffer[0] == '[')
|
||||
{
|
||||
char *line{&buffer[0]};
|
||||
char *section = line+1;
|
||||
char *endsection;
|
||||
|
||||
endsection = std::strchr(section, ']');
|
||||
if(!endsection || section == endsection)
|
||||
{
|
||||
ERR(" config parse error: bad line \"%s\"\n", line);
|
||||
continue;
|
||||
}
|
||||
if(endsection[1] != 0)
|
||||
{
|
||||
char *end = endsection+1;
|
||||
while(std::isspace(*end))
|
||||
++end;
|
||||
if(*end != 0 && *end != '#')
|
||||
{
|
||||
ERR(" config parse error: bad line \"%s\"\n", line);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
*endsection = 0;
|
||||
|
||||
curSection.clear();
|
||||
if(al::strcasecmp(section, "general") != 0)
|
||||
{
|
||||
do {
|
||||
char *nextp = std::strchr(section, '%');
|
||||
if(!nextp)
|
||||
{
|
||||
curSection += section;
|
||||
break;
|
||||
}
|
||||
|
||||
curSection.append(section, nextp);
|
||||
section = nextp;
|
||||
|
||||
if(((section[1] >= '0' && section[1] <= '9') ||
|
||||
(section[1] >= 'a' && section[1] <= 'f') ||
|
||||
(section[1] >= 'A' && section[1] <= 'F')) &&
|
||||
((section[2] >= '0' && section[2] <= '9') ||
|
||||
(section[2] >= 'a' && section[2] <= 'f') ||
|
||||
(section[2] >= 'A' && section[2] <= 'F')))
|
||||
{
|
||||
int b{0};
|
||||
if(section[1] >= '0' && section[1] <= '9')
|
||||
b = (section[1]-'0') << 4;
|
||||
else if(section[1] >= 'a' && section[1] <= 'f')
|
||||
b = (section[1]-'a'+0xa) << 4;
|
||||
else if(section[1] >= 'A' && section[1] <= 'F')
|
||||
b = (section[1]-'A'+0x0a) << 4;
|
||||
if(section[2] >= '0' && section[2] <= '9')
|
||||
b |= (section[2]-'0');
|
||||
else if(section[2] >= 'a' && section[2] <= 'f')
|
||||
b |= (section[2]-'a'+0xa);
|
||||
else if(section[2] >= 'A' && section[2] <= 'F')
|
||||
b |= (section[2]-'A'+0x0a);
|
||||
curSection += static_cast<char>(b);
|
||||
section += 3;
|
||||
}
|
||||
else if(section[1] == '%')
|
||||
{
|
||||
curSection += '%';
|
||||
section += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
curSection += '%';
|
||||
section += 1;
|
||||
}
|
||||
} while(*section != 0);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
auto cmtpos = std::min(buffer.find('#'), buffer.size());
|
||||
while(cmtpos > 0 && std::isspace(buffer[cmtpos-1]))
|
||||
--cmtpos;
|
||||
if(!cmtpos) continue;
|
||||
buffer.erase(cmtpos);
|
||||
|
||||
auto sep = buffer.find('=');
|
||||
if(sep == std::string::npos)
|
||||
{
|
||||
ERR(" config parse error: malformed option line: \"%s\"\n", buffer.c_str());
|
||||
continue;
|
||||
}
|
||||
auto keyend = sep++;
|
||||
while(keyend > 0 && std::isspace(buffer[keyend-1]))
|
||||
--keyend;
|
||||
if(!keyend)
|
||||
{
|
||||
ERR(" config parse error: malformed option line: \"%s\"\n", buffer.c_str());
|
||||
continue;
|
||||
}
|
||||
while(sep < buffer.size() && std::isspace(buffer[sep]))
|
||||
sep++;
|
||||
|
||||
std::string fullKey;
|
||||
if(!curSection.empty())
|
||||
{
|
||||
fullKey += curSection;
|
||||
fullKey += '/';
|
||||
}
|
||||
fullKey += buffer.substr(0u, keyend);
|
||||
|
||||
std::string value{(sep < buffer.size()) ? buffer.substr(sep) : std::string{}};
|
||||
if(value.size() > 1)
|
||||
{
|
||||
if((value.front() == '"' && value.back() == '"')
|
||||
|| (value.front() == '\'' && value.back() == '\''))
|
||||
{
|
||||
value.pop_back();
|
||||
value.erase(value.begin());
|
||||
}
|
||||
}
|
||||
|
||||
TRACE(" found '%s' = '%s'\n", fullKey.c_str(), value.c_str());
|
||||
|
||||
/* Check if we already have this option set */
|
||||
auto find_key = [&fullKey](const ConfigEntry &entry) -> bool
|
||||
{ return entry.key == fullKey; };
|
||||
auto ent = std::find_if(ConfOpts.begin(), ConfOpts.end(), find_key);
|
||||
if(ent != ConfOpts.end())
|
||||
{
|
||||
if(!value.empty())
|
||||
ent->value = expdup(value.c_str());
|
||||
else
|
||||
ConfOpts.erase(ent);
|
||||
}
|
||||
else if(!value.empty())
|
||||
ConfOpts.emplace_back(ConfigEntry{std::move(fullKey), expdup(value.c_str())});
|
||||
}
|
||||
ConfOpts.shrink_to_fit();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
void ReadALConfig()
|
||||
{
|
||||
WCHAR buffer[MAX_PATH];
|
||||
if(SHGetSpecialFolderPathW(nullptr, buffer, CSIDL_APPDATA, FALSE) != FALSE)
|
||||
{
|
||||
std::string filepath{wstr_to_utf8(buffer)};
|
||||
filepath += "\\alsoft.ini";
|
||||
|
||||
TRACE("Loading config %s...\n", filepath.c_str());
|
||||
al::ifstream f{filepath};
|
||||
if(f.is_open())
|
||||
LoadConfigFromFile(f);
|
||||
}
|
||||
|
||||
std::string ppath{GetProcBinary().path};
|
||||
if(!ppath.empty())
|
||||
{
|
||||
ppath += "\\alsoft.ini";
|
||||
TRACE("Loading config %s...\n", ppath.c_str());
|
||||
al::ifstream f{ppath};
|
||||
if(f.is_open())
|
||||
LoadConfigFromFile(f);
|
||||
}
|
||||
|
||||
if(auto confpath = al::getenv(L"ALSOFT_CONF"))
|
||||
{
|
||||
TRACE("Loading config %s...\n", wstr_to_utf8(confpath->c_str()).c_str());
|
||||
al::ifstream f{*confpath};
|
||||
if(f.is_open())
|
||||
LoadConfigFromFile(f);
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void ReadALConfig()
|
||||
{
|
||||
const char *str{"/etc/openal/alsoft.conf"};
|
||||
|
||||
TRACE("Loading config %s...\n", str);
|
||||
al::ifstream f{str};
|
||||
if(f.is_open())
|
||||
LoadConfigFromFile(f);
|
||||
f.close();
|
||||
|
||||
std::string confpaths{al::getenv("XDG_CONFIG_DIRS").value_or("/etc/xdg")};
|
||||
/* Go through the list in reverse, since "the order of base directories
|
||||
* denotes their importance; the first directory listed is the most
|
||||
* important". Ergo, we need to load the settings from the later dirs
|
||||
* first so that the settings in the earlier dirs override them.
|
||||
*/
|
||||
std::string fname;
|
||||
while(!confpaths.empty())
|
||||
{
|
||||
auto next = confpaths.find_last_of(':');
|
||||
if(next < confpaths.length())
|
||||
{
|
||||
fname = confpaths.substr(next+1);
|
||||
confpaths.erase(next);
|
||||
}
|
||||
else
|
||||
{
|
||||
fname = confpaths;
|
||||
confpaths.clear();
|
||||
}
|
||||
|
||||
if(fname.empty() || fname.front() != '/')
|
||||
WARN("Ignoring XDG config dir: %s\n", fname.c_str());
|
||||
else
|
||||
{
|
||||
if(fname.back() != '/') fname += "/alsoft.conf";
|
||||
else fname += "alsoft.conf";
|
||||
|
||||
TRACE("Loading config %s...\n", fname.c_str());
|
||||
f = al::ifstream{fname};
|
||||
if(f.is_open())
|
||||
LoadConfigFromFile(f);
|
||||
}
|
||||
fname.clear();
|
||||
}
|
||||
|
||||
#ifdef __APPLE__
|
||||
CFBundleRef mainBundle = CFBundleGetMainBundle();
|
||||
if(mainBundle)
|
||||
{
|
||||
unsigned char fileName[PATH_MAX];
|
||||
CFURLRef configURL;
|
||||
|
||||
if((configURL=CFBundleCopyResourceURL(mainBundle, CFSTR(".alsoftrc"), CFSTR(""), nullptr)) &&
|
||||
CFURLGetFileSystemRepresentation(configURL, true, fileName, sizeof(fileName)))
|
||||
{
|
||||
f = al::ifstream{reinterpret_cast<char*>(fileName)};
|
||||
if(f.is_open())
|
||||
LoadConfigFromFile(f);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if(auto homedir = al::getenv("HOME"))
|
||||
{
|
||||
fname = *homedir;
|
||||
if(fname.back() != '/') fname += "/.alsoftrc";
|
||||
else fname += ".alsoftrc";
|
||||
|
||||
TRACE("Loading config %s...\n", fname.c_str());
|
||||
f = al::ifstream{fname};
|
||||
if(f.is_open())
|
||||
LoadConfigFromFile(f);
|
||||
}
|
||||
|
||||
if(auto configdir = al::getenv("XDG_CONFIG_HOME"))
|
||||
{
|
||||
fname = *configdir;
|
||||
if(fname.back() != '/') fname += "/alsoft.conf";
|
||||
else fname += "alsoft.conf";
|
||||
}
|
||||
else
|
||||
{
|
||||
fname.clear();
|
||||
if(auto homedir = al::getenv("HOME"))
|
||||
{
|
||||
fname = *homedir;
|
||||
if(fname.back() != '/') fname += "/.config/alsoft.conf";
|
||||
else fname += ".config/alsoft.conf";
|
||||
}
|
||||
}
|
||||
if(!fname.empty())
|
||||
{
|
||||
TRACE("Loading config %s...\n", fname.c_str());
|
||||
f = al::ifstream{fname};
|
||||
if(f.is_open())
|
||||
LoadConfigFromFile(f);
|
||||
}
|
||||
|
||||
std::string ppath{GetProcBinary().path};
|
||||
if(!ppath.empty())
|
||||
{
|
||||
if(ppath.back() != '/') ppath += "/alsoft.conf";
|
||||
else ppath += "alsoft.conf";
|
||||
|
||||
TRACE("Loading config %s...\n", ppath.c_str());
|
||||
f = al::ifstream{ppath};
|
||||
if(f.is_open())
|
||||
LoadConfigFromFile(f);
|
||||
}
|
||||
|
||||
if(auto confname = al::getenv("ALSOFT_CONF"))
|
||||
{
|
||||
TRACE("Loading config %s...\n", confname->c_str());
|
||||
f = al::ifstream{*confname};
|
||||
if(f.is_open())
|
||||
LoadConfigFromFile(f);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
const char *GetConfigValue(const char *devName, const char *blockName, const char *keyName, const char *def)
|
||||
{
|
||||
if(!keyName)
|
||||
return def;
|
||||
|
||||
std::string key;
|
||||
if(blockName && al::strcasecmp(blockName, "general") != 0)
|
||||
{
|
||||
key = blockName;
|
||||
if(devName)
|
||||
{
|
||||
key += '/';
|
||||
key += devName;
|
||||
}
|
||||
key += '/';
|
||||
key += keyName;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(devName)
|
||||
{
|
||||
key = devName;
|
||||
key += '/';
|
||||
}
|
||||
key += keyName;
|
||||
}
|
||||
|
||||
auto iter = std::find_if(ConfOpts.cbegin(), ConfOpts.cend(),
|
||||
[&key](const ConfigEntry &entry) -> bool
|
||||
{ return entry.key == key; }
|
||||
);
|
||||
if(iter != ConfOpts.cend())
|
||||
{
|
||||
TRACE("Found %s = \"%s\"\n", key.c_str(), iter->value.c_str());
|
||||
if(!iter->value.empty())
|
||||
return iter->value.c_str();
|
||||
return def;
|
||||
}
|
||||
|
||||
if(!devName)
|
||||
{
|
||||
TRACE("Key %s not found\n", key.c_str());
|
||||
return def;
|
||||
}
|
||||
return GetConfigValue(nullptr, blockName, keyName, def);
|
||||
}
|
||||
|
||||
int ConfigValueExists(const char *devName, const char *blockName, const char *keyName)
|
||||
{
|
||||
const char *val = GetConfigValue(devName, blockName, keyName, "");
|
||||
return val[0] != 0;
|
||||
}
|
||||
|
||||
al::optional<std::string> ConfigValueStr(const char *devName, const char *blockName, const char *keyName)
|
||||
{
|
||||
const char *val = GetConfigValue(devName, blockName, keyName, "");
|
||||
if(!val[0]) return al::nullopt;
|
||||
|
||||
return al::make_optional<std::string>(val);
|
||||
}
|
||||
|
||||
al::optional<int> ConfigValueInt(const char *devName, const char *blockName, const char *keyName)
|
||||
{
|
||||
const char *val = GetConfigValue(devName, blockName, keyName, "");
|
||||
if(!val[0]) return al::nullopt;
|
||||
|
||||
return al::make_optional(static_cast<int>(std::strtol(val, nullptr, 0)));
|
||||
}
|
||||
|
||||
al::optional<unsigned int> ConfigValueUInt(const char *devName, const char *blockName, const char *keyName)
|
||||
{
|
||||
const char *val = GetConfigValue(devName, blockName, keyName, "");
|
||||
if(!val[0]) return al::nullopt;
|
||||
|
||||
return al::make_optional(static_cast<unsigned int>(std::strtoul(val, nullptr, 0)));
|
||||
}
|
||||
|
||||
al::optional<float> ConfigValueFloat(const char *devName, const char *blockName, const char *keyName)
|
||||
{
|
||||
const char *val = GetConfigValue(devName, blockName, keyName, "");
|
||||
if(!val[0]) return al::nullopt;
|
||||
|
||||
return al::make_optional(std::strtof(val, nullptr));
|
||||
}
|
||||
|
||||
al::optional<bool> ConfigValueBool(const char *devName, const char *blockName, const char *keyName)
|
||||
{
|
||||
const char *val = GetConfigValue(devName, blockName, keyName, "");
|
||||
if(!val[0]) return al::nullopt;
|
||||
|
||||
return al::make_optional(
|
||||
al::strcasecmp(val, "true") == 0 || al::strcasecmp(val, "yes") == 0 ||
|
||||
al::strcasecmp(val, "on") == 0 || atoi(val) != 0);
|
||||
}
|
||||
|
||||
int GetConfigValueBool(const char *devName, const char *blockName, const char *keyName, int def)
|
||||
{
|
||||
const char *val = GetConfigValue(devName, blockName, keyName, "");
|
||||
|
||||
if(!val[0]) return def != 0;
|
||||
return (al::strcasecmp(val, "true") == 0 || al::strcasecmp(val, "yes") == 0 ||
|
||||
al::strcasecmp(val, "on") == 0 || atoi(val) != 0);
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
#ifndef ALCONFIG_H
|
||||
#define ALCONFIG_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "aloptional.h"
|
||||
|
||||
void ReadALConfig();
|
||||
|
||||
int ConfigValueExists(const char *devName, const char *blockName, const char *keyName);
|
||||
const char *GetConfigValue(const char *devName, const char *blockName, const char *keyName, const char *def);
|
||||
int GetConfigValueBool(const char *devName, const char *blockName, const char *keyName, int def);
|
||||
|
||||
al::optional<std::string> ConfigValueStr(const char *devName, const char *blockName, const char *keyName);
|
||||
al::optional<int> ConfigValueInt(const char *devName, const char *blockName, const char *keyName);
|
||||
al::optional<unsigned int> ConfigValueUInt(const char *devName, const char *blockName, const char *keyName);
|
||||
al::optional<float> ConfigValueFloat(const char *devName, const char *blockName, const char *keyName);
|
||||
al::optional<bool> ConfigValueBool(const char *devName, const char *blockName, const char *keyName);
|
||||
|
||||
#endif /* ALCONFIG_H */
|
||||
-196
@@ -1,196 +0,0 @@
|
||||
#ifndef ALCONTEXT_H
|
||||
#define ALCONTEXT_H
|
||||
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
#include "AL/al.h"
|
||||
#include "AL/alc.h"
|
||||
|
||||
#include "al/listener.h"
|
||||
#include "almalloc.h"
|
||||
#include "alnumeric.h"
|
||||
#include "alu.h"
|
||||
#include "atomic.h"
|
||||
#include "inprogext.h"
|
||||
#include "intrusive_ptr.h"
|
||||
#include "logging.h"
|
||||
#include "threads.h"
|
||||
#include "vector.h"
|
||||
#include "voice.h"
|
||||
|
||||
struct ALeffectslot;
|
||||
struct ALeffectslotProps;
|
||||
struct ALsource;
|
||||
struct RingBuffer;
|
||||
|
||||
|
||||
enum class DistanceModel {
|
||||
InverseClamped = AL_INVERSE_DISTANCE_CLAMPED,
|
||||
LinearClamped = AL_LINEAR_DISTANCE_CLAMPED,
|
||||
ExponentClamped = AL_EXPONENT_DISTANCE_CLAMPED,
|
||||
Inverse = AL_INVERSE_DISTANCE,
|
||||
Linear = AL_LINEAR_DISTANCE,
|
||||
Exponent = AL_EXPONENT_DISTANCE,
|
||||
Disable = AL_NONE,
|
||||
|
||||
Default = InverseClamped
|
||||
};
|
||||
|
||||
|
||||
struct ALcontextProps {
|
||||
ALfloat DopplerFactor;
|
||||
ALfloat DopplerVelocity;
|
||||
ALfloat SpeedOfSound;
|
||||
ALboolean SourceDistanceModel;
|
||||
DistanceModel mDistanceModel;
|
||||
|
||||
std::atomic<ALcontextProps*> next;
|
||||
|
||||
DEF_NEWDEL(ALcontextProps)
|
||||
};
|
||||
|
||||
|
||||
struct SourceSubList {
|
||||
uint64_t FreeMask{~0_u64};
|
||||
ALsource *Sources{nullptr}; /* 64 */
|
||||
|
||||
SourceSubList() noexcept = default;
|
||||
SourceSubList(const SourceSubList&) = delete;
|
||||
SourceSubList(SourceSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Sources{rhs.Sources}
|
||||
{ rhs.FreeMask = ~0_u64; rhs.Sources = nullptr; }
|
||||
~SourceSubList();
|
||||
|
||||
SourceSubList& operator=(const SourceSubList&) = delete;
|
||||
SourceSubList& operator=(SourceSubList&& rhs) noexcept
|
||||
{ std::swap(FreeMask, rhs.FreeMask); std::swap(Sources, rhs.Sources); return *this; }
|
||||
};
|
||||
|
||||
struct EffectSlotSubList {
|
||||
uint64_t FreeMask{~0_u64};
|
||||
ALeffectslot *EffectSlots{nullptr}; /* 64 */
|
||||
|
||||
EffectSlotSubList() noexcept = default;
|
||||
EffectSlotSubList(const EffectSlotSubList&) = delete;
|
||||
EffectSlotSubList(EffectSlotSubList&& rhs) noexcept
|
||||
: FreeMask{rhs.FreeMask}, EffectSlots{rhs.EffectSlots}
|
||||
{ rhs.FreeMask = ~0_u64; rhs.EffectSlots = nullptr; }
|
||||
~EffectSlotSubList();
|
||||
|
||||
EffectSlotSubList& operator=(const EffectSlotSubList&) = delete;
|
||||
EffectSlotSubList& operator=(EffectSlotSubList&& rhs) noexcept
|
||||
{ std::swap(FreeMask, rhs.FreeMask); std::swap(EffectSlots, rhs.EffectSlots); return *this; }
|
||||
};
|
||||
|
||||
struct ALCcontext : public al::intrusive_ref<ALCcontext> {
|
||||
al::vector<SourceSubList> mSourceList;
|
||||
ALuint mNumSources{0};
|
||||
std::mutex mSourceLock;
|
||||
|
||||
al::vector<EffectSlotSubList> mEffectSlotList;
|
||||
ALuint mNumEffectSlots{0u};
|
||||
std::mutex mEffectSlotLock;
|
||||
|
||||
std::atomic<ALenum> mLastError{AL_NO_ERROR};
|
||||
|
||||
DistanceModel mDistanceModel{DistanceModel::Default};
|
||||
ALboolean mSourceDistanceModel{AL_FALSE};
|
||||
|
||||
ALfloat mDopplerFactor{1.0f};
|
||||
ALfloat mDopplerVelocity{1.0f};
|
||||
ALfloat mSpeedOfSound{SPEEDOFSOUNDMETRESPERSEC};
|
||||
|
||||
std::atomic_flag mPropsClean;
|
||||
std::atomic<bool> mDeferUpdates{false};
|
||||
|
||||
std::mutex mPropLock;
|
||||
|
||||
/* Counter for the pre-mixing updates, in 31.1 fixed point (lowest bit
|
||||
* indicates if updates are currently happening).
|
||||
*/
|
||||
RefCount mUpdateCount{0u};
|
||||
std::atomic<bool> mHoldUpdates{false};
|
||||
|
||||
ALfloat mGainBoost{1.0f};
|
||||
|
||||
std::atomic<ALcontextProps*> mUpdate{nullptr};
|
||||
|
||||
/* Linked lists of unused property containers, free to use for future
|
||||
* updates.
|
||||
*/
|
||||
std::atomic<ALcontextProps*> mFreeContextProps{nullptr};
|
||||
std::atomic<ALlistenerProps*> mFreeListenerProps{nullptr};
|
||||
std::atomic<ALvoiceProps*> mFreeVoiceProps{nullptr};
|
||||
std::atomic<ALeffectslotProps*> mFreeEffectslotProps{nullptr};
|
||||
|
||||
al::vector<ALvoice> mVoices;
|
||||
|
||||
using ALeffectslotArray = al::FlexArray<ALeffectslot*>;
|
||||
std::atomic<ALeffectslotArray*> mActiveAuxSlots{nullptr};
|
||||
|
||||
std::thread mEventThread;
|
||||
al::semaphore mEventSem;
|
||||
std::unique_ptr<RingBuffer> mAsyncEvents;
|
||||
std::atomic<ALbitfieldSOFT> mEnabledEvts{0u};
|
||||
std::mutex mEventCbLock;
|
||||
ALEVENTPROCSOFT mEventCb{};
|
||||
void *mEventParam{nullptr};
|
||||
|
||||
/* Default effect slot */
|
||||
std::unique_ptr<ALeffectslot> mDefaultSlot;
|
||||
|
||||
const al::intrusive_ptr<ALCdevice> mDevice;
|
||||
const ALCchar *mExtensionList{nullptr};
|
||||
|
||||
ALlistener mListener{};
|
||||
|
||||
|
||||
ALCcontext(al::intrusive_ptr<ALCdevice> device);
|
||||
ALCcontext(const ALCcontext&) = delete;
|
||||
ALCcontext& operator=(const ALCcontext&) = delete;
|
||||
~ALCcontext();
|
||||
|
||||
void init();
|
||||
/**
|
||||
* Removes the context from its device and removes it from being current on
|
||||
* the running thread or globally. Returns true if other contexts still
|
||||
* exist on the device.
|
||||
*/
|
||||
bool deinit();
|
||||
|
||||
/**
|
||||
* Defers/suspends updates for the given context's listener and sources.
|
||||
* This does *NOT* stop mixing, but rather prevents certain property
|
||||
* changes from taking effect.
|
||||
*/
|
||||
void deferUpdates() noexcept { mDeferUpdates.store(true); }
|
||||
|
||||
/** Resumes update processing after being deferred. */
|
||||
void processUpdates();
|
||||
|
||||
void setError(ALenum errorCode, const char *msg, ...) DECL_FORMAT(printf, 3, 4);
|
||||
|
||||
DEF_NEWDEL(ALCcontext)
|
||||
};
|
||||
|
||||
#define SETERR_RETURN(ctx, err, retval, ...) do { \
|
||||
(ctx)->setError((err), __VA_ARGS__); \
|
||||
return retval; \
|
||||
} while(0)
|
||||
|
||||
|
||||
using ContextRef = al::intrusive_ptr<ALCcontext>;
|
||||
|
||||
ContextRef GetContextRef(void);
|
||||
|
||||
void UpdateContextProps(ALCcontext *context);
|
||||
|
||||
|
||||
extern bool TrapALError;
|
||||
|
||||
#endif /* ALCONTEXT_H */
|
||||
-2042
File diff suppressed because it is too large
Load Diff
@@ -1,159 +0,0 @@
|
||||
#ifndef ALU_H
|
||||
#define ALU_H
|
||||
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
|
||||
#include "AL/al.h"
|
||||
|
||||
#include "alcmain.h"
|
||||
#include "alspan.h"
|
||||
#include "logging.h"
|
||||
|
||||
struct ALbufferlistitem;
|
||||
struct ALeffectslot;
|
||||
|
||||
|
||||
#define MAX_PITCH 255
|
||||
#define MAX_SENDS 16
|
||||
|
||||
|
||||
using MixerFunc = void(*)(const al::span<const float> InSamples,
|
||||
const al::span<FloatBufferLine> OutBuffer, float *CurrentGains, const float *TargetGains,
|
||||
const size_t Counter, const size_t OutPos);
|
||||
using RowMixerFunc = void(*)(const al::span<float> OutBuffer, const al::span<const float> Gains,
|
||||
const float *InSamples, const size_t InStride);
|
||||
using HrtfDirectMixerFunc = void(*)(FloatBufferLine &LeftOut, FloatBufferLine &RightOut,
|
||||
const al::span<const FloatBufferLine> InSamples, float2 *AccumSamples, DirectHrtfState *State,
|
||||
const size_t BufferSize);
|
||||
|
||||
extern MixerFunc MixSamples;
|
||||
extern RowMixerFunc MixRowSamples;
|
||||
|
||||
|
||||
#define GAIN_MIX_MAX (1000.0f) /* +60dB */
|
||||
|
||||
#define GAIN_SILENCE_THRESHOLD (0.00001f) /* -100dB */
|
||||
|
||||
#define SPEEDOFSOUNDMETRESPERSEC (343.3f)
|
||||
#define AIRABSORBGAINHF (0.99426f) /* -0.05dB */
|
||||
|
||||
/* Target gain for the reverb decay feedback reaching the decay time. */
|
||||
#define REVERB_DECAY_GAIN (0.001f) /* -60 dB */
|
||||
|
||||
#define FRACTIONBITS (12)
|
||||
#define FRACTIONONE (1<<FRACTIONBITS)
|
||||
#define FRACTIONMASK (FRACTIONONE-1)
|
||||
|
||||
|
||||
inline ALfloat lerp(ALfloat val1, ALfloat val2, ALfloat mu) noexcept
|
||||
{ return val1 + (val2-val1)*mu; }
|
||||
inline ALfloat cubic(ALfloat val1, ALfloat val2, ALfloat val3, ALfloat val4, ALfloat mu) noexcept
|
||||
{
|
||||
ALfloat mu2 = mu*mu, mu3 = mu2*mu;
|
||||
ALfloat a0 = -0.5f*mu3 + mu2 + -0.5f*mu;
|
||||
ALfloat a1 = 1.5f*mu3 + -2.5f*mu2 + 1.0f;
|
||||
ALfloat a2 = -1.5f*mu3 + 2.0f*mu2 + 0.5f*mu;
|
||||
ALfloat a3 = 0.5f*mu3 + -0.5f*mu2;
|
||||
return val1*a0 + val2*a1 + val3*a2 + val4*a3;
|
||||
}
|
||||
|
||||
|
||||
enum HrtfRequestMode {
|
||||
Hrtf_Default = 0,
|
||||
Hrtf_Enable = 1,
|
||||
Hrtf_Disable = 2,
|
||||
};
|
||||
|
||||
void aluInit(void);
|
||||
|
||||
void aluInitMixer(void);
|
||||
|
||||
/* aluInitRenderer
|
||||
*
|
||||
* Set up the appropriate panning method and mixing method given the device
|
||||
* properties.
|
||||
*/
|
||||
void aluInitRenderer(ALCdevice *device, ALint hrtf_id, HrtfRequestMode hrtf_appreq, HrtfRequestMode hrtf_userreq);
|
||||
|
||||
void aluInitEffectPanning(ALeffectslot *slot, ALCdevice *device);
|
||||
|
||||
/**
|
||||
* Calculates ambisonic encoder coefficients using the X, Y, and Z direction
|
||||
* components, which must represent a normalized (unit length) vector, and the
|
||||
* spread is the angular width of the sound (0...tau).
|
||||
*
|
||||
* NOTE: The components use ambisonic coordinates. As a result:
|
||||
*
|
||||
* Ambisonic Y = OpenAL -X
|
||||
* Ambisonic Z = OpenAL Y
|
||||
* Ambisonic X = OpenAL -Z
|
||||
*
|
||||
* The components are ordered such that OpenAL's X, Y, and Z are the first,
|
||||
* second, and third parameters respectively -- simply negate X and Z.
|
||||
*/
|
||||
void CalcAmbiCoeffs(const float y, const float z, const float x, const float spread,
|
||||
const al::span<float,MAX_AMBI_CHANNELS> coeffs);
|
||||
|
||||
/**
|
||||
* CalcDirectionCoeffs
|
||||
*
|
||||
* Calculates ambisonic coefficients based on an OpenAL direction vector. The
|
||||
* vector must be normalized (unit length), and the spread is the angular width
|
||||
* of the sound (0...tau).
|
||||
*/
|
||||
inline void CalcDirectionCoeffs(const float (&dir)[3], const float spread,
|
||||
const al::span<float,MAX_AMBI_CHANNELS> coeffs)
|
||||
{
|
||||
/* Convert from OpenAL coords to Ambisonics. */
|
||||
CalcAmbiCoeffs(-dir[0], dir[1], -dir[2], spread, coeffs);
|
||||
}
|
||||
|
||||
/**
|
||||
* CalcAngleCoeffs
|
||||
*
|
||||
* Calculates ambisonic coefficients based on azimuth and elevation. The
|
||||
* azimuth and elevation parameters are in radians, going right and up
|
||||
* respectively.
|
||||
*/
|
||||
inline void CalcAngleCoeffs(const float azimuth, const float elevation, const float spread,
|
||||
const al::span<float,MAX_AMBI_CHANNELS> coeffs)
|
||||
{
|
||||
const float x{-std::sin(azimuth) * std::cos(elevation)};
|
||||
const float y{ std::sin(elevation)};
|
||||
const float z{ std::cos(azimuth) * std::cos(elevation)};
|
||||
|
||||
CalcAmbiCoeffs(x, y, z, spread, coeffs);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* ComputePanGains
|
||||
*
|
||||
* Computes panning gains using the given channel decoder coefficients and the
|
||||
* pre-calculated direction or angle coefficients. For B-Format sources, the
|
||||
* coeffs are a 'slice' of a transform matrix for the input channel, used to
|
||||
* scale and orient the sound samples.
|
||||
*/
|
||||
void ComputePanGains(const MixParams *mix, const float*RESTRICT coeffs, const float ingain,
|
||||
const al::span<float,MAX_OUTPUT_CHANNELS> gains);
|
||||
|
||||
|
||||
inline std::array<ALfloat,MAX_AMBI_CHANNELS> GetAmbiIdentityRow(size_t i) noexcept
|
||||
{
|
||||
std::array<ALfloat,MAX_AMBI_CHANNELS> ret{};
|
||||
ret[i] = 1.0f;
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
void aluMixData(ALCdevice *device, void *OutBuffer, const ALuint NumSamples,
|
||||
const size_t FrameStep);
|
||||
/* Caller must lock the device state, and the mixer must not be running. */
|
||||
void aluHandleDisconnect(ALCdevice *device, const char *msg, ...) DECL_FORMAT(printf, 2, 3);
|
||||
|
||||
extern const ALfloat ConeScale;
|
||||
extern const ALfloat ZScale;
|
||||
|
||||
#endif
|
||||
-434
@@ -1,434 +0,0 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "ambdec.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstddef>
|
||||
#include <iterator>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include "alfstream.h"
|
||||
#include "logging.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
template<typename T, std::size_t N>
|
||||
constexpr inline std::size_t size(const T(&)[N]) noexcept
|
||||
{ return N; }
|
||||
|
||||
int readline(std::istream &f, std::string &output)
|
||||
{
|
||||
while(f.good() && f.peek() == '\n')
|
||||
f.ignore();
|
||||
|
||||
return std::getline(f, output) && !output.empty();
|
||||
}
|
||||
|
||||
bool read_clipped_line(std::istream &f, std::string &buffer)
|
||||
{
|
||||
while(readline(f, buffer))
|
||||
{
|
||||
std::size_t pos{0};
|
||||
while(pos < buffer.length() && std::isspace(buffer[pos]))
|
||||
pos++;
|
||||
buffer.erase(0, pos);
|
||||
|
||||
std::size_t cmtpos{buffer.find_first_of('#')};
|
||||
if(cmtpos < buffer.length())
|
||||
buffer.resize(cmtpos);
|
||||
while(!buffer.empty() && std::isspace(buffer.back()))
|
||||
buffer.pop_back();
|
||||
|
||||
if(!buffer.empty())
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
std::string read_word(std::istream &f)
|
||||
{
|
||||
std::string ret;
|
||||
f >> ret;
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool is_at_end(const std::string &buffer, std::size_t endpos)
|
||||
{
|
||||
while(endpos < buffer.length() && std::isspace(buffer[endpos]))
|
||||
++endpos;
|
||||
return !(endpos < buffer.length());
|
||||
}
|
||||
|
||||
|
||||
bool load_ambdec_speakers(al::vector<AmbDecConf::SpeakerConf> &spkrs, const std::size_t num_speakers, std::istream &f, std::string &buffer)
|
||||
{
|
||||
while(spkrs.size() < num_speakers)
|
||||
{
|
||||
std::istringstream istr{buffer};
|
||||
|
||||
std::string cmd{read_word(istr)};
|
||||
if(cmd.empty())
|
||||
{
|
||||
if(!read_clipped_line(f, buffer))
|
||||
{
|
||||
ERR("Unexpected end of file\n");
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if(cmd == "add_spkr")
|
||||
{
|
||||
spkrs.emplace_back();
|
||||
AmbDecConf::SpeakerConf &spkr = spkrs.back();
|
||||
const size_t spkr_num{spkrs.size()};
|
||||
|
||||
istr >> spkr.Name;
|
||||
if(istr.fail()) WARN("Name not specified for speaker %zu\n", spkr_num);
|
||||
istr >> spkr.Distance;
|
||||
if(istr.fail()) WARN("Distance not specified for speaker %zu\n", spkr_num);
|
||||
istr >> spkr.Azimuth;
|
||||
if(istr.fail()) WARN("Azimuth not specified for speaker %zu\n", spkr_num);
|
||||
istr >> spkr.Elevation;
|
||||
if(istr.fail()) WARN("Elevation not specified for speaker %zu\n", spkr_num);
|
||||
istr >> spkr.Connection;
|
||||
if(istr.fail()) TRACE("Connection not specified for speaker %zu\n", spkr_num);
|
||||
}
|
||||
else
|
||||
{
|
||||
ERR("Unexpected speakers command: %s\n", cmd.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
istr.clear();
|
||||
const auto endpos = static_cast<std::size_t>(istr.tellg());
|
||||
if(!is_at_end(buffer, endpos))
|
||||
{
|
||||
ERR("Unexpected junk on line: %s\n", buffer.c_str()+endpos);
|
||||
return false;
|
||||
}
|
||||
buffer.clear();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool load_ambdec_matrix(float (&gains)[MAX_AMBI_ORDER+1], al::vector<AmbDecConf::CoeffArray> &matrix, const std::size_t maxrow, std::istream &f, std::string &buffer)
|
||||
{
|
||||
bool gotgains{false};
|
||||
std::size_t cur{0u};
|
||||
while(cur < maxrow)
|
||||
{
|
||||
std::istringstream istr{buffer};
|
||||
|
||||
std::string cmd{read_word(istr)};
|
||||
if(cmd.empty())
|
||||
{
|
||||
if(!read_clipped_line(f, buffer))
|
||||
{
|
||||
ERR("Unexpected end of file\n");
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if(cmd == "order_gain")
|
||||
{
|
||||
std::size_t curgain{0u};
|
||||
float value;
|
||||
while(istr.good())
|
||||
{
|
||||
istr >> value;
|
||||
if(istr.fail()) break;
|
||||
if(!istr.eof() && !std::isspace(istr.peek()))
|
||||
{
|
||||
ERR("Extra junk on gain %zu: %s\n", curgain+1,
|
||||
buffer.c_str()+static_cast<std::size_t>(istr.tellg()));
|
||||
return false;
|
||||
}
|
||||
if(curgain < size(gains))
|
||||
gains[curgain++] = value;
|
||||
}
|
||||
std::fill(std::begin(gains)+curgain, std::end(gains), 0.0f);
|
||||
gotgains = true;
|
||||
}
|
||||
else if(cmd == "add_row")
|
||||
{
|
||||
matrix.emplace_back();
|
||||
AmbDecConf::CoeffArray &mtxrow = matrix.back();
|
||||
std::size_t curidx{0u};
|
||||
float value{};
|
||||
while(istr.good())
|
||||
{
|
||||
istr >> value;
|
||||
if(istr.fail()) break;
|
||||
if(!istr.eof() && !std::isspace(istr.peek()))
|
||||
{
|
||||
ERR("Extra junk on matrix element %zux%zu: %s\n", curidx,
|
||||
matrix.size(), buffer.c_str()+static_cast<std::size_t>(istr.tellg()));
|
||||
matrix.pop_back();
|
||||
return false;
|
||||
}
|
||||
if(curidx < mtxrow.size())
|
||||
mtxrow[curidx++] = value;
|
||||
}
|
||||
std::fill(mtxrow.begin()+curidx, mtxrow.end(), 0.0f);
|
||||
cur++;
|
||||
}
|
||||
else
|
||||
{
|
||||
ERR("Unexpected matrix command: %s\n", cmd.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
istr.clear();
|
||||
const auto endpos = static_cast<std::size_t>(istr.tellg());
|
||||
if(!is_at_end(buffer, endpos))
|
||||
{
|
||||
ERR("Unexpected junk on line: %s\n", buffer.c_str()+endpos);
|
||||
return false;
|
||||
}
|
||||
buffer.clear();
|
||||
}
|
||||
|
||||
if(!gotgains)
|
||||
{
|
||||
ERR("Matrix order_gain not specified\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int AmbDecConf::load(const char *fname) noexcept
|
||||
{
|
||||
al::ifstream f{fname};
|
||||
if(!f.is_open())
|
||||
{
|
||||
ERR("Failed to open: %s\n", fname);
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::size_t num_speakers{0u};
|
||||
std::string buffer;
|
||||
while(read_clipped_line(f, buffer))
|
||||
{
|
||||
std::istringstream istr{buffer};
|
||||
|
||||
std::string command{read_word(istr)};
|
||||
if(command.empty())
|
||||
{
|
||||
ERR("Malformed line: %s\n", buffer.c_str());
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(command == "/description")
|
||||
istr >> Description;
|
||||
else if(command == "/version")
|
||||
{
|
||||
istr >> Version;
|
||||
if(!istr.eof() && !std::isspace(istr.peek()))
|
||||
{
|
||||
ERR("Extra junk after version: %s\n",
|
||||
buffer.c_str()+static_cast<std::size_t>(istr.tellg()));
|
||||
return 0;
|
||||
}
|
||||
if(Version != 3)
|
||||
{
|
||||
ERR("Unsupported version: %u\n", Version);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else if(command == "/dec/chan_mask")
|
||||
{
|
||||
istr >> std::hex >> ChanMask >> std::dec;
|
||||
if(!istr.eof() && !std::isspace(istr.peek()))
|
||||
{
|
||||
ERR("Extra junk after mask: %s\n",
|
||||
buffer.c_str()+static_cast<std::size_t>(istr.tellg()));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else if(command == "/dec/freq_bands")
|
||||
{
|
||||
istr >> FreqBands;
|
||||
if(!istr.eof() && !std::isspace(istr.peek()))
|
||||
{
|
||||
ERR("Extra junk after freq_bands: %s\n",
|
||||
buffer.c_str()+static_cast<std::size_t>(istr.tellg()));
|
||||
return 0;
|
||||
}
|
||||
if(FreqBands != 1 && FreqBands != 2)
|
||||
{
|
||||
ERR("Invalid freq_bands value: %u\n", FreqBands);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else if(command == "/dec/speakers")
|
||||
{
|
||||
istr >> num_speakers;
|
||||
if(!istr.eof() && !std::isspace(istr.peek()))
|
||||
{
|
||||
ERR("Extra junk after speakers: %s\n",
|
||||
buffer.c_str()+static_cast<std::size_t>(istr.tellg()));
|
||||
return 0;
|
||||
}
|
||||
Speakers.reserve(num_speakers);
|
||||
LFMatrix.reserve(num_speakers);
|
||||
HFMatrix.reserve(num_speakers);
|
||||
}
|
||||
else if(command == "/dec/coeff_scale")
|
||||
{
|
||||
std::string scale = read_word(istr);
|
||||
if(scale == "n3d") CoeffScale = AmbDecScale::N3D;
|
||||
else if(scale == "sn3d") CoeffScale = AmbDecScale::SN3D;
|
||||
else if(scale == "fuma") CoeffScale = AmbDecScale::FuMa;
|
||||
else
|
||||
{
|
||||
ERR("Unsupported coeff scale: %s\n", scale.c_str());
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else if(command == "/opt/xover_freq")
|
||||
{
|
||||
istr >> XOverFreq;
|
||||
if(!istr.eof() && !std::isspace(istr.peek()))
|
||||
{
|
||||
ERR("Extra junk after xover_freq: %s\n",
|
||||
buffer.c_str()+static_cast<std::size_t>(istr.tellg()));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else if(command == "/opt/xover_ratio")
|
||||
{
|
||||
istr >> XOverRatio;
|
||||
if(!istr.eof() && !std::isspace(istr.peek()))
|
||||
{
|
||||
ERR("Extra junk after xover_ratio: %s\n",
|
||||
buffer.c_str()+static_cast<std::size_t>(istr.tellg()));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else if(command == "/opt/input_scale" || command == "/opt/nfeff_comp" ||
|
||||
command == "/opt/delay_comp" || command == "/opt/level_comp")
|
||||
{
|
||||
/* Unused */
|
||||
read_word(istr);
|
||||
}
|
||||
else if(command == "/speakers/{")
|
||||
{
|
||||
const auto endpos = static_cast<std::size_t>(istr.tellg());
|
||||
if(!is_at_end(buffer, endpos))
|
||||
{
|
||||
ERR("Unexpected junk on line: %s\n", buffer.c_str()+endpos);
|
||||
return 0;
|
||||
}
|
||||
buffer.clear();
|
||||
|
||||
if(!load_ambdec_speakers(Speakers, num_speakers, f, buffer))
|
||||
return 0;
|
||||
|
||||
if(!read_clipped_line(f, buffer))
|
||||
{
|
||||
ERR("Unexpected end of file\n");
|
||||
return 0;
|
||||
}
|
||||
std::istringstream istr2{buffer};
|
||||
std::string endmark{read_word(istr2)};
|
||||
if(endmark != "/}")
|
||||
{
|
||||
ERR("Expected /} after speaker definitions, got %s\n", endmark.c_str());
|
||||
return 0;
|
||||
}
|
||||
istr.swap(istr2);
|
||||
}
|
||||
else if(command == "/lfmatrix/{" || command == "/hfmatrix/{" || command == "/matrix/{")
|
||||
{
|
||||
const auto endpos = static_cast<std::size_t>(istr.tellg());
|
||||
if(!is_at_end(buffer, endpos))
|
||||
{
|
||||
ERR("Unexpected junk on line: %s\n", buffer.c_str()+endpos);
|
||||
return 0;
|
||||
}
|
||||
buffer.clear();
|
||||
|
||||
if(FreqBands == 1)
|
||||
{
|
||||
if(command != "/matrix/{")
|
||||
{
|
||||
ERR("Unexpected \"%s\" type for a single-band decoder\n", command.c_str());
|
||||
return 0;
|
||||
}
|
||||
if(!load_ambdec_matrix(HFOrderGain, HFMatrix, num_speakers, f, buffer))
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(command == "/lfmatrix/{")
|
||||
{
|
||||
if(!load_ambdec_matrix(LFOrderGain, LFMatrix, num_speakers, f, buffer))
|
||||
return 0;
|
||||
}
|
||||
else if(command == "/hfmatrix/{")
|
||||
{
|
||||
if(!load_ambdec_matrix(HFOrderGain, HFMatrix, num_speakers, f, buffer))
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
ERR("Unexpected \"%s\" type for a dual-band decoder\n", command.c_str());
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if(!read_clipped_line(f, buffer))
|
||||
{
|
||||
ERR("Unexpected end of file\n");
|
||||
return 0;
|
||||
}
|
||||
std::istringstream istr2{buffer};
|
||||
std::string endmark{read_word(istr2)};
|
||||
if(endmark != "/}")
|
||||
{
|
||||
ERR("Expected /} after matrix definitions, got %s\n", endmark.c_str());
|
||||
return 0;
|
||||
}
|
||||
istr.swap(istr2);
|
||||
}
|
||||
else if(command == "/end")
|
||||
{
|
||||
const auto endpos = static_cast<std::size_t>(istr.tellg());
|
||||
if(!is_at_end(buffer, endpos))
|
||||
{
|
||||
ERR("Unexpected junk on end: %s\n", buffer.c_str()+endpos);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
ERR("Unexpected command: %s\n", command.c_str());
|
||||
return 0;
|
||||
}
|
||||
|
||||
istr.clear();
|
||||
const auto endpos = static_cast<std::size_t>(istr.tellg());
|
||||
if(!is_at_end(buffer, endpos))
|
||||
{
|
||||
ERR("Unexpected junk on line: %s\n", buffer.c_str()+endpos);
|
||||
return 0;
|
||||
}
|
||||
buffer.clear();
|
||||
}
|
||||
ERR("Unexpected end of file\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
#ifndef AMBDEC_H
|
||||
#define AMBDEC_H
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
#include "ambidefs.h"
|
||||
#include "vector.h"
|
||||
|
||||
/* Helpers to read .ambdec configuration files. */
|
||||
|
||||
enum class AmbDecScale {
|
||||
N3D,
|
||||
SN3D,
|
||||
FuMa,
|
||||
};
|
||||
struct AmbDecConf {
|
||||
std::string Description;
|
||||
int Version{0}; /* Must be 3 */
|
||||
|
||||
unsigned int ChanMask{0u};
|
||||
unsigned int FreqBands{0u}; /* Must be 1 or 2 */
|
||||
AmbDecScale CoeffScale{};
|
||||
|
||||
float XOverFreq{0.0f};
|
||||
float XOverRatio{0.0f};
|
||||
|
||||
struct SpeakerConf {
|
||||
std::string Name;
|
||||
float Distance{0.0f};
|
||||
float Azimuth{0.0f};
|
||||
float Elevation{0.0f};
|
||||
std::string Connection;
|
||||
};
|
||||
al::vector<SpeakerConf> Speakers;
|
||||
|
||||
using CoeffArray = std::array<float,MAX_AMBI_CHANNELS>;
|
||||
/* Unused when FreqBands == 1 */
|
||||
float LFOrderGain[MAX_AMBI_ORDER+1]{};
|
||||
al::vector<CoeffArray> LFMatrix;
|
||||
|
||||
float HFOrderGain[MAX_AMBI_ORDER+1]{};
|
||||
al::vector<CoeffArray> HFMatrix;
|
||||
|
||||
int load(const char *fname) noexcept;
|
||||
};
|
||||
|
||||
#endif /* AMBDEC_H */
|
||||
-132
@@ -1,132 +0,0 @@
|
||||
#ifndef AMBIDEFS_H
|
||||
#define AMBIDEFS_H
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
/* The maximum number of Ambisonics channels. For a given order (o), the size
|
||||
* needed will be (o+1)**2, thus zero-order has 1, first-order has 4, second-
|
||||
* order has 9, third-order has 16, and fourth-order has 25.
|
||||
*/
|
||||
#define MAX_AMBI_ORDER 3
|
||||
constexpr inline size_t AmbiChannelsFromOrder(size_t order) noexcept
|
||||
{ return (order+1) * (order+1); }
|
||||
#define MAX_AMBI_CHANNELS AmbiChannelsFromOrder(MAX_AMBI_ORDER)
|
||||
|
||||
/* A bitmask of ambisonic channels for 0 to 4th order. This only specifies up
|
||||
* to 4th order, which is the highest order a 32-bit mask value can specify (a
|
||||
* 64-bit mask could handle up to 7th order).
|
||||
*/
|
||||
#define AMBI_0ORDER_MASK 0x00000001
|
||||
#define AMBI_1ORDER_MASK 0x0000000f
|
||||
#define AMBI_2ORDER_MASK 0x000001ff
|
||||
#define AMBI_3ORDER_MASK 0x0000ffff
|
||||
#define AMBI_4ORDER_MASK 0x01ffffff
|
||||
|
||||
/* A bitmask of ambisonic channels with height information. If none of these
|
||||
* channels are used/needed, there's no height (e.g. with most surround sound
|
||||
* speaker setups). This is ACN ordering, with bit 0 being ACN 0, etc.
|
||||
*/
|
||||
#define AMBI_PERIPHONIC_MASK (0xfe7ce4)
|
||||
|
||||
/* The maximum number of ambisonic channels for 2D (non-periphonic)
|
||||
* representation. This is 2 per each order above zero-order, plus 1 for zero-
|
||||
* order. Or simply, o*2 + 1.
|
||||
*/
|
||||
constexpr inline size_t Ambi2DChannelsFromOrder(size_t order) noexcept
|
||||
{ return order*2 + 1; }
|
||||
#define MAX_AMBI2D_CHANNELS Ambi2DChannelsFromOrder(MAX_AMBI_ORDER)
|
||||
|
||||
|
||||
/* NOTE: These are scale factors as applied to Ambisonics content. Decoder
|
||||
* coefficients should be divided by these values to get proper scalings.
|
||||
*/
|
||||
struct AmbiScale {
|
||||
static constexpr std::array<float,MAX_AMBI_CHANNELS> FromN3D{{
|
||||
1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
|
||||
1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f
|
||||
}};
|
||||
static constexpr std::array<float,MAX_AMBI_CHANNELS> FromSN3D{{
|
||||
1.000000000f, /* ACN 0, sqrt(1) */
|
||||
1.732050808f, /* ACN 1, sqrt(3) */
|
||||
1.732050808f, /* ACN 2, sqrt(3) */
|
||||
1.732050808f, /* ACN 3, sqrt(3) */
|
||||
2.236067978f, /* ACN 4, sqrt(5) */
|
||||
2.236067978f, /* ACN 5, sqrt(5) */
|
||||
2.236067978f, /* ACN 6, sqrt(5) */
|
||||
2.236067978f, /* ACN 7, sqrt(5) */
|
||||
2.236067978f, /* ACN 8, sqrt(5) */
|
||||
2.645751311f, /* ACN 9, sqrt(7) */
|
||||
2.645751311f, /* ACN 10, sqrt(7) */
|
||||
2.645751311f, /* ACN 11, sqrt(7) */
|
||||
2.645751311f, /* ACN 12, sqrt(7) */
|
||||
2.645751311f, /* ACN 13, sqrt(7) */
|
||||
2.645751311f, /* ACN 14, sqrt(7) */
|
||||
2.645751311f, /* ACN 15, sqrt(7) */
|
||||
}};
|
||||
static constexpr std::array<float,MAX_AMBI_CHANNELS> FromFuMa{{
|
||||
1.414213562f, /* ACN 0 (W), sqrt(2) */
|
||||
1.732050808f, /* ACN 1 (Y), sqrt(3) */
|
||||
1.732050808f, /* ACN 2 (Z), sqrt(3) */
|
||||
1.732050808f, /* ACN 3 (X), sqrt(3) */
|
||||
1.936491673f, /* ACN 4 (V), sqrt(15)/2 */
|
||||
1.936491673f, /* ACN 5 (T), sqrt(15)/2 */
|
||||
2.236067978f, /* ACN 6 (R), sqrt(5) */
|
||||
1.936491673f, /* ACN 7 (S), sqrt(15)/2 */
|
||||
1.936491673f, /* ACN 8 (U), sqrt(15)/2 */
|
||||
2.091650066f, /* ACN 9 (Q), sqrt(35/8) */
|
||||
1.972026594f, /* ACN 10 (O), sqrt(35)/3 */
|
||||
2.231093404f, /* ACN 11 (M), sqrt(224/45) */
|
||||
2.645751311f, /* ACN 12 (K), sqrt(7) */
|
||||
2.231093404f, /* ACN 13 (L), sqrt(224/45) */
|
||||
1.972026594f, /* ACN 14 (N), sqrt(35)/3 */
|
||||
2.091650066f, /* ACN 15 (P), sqrt(35/8) */
|
||||
}};
|
||||
};
|
||||
|
||||
struct AmbiIndex {
|
||||
static constexpr std::array<uint8_t,MAX_AMBI_CHANNELS> FromFuMa{{
|
||||
0, /* W */
|
||||
3, /* X */
|
||||
1, /* Y */
|
||||
2, /* Z */
|
||||
6, /* R */
|
||||
7, /* S */
|
||||
5, /* T */
|
||||
8, /* U */
|
||||
4, /* V */
|
||||
12, /* K */
|
||||
13, /* L */
|
||||
11, /* M */
|
||||
14, /* N */
|
||||
10, /* O */
|
||||
15, /* P */
|
||||
9, /* Q */
|
||||
}};
|
||||
static constexpr std::array<uint8_t,MAX_AMBI2D_CHANNELS> FromFuMa2D{{
|
||||
0, /* W */
|
||||
3, /* X */
|
||||
1, /* Y */
|
||||
8, /* U */
|
||||
4, /* V */
|
||||
15, /* P */
|
||||
9, /* Q */
|
||||
}};
|
||||
|
||||
static constexpr std::array<uint8_t,MAX_AMBI_CHANNELS> FromACN{{
|
||||
0, 1, 2, 3, 4, 5, 6, 7,
|
||||
8, 9, 10, 11, 12, 13, 14, 15
|
||||
}};
|
||||
static constexpr std::array<uint8_t,MAX_AMBI2D_CHANNELS> From2D{{
|
||||
0, 1,3, 4,8, 9,15
|
||||
}};
|
||||
|
||||
static constexpr std::array<uint8_t,MAX_AMBI_CHANNELS> OrderFromChannel{{
|
||||
0, 1,1,1, 2,2,2,2,2, 3,3,3,3,3,3,3,
|
||||
}};
|
||||
static constexpr std::array<uint8_t,MAX_AMBI2D_CHANNELS> OrderFrom2DChannel{{
|
||||
0, 1,1, 2,2, 3,3,
|
||||
}};
|
||||
};
|
||||
|
||||
#endif /* AMBIDEFS_H */
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,19 +0,0 @@
|
||||
#ifndef BACKENDS_ALSA_H
|
||||
#define BACKENDS_ALSA_H
|
||||
|
||||
#include "backends/base.h"
|
||||
|
||||
struct AlsaBackendFactory final : public BackendFactory {
|
||||
public:
|
||||
bool init() override;
|
||||
|
||||
bool querySupport(BackendType type) override;
|
||||
|
||||
void probe(DevProbe type, std::string *outnames) override;
|
||||
|
||||
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
|
||||
|
||||
static BackendFactory &getFactory();
|
||||
};
|
||||
|
||||
#endif /* BACKENDS_ALSA_H */
|
||||
@@ -1,62 +0,0 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "base.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
|
||||
#include "AL/al.h"
|
||||
|
||||
#include "alcmain.h"
|
||||
#include "alexcpt.h"
|
||||
#include "alnumeric.h"
|
||||
#include "atomic.h"
|
||||
|
||||
|
||||
ClockLatency GetClockLatency(ALCdevice *device)
|
||||
{
|
||||
BackendBase *backend{device->Backend.get()};
|
||||
ClockLatency ret{backend->getClockLatency()};
|
||||
ret.Latency += device->FixedLatency;
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/* BackendBase method implementations. */
|
||||
BackendBase::BackendBase(ALCdevice *device) noexcept : mDevice{device}
|
||||
{ }
|
||||
|
||||
BackendBase::~BackendBase() = default;
|
||||
|
||||
bool BackendBase::reset()
|
||||
{ throw al::backend_exception{ALC_INVALID_DEVICE, "Invalid BackendBase call"}; }
|
||||
|
||||
ALCenum BackendBase::captureSamples(al::byte*, ALCuint)
|
||||
{ return ALC_INVALID_DEVICE; }
|
||||
|
||||
ALCuint BackendBase::availableSamples()
|
||||
{ return 0; }
|
||||
|
||||
ClockLatency BackendBase::getClockLatency()
|
||||
{
|
||||
ClockLatency ret;
|
||||
|
||||
ALuint refcount;
|
||||
do {
|
||||
while(((refcount=ReadRef(mDevice->MixCount))&1) != 0)
|
||||
std::this_thread::yield();
|
||||
ret.ClockTime = GetDeviceClockTime(mDevice);
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
} while(refcount != ReadRef(mDevice->MixCount));
|
||||
|
||||
/* NOTE: The device will generally have about all but one periods filled at
|
||||
* any given time during playback. Without a more accurate measurement from
|
||||
* the output, this is an okay approximation.
|
||||
*/
|
||||
ret.Latency = std::max(std::chrono::seconds{mDevice->BufferSize-mDevice->UpdateSize},
|
||||
std::chrono::seconds::zero());
|
||||
ret.Latency /= mDevice->Frequency;
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
#ifndef ALC_BACKENDS_BASE_H
|
||||
#define ALC_BACKENDS_BASE_H
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
#include "AL/alc.h"
|
||||
|
||||
#include "alcmain.h"
|
||||
#include "albyte.h"
|
||||
|
||||
|
||||
struct ClockLatency {
|
||||
std::chrono::nanoseconds ClockTime;
|
||||
std::chrono::nanoseconds Latency;
|
||||
};
|
||||
|
||||
/* Helper to get the current clock time from the device's ClockBase, and
|
||||
* SamplesDone converted from the sample rate.
|
||||
*/
|
||||
inline std::chrono::nanoseconds GetDeviceClockTime(ALCdevice *device)
|
||||
{
|
||||
using std::chrono::seconds;
|
||||
using std::chrono::nanoseconds;
|
||||
|
||||
auto ns = nanoseconds{seconds{device->SamplesDone}} / device->Frequency;
|
||||
return device->ClockBase + ns;
|
||||
}
|
||||
|
||||
ClockLatency GetClockLatency(ALCdevice *device);
|
||||
|
||||
struct BackendBase {
|
||||
virtual void open(const ALCchar *name) = 0;
|
||||
|
||||
virtual bool reset();
|
||||
virtual bool start() = 0;
|
||||
virtual void stop() = 0;
|
||||
|
||||
virtual ALCenum captureSamples(al::byte *buffer, ALCuint samples);
|
||||
virtual ALCuint availableSamples();
|
||||
|
||||
virtual ClockLatency getClockLatency();
|
||||
|
||||
virtual void lock() { mMutex.lock(); }
|
||||
virtual void unlock() { mMutex.unlock(); }
|
||||
|
||||
ALCdevice *mDevice;
|
||||
|
||||
std::recursive_mutex mMutex;
|
||||
|
||||
BackendBase(ALCdevice *device) noexcept;
|
||||
virtual ~BackendBase();
|
||||
};
|
||||
using BackendPtr = std::unique_ptr<BackendBase>;
|
||||
using BackendUniqueLock = std::unique_lock<BackendBase>;
|
||||
using BackendLockGuard = std::lock_guard<BackendBase>;
|
||||
|
||||
enum class BackendType {
|
||||
Playback,
|
||||
Capture
|
||||
};
|
||||
|
||||
enum class DevProbe {
|
||||
Playback,
|
||||
Capture
|
||||
};
|
||||
|
||||
|
||||
struct BackendFactory {
|
||||
virtual bool init() = 0;
|
||||
|
||||
virtual bool querySupport(BackendType type) = 0;
|
||||
|
||||
virtual void probe(DevProbe type, std::string *outnames) = 0;
|
||||
|
||||
virtual BackendPtr createBackend(ALCdevice *device, BackendType type) = 0;
|
||||
|
||||
protected:
|
||||
virtual ~BackendFactory() = default;
|
||||
};
|
||||
|
||||
#endif /* ALC_BACKENDS_BASE_H */
|
||||
@@ -1,660 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 1999-2007 by authors.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "backends/coreaudio.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "alcmain.h"
|
||||
#include "alexcpt.h"
|
||||
#include "alu.h"
|
||||
#include "ringbuffer.h"
|
||||
#include "converter.h"
|
||||
#include "backends/base.h"
|
||||
|
||||
#include <unistd.h>
|
||||
#include <AudioUnit/AudioUnit.h>
|
||||
#include <AudioToolbox/AudioToolbox.h>
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
static const ALCchar ca_device[] = "CoreAudio Default";
|
||||
|
||||
|
||||
struct CoreAudioPlayback final : public BackendBase {
|
||||
CoreAudioPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
|
||||
~CoreAudioPlayback() override;
|
||||
|
||||
OSStatus MixerProc(AudioUnitRenderActionFlags *ioActionFlags,
|
||||
const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames,
|
||||
AudioBufferList *ioData) noexcept;
|
||||
static OSStatus MixerProcC(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags,
|
||||
const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames,
|
||||
AudioBufferList *ioData) noexcept
|
||||
{
|
||||
return static_cast<CoreAudioPlayback*>(inRefCon)->MixerProc(ioActionFlags, inTimeStamp,
|
||||
inBusNumber, inNumberFrames, ioData);
|
||||
}
|
||||
|
||||
void open(const ALCchar *name) override;
|
||||
bool reset() override;
|
||||
bool start() override;
|
||||
void stop() override;
|
||||
|
||||
AudioUnit mAudioUnit{};
|
||||
|
||||
ALuint mFrameSize{0u};
|
||||
AudioStreamBasicDescription mFormat{}; // This is the OpenAL format as a CoreAudio ASBD
|
||||
|
||||
DEF_NEWDEL(CoreAudioPlayback)
|
||||
};
|
||||
|
||||
CoreAudioPlayback::~CoreAudioPlayback()
|
||||
{
|
||||
AudioUnitUninitialize(mAudioUnit);
|
||||
AudioComponentInstanceDispose(mAudioUnit);
|
||||
}
|
||||
|
||||
|
||||
OSStatus CoreAudioPlayback::MixerProc(AudioUnitRenderActionFlags*, const AudioTimeStamp*, UInt32,
|
||||
UInt32, AudioBufferList *ioData) noexcept
|
||||
{
|
||||
std::lock_guard<CoreAudioPlayback> _{*this};
|
||||
aluMixData(mDevice, ioData->mBuffers[0].mData, ioData->mBuffers[0].mDataByteSize/mFrameSize,
|
||||
ioData->mBuffers[0].mNumberChannels);
|
||||
return noErr;
|
||||
}
|
||||
|
||||
|
||||
void CoreAudioPlayback::open(const ALCchar *name)
|
||||
{
|
||||
if(!name)
|
||||
name = ca_device;
|
||||
else if(strcmp(name, ca_device) != 0)
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
|
||||
|
||||
/* open the default output unit */
|
||||
AudioComponentDescription desc{};
|
||||
desc.componentType = kAudioUnitType_Output;
|
||||
#if TARGET_OS_IOS
|
||||
desc.componentSubType = kAudioUnitSubType_RemoteIO;
|
||||
#else
|
||||
desc.componentSubType = kAudioUnitSubType_DefaultOutput;
|
||||
#endif
|
||||
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
|
||||
desc.componentFlags = 0;
|
||||
desc.componentFlagsMask = 0;
|
||||
|
||||
AudioComponent comp{AudioComponentFindNext(NULL, &desc)};
|
||||
if(comp == nullptr)
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "Could not find audio component"};
|
||||
|
||||
OSStatus err{AudioComponentInstanceNew(comp, &mAudioUnit)};
|
||||
if(err != noErr)
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "Could not create component instance: %u",
|
||||
err};
|
||||
|
||||
/* init and start the default audio unit... */
|
||||
err = AudioUnitInitialize(mAudioUnit);
|
||||
if(err != noErr)
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "Could not initialize audio unit: %u", err};
|
||||
|
||||
mDevice->DeviceName = name;
|
||||
}
|
||||
|
||||
bool CoreAudioPlayback::reset()
|
||||
{
|
||||
OSStatus err{AudioUnitUninitialize(mAudioUnit)};
|
||||
if(err != noErr)
|
||||
ERR("-- AudioUnitUninitialize failed.\n");
|
||||
|
||||
/* retrieve default output unit's properties (output side) */
|
||||
AudioStreamBasicDescription streamFormat{};
|
||||
auto size = static_cast<UInt32>(sizeof(AudioStreamBasicDescription));
|
||||
err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output,
|
||||
0, &streamFormat, &size);
|
||||
if(err != noErr || size != sizeof(AudioStreamBasicDescription))
|
||||
{
|
||||
ERR("AudioUnitGetProperty failed\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
#if 0
|
||||
TRACE("Output streamFormat of default output unit -\n");
|
||||
TRACE(" streamFormat.mFramesPerPacket = %d\n", streamFormat.mFramesPerPacket);
|
||||
TRACE(" streamFormat.mChannelsPerFrame = %d\n", streamFormat.mChannelsPerFrame);
|
||||
TRACE(" streamFormat.mBitsPerChannel = %d\n", streamFormat.mBitsPerChannel);
|
||||
TRACE(" streamFormat.mBytesPerPacket = %d\n", streamFormat.mBytesPerPacket);
|
||||
TRACE(" streamFormat.mBytesPerFrame = %d\n", streamFormat.mBytesPerFrame);
|
||||
TRACE(" streamFormat.mSampleRate = %5.0f\n", streamFormat.mSampleRate);
|
||||
#endif
|
||||
|
||||
/* set default output unit's input side to match output side */
|
||||
err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,
|
||||
0, &streamFormat, size);
|
||||
if(err != noErr)
|
||||
{
|
||||
ERR("AudioUnitSetProperty failed\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if(mDevice->Frequency != streamFormat.mSampleRate)
|
||||
{
|
||||
mDevice->BufferSize = static_cast<ALuint>(uint64_t{mDevice->BufferSize} *
|
||||
streamFormat.mSampleRate / mDevice->Frequency);
|
||||
mDevice->Frequency = static_cast<ALuint>(streamFormat.mSampleRate);
|
||||
}
|
||||
|
||||
/* FIXME: How to tell what channels are what in the output device, and how
|
||||
* to specify what we're giving? eg, 6.0 vs 5.1 */
|
||||
switch(streamFormat.mChannelsPerFrame)
|
||||
{
|
||||
case 1:
|
||||
mDevice->FmtChans = DevFmtMono;
|
||||
break;
|
||||
case 2:
|
||||
mDevice->FmtChans = DevFmtStereo;
|
||||
break;
|
||||
case 4:
|
||||
mDevice->FmtChans = DevFmtQuad;
|
||||
break;
|
||||
case 6:
|
||||
mDevice->FmtChans = DevFmtX51;
|
||||
break;
|
||||
case 7:
|
||||
mDevice->FmtChans = DevFmtX61;
|
||||
break;
|
||||
case 8:
|
||||
mDevice->FmtChans = DevFmtX71;
|
||||
break;
|
||||
default:
|
||||
ERR("Unhandled channel count (%d), using Stereo\n", streamFormat.mChannelsPerFrame);
|
||||
mDevice->FmtChans = DevFmtStereo;
|
||||
streamFormat.mChannelsPerFrame = 2;
|
||||
break;
|
||||
}
|
||||
SetDefaultWFXChannelOrder(mDevice);
|
||||
|
||||
/* use channel count and sample rate from the default output unit's current
|
||||
* parameters, but reset everything else */
|
||||
streamFormat.mFramesPerPacket = 1;
|
||||
streamFormat.mFormatFlags = 0;
|
||||
switch(mDevice->FmtType)
|
||||
{
|
||||
case DevFmtUByte:
|
||||
mDevice->FmtType = DevFmtByte;
|
||||
/* fall-through */
|
||||
case DevFmtByte:
|
||||
streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
|
||||
streamFormat.mBitsPerChannel = 8;
|
||||
break;
|
||||
case DevFmtUShort:
|
||||
mDevice->FmtType = DevFmtShort;
|
||||
/* fall-through */
|
||||
case DevFmtShort:
|
||||
streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
|
||||
streamFormat.mBitsPerChannel = 16;
|
||||
break;
|
||||
case DevFmtUInt:
|
||||
mDevice->FmtType = DevFmtInt;
|
||||
/* fall-through */
|
||||
case DevFmtInt:
|
||||
streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
|
||||
streamFormat.mBitsPerChannel = 32;
|
||||
break;
|
||||
case DevFmtFloat:
|
||||
streamFormat.mFormatFlags = kLinearPCMFormatFlagIsFloat;
|
||||
streamFormat.mBitsPerChannel = 32;
|
||||
break;
|
||||
}
|
||||
streamFormat.mBytesPerFrame = streamFormat.mChannelsPerFrame *
|
||||
streamFormat.mBitsPerChannel / 8;
|
||||
streamFormat.mBytesPerPacket = streamFormat.mBytesPerFrame;
|
||||
streamFormat.mFormatID = kAudioFormatLinearPCM;
|
||||
streamFormat.mFormatFlags |= kAudioFormatFlagsNativeEndian |
|
||||
kLinearPCMFormatFlagIsPacked;
|
||||
|
||||
err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,
|
||||
0, &streamFormat, sizeof(AudioStreamBasicDescription));
|
||||
if(err != noErr)
|
||||
{
|
||||
ERR("AudioUnitSetProperty failed\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* setup callback */
|
||||
mFrameSize = mDevice->frameSizeFromFmt();
|
||||
AURenderCallbackStruct input{};
|
||||
input.inputProc = CoreAudioPlayback::MixerProcC;
|
||||
input.inputProcRefCon = this;
|
||||
|
||||
err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_SetRenderCallback,
|
||||
kAudioUnitScope_Input, 0, &input, sizeof(AURenderCallbackStruct));
|
||||
if(err != noErr)
|
||||
{
|
||||
ERR("AudioUnitSetProperty failed\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* init the default audio unit... */
|
||||
err = AudioUnitInitialize(mAudioUnit);
|
||||
if(err != noErr)
|
||||
{
|
||||
ERR("AudioUnitInitialize failed\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CoreAudioPlayback::start()
|
||||
{
|
||||
OSStatus err{AudioOutputUnitStart(mAudioUnit)};
|
||||
if(err != noErr)
|
||||
{
|
||||
ERR("AudioOutputUnitStart failed\n");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void CoreAudioPlayback::stop()
|
||||
{
|
||||
OSStatus err{AudioOutputUnitStop(mAudioUnit)};
|
||||
if(err != noErr)
|
||||
ERR("AudioOutputUnitStop failed\n");
|
||||
}
|
||||
|
||||
|
||||
struct CoreAudioCapture final : public BackendBase {
|
||||
CoreAudioCapture(ALCdevice *device) noexcept : BackendBase{device} { }
|
||||
~CoreAudioCapture() override;
|
||||
|
||||
OSStatus RecordProc(AudioUnitRenderActionFlags *ioActionFlags,
|
||||
const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber,
|
||||
UInt32 inNumberFrames, AudioBufferList *ioData) noexcept;
|
||||
static OSStatus RecordProcC(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags,
|
||||
const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames,
|
||||
AudioBufferList *ioData) noexcept
|
||||
{
|
||||
return static_cast<CoreAudioCapture*>(inRefCon)->RecordProc(ioActionFlags, inTimeStamp,
|
||||
inBusNumber, inNumberFrames, ioData);
|
||||
}
|
||||
|
||||
void open(const ALCchar *name) override;
|
||||
bool start() override;
|
||||
void stop() override;
|
||||
ALCenum captureSamples(al::byte *buffer, ALCuint samples) override;
|
||||
ALCuint availableSamples() override;
|
||||
|
||||
AudioUnit mAudioUnit{0};
|
||||
|
||||
ALuint mFrameSize{0u};
|
||||
AudioStreamBasicDescription mFormat{}; // This is the OpenAL format as a CoreAudio ASBD
|
||||
|
||||
SampleConverterPtr mConverter;
|
||||
|
||||
RingBufferPtr mRing{nullptr};
|
||||
|
||||
DEF_NEWDEL(CoreAudioCapture)
|
||||
};
|
||||
|
||||
CoreAudioCapture::~CoreAudioCapture()
|
||||
{
|
||||
if(mAudioUnit)
|
||||
AudioComponentInstanceDispose(mAudioUnit);
|
||||
mAudioUnit = 0;
|
||||
}
|
||||
|
||||
|
||||
OSStatus CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags*,
|
||||
const AudioTimeStamp *inTimeStamp, UInt32, UInt32 inNumberFrames,
|
||||
AudioBufferList*) noexcept
|
||||
{
|
||||
AudioUnitRenderActionFlags flags = 0;
|
||||
union {
|
||||
al::byte _[sizeof(AudioBufferList) + sizeof(AudioBuffer)*2];
|
||||
AudioBufferList list;
|
||||
} audiobuf{};
|
||||
|
||||
auto rec_vec = mRing->getWriteVector();
|
||||
inNumberFrames = static_cast<UInt32>(minz(inNumberFrames,
|
||||
rec_vec.first.len+rec_vec.second.len));
|
||||
|
||||
// Fill the ringbuffer's two segments with data from the input device
|
||||
if(rec_vec.first.len >= inNumberFrames)
|
||||
{
|
||||
audiobuf.list.mNumberBuffers = 1;
|
||||
audiobuf.list.mBuffers[0].mNumberChannels = mFormat.mChannelsPerFrame;
|
||||
audiobuf.list.mBuffers[0].mData = rec_vec.first.buf;
|
||||
audiobuf.list.mBuffers[0].mDataByteSize = inNumberFrames * mFormat.mBytesPerFrame;
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto remaining = static_cast<ALuint>(inNumberFrames - rec_vec.first.len);
|
||||
audiobuf.list.mNumberBuffers = 2;
|
||||
audiobuf.list.mBuffers[0].mNumberChannels = mFormat.mChannelsPerFrame;
|
||||
audiobuf.list.mBuffers[0].mData = rec_vec.first.buf;
|
||||
audiobuf.list.mBuffers[0].mDataByteSize = static_cast<UInt32>(rec_vec.first.len) *
|
||||
mFormat.mBytesPerFrame;
|
||||
audiobuf.list.mBuffers[1].mNumberChannels = mFormat.mChannelsPerFrame;
|
||||
audiobuf.list.mBuffers[1].mData = rec_vec.second.buf;
|
||||
audiobuf.list.mBuffers[1].mDataByteSize = remaining * mFormat.mBytesPerFrame;
|
||||
}
|
||||
OSStatus err{AudioUnitRender(mAudioUnit, &flags, inTimeStamp, audiobuf.list.mNumberBuffers,
|
||||
inNumberFrames, &audiobuf.list)};
|
||||
if(err != noErr)
|
||||
{
|
||||
ERR("AudioUnitRender error: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
mRing->writeAdvance(inNumberFrames);
|
||||
return noErr;
|
||||
}
|
||||
|
||||
|
||||
void CoreAudioCapture::open(const ALCchar *name)
|
||||
{
|
||||
AudioStreamBasicDescription requestedFormat; // The application requested format
|
||||
AudioStreamBasicDescription hardwareFormat; // The hardware format
|
||||
AudioStreamBasicDescription outputFormat; // The AudioUnit output format
|
||||
AURenderCallbackStruct input;
|
||||
AudioComponentDescription desc;
|
||||
UInt32 outputFrameCount;
|
||||
UInt32 propertySize;
|
||||
#if !TARGET_OS_IOS
|
||||
AudioObjectPropertyAddress propertyAddress;
|
||||
#endif
|
||||
UInt32 enableIO;
|
||||
AudioComponent comp;
|
||||
OSStatus err;
|
||||
|
||||
if(!name)
|
||||
name = ca_device;
|
||||
else if(strcmp(name, ca_device) != 0)
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
|
||||
|
||||
desc.componentType = kAudioUnitType_Output;
|
||||
#if TARGET_OS_IOS
|
||||
desc.componentSubType = kAudioUnitSubType_RemoteIO;
|
||||
#else
|
||||
desc.componentSubType = kAudioUnitSubType_HALOutput;
|
||||
#endif
|
||||
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
|
||||
desc.componentFlags = 0;
|
||||
desc.componentFlagsMask = 0;
|
||||
|
||||
// Search for component with given description
|
||||
comp = AudioComponentFindNext(NULL, &desc);
|
||||
if(comp == NULL)
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "Could not find audio component"};
|
||||
|
||||
// Open the component
|
||||
err = AudioComponentInstanceNew(comp, &mAudioUnit);
|
||||
if(err != noErr)
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "Could not create component instance: %u",
|
||||
err};
|
||||
|
||||
// Turn off AudioUnit output
|
||||
enableIO = 0;
|
||||
err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_EnableIO,
|
||||
kAudioUnitScope_Output, 0, &enableIO, sizeof(ALuint));
|
||||
if(err != noErr)
|
||||
throw al::backend_exception{ALC_INVALID_VALUE,
|
||||
"Could not disable audio unit output property: %u", err};
|
||||
|
||||
// Turn on AudioUnit input
|
||||
enableIO = 1;
|
||||
err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_EnableIO,
|
||||
kAudioUnitScope_Input, 1, &enableIO, sizeof(ALuint));
|
||||
if(err != noErr)
|
||||
throw al::backend_exception{ALC_INVALID_VALUE,
|
||||
"Could not enable audio unit input property: %u", err};
|
||||
|
||||
#if !TARGET_OS_IOS
|
||||
{
|
||||
// Get the default input device
|
||||
AudioDeviceID inputDevice = kAudioDeviceUnknown;
|
||||
|
||||
propertySize = sizeof(AudioDeviceID);
|
||||
propertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice;
|
||||
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
|
||||
propertyAddress.mElement = kAudioObjectPropertyElementMaster;
|
||||
|
||||
err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, nullptr,
|
||||
&propertySize, &inputDevice);
|
||||
if(err != noErr)
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "Could not get input device: %u", err};
|
||||
if(inputDevice == kAudioDeviceUnknown)
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "Unknown input device"};
|
||||
|
||||
// Track the input device
|
||||
err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_CurrentDevice,
|
||||
kAudioUnitScope_Global, 0, &inputDevice, sizeof(AudioDeviceID));
|
||||
if(err != noErr)
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "Could not set input device: %u", err};
|
||||
}
|
||||
#endif
|
||||
|
||||
// set capture callback
|
||||
input.inputProc = CoreAudioCapture::RecordProcC;
|
||||
input.inputProcRefCon = this;
|
||||
|
||||
err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_SetInputCallback,
|
||||
kAudioUnitScope_Global, 0, &input, sizeof(AURenderCallbackStruct));
|
||||
if(err != noErr)
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "Could not set capture callback: %u", err};
|
||||
|
||||
// Initialize the device
|
||||
err = AudioUnitInitialize(mAudioUnit);
|
||||
if(err != noErr)
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "Could not initialize audio unit: %u", err};
|
||||
|
||||
// Get the hardware format
|
||||
propertySize = sizeof(AudioStreamBasicDescription);
|
||||
err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,
|
||||
1, &hardwareFormat, &propertySize);
|
||||
if(err != noErr || propertySize != sizeof(AudioStreamBasicDescription))
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "Could not get input format: %u", err};
|
||||
|
||||
// Set up the requested format description
|
||||
switch(mDevice->FmtType)
|
||||
{
|
||||
case DevFmtUByte:
|
||||
requestedFormat.mBitsPerChannel = 8;
|
||||
requestedFormat.mFormatFlags = kAudioFormatFlagIsPacked;
|
||||
break;
|
||||
case DevFmtShort:
|
||||
requestedFormat.mBitsPerChannel = 16;
|
||||
requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
|
||||
break;
|
||||
case DevFmtInt:
|
||||
requestedFormat.mBitsPerChannel = 32;
|
||||
requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
|
||||
break;
|
||||
case DevFmtFloat:
|
||||
requestedFormat.mBitsPerChannel = 32;
|
||||
requestedFormat.mFormatFlags = kAudioFormatFlagIsPacked;
|
||||
break;
|
||||
case DevFmtByte:
|
||||
case DevFmtUShort:
|
||||
case DevFmtUInt:
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "%s samples not suppoted",
|
||||
DevFmtTypeString(mDevice->FmtType)};
|
||||
}
|
||||
|
||||
switch(mDevice->FmtChans)
|
||||
{
|
||||
case DevFmtMono:
|
||||
requestedFormat.mChannelsPerFrame = 1;
|
||||
break;
|
||||
case DevFmtStereo:
|
||||
requestedFormat.mChannelsPerFrame = 2;
|
||||
break;
|
||||
|
||||
case DevFmtQuad:
|
||||
case DevFmtX51:
|
||||
case DevFmtX51Rear:
|
||||
case DevFmtX61:
|
||||
case DevFmtX71:
|
||||
case DevFmtAmbi3D:
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "%s not supported",
|
||||
DevFmtChannelsString(mDevice->FmtChans)};
|
||||
}
|
||||
|
||||
requestedFormat.mBytesPerFrame = requestedFormat.mChannelsPerFrame * requestedFormat.mBitsPerChannel / 8;
|
||||
requestedFormat.mBytesPerPacket = requestedFormat.mBytesPerFrame;
|
||||
requestedFormat.mSampleRate = mDevice->Frequency;
|
||||
requestedFormat.mFormatID = kAudioFormatLinearPCM;
|
||||
requestedFormat.mReserved = 0;
|
||||
requestedFormat.mFramesPerPacket = 1;
|
||||
|
||||
// save requested format description for later use
|
||||
mFormat = requestedFormat;
|
||||
mFrameSize = mDevice->frameSizeFromFmt();
|
||||
|
||||
// Use intermediate format for sample rate conversion (outputFormat)
|
||||
// Set sample rate to the same as hardware for resampling later
|
||||
outputFormat = requestedFormat;
|
||||
outputFormat.mSampleRate = hardwareFormat.mSampleRate;
|
||||
|
||||
// The output format should be the requested format, but using the hardware sample rate
|
||||
// This is because the AudioUnit will automatically scale other properties, except for sample rate
|
||||
err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output,
|
||||
1, &outputFormat, sizeof(outputFormat));
|
||||
if(err != noErr)
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "Could not set input format: %u", err};
|
||||
|
||||
// Set the AudioUnit output format frame count
|
||||
uint64_t FrameCount64{mDevice->UpdateSize};
|
||||
FrameCount64 = static_cast<uint64_t>(FrameCount64*outputFormat.mSampleRate + mDevice->Frequency-1) /
|
||||
mDevice->Frequency;
|
||||
FrameCount64 += MAX_RESAMPLER_PADDING;
|
||||
if(FrameCount64 > std::numeric_limits<uint32_t>::max()/2)
|
||||
throw al::backend_exception{ALC_INVALID_VALUE,
|
||||
"Calculated frame count is too large: %" PRIu64, FrameCount64};
|
||||
|
||||
outputFrameCount = static_cast<uint32_t>(FrameCount64);
|
||||
err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_MaximumFramesPerSlice,
|
||||
kAudioUnitScope_Output, 0, &outputFrameCount, sizeof(outputFrameCount));
|
||||
if(err != noErr)
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "Failed to set capture frame count: %u",
|
||||
err};
|
||||
|
||||
// Set up sample converter if needed
|
||||
if(outputFormat.mSampleRate != mDevice->Frequency)
|
||||
mConverter = CreateSampleConverter(mDevice->FmtType, mDevice->FmtType,
|
||||
mFormat.mChannelsPerFrame, static_cast<ALuint>(hardwareFormat.mSampleRate),
|
||||
mDevice->Frequency, Resampler::FastBSinc24);
|
||||
|
||||
mRing = RingBuffer::Create(outputFrameCount, mFrameSize, false);
|
||||
|
||||
mDevice->DeviceName = name;
|
||||
}
|
||||
|
||||
|
||||
bool CoreAudioCapture::start()
|
||||
{
|
||||
OSStatus err{AudioOutputUnitStart(mAudioUnit)};
|
||||
if(err != noErr)
|
||||
{
|
||||
ERR("AudioOutputUnitStart failed\n");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void CoreAudioCapture::stop()
|
||||
{
|
||||
OSStatus err{AudioOutputUnitStop(mAudioUnit)};
|
||||
if(err != noErr)
|
||||
ERR("AudioOutputUnitStop failed\n");
|
||||
}
|
||||
|
||||
ALCenum CoreAudioCapture::captureSamples(al::byte *buffer, ALCuint samples)
|
||||
{
|
||||
if(!mConverter)
|
||||
{
|
||||
mRing->read(buffer, samples);
|
||||
return ALC_NO_ERROR;
|
||||
}
|
||||
|
||||
auto rec_vec = mRing->getReadVector();
|
||||
const void *src0{rec_vec.first.buf};
|
||||
auto src0len = static_cast<ALuint>(rec_vec.first.len);
|
||||
ALuint got{mConverter->convert(&src0, &src0len, buffer, samples)};
|
||||
size_t total_read{rec_vec.first.len - src0len};
|
||||
if(got < samples && !src0len && rec_vec.second.len > 0)
|
||||
{
|
||||
const void *src1{rec_vec.second.buf};
|
||||
auto src1len = static_cast<ALuint>(rec_vec.second.len);
|
||||
got += mConverter->convert(&src1, &src1len, buffer+got, samples-got);
|
||||
total_read += rec_vec.second.len - src1len;
|
||||
}
|
||||
|
||||
mRing->readAdvance(total_read);
|
||||
return ALC_NO_ERROR;
|
||||
}
|
||||
|
||||
ALCuint CoreAudioCapture::availableSamples()
|
||||
{
|
||||
if(!mConverter) return static_cast<ALCuint>(mRing->readSpace());
|
||||
return mConverter->availableOut(static_cast<ALCuint>(mRing->readSpace()));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
BackendFactory &CoreAudioBackendFactory::getFactory()
|
||||
{
|
||||
static CoreAudioBackendFactory factory{};
|
||||
return factory;
|
||||
}
|
||||
|
||||
bool CoreAudioBackendFactory::init() { return true; }
|
||||
|
||||
bool CoreAudioBackendFactory::querySupport(BackendType type)
|
||||
{ return type == BackendType::Playback || type == BackendType::Capture; }
|
||||
|
||||
void CoreAudioBackendFactory::probe(DevProbe type, std::string *outnames)
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
case DevProbe::Playback:
|
||||
case DevProbe::Capture:
|
||||
/* Includes null char. */
|
||||
outnames->append(ca_device, sizeof(ca_device));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
BackendPtr CoreAudioBackendFactory::createBackend(ALCdevice *device, BackendType type)
|
||||
{
|
||||
if(type == BackendType::Playback)
|
||||
return BackendPtr{new CoreAudioPlayback{device}};
|
||||
if(type == BackendType::Capture)
|
||||
return BackendPtr{new CoreAudioCapture{device}};
|
||||
return nullptr;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
#ifndef BACKENDS_COREAUDIO_H
|
||||
#define BACKENDS_COREAUDIO_H
|
||||
|
||||
#include "backends/base.h"
|
||||
|
||||
struct CoreAudioBackendFactory final : public BackendFactory {
|
||||
public:
|
||||
bool init() override;
|
||||
|
||||
bool querySupport(BackendType type) override;
|
||||
|
||||
void probe(DevProbe type, std::string *outnames) override;
|
||||
|
||||
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
|
||||
|
||||
static BackendFactory &getFactory();
|
||||
};
|
||||
|
||||
#endif /* BACKENDS_COREAUDIO_H */
|
||||
@@ -1,917 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 1999-2007 by authors.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "backends/dsound.h"
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <memory.h>
|
||||
|
||||
#include <cguid.h>
|
||||
#include <mmreg.h>
|
||||
#ifndef _WAVEFORMATEXTENSIBLE_
|
||||
#include <ks.h>
|
||||
#include <ksmedia.h>
|
||||
#endif
|
||||
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <thread>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
|
||||
#include "alcmain.h"
|
||||
#include "alexcpt.h"
|
||||
#include "alu.h"
|
||||
#include "ringbuffer.h"
|
||||
#include "compat.h"
|
||||
#include "dynload.h"
|
||||
#include "strutils.h"
|
||||
#include "threads.h"
|
||||
|
||||
/* MinGW-w64 needs this for some unknown reason now. */
|
||||
using LPCWAVEFORMATEX = const WAVEFORMATEX*;
|
||||
#include <dsound.h>
|
||||
|
||||
|
||||
#ifndef DSSPEAKER_5POINT1
|
||||
# define DSSPEAKER_5POINT1 0x00000006
|
||||
#endif
|
||||
#ifndef DSSPEAKER_5POINT1_BACK
|
||||
# define DSSPEAKER_5POINT1_BACK 0x00000006
|
||||
#endif
|
||||
#ifndef DSSPEAKER_7POINT1
|
||||
# define DSSPEAKER_7POINT1 0x00000007
|
||||
#endif
|
||||
#ifndef DSSPEAKER_7POINT1_SURROUND
|
||||
# define DSSPEAKER_7POINT1_SURROUND 0x00000008
|
||||
#endif
|
||||
#ifndef DSSPEAKER_5POINT1_SURROUND
|
||||
# define DSSPEAKER_5POINT1_SURROUND 0x00000009
|
||||
#endif
|
||||
|
||||
|
||||
/* Some headers seem to define these as macros for __uuidof, which is annoying
|
||||
* since some headers don't declare them at all. Hopefully the ifdef is enough
|
||||
* to tell if they need to be declared.
|
||||
*/
|
||||
#ifndef KSDATAFORMAT_SUBTYPE_PCM
|
||||
DEFINE_GUID(KSDATAFORMAT_SUBTYPE_PCM, 0x00000001, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
|
||||
#endif
|
||||
#ifndef KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
|
||||
DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
#define DEVNAME_HEAD "OpenAL Soft on "
|
||||
|
||||
|
||||
#ifdef HAVE_DYNLOAD
|
||||
void *ds_handle;
|
||||
HRESULT (WINAPI *pDirectSoundCreate)(const GUID *pcGuidDevice, IDirectSound **ppDS, IUnknown *pUnkOuter);
|
||||
HRESULT (WINAPI *pDirectSoundEnumerateW)(LPDSENUMCALLBACKW pDSEnumCallback, void *pContext);
|
||||
HRESULT (WINAPI *pDirectSoundCaptureCreate)(const GUID *pcGuidDevice, IDirectSoundCapture **ppDSC, IUnknown *pUnkOuter);
|
||||
HRESULT (WINAPI *pDirectSoundCaptureEnumerateW)(LPDSENUMCALLBACKW pDSEnumCallback, void *pContext);
|
||||
|
||||
#ifndef IN_IDE_PARSER
|
||||
#define DirectSoundCreate pDirectSoundCreate
|
||||
#define DirectSoundEnumerateW pDirectSoundEnumerateW
|
||||
#define DirectSoundCaptureCreate pDirectSoundCaptureCreate
|
||||
#define DirectSoundCaptureEnumerateW pDirectSoundCaptureEnumerateW
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
#define MAX_UPDATES 128
|
||||
|
||||
struct DevMap {
|
||||
std::string name;
|
||||
GUID guid;
|
||||
|
||||
template<typename T0, typename T1>
|
||||
DevMap(T0&& name_, T1&& guid_)
|
||||
: name{std::forward<T0>(name_)}, guid{std::forward<T1>(guid_)}
|
||||
{ }
|
||||
};
|
||||
|
||||
al::vector<DevMap> PlaybackDevices;
|
||||
al::vector<DevMap> CaptureDevices;
|
||||
|
||||
bool checkName(const al::vector<DevMap> &list, const std::string &name)
|
||||
{
|
||||
auto match_name = [&name](const DevMap &entry) -> bool
|
||||
{ return entry.name == name; };
|
||||
return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend();
|
||||
}
|
||||
|
||||
BOOL CALLBACK DSoundEnumDevices(GUID *guid, const WCHAR *desc, const WCHAR*, void *data) noexcept
|
||||
{
|
||||
if(!guid)
|
||||
return TRUE;
|
||||
|
||||
auto& devices = *static_cast<al::vector<DevMap>*>(data);
|
||||
const std::string basename{DEVNAME_HEAD + wstr_to_utf8(desc)};
|
||||
|
||||
int count{1};
|
||||
std::string newname{basename};
|
||||
while(checkName(devices, newname))
|
||||
{
|
||||
newname = basename;
|
||||
newname += " #";
|
||||
newname += std::to_string(++count);
|
||||
}
|
||||
devices.emplace_back(std::move(newname), *guid);
|
||||
const DevMap &newentry = devices.back();
|
||||
|
||||
OLECHAR *guidstr{nullptr};
|
||||
HRESULT hr{StringFromCLSID(*guid, &guidstr)};
|
||||
if(SUCCEEDED(hr))
|
||||
{
|
||||
TRACE("Got device \"%s\", GUID \"%ls\"\n", newentry.name.c_str(), guidstr);
|
||||
CoTaskMemFree(guidstr);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
struct DSoundPlayback final : public BackendBase {
|
||||
DSoundPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
|
||||
~DSoundPlayback() override;
|
||||
|
||||
int mixerProc();
|
||||
|
||||
void open(const ALCchar *name) override;
|
||||
bool reset() override;
|
||||
bool start() override;
|
||||
void stop() override;
|
||||
|
||||
IDirectSound *mDS{nullptr};
|
||||
IDirectSoundBuffer *mPrimaryBuffer{nullptr};
|
||||
IDirectSoundBuffer *mBuffer{nullptr};
|
||||
IDirectSoundNotify *mNotifies{nullptr};
|
||||
HANDLE mNotifyEvent{nullptr};
|
||||
|
||||
std::atomic<bool> mKillNow{true};
|
||||
std::thread mThread;
|
||||
|
||||
DEF_NEWDEL(DSoundPlayback)
|
||||
};
|
||||
|
||||
DSoundPlayback::~DSoundPlayback()
|
||||
{
|
||||
if(mNotifies)
|
||||
mNotifies->Release();
|
||||
mNotifies = nullptr;
|
||||
if(mBuffer)
|
||||
mBuffer->Release();
|
||||
mBuffer = nullptr;
|
||||
if(mPrimaryBuffer)
|
||||
mPrimaryBuffer->Release();
|
||||
mPrimaryBuffer = nullptr;
|
||||
|
||||
if(mDS)
|
||||
mDS->Release();
|
||||
mDS = nullptr;
|
||||
if(mNotifyEvent)
|
||||
CloseHandle(mNotifyEvent);
|
||||
mNotifyEvent = nullptr;
|
||||
}
|
||||
|
||||
|
||||
FORCE_ALIGN int DSoundPlayback::mixerProc()
|
||||
{
|
||||
SetRTPriority();
|
||||
althrd_setname(MIXER_THREAD_NAME);
|
||||
|
||||
DSBCAPS DSBCaps{};
|
||||
DSBCaps.dwSize = sizeof(DSBCaps);
|
||||
HRESULT err{mBuffer->GetCaps(&DSBCaps)};
|
||||
if(FAILED(err))
|
||||
{
|
||||
ERR("Failed to get buffer caps: 0x%lx\n", err);
|
||||
aluHandleDisconnect(mDevice, "Failure retrieving playback buffer info: 0x%lx", err);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const size_t FrameStep{mDevice->channelsFromFmt()};
|
||||
ALuint FrameSize{mDevice->frameSizeFromFmt()};
|
||||
DWORD FragSize{mDevice->UpdateSize * FrameSize};
|
||||
|
||||
bool Playing{false};
|
||||
DWORD LastCursor{0u};
|
||||
mBuffer->GetCurrentPosition(&LastCursor, nullptr);
|
||||
while(!mKillNow.load(std::memory_order_acquire) &&
|
||||
mDevice->Connected.load(std::memory_order_acquire))
|
||||
{
|
||||
// Get current play cursor
|
||||
DWORD PlayCursor;
|
||||
mBuffer->GetCurrentPosition(&PlayCursor, nullptr);
|
||||
DWORD avail = (PlayCursor-LastCursor+DSBCaps.dwBufferBytes) % DSBCaps.dwBufferBytes;
|
||||
|
||||
if(avail < FragSize)
|
||||
{
|
||||
if(!Playing)
|
||||
{
|
||||
err = mBuffer->Play(0, 0, DSBPLAY_LOOPING);
|
||||
if(FAILED(err))
|
||||
{
|
||||
ERR("Failed to play buffer: 0x%lx\n", err);
|
||||
aluHandleDisconnect(mDevice, "Failure starting playback: 0x%lx", err);
|
||||
return 1;
|
||||
}
|
||||
Playing = true;
|
||||
}
|
||||
|
||||
avail = WaitForSingleObjectEx(mNotifyEvent, 2000, FALSE);
|
||||
if(avail != WAIT_OBJECT_0)
|
||||
ERR("WaitForSingleObjectEx error: 0x%lx\n", avail);
|
||||
continue;
|
||||
}
|
||||
avail -= avail%FragSize;
|
||||
|
||||
// Lock output buffer
|
||||
void *WritePtr1, *WritePtr2;
|
||||
DWORD WriteCnt1{0u}, WriteCnt2{0u};
|
||||
err = mBuffer->Lock(LastCursor, avail, &WritePtr1, &WriteCnt1, &WritePtr2, &WriteCnt2, 0);
|
||||
|
||||
// If the buffer is lost, restore it and lock
|
||||
if(err == DSERR_BUFFERLOST)
|
||||
{
|
||||
WARN("Buffer lost, restoring...\n");
|
||||
err = mBuffer->Restore();
|
||||
if(SUCCEEDED(err))
|
||||
{
|
||||
Playing = false;
|
||||
LastCursor = 0;
|
||||
err = mBuffer->Lock(0, DSBCaps.dwBufferBytes, &WritePtr1, &WriteCnt1,
|
||||
&WritePtr2, &WriteCnt2, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if(SUCCEEDED(err))
|
||||
{
|
||||
std::unique_lock<DSoundPlayback> dlock{*this};
|
||||
aluMixData(mDevice, WritePtr1, WriteCnt1/FrameSize, FrameStep);
|
||||
if(WriteCnt2 > 0)
|
||||
aluMixData(mDevice, WritePtr2, WriteCnt2/FrameSize, FrameStep);
|
||||
dlock.unlock();
|
||||
|
||||
mBuffer->Unlock(WritePtr1, WriteCnt1, WritePtr2, WriteCnt2);
|
||||
}
|
||||
else
|
||||
{
|
||||
ERR("Buffer lock error: %#lx\n", err);
|
||||
std::lock_guard<DSoundPlayback> _{*this};
|
||||
aluHandleDisconnect(mDevice, "Failed to lock output buffer: 0x%lx", err);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Update old write cursor location
|
||||
LastCursor += WriteCnt1+WriteCnt2;
|
||||
LastCursor %= DSBCaps.dwBufferBytes;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void DSoundPlayback::open(const ALCchar *name)
|
||||
{
|
||||
HRESULT hr;
|
||||
if(PlaybackDevices.empty())
|
||||
{
|
||||
/* Initialize COM to prevent name truncation */
|
||||
HRESULT hrcom{CoInitialize(nullptr)};
|
||||
hr = DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices);
|
||||
if(FAILED(hr))
|
||||
ERR("Error enumerating DirectSound devices (0x%lx)!\n", hr);
|
||||
if(SUCCEEDED(hrcom))
|
||||
CoUninitialize();
|
||||
}
|
||||
|
||||
const GUID *guid{nullptr};
|
||||
if(!name && !PlaybackDevices.empty())
|
||||
{
|
||||
name = PlaybackDevices[0].name.c_str();
|
||||
guid = &PlaybackDevices[0].guid;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(),
|
||||
[name](const DevMap &entry) -> bool
|
||||
{ return entry.name == name; }
|
||||
);
|
||||
if(iter == PlaybackDevices.cend())
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
|
||||
guid = &iter->guid;
|
||||
}
|
||||
|
||||
hr = DS_OK;
|
||||
mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
|
||||
if(!mNotifyEvent) hr = E_FAIL;
|
||||
|
||||
//DirectSound Init code
|
||||
if(SUCCEEDED(hr))
|
||||
hr = DirectSoundCreate(guid, &mDS, nullptr);
|
||||
if(SUCCEEDED(hr))
|
||||
hr = mDS->SetCooperativeLevel(GetForegroundWindow(), DSSCL_PRIORITY);
|
||||
if(FAILED(hr))
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "Device init failed: 0x%08lx", hr};
|
||||
|
||||
mDevice->DeviceName = name;
|
||||
}
|
||||
|
||||
bool DSoundPlayback::reset()
|
||||
{
|
||||
if(mNotifies)
|
||||
mNotifies->Release();
|
||||
mNotifies = nullptr;
|
||||
if(mBuffer)
|
||||
mBuffer->Release();
|
||||
mBuffer = nullptr;
|
||||
if(mPrimaryBuffer)
|
||||
mPrimaryBuffer->Release();
|
||||
mPrimaryBuffer = nullptr;
|
||||
|
||||
switch(mDevice->FmtType)
|
||||
{
|
||||
case DevFmtByte:
|
||||
mDevice->FmtType = DevFmtUByte;
|
||||
break;
|
||||
case DevFmtFloat:
|
||||
if(mDevice->Flags.get<SampleTypeRequest>())
|
||||
break;
|
||||
/* fall-through */
|
||||
case DevFmtUShort:
|
||||
mDevice->FmtType = DevFmtShort;
|
||||
break;
|
||||
case DevFmtUInt:
|
||||
mDevice->FmtType = DevFmtInt;
|
||||
break;
|
||||
case DevFmtUByte:
|
||||
case DevFmtShort:
|
||||
case DevFmtInt:
|
||||
break;
|
||||
}
|
||||
|
||||
WAVEFORMATEXTENSIBLE OutputType{};
|
||||
DWORD speakers;
|
||||
HRESULT hr{mDS->GetSpeakerConfig(&speakers)};
|
||||
if(SUCCEEDED(hr))
|
||||
{
|
||||
speakers = DSSPEAKER_CONFIG(speakers);
|
||||
if(!mDevice->Flags.get<ChannelsRequest>())
|
||||
{
|
||||
if(speakers == DSSPEAKER_MONO)
|
||||
mDevice->FmtChans = DevFmtMono;
|
||||
else if(speakers == DSSPEAKER_STEREO || speakers == DSSPEAKER_HEADPHONE)
|
||||
mDevice->FmtChans = DevFmtStereo;
|
||||
else if(speakers == DSSPEAKER_QUAD)
|
||||
mDevice->FmtChans = DevFmtQuad;
|
||||
else if(speakers == DSSPEAKER_5POINT1_SURROUND)
|
||||
mDevice->FmtChans = DevFmtX51;
|
||||
else if(speakers == DSSPEAKER_5POINT1_BACK)
|
||||
mDevice->FmtChans = DevFmtX51Rear;
|
||||
else if(speakers == DSSPEAKER_7POINT1 || speakers == DSSPEAKER_7POINT1_SURROUND)
|
||||
mDevice->FmtChans = DevFmtX71;
|
||||
else
|
||||
ERR("Unknown system speaker config: 0x%lx\n", speakers);
|
||||
}
|
||||
mDevice->IsHeadphones = (mDevice->FmtChans == DevFmtStereo &&
|
||||
speakers == DSSPEAKER_HEADPHONE);
|
||||
|
||||
switch(mDevice->FmtChans)
|
||||
{
|
||||
case DevFmtMono:
|
||||
OutputType.dwChannelMask = SPEAKER_FRONT_CENTER;
|
||||
break;
|
||||
case DevFmtAmbi3D:
|
||||
mDevice->FmtChans = DevFmtStereo;
|
||||
/*fall-through*/
|
||||
case DevFmtStereo:
|
||||
OutputType.dwChannelMask = SPEAKER_FRONT_LEFT |
|
||||
SPEAKER_FRONT_RIGHT;
|
||||
break;
|
||||
case DevFmtQuad:
|
||||
OutputType.dwChannelMask = SPEAKER_FRONT_LEFT |
|
||||
SPEAKER_FRONT_RIGHT |
|
||||
SPEAKER_BACK_LEFT |
|
||||
SPEAKER_BACK_RIGHT;
|
||||
break;
|
||||
case DevFmtX51:
|
||||
OutputType.dwChannelMask = SPEAKER_FRONT_LEFT |
|
||||
SPEAKER_FRONT_RIGHT |
|
||||
SPEAKER_FRONT_CENTER |
|
||||
SPEAKER_LOW_FREQUENCY |
|
||||
SPEAKER_SIDE_LEFT |
|
||||
SPEAKER_SIDE_RIGHT;
|
||||
break;
|
||||
case DevFmtX51Rear:
|
||||
OutputType.dwChannelMask = SPEAKER_FRONT_LEFT |
|
||||
SPEAKER_FRONT_RIGHT |
|
||||
SPEAKER_FRONT_CENTER |
|
||||
SPEAKER_LOW_FREQUENCY |
|
||||
SPEAKER_BACK_LEFT |
|
||||
SPEAKER_BACK_RIGHT;
|
||||
break;
|
||||
case DevFmtX61:
|
||||
OutputType.dwChannelMask = SPEAKER_FRONT_LEFT |
|
||||
SPEAKER_FRONT_RIGHT |
|
||||
SPEAKER_FRONT_CENTER |
|
||||
SPEAKER_LOW_FREQUENCY |
|
||||
SPEAKER_BACK_CENTER |
|
||||
SPEAKER_SIDE_LEFT |
|
||||
SPEAKER_SIDE_RIGHT;
|
||||
break;
|
||||
case DevFmtX71:
|
||||
OutputType.dwChannelMask = SPEAKER_FRONT_LEFT |
|
||||
SPEAKER_FRONT_RIGHT |
|
||||
SPEAKER_FRONT_CENTER |
|
||||
SPEAKER_LOW_FREQUENCY |
|
||||
SPEAKER_BACK_LEFT |
|
||||
SPEAKER_BACK_RIGHT |
|
||||
SPEAKER_SIDE_LEFT |
|
||||
SPEAKER_SIDE_RIGHT;
|
||||
break;
|
||||
}
|
||||
|
||||
retry_open:
|
||||
hr = S_OK;
|
||||
OutputType.Format.wFormatTag = WAVE_FORMAT_PCM;
|
||||
OutputType.Format.nChannels = static_cast<WORD>(mDevice->channelsFromFmt());
|
||||
OutputType.Format.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8);
|
||||
OutputType.Format.nBlockAlign = static_cast<WORD>(OutputType.Format.nChannels *
|
||||
OutputType.Format.wBitsPerSample / 8);
|
||||
OutputType.Format.nSamplesPerSec = mDevice->Frequency;
|
||||
OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec *
|
||||
OutputType.Format.nBlockAlign;
|
||||
OutputType.Format.cbSize = 0;
|
||||
}
|
||||
|
||||
if(OutputType.Format.nChannels > 2 || mDevice->FmtType == DevFmtFloat)
|
||||
{
|
||||
OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
|
||||
OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample;
|
||||
OutputType.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
|
||||
if(mDevice->FmtType == DevFmtFloat)
|
||||
OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
|
||||
else
|
||||
OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
|
||||
|
||||
if(mPrimaryBuffer)
|
||||
mPrimaryBuffer->Release();
|
||||
mPrimaryBuffer = nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(SUCCEEDED(hr) && !mPrimaryBuffer)
|
||||
{
|
||||
DSBUFFERDESC DSBDescription{};
|
||||
DSBDescription.dwSize = sizeof(DSBDescription);
|
||||
DSBDescription.dwFlags = DSBCAPS_PRIMARYBUFFER;
|
||||
hr = mDS->CreateSoundBuffer(&DSBDescription, &mPrimaryBuffer, nullptr);
|
||||
}
|
||||
if(SUCCEEDED(hr))
|
||||
hr = mPrimaryBuffer->SetFormat(&OutputType.Format);
|
||||
}
|
||||
|
||||
if(SUCCEEDED(hr))
|
||||
{
|
||||
ALuint num_updates{mDevice->BufferSize / mDevice->UpdateSize};
|
||||
if(num_updates > MAX_UPDATES)
|
||||
num_updates = MAX_UPDATES;
|
||||
mDevice->BufferSize = mDevice->UpdateSize * num_updates;
|
||||
|
||||
DSBUFFERDESC DSBDescription{};
|
||||
DSBDescription.dwSize = sizeof(DSBDescription);
|
||||
DSBDescription.dwFlags = DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GETCURRENTPOSITION2 |
|
||||
DSBCAPS_GLOBALFOCUS;
|
||||
DSBDescription.dwBufferBytes = mDevice->BufferSize * OutputType.Format.nBlockAlign;
|
||||
DSBDescription.lpwfxFormat = &OutputType.Format;
|
||||
|
||||
hr = mDS->CreateSoundBuffer(&DSBDescription, &mBuffer, nullptr);
|
||||
if(FAILED(hr) && mDevice->FmtType == DevFmtFloat)
|
||||
{
|
||||
mDevice->FmtType = DevFmtShort;
|
||||
goto retry_open;
|
||||
}
|
||||
}
|
||||
|
||||
if(SUCCEEDED(hr))
|
||||
{
|
||||
void *ptr;
|
||||
hr = mBuffer->QueryInterface(IID_IDirectSoundNotify, &ptr);
|
||||
if(SUCCEEDED(hr))
|
||||
{
|
||||
auto Notifies = static_cast<IDirectSoundNotify*>(ptr);
|
||||
mNotifies = Notifies;
|
||||
|
||||
ALuint num_updates{mDevice->BufferSize / mDevice->UpdateSize};
|
||||
assert(num_updates <= MAX_UPDATES);
|
||||
|
||||
std::array<DSBPOSITIONNOTIFY,MAX_UPDATES> nots;
|
||||
for(ALuint i{0};i < num_updates;++i)
|
||||
{
|
||||
nots[i].dwOffset = i * mDevice->UpdateSize * OutputType.Format.nBlockAlign;
|
||||
nots[i].hEventNotify = mNotifyEvent;
|
||||
}
|
||||
if(Notifies->SetNotificationPositions(num_updates, nots.data()) != DS_OK)
|
||||
hr = E_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
if(FAILED(hr))
|
||||
{
|
||||
if(mNotifies)
|
||||
mNotifies->Release();
|
||||
mNotifies = nullptr;
|
||||
if(mBuffer)
|
||||
mBuffer->Release();
|
||||
mBuffer = nullptr;
|
||||
if(mPrimaryBuffer)
|
||||
mPrimaryBuffer->Release();
|
||||
mPrimaryBuffer = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
ResetEvent(mNotifyEvent);
|
||||
SetDefaultWFXChannelOrder(mDevice);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DSoundPlayback::start()
|
||||
{
|
||||
try {
|
||||
mKillNow.store(false, std::memory_order_release);
|
||||
mThread = std::thread{std::mem_fn(&DSoundPlayback::mixerProc), this};
|
||||
return true;
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
ERR("Failed to start mixing thread: %s\n", e.what());
|
||||
}
|
||||
catch(...) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void DSoundPlayback::stop()
|
||||
{
|
||||
if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
|
||||
return;
|
||||
mThread.join();
|
||||
|
||||
mBuffer->Stop();
|
||||
}
|
||||
|
||||
|
||||
struct DSoundCapture final : public BackendBase {
|
||||
DSoundCapture(ALCdevice *device) noexcept : BackendBase{device} { }
|
||||
~DSoundCapture() override;
|
||||
|
||||
void open(const ALCchar *name) override;
|
||||
bool start() override;
|
||||
void stop() override;
|
||||
ALCenum captureSamples(al::byte *buffer, ALCuint samples) override;
|
||||
ALCuint availableSamples() override;
|
||||
|
||||
IDirectSoundCapture *mDSC{nullptr};
|
||||
IDirectSoundCaptureBuffer *mDSCbuffer{nullptr};
|
||||
DWORD mBufferBytes{0u};
|
||||
DWORD mCursor{0u};
|
||||
|
||||
RingBufferPtr mRing;
|
||||
|
||||
DEF_NEWDEL(DSoundCapture)
|
||||
};
|
||||
|
||||
DSoundCapture::~DSoundCapture()
|
||||
{
|
||||
if(mDSCbuffer)
|
||||
{
|
||||
mDSCbuffer->Stop();
|
||||
mDSCbuffer->Release();
|
||||
mDSCbuffer = nullptr;
|
||||
}
|
||||
|
||||
if(mDSC)
|
||||
mDSC->Release();
|
||||
mDSC = nullptr;
|
||||
}
|
||||
|
||||
|
||||
void DSoundCapture::open(const ALCchar *name)
|
||||
{
|
||||
HRESULT hr;
|
||||
if(CaptureDevices.empty())
|
||||
{
|
||||
/* Initialize COM to prevent name truncation */
|
||||
HRESULT hrcom{CoInitialize(nullptr)};
|
||||
hr = DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices);
|
||||
if(FAILED(hr))
|
||||
ERR("Error enumerating DirectSound devices (0x%lx)!\n", hr);
|
||||
if(SUCCEEDED(hrcom))
|
||||
CoUninitialize();
|
||||
}
|
||||
|
||||
const GUID *guid{nullptr};
|
||||
if(!name && !CaptureDevices.empty())
|
||||
{
|
||||
name = CaptureDevices[0].name.c_str();
|
||||
guid = &CaptureDevices[0].guid;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(),
|
||||
[name](const DevMap &entry) -> bool
|
||||
{ return entry.name == name; }
|
||||
);
|
||||
if(iter == CaptureDevices.cend())
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
|
||||
guid = &iter->guid;
|
||||
}
|
||||
|
||||
switch(mDevice->FmtType)
|
||||
{
|
||||
case DevFmtByte:
|
||||
case DevFmtUShort:
|
||||
case DevFmtUInt:
|
||||
WARN("%s capture samples not supported\n", DevFmtTypeString(mDevice->FmtType));
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "%s capture samples not supported",
|
||||
DevFmtTypeString(mDevice->FmtType)};
|
||||
|
||||
case DevFmtUByte:
|
||||
case DevFmtShort:
|
||||
case DevFmtInt:
|
||||
case DevFmtFloat:
|
||||
break;
|
||||
}
|
||||
|
||||
WAVEFORMATEXTENSIBLE InputType{};
|
||||
switch(mDevice->FmtChans)
|
||||
{
|
||||
case DevFmtMono:
|
||||
InputType.dwChannelMask = SPEAKER_FRONT_CENTER;
|
||||
break;
|
||||
case DevFmtStereo:
|
||||
InputType.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
|
||||
break;
|
||||
case DevFmtQuad:
|
||||
InputType.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT |
|
||||
SPEAKER_BACK_RIGHT;
|
||||
break;
|
||||
case DevFmtX51:
|
||||
InputType.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER |
|
||||
SPEAKER_LOW_FREQUENCY | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT;
|
||||
break;
|
||||
case DevFmtX51Rear:
|
||||
InputType.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER |
|
||||
SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT;
|
||||
break;
|
||||
case DevFmtX61:
|
||||
InputType.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER |
|
||||
SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_CENTER | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT;
|
||||
break;
|
||||
case DevFmtX71:
|
||||
InputType.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER |
|
||||
SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_SIDE_LEFT |
|
||||
SPEAKER_SIDE_RIGHT;
|
||||
break;
|
||||
case DevFmtAmbi3D:
|
||||
WARN("%s capture not supported\n", DevFmtChannelsString(mDevice->FmtChans));
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "%s capture not supported",
|
||||
DevFmtChannelsString(mDevice->FmtChans)};
|
||||
}
|
||||
|
||||
InputType.Format.wFormatTag = WAVE_FORMAT_PCM;
|
||||
InputType.Format.nChannels = static_cast<WORD>(mDevice->channelsFromFmt());
|
||||
InputType.Format.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8);
|
||||
InputType.Format.nBlockAlign = static_cast<WORD>(InputType.Format.nChannels *
|
||||
InputType.Format.wBitsPerSample / 8);
|
||||
InputType.Format.nSamplesPerSec = mDevice->Frequency;
|
||||
InputType.Format.nAvgBytesPerSec = InputType.Format.nSamplesPerSec *
|
||||
InputType.Format.nBlockAlign;
|
||||
InputType.Format.cbSize = 0;
|
||||
InputType.Samples.wValidBitsPerSample = InputType.Format.wBitsPerSample;
|
||||
if(mDevice->FmtType == DevFmtFloat)
|
||||
InputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
|
||||
else
|
||||
InputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
|
||||
|
||||
if(InputType.Format.nChannels > 2 || mDevice->FmtType == DevFmtFloat)
|
||||
{
|
||||
InputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
|
||||
InputType.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
|
||||
}
|
||||
|
||||
ALuint samples{mDevice->BufferSize};
|
||||
samples = maxu(samples, 100 * mDevice->Frequency / 1000);
|
||||
|
||||
DSCBUFFERDESC DSCBDescription{};
|
||||
DSCBDescription.dwSize = sizeof(DSCBDescription);
|
||||
DSCBDescription.dwFlags = 0;
|
||||
DSCBDescription.dwBufferBytes = samples * InputType.Format.nBlockAlign;
|
||||
DSCBDescription.lpwfxFormat = &InputType.Format;
|
||||
|
||||
//DirectSoundCapture Init code
|
||||
hr = DirectSoundCaptureCreate(guid, &mDSC, nullptr);
|
||||
if(SUCCEEDED(hr))
|
||||
mDSC->CreateCaptureBuffer(&DSCBDescription, &mDSCbuffer, nullptr);
|
||||
if(SUCCEEDED(hr))
|
||||
mRing = RingBuffer::Create(mDevice->BufferSize, InputType.Format.nBlockAlign, false);
|
||||
|
||||
if(FAILED(hr))
|
||||
{
|
||||
mRing = nullptr;
|
||||
if(mDSCbuffer)
|
||||
mDSCbuffer->Release();
|
||||
mDSCbuffer = nullptr;
|
||||
if(mDSC)
|
||||
mDSC->Release();
|
||||
mDSC = nullptr;
|
||||
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "Device init failed: 0x%08lx", hr};
|
||||
}
|
||||
|
||||
mBufferBytes = DSCBDescription.dwBufferBytes;
|
||||
SetDefaultWFXChannelOrder(mDevice);
|
||||
|
||||
mDevice->DeviceName = name;
|
||||
}
|
||||
|
||||
bool DSoundCapture::start()
|
||||
{
|
||||
HRESULT hr{mDSCbuffer->Start(DSCBSTART_LOOPING)};
|
||||
if(FAILED(hr))
|
||||
{
|
||||
ERR("start failed: 0x%08lx\n", hr);
|
||||
aluHandleDisconnect(mDevice, "Failure starting capture: 0x%lx", hr);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void DSoundCapture::stop()
|
||||
{
|
||||
HRESULT hr{mDSCbuffer->Stop()};
|
||||
if(FAILED(hr))
|
||||
{
|
||||
ERR("stop failed: 0x%08lx\n", hr);
|
||||
aluHandleDisconnect(mDevice, "Failure stopping capture: 0x%lx", hr);
|
||||
}
|
||||
}
|
||||
|
||||
ALCenum DSoundCapture::captureSamples(al::byte *buffer, ALCuint samples)
|
||||
{
|
||||
mRing->read(buffer, samples);
|
||||
return ALC_NO_ERROR;
|
||||
}
|
||||
|
||||
ALCuint DSoundCapture::availableSamples()
|
||||
{
|
||||
if(!mDevice->Connected.load(std::memory_order_acquire))
|
||||
return static_cast<ALCuint>(mRing->readSpace());
|
||||
|
||||
ALuint FrameSize{mDevice->frameSizeFromFmt()};
|
||||
DWORD BufferBytes{mBufferBytes};
|
||||
DWORD LastCursor{mCursor};
|
||||
|
||||
DWORD ReadCursor{};
|
||||
void *ReadPtr1{}, *ReadPtr2{};
|
||||
DWORD ReadCnt1{}, ReadCnt2{};
|
||||
HRESULT hr{mDSCbuffer->GetCurrentPosition(nullptr, &ReadCursor)};
|
||||
if(SUCCEEDED(hr))
|
||||
{
|
||||
DWORD NumBytes{(ReadCursor-LastCursor + BufferBytes) % BufferBytes};
|
||||
if(!NumBytes) return static_cast<ALCubyte>(mRing->readSpace());
|
||||
hr = mDSCbuffer->Lock(LastCursor, NumBytes, &ReadPtr1, &ReadCnt1, &ReadPtr2, &ReadCnt2, 0);
|
||||
}
|
||||
if(SUCCEEDED(hr))
|
||||
{
|
||||
mRing->write(ReadPtr1, ReadCnt1/FrameSize);
|
||||
if(ReadPtr2 != nullptr && ReadCnt2 > 0)
|
||||
mRing->write(ReadPtr2, ReadCnt2/FrameSize);
|
||||
hr = mDSCbuffer->Unlock(ReadPtr1, ReadCnt1, ReadPtr2, ReadCnt2);
|
||||
mCursor = (LastCursor+ReadCnt1+ReadCnt2) % BufferBytes;
|
||||
}
|
||||
|
||||
if(FAILED(hr))
|
||||
{
|
||||
ERR("update failed: 0x%08lx\n", hr);
|
||||
aluHandleDisconnect(mDevice, "Failure retrieving capture data: 0x%lx", hr);
|
||||
}
|
||||
|
||||
return static_cast<ALCuint>(mRing->readSpace());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
BackendFactory &DSoundBackendFactory::getFactory()
|
||||
{
|
||||
static DSoundBackendFactory factory{};
|
||||
return factory;
|
||||
}
|
||||
|
||||
bool DSoundBackendFactory::init()
|
||||
{
|
||||
#ifdef HAVE_DYNLOAD
|
||||
if(!ds_handle)
|
||||
{
|
||||
ds_handle = LoadLib("dsound.dll");
|
||||
if(!ds_handle)
|
||||
{
|
||||
ERR("Failed to load dsound.dll\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
#define LOAD_FUNC(f) do { \
|
||||
p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(ds_handle, #f)); \
|
||||
if(!p##f) \
|
||||
{ \
|
||||
CloseLib(ds_handle); \
|
||||
ds_handle = nullptr; \
|
||||
return false; \
|
||||
} \
|
||||
} while(0)
|
||||
LOAD_FUNC(DirectSoundCreate);
|
||||
LOAD_FUNC(DirectSoundEnumerateW);
|
||||
LOAD_FUNC(DirectSoundCaptureCreate);
|
||||
LOAD_FUNC(DirectSoundCaptureEnumerateW);
|
||||
#undef LOAD_FUNC
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DSoundBackendFactory::querySupport(BackendType type)
|
||||
{ return (type == BackendType::Playback || type == BackendType::Capture); }
|
||||
|
||||
void DSoundBackendFactory::probe(DevProbe type, std::string *outnames)
|
||||
{
|
||||
auto add_device = [outnames](const DevMap &entry) -> void
|
||||
{
|
||||
/* +1 to also append the null char (to ensure a null-separated list and
|
||||
* double-null terminated list).
|
||||
*/
|
||||
outnames->append(entry.name.c_str(), entry.name.length()+1);
|
||||
};
|
||||
|
||||
/* Initialize COM to prevent name truncation */
|
||||
HRESULT hr;
|
||||
HRESULT hrcom{CoInitialize(nullptr)};
|
||||
switch(type)
|
||||
{
|
||||
case DevProbe::Playback:
|
||||
PlaybackDevices.clear();
|
||||
hr = DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices);
|
||||
if(FAILED(hr))
|
||||
ERR("Error enumerating DirectSound playback devices (0x%lx)!\n", hr);
|
||||
std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
|
||||
break;
|
||||
|
||||
case DevProbe::Capture:
|
||||
CaptureDevices.clear();
|
||||
hr = DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices);
|
||||
if(FAILED(hr))
|
||||
ERR("Error enumerating DirectSound capture devices (0x%lx)!\n", hr);
|
||||
std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
|
||||
break;
|
||||
}
|
||||
if(SUCCEEDED(hrcom))
|
||||
CoUninitialize();
|
||||
}
|
||||
|
||||
BackendPtr DSoundBackendFactory::createBackend(ALCdevice *device, BackendType type)
|
||||
{
|
||||
if(type == BackendType::Playback)
|
||||
return BackendPtr{new DSoundPlayback{device}};
|
||||
if(type == BackendType::Capture)
|
||||
return BackendPtr{new DSoundCapture{device}};
|
||||
return nullptr;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
#ifndef BACKENDS_DSOUND_H
|
||||
#define BACKENDS_DSOUND_H
|
||||
|
||||
#include "backends/base.h"
|
||||
|
||||
struct DSoundBackendFactory final : public BackendFactory {
|
||||
public:
|
||||
bool init() override;
|
||||
|
||||
bool querySupport(BackendType type) override;
|
||||
|
||||
void probe(DevProbe type, std::string *outnames) override;
|
||||
|
||||
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
|
||||
|
||||
static BackendFactory &getFactory();
|
||||
};
|
||||
|
||||
#endif /* BACKENDS_DSOUND_H */
|
||||
@@ -1,532 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 1999-2007 by authors.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "backends/jack.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include <memory.h>
|
||||
|
||||
#include <thread>
|
||||
#include <functional>
|
||||
|
||||
#include "alcmain.h"
|
||||
#include "alu.h"
|
||||
#include "alconfig.h"
|
||||
#include "alexcpt.h"
|
||||
#include "dynload.h"
|
||||
#include "ringbuffer.h"
|
||||
#include "threads.h"
|
||||
|
||||
#include <jack/jack.h>
|
||||
#include <jack/ringbuffer.h>
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr ALCchar jackDevice[] = "JACK Default";
|
||||
|
||||
|
||||
#ifdef HAVE_DYNLOAD
|
||||
#define JACK_FUNCS(MAGIC) \
|
||||
MAGIC(jack_client_open); \
|
||||
MAGIC(jack_client_close); \
|
||||
MAGIC(jack_client_name_size); \
|
||||
MAGIC(jack_get_client_name); \
|
||||
MAGIC(jack_connect); \
|
||||
MAGIC(jack_activate); \
|
||||
MAGIC(jack_deactivate); \
|
||||
MAGIC(jack_port_register); \
|
||||
MAGIC(jack_port_unregister); \
|
||||
MAGIC(jack_port_get_buffer); \
|
||||
MAGIC(jack_port_name); \
|
||||
MAGIC(jack_get_ports); \
|
||||
MAGIC(jack_free); \
|
||||
MAGIC(jack_get_sample_rate); \
|
||||
MAGIC(jack_set_error_function); \
|
||||
MAGIC(jack_set_process_callback); \
|
||||
MAGIC(jack_set_buffer_size_callback); \
|
||||
MAGIC(jack_set_buffer_size); \
|
||||
MAGIC(jack_get_buffer_size);
|
||||
|
||||
void *jack_handle;
|
||||
#define MAKE_FUNC(f) decltype(f) * p##f
|
||||
JACK_FUNCS(MAKE_FUNC);
|
||||
decltype(jack_error_callback) * pjack_error_callback;
|
||||
#undef MAKE_FUNC
|
||||
|
||||
#ifndef IN_IDE_PARSER
|
||||
#define jack_client_open pjack_client_open
|
||||
#define jack_client_close pjack_client_close
|
||||
#define jack_client_name_size pjack_client_name_size
|
||||
#define jack_get_client_name pjack_get_client_name
|
||||
#define jack_connect pjack_connect
|
||||
#define jack_activate pjack_activate
|
||||
#define jack_deactivate pjack_deactivate
|
||||
#define jack_port_register pjack_port_register
|
||||
#define jack_port_unregister pjack_port_unregister
|
||||
#define jack_port_get_buffer pjack_port_get_buffer
|
||||
#define jack_port_name pjack_port_name
|
||||
#define jack_get_ports pjack_get_ports
|
||||
#define jack_free pjack_free
|
||||
#define jack_get_sample_rate pjack_get_sample_rate
|
||||
#define jack_set_error_function pjack_set_error_function
|
||||
#define jack_set_process_callback pjack_set_process_callback
|
||||
#define jack_set_buffer_size_callback pjack_set_buffer_size_callback
|
||||
#define jack_set_buffer_size pjack_set_buffer_size
|
||||
#define jack_get_buffer_size pjack_get_buffer_size
|
||||
#define jack_error_callback (*pjack_error_callback)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
jack_options_t ClientOptions = JackNullOption;
|
||||
|
||||
ALCboolean jack_load()
|
||||
{
|
||||
ALCboolean error = ALC_FALSE;
|
||||
|
||||
#ifdef HAVE_DYNLOAD
|
||||
if(!jack_handle)
|
||||
{
|
||||
std::string missing_funcs;
|
||||
|
||||
#ifdef _WIN32
|
||||
#define JACKLIB "libjack.dll"
|
||||
#else
|
||||
#define JACKLIB "libjack.so.0"
|
||||
#endif
|
||||
jack_handle = LoadLib(JACKLIB);
|
||||
if(!jack_handle)
|
||||
{
|
||||
WARN("Failed to load %s\n", JACKLIB);
|
||||
return ALC_FALSE;
|
||||
}
|
||||
|
||||
error = ALC_FALSE;
|
||||
#define LOAD_FUNC(f) do { \
|
||||
p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(jack_handle, #f)); \
|
||||
if(p##f == nullptr) { \
|
||||
error = ALC_TRUE; \
|
||||
missing_funcs += "\n" #f; \
|
||||
} \
|
||||
} while(0)
|
||||
JACK_FUNCS(LOAD_FUNC);
|
||||
#undef LOAD_FUNC
|
||||
/* Optional symbols. These don't exist in all versions of JACK. */
|
||||
#define LOAD_SYM(f) p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(jack_handle, #f))
|
||||
LOAD_SYM(jack_error_callback);
|
||||
#undef LOAD_SYM
|
||||
|
||||
if(error)
|
||||
{
|
||||
WARN("Missing expected functions:%s\n", missing_funcs.c_str());
|
||||
CloseLib(jack_handle);
|
||||
jack_handle = nullptr;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return !error;
|
||||
}
|
||||
|
||||
|
||||
struct JackPlayback final : public BackendBase {
|
||||
JackPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
|
||||
~JackPlayback() override;
|
||||
|
||||
int process(jack_nframes_t numframes) noexcept;
|
||||
static int processC(jack_nframes_t numframes, void *arg) noexcept
|
||||
{ return static_cast<JackPlayback*>(arg)->process(numframes); }
|
||||
|
||||
int mixerProc();
|
||||
|
||||
void open(const ALCchar *name) override;
|
||||
bool reset() override;
|
||||
bool start() override;
|
||||
void stop() override;
|
||||
ClockLatency getClockLatency() override;
|
||||
|
||||
jack_client_t *mClient{nullptr};
|
||||
jack_port_t *mPort[MAX_OUTPUT_CHANNELS]{};
|
||||
|
||||
std::atomic<bool> mPlaying{false};
|
||||
RingBufferPtr mRing;
|
||||
al::semaphore mSem;
|
||||
|
||||
std::atomic<bool> mKillNow{true};
|
||||
std::thread mThread;
|
||||
|
||||
DEF_NEWDEL(JackPlayback)
|
||||
};
|
||||
|
||||
JackPlayback::~JackPlayback()
|
||||
{
|
||||
if(!mClient)
|
||||
return;
|
||||
|
||||
std::for_each(std::begin(mPort), std::end(mPort),
|
||||
[this](jack_port_t *port) -> void
|
||||
{ if(port) jack_port_unregister(mClient, port); }
|
||||
);
|
||||
std::fill(std::begin(mPort), std::end(mPort), nullptr);
|
||||
jack_client_close(mClient);
|
||||
mClient = nullptr;
|
||||
}
|
||||
|
||||
|
||||
int JackPlayback::process(jack_nframes_t numframes) noexcept
|
||||
{
|
||||
jack_default_audio_sample_t *out[MAX_OUTPUT_CHANNELS];
|
||||
size_t numchans{0};
|
||||
for(auto port : mPort)
|
||||
{
|
||||
if(!port) break;
|
||||
out[numchans++] = static_cast<float*>(jack_port_get_buffer(port, numframes));
|
||||
}
|
||||
|
||||
jack_nframes_t total{0};
|
||||
if LIKELY(mPlaying.load(std::memory_order_acquire))
|
||||
{
|
||||
auto data = mRing->getReadVector();
|
||||
jack_nframes_t todo{minu(numframes, static_cast<ALuint>(data.first.len))};
|
||||
auto write_first = [&data,numchans,todo](float *outbuf) -> float*
|
||||
{
|
||||
const float *RESTRICT in = reinterpret_cast<float*>(data.first.buf);
|
||||
auto deinterlace_input = [&in,numchans]() noexcept -> float
|
||||
{
|
||||
float ret{*in};
|
||||
in += numchans;
|
||||
return ret;
|
||||
};
|
||||
std::generate_n(outbuf, todo, deinterlace_input);
|
||||
data.first.buf += sizeof(float);
|
||||
return outbuf + todo;
|
||||
};
|
||||
std::transform(out, out+numchans, out, write_first);
|
||||
total += todo;
|
||||
|
||||
todo = minu(numframes-total, static_cast<ALuint>(data.second.len));
|
||||
if(todo > 0)
|
||||
{
|
||||
auto write_second = [&data,numchans,todo](float *outbuf) -> float*
|
||||
{
|
||||
const float *RESTRICT in = reinterpret_cast<float*>(data.second.buf);
|
||||
auto deinterlace_input = [&in,numchans]() noexcept -> float
|
||||
{
|
||||
float ret{*in};
|
||||
in += numchans;
|
||||
return ret;
|
||||
};
|
||||
std::generate_n(outbuf, todo, deinterlace_input);
|
||||
data.second.buf += sizeof(float);
|
||||
return outbuf + todo;
|
||||
};
|
||||
std::transform(out, out+numchans, out, write_second);
|
||||
total += todo;
|
||||
}
|
||||
|
||||
mRing->readAdvance(total);
|
||||
mSem.post();
|
||||
}
|
||||
|
||||
if(numframes > total)
|
||||
{
|
||||
jack_nframes_t todo{numframes - total};
|
||||
auto clear_buf = [todo](ALfloat *outbuf) -> void { std::fill_n(outbuf, todo, 0.0f); };
|
||||
std::for_each(out, out+numchans, clear_buf);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int JackPlayback::mixerProc()
|
||||
{
|
||||
SetRTPriority();
|
||||
althrd_setname(MIXER_THREAD_NAME);
|
||||
|
||||
const size_t frame_step{mDevice->channelsFromFmt()};
|
||||
|
||||
std::unique_lock<JackPlayback> dlock{*this};
|
||||
while(!mKillNow.load(std::memory_order_acquire) &&
|
||||
mDevice->Connected.load(std::memory_order_acquire))
|
||||
{
|
||||
if(mRing->writeSpace() < mDevice->UpdateSize)
|
||||
{
|
||||
dlock.unlock();
|
||||
mSem.wait();
|
||||
dlock.lock();
|
||||
continue;
|
||||
}
|
||||
|
||||
auto data = mRing->getWriteVector();
|
||||
auto todo = static_cast<ALuint>(data.first.len + data.second.len);
|
||||
todo -= todo%mDevice->UpdateSize;
|
||||
|
||||
ALuint len1{minu(static_cast<ALuint>(data.first.len), todo)};
|
||||
ALuint len2{minu(static_cast<ALuint>(data.second.len), todo-len1)};
|
||||
|
||||
aluMixData(mDevice, data.first.buf, len1, frame_step);
|
||||
if(len2 > 0)
|
||||
aluMixData(mDevice, data.second.buf, len2, frame_step);
|
||||
mRing->writeAdvance(todo);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void JackPlayback::open(const ALCchar *name)
|
||||
{
|
||||
if(!name)
|
||||
name = jackDevice;
|
||||
else if(strcmp(name, jackDevice) != 0)
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
|
||||
|
||||
const char *client_name{"alsoft"};
|
||||
jack_status_t status;
|
||||
mClient = jack_client_open(client_name, ClientOptions, &status, nullptr);
|
||||
if(mClient == nullptr)
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "Failed to open client connection: 0x%02x",
|
||||
status};
|
||||
|
||||
if((status&JackServerStarted))
|
||||
TRACE("JACK server started\n");
|
||||
if((status&JackNameNotUnique))
|
||||
{
|
||||
client_name = jack_get_client_name(mClient);
|
||||
TRACE("Client name not unique, got '%s' instead\n", client_name);
|
||||
}
|
||||
|
||||
jack_set_process_callback(mClient, &JackPlayback::processC, this);
|
||||
|
||||
mDevice->DeviceName = name;
|
||||
}
|
||||
|
||||
bool JackPlayback::reset()
|
||||
{
|
||||
std::for_each(std::begin(mPort), std::end(mPort),
|
||||
[this](jack_port_t *port) -> void
|
||||
{ if(port) jack_port_unregister(mClient, port); }
|
||||
);
|
||||
std::fill(std::begin(mPort), std::end(mPort), nullptr);
|
||||
|
||||
/* Ignore the requested buffer metrics and just keep one JACK-sized buffer
|
||||
* ready for when requested.
|
||||
*/
|
||||
mDevice->Frequency = jack_get_sample_rate(mClient);
|
||||
mDevice->UpdateSize = jack_get_buffer_size(mClient);
|
||||
mDevice->BufferSize = mDevice->UpdateSize * 2;
|
||||
|
||||
const char *devname{mDevice->DeviceName.c_str()};
|
||||
ALuint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)};
|
||||
bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize);
|
||||
mDevice->BufferSize = bufsize + mDevice->UpdateSize;
|
||||
|
||||
/* Force 32-bit float output. */
|
||||
mDevice->FmtType = DevFmtFloat;
|
||||
|
||||
auto ports_end = std::begin(mPort) + mDevice->channelsFromFmt();
|
||||
auto bad_port = std::find_if_not(std::begin(mPort), ports_end,
|
||||
[this](jack_port_t *&port) -> bool
|
||||
{
|
||||
std::string name{"channel_" + std::to_string(&port - mPort + 1)};
|
||||
port = jack_port_register(mClient, name.c_str(), JACK_DEFAULT_AUDIO_TYPE,
|
||||
JackPortIsOutput, 0);
|
||||
return port != nullptr;
|
||||
}
|
||||
);
|
||||
if(bad_port != ports_end)
|
||||
{
|
||||
ERR("Not enough JACK ports available for %s output\n", DevFmtChannelsString(mDevice->FmtChans));
|
||||
if(bad_port == std::begin(mPort)) return false;
|
||||
|
||||
if(bad_port == std::begin(mPort)+1)
|
||||
mDevice->FmtChans = DevFmtMono;
|
||||
else
|
||||
{
|
||||
ports_end = mPort+2;
|
||||
while(bad_port != ports_end)
|
||||
{
|
||||
jack_port_unregister(mClient, *(--bad_port));
|
||||
*bad_port = nullptr;
|
||||
}
|
||||
mDevice->FmtChans = DevFmtStereo;
|
||||
}
|
||||
}
|
||||
|
||||
SetDefaultChannelOrder(mDevice);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool JackPlayback::start()
|
||||
{
|
||||
if(jack_activate(mClient))
|
||||
{
|
||||
ERR("Failed to activate client\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
const char **ports{jack_get_ports(mClient, nullptr, nullptr,
|
||||
JackPortIsPhysical|JackPortIsInput)};
|
||||
if(ports == nullptr)
|
||||
{
|
||||
ERR("No physical playback ports found\n");
|
||||
jack_deactivate(mClient);
|
||||
return false;
|
||||
}
|
||||
std::mismatch(std::begin(mPort), std::end(mPort), ports,
|
||||
[this](const jack_port_t *port, const char *pname) -> bool
|
||||
{
|
||||
if(!port) return false;
|
||||
if(!pname)
|
||||
{
|
||||
ERR("No physical playback port for \"%s\"\n", jack_port_name(port));
|
||||
return false;
|
||||
}
|
||||
if(jack_connect(mClient, jack_port_name(port), pname))
|
||||
ERR("Failed to connect output port \"%s\" to \"%s\"\n", jack_port_name(port),
|
||||
pname);
|
||||
return true;
|
||||
});
|
||||
jack_free(ports);
|
||||
|
||||
/* Reconfigure buffer metrics in case the server changed it since the reset
|
||||
* (it won't change again after jack_activate), then allocate the ring
|
||||
* buffer with the appropriate size.
|
||||
*/
|
||||
mDevice->Frequency = jack_get_sample_rate(mClient);
|
||||
mDevice->UpdateSize = jack_get_buffer_size(mClient);
|
||||
mDevice->BufferSize = mDevice->UpdateSize * 2;
|
||||
|
||||
const char *devname{mDevice->DeviceName.c_str()};
|
||||
ALuint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)};
|
||||
bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize);
|
||||
mDevice->BufferSize = bufsize + mDevice->UpdateSize;
|
||||
|
||||
mRing = nullptr;
|
||||
mRing = RingBuffer::Create(bufsize, mDevice->frameSizeFromFmt(), true);
|
||||
|
||||
try {
|
||||
mPlaying.store(true, std::memory_order_release);
|
||||
mKillNow.store(false, std::memory_order_release);
|
||||
mThread = std::thread{std::mem_fn(&JackPlayback::mixerProc), this};
|
||||
return true;
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
ERR("Could not create playback thread: %s\n", e.what());
|
||||
}
|
||||
catch(...) {
|
||||
}
|
||||
jack_deactivate(mClient);
|
||||
mPlaying.store(false, std::memory_order_release);
|
||||
return false;
|
||||
}
|
||||
|
||||
void JackPlayback::stop()
|
||||
{
|
||||
if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
|
||||
return;
|
||||
|
||||
mSem.post();
|
||||
mThread.join();
|
||||
|
||||
jack_deactivate(mClient);
|
||||
mPlaying.store(false, std::memory_order_release);
|
||||
}
|
||||
|
||||
|
||||
ClockLatency JackPlayback::getClockLatency()
|
||||
{
|
||||
ClockLatency ret;
|
||||
|
||||
std::lock_guard<JackPlayback> _{*this};
|
||||
ret.ClockTime = GetDeviceClockTime(mDevice);
|
||||
ret.Latency = std::chrono::seconds{mRing->readSpace()};
|
||||
ret.Latency /= mDevice->Frequency;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
void jack_msg_handler(const char *message)
|
||||
{
|
||||
WARN("%s\n", message);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool JackBackendFactory::init()
|
||||
{
|
||||
if(!jack_load())
|
||||
return false;
|
||||
|
||||
if(!GetConfigValueBool(nullptr, "jack", "spawn-server", 0))
|
||||
ClientOptions = static_cast<jack_options_t>(ClientOptions | JackNoStartServer);
|
||||
|
||||
void (*old_error_cb)(const char*){&jack_error_callback ? jack_error_callback : nullptr};
|
||||
jack_set_error_function(jack_msg_handler);
|
||||
jack_status_t status;
|
||||
jack_client_t *client{jack_client_open("alsoft", ClientOptions, &status, nullptr)};
|
||||
jack_set_error_function(old_error_cb);
|
||||
if(!client)
|
||||
{
|
||||
WARN("jack_client_open() failed, 0x%02x\n", status);
|
||||
if((status&JackServerFailed) && !(ClientOptions&JackNoStartServer))
|
||||
ERR("Unable to connect to JACK server\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
jack_client_close(client);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool JackBackendFactory::querySupport(BackendType type)
|
||||
{ return (type == BackendType::Playback); }
|
||||
|
||||
void JackBackendFactory::probe(DevProbe type, std::string *outnames)
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
case DevProbe::Playback:
|
||||
/* Includes null char. */
|
||||
outnames->append(jackDevice, sizeof(jackDevice));
|
||||
break;
|
||||
|
||||
case DevProbe::Capture:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
BackendPtr JackBackendFactory::createBackend(ALCdevice *device, BackendType type)
|
||||
{
|
||||
if(type == BackendType::Playback)
|
||||
return BackendPtr{new JackPlayback{device}};
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
BackendFactory &JackBackendFactory::getFactory()
|
||||
{
|
||||
static JackBackendFactory factory{};
|
||||
return factory;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
#ifndef BACKENDS_JACK_H
|
||||
#define BACKENDS_JACK_H
|
||||
|
||||
#include "backends/base.h"
|
||||
|
||||
struct JackBackendFactory final : public BackendFactory {
|
||||
public:
|
||||
bool init() override;
|
||||
|
||||
bool querySupport(BackendType type) override;
|
||||
|
||||
void probe(DevProbe type, std::string *outnames) override;
|
||||
|
||||
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
|
||||
|
||||
static BackendFactory &getFactory();
|
||||
};
|
||||
|
||||
#endif /* BACKENDS_JACK_H */
|
||||
@@ -1,79 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 2011 by Chris Robinson
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "backends/loopback.h"
|
||||
|
||||
#include "alcmain.h"
|
||||
#include "alu.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
struct LoopbackBackend final : public BackendBase {
|
||||
LoopbackBackend(ALCdevice *device) noexcept : BackendBase{device} { }
|
||||
|
||||
void open(const ALCchar *name) override;
|
||||
bool reset() override;
|
||||
bool start() override;
|
||||
void stop() override;
|
||||
|
||||
DEF_NEWDEL(LoopbackBackend)
|
||||
};
|
||||
|
||||
|
||||
void LoopbackBackend::open(const ALCchar *name)
|
||||
{
|
||||
mDevice->DeviceName = name;
|
||||
}
|
||||
|
||||
bool LoopbackBackend::reset()
|
||||
{
|
||||
SetDefaultWFXChannelOrder(mDevice);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LoopbackBackend::start()
|
||||
{ return true; }
|
||||
|
||||
void LoopbackBackend::stop()
|
||||
{ }
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
bool LoopbackBackendFactory::init()
|
||||
{ return true; }
|
||||
|
||||
bool LoopbackBackendFactory::querySupport(BackendType)
|
||||
{ return true; }
|
||||
|
||||
void LoopbackBackendFactory::probe(DevProbe, std::string*)
|
||||
{ }
|
||||
|
||||
BackendPtr LoopbackBackendFactory::createBackend(ALCdevice *device, BackendType)
|
||||
{ return BackendPtr{new LoopbackBackend{device}}; }
|
||||
|
||||
BackendFactory &LoopbackBackendFactory::getFactory()
|
||||
{
|
||||
static LoopbackBackendFactory factory{};
|
||||
return factory;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
#ifndef BACKENDS_LOOPBACK_H
|
||||
#define BACKENDS_LOOPBACK_H
|
||||
|
||||
#include "backends/base.h"
|
||||
|
||||
struct LoopbackBackendFactory final : public BackendFactory {
|
||||
public:
|
||||
bool init() override;
|
||||
|
||||
bool querySupport(BackendType type) override;
|
||||
|
||||
void probe(DevProbe type, std::string *outnames) override;
|
||||
|
||||
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
|
||||
|
||||
static BackendFactory &getFactory();
|
||||
};
|
||||
|
||||
#endif /* BACKENDS_LOOPBACK_H */
|
||||
@@ -1,182 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 2010 by Chris Robinson
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "backends/null.h"
|
||||
|
||||
#include <exception>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <thread>
|
||||
|
||||
#include "alcmain.h"
|
||||
#include "alexcpt.h"
|
||||
#include "almalloc.h"
|
||||
#include "alu.h"
|
||||
#include "logging.h"
|
||||
#include "threads.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
using std::chrono::seconds;
|
||||
using std::chrono::milliseconds;
|
||||
using std::chrono::nanoseconds;
|
||||
|
||||
constexpr ALCchar nullDevice[] = "No Output";
|
||||
|
||||
|
||||
struct NullBackend final : public BackendBase {
|
||||
NullBackend(ALCdevice *device) noexcept : BackendBase{device} { }
|
||||
|
||||
int mixerProc();
|
||||
|
||||
void open(const ALCchar *name) override;
|
||||
bool reset() override;
|
||||
bool start() override;
|
||||
void stop() override;
|
||||
|
||||
std::atomic<bool> mKillNow{true};
|
||||
std::thread mThread;
|
||||
|
||||
DEF_NEWDEL(NullBackend)
|
||||
};
|
||||
|
||||
int NullBackend::mixerProc()
|
||||
{
|
||||
const milliseconds restTime{mDevice->UpdateSize*1000/mDevice->Frequency / 2};
|
||||
|
||||
SetRTPriority();
|
||||
althrd_setname(MIXER_THREAD_NAME);
|
||||
|
||||
int64_t done{0};
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
while(!mKillNow.load(std::memory_order_acquire) &&
|
||||
mDevice->Connected.load(std::memory_order_acquire))
|
||||
{
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
|
||||
/* This converts from nanoseconds to nanosamples, then to samples. */
|
||||
int64_t avail{std::chrono::duration_cast<seconds>((now-start) * mDevice->Frequency).count()};
|
||||
if(avail-done < mDevice->UpdateSize)
|
||||
{
|
||||
std::this_thread::sleep_for(restTime);
|
||||
continue;
|
||||
}
|
||||
while(avail-done >= mDevice->UpdateSize)
|
||||
{
|
||||
std::lock_guard<NullBackend> _{*this};
|
||||
aluMixData(mDevice, nullptr, mDevice->UpdateSize, 0u);
|
||||
done += mDevice->UpdateSize;
|
||||
}
|
||||
|
||||
/* For every completed second, increment the start time and reduce the
|
||||
* samples done. This prevents the difference between the start time
|
||||
* and current time from growing too large, while maintaining the
|
||||
* correct number of samples to render.
|
||||
*/
|
||||
if(done >= mDevice->Frequency)
|
||||
{
|
||||
seconds s{done/mDevice->Frequency};
|
||||
start += s;
|
||||
done -= mDevice->Frequency*s.count();
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void NullBackend::open(const ALCchar *name)
|
||||
{
|
||||
if(!name)
|
||||
name = nullDevice;
|
||||
else if(strcmp(name, nullDevice) != 0)
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
|
||||
|
||||
mDevice->DeviceName = name;
|
||||
}
|
||||
|
||||
bool NullBackend::reset()
|
||||
{
|
||||
SetDefaultWFXChannelOrder(mDevice);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NullBackend::start()
|
||||
{
|
||||
try {
|
||||
mKillNow.store(false, std::memory_order_release);
|
||||
mThread = std::thread{std::mem_fn(&NullBackend::mixerProc), this};
|
||||
return true;
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
ERR("Failed to start mixing thread: %s\n", e.what());
|
||||
}
|
||||
catch(...) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void NullBackend::stop()
|
||||
{
|
||||
if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
|
||||
return;
|
||||
mThread.join();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
bool NullBackendFactory::init()
|
||||
{ return true; }
|
||||
|
||||
bool NullBackendFactory::querySupport(BackendType type)
|
||||
{ return (type == BackendType::Playback); }
|
||||
|
||||
void NullBackendFactory::probe(DevProbe type, std::string *outnames)
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
case DevProbe::Playback:
|
||||
/* Includes null char. */
|
||||
outnames->append(nullDevice, sizeof(nullDevice));
|
||||
break;
|
||||
case DevProbe::Capture:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
BackendPtr NullBackendFactory::createBackend(ALCdevice *device, BackendType type)
|
||||
{
|
||||
if(type == BackendType::Playback)
|
||||
return BackendPtr{new NullBackend{device}};
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
BackendFactory &NullBackendFactory::getFactory()
|
||||
{
|
||||
static NullBackendFactory factory{};
|
||||
return factory;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
#ifndef BACKENDS_NULL_H
|
||||
#define BACKENDS_NULL_H
|
||||
|
||||
#include "backends/base.h"
|
||||
|
||||
struct NullBackendFactory final : public BackendFactory {
|
||||
public:
|
||||
bool init() override;
|
||||
|
||||
bool querySupport(BackendType type) override;
|
||||
|
||||
void probe(DevProbe type, std::string *outnames) override;
|
||||
|
||||
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
|
||||
|
||||
static BackendFactory &getFactory();
|
||||
};
|
||||
|
||||
#endif /* BACKENDS_NULL_H */
|
||||
@@ -1,966 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* This is an OpenAL backend for Android using the native audio APIs based on
|
||||
* OpenSL ES 1.0.1. It is based on source code for the native-audio sample app
|
||||
* bundled with NDK.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "backends/opensl.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <jni.h>
|
||||
|
||||
#include <new>
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <thread>
|
||||
#include <functional>
|
||||
|
||||
#include "alcmain.h"
|
||||
#include "alexcpt.h"
|
||||
#include "alu.h"
|
||||
#include "compat.h"
|
||||
#include "endiantest.h"
|
||||
#include "ringbuffer.h"
|
||||
#include "threads.h"
|
||||
|
||||
#include <SLES/OpenSLES.h>
|
||||
#include <SLES/OpenSLES_Android.h>
|
||||
#include <SLES/OpenSLES_AndroidConfiguration.h>
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
/* Helper macros */
|
||||
#define EXTRACT_VCALL_ARGS(...) __VA_ARGS__))
|
||||
#define VCALL(obj, func) ((*(obj))->func((obj), EXTRACT_VCALL_ARGS
|
||||
#define VCALL0(obj, func) ((*(obj))->func((obj) EXTRACT_VCALL_ARGS
|
||||
|
||||
|
||||
constexpr ALCchar opensl_device[] = "OpenSL";
|
||||
|
||||
|
||||
SLuint32 GetChannelMask(DevFmtChannels chans)
|
||||
{
|
||||
switch(chans)
|
||||
{
|
||||
case DevFmtMono: return SL_SPEAKER_FRONT_CENTER;
|
||||
case DevFmtStereo: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
|
||||
case DevFmtQuad: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT |
|
||||
SL_SPEAKER_BACK_LEFT | SL_SPEAKER_BACK_RIGHT;
|
||||
case DevFmtX51: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT |
|
||||
SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_SIDE_LEFT |
|
||||
SL_SPEAKER_SIDE_RIGHT;
|
||||
case DevFmtX51Rear: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT |
|
||||
SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_BACK_LEFT |
|
||||
SL_SPEAKER_BACK_RIGHT;
|
||||
case DevFmtX61: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT |
|
||||
SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_BACK_CENTER |
|
||||
SL_SPEAKER_SIDE_LEFT | SL_SPEAKER_SIDE_RIGHT;
|
||||
case DevFmtX71: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT |
|
||||
SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_BACK_LEFT |
|
||||
SL_SPEAKER_BACK_RIGHT | SL_SPEAKER_SIDE_LEFT | SL_SPEAKER_SIDE_RIGHT;
|
||||
case DevFmtAmbi3D:
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef SL_ANDROID_DATAFORMAT_PCM_EX
|
||||
SLuint32 GetTypeRepresentation(DevFmtType type)
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
case DevFmtUByte:
|
||||
case DevFmtUShort:
|
||||
case DevFmtUInt:
|
||||
return SL_ANDROID_PCM_REPRESENTATION_UNSIGNED_INT;
|
||||
case DevFmtByte:
|
||||
case DevFmtShort:
|
||||
case DevFmtInt:
|
||||
return SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT;
|
||||
case DevFmtFloat:
|
||||
return SL_ANDROID_PCM_REPRESENTATION_FLOAT;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
const char *res_str(SLresult result)
|
||||
{
|
||||
switch(result)
|
||||
{
|
||||
case SL_RESULT_SUCCESS: return "Success";
|
||||
case SL_RESULT_PRECONDITIONS_VIOLATED: return "Preconditions violated";
|
||||
case SL_RESULT_PARAMETER_INVALID: return "Parameter invalid";
|
||||
case SL_RESULT_MEMORY_FAILURE: return "Memory failure";
|
||||
case SL_RESULT_RESOURCE_ERROR: return "Resource error";
|
||||
case SL_RESULT_RESOURCE_LOST: return "Resource lost";
|
||||
case SL_RESULT_IO_ERROR: return "I/O error";
|
||||
case SL_RESULT_BUFFER_INSUFFICIENT: return "Buffer insufficient";
|
||||
case SL_RESULT_CONTENT_CORRUPTED: return "Content corrupted";
|
||||
case SL_RESULT_CONTENT_UNSUPPORTED: return "Content unsupported";
|
||||
case SL_RESULT_CONTENT_NOT_FOUND: return "Content not found";
|
||||
case SL_RESULT_PERMISSION_DENIED: return "Permission denied";
|
||||
case SL_RESULT_FEATURE_UNSUPPORTED: return "Feature unsupported";
|
||||
case SL_RESULT_INTERNAL_ERROR: return "Internal error";
|
||||
case SL_RESULT_UNKNOWN_ERROR: return "Unknown error";
|
||||
case SL_RESULT_OPERATION_ABORTED: return "Operation aborted";
|
||||
case SL_RESULT_CONTROL_LOST: return "Control lost";
|
||||
#ifdef SL_RESULT_READONLY
|
||||
case SL_RESULT_READONLY: return "ReadOnly";
|
||||
#endif
|
||||
#ifdef SL_RESULT_ENGINEOPTION_UNSUPPORTED
|
||||
case SL_RESULT_ENGINEOPTION_UNSUPPORTED: return "Engine option unsupported";
|
||||
#endif
|
||||
#ifdef SL_RESULT_SOURCE_SINK_INCOMPATIBLE
|
||||
case SL_RESULT_SOURCE_SINK_INCOMPATIBLE: return "Source/Sink incompatible";
|
||||
#endif
|
||||
}
|
||||
return "Unknown error code";
|
||||
}
|
||||
|
||||
#define PRINTERR(x, s) do { \
|
||||
if UNLIKELY((x) != SL_RESULT_SUCCESS) \
|
||||
ERR("%s: %s\n", (s), res_str((x))); \
|
||||
} while(0)
|
||||
|
||||
|
||||
struct OpenSLPlayback final : public BackendBase {
|
||||
OpenSLPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
|
||||
~OpenSLPlayback() override;
|
||||
|
||||
void process(SLAndroidSimpleBufferQueueItf bq) noexcept;
|
||||
static void processC(SLAndroidSimpleBufferQueueItf bq, void *context) noexcept
|
||||
{ static_cast<OpenSLPlayback*>(context)->process(bq); }
|
||||
|
||||
int mixerProc();
|
||||
|
||||
void open(const ALCchar *name) override;
|
||||
bool reset() override;
|
||||
bool start() override;
|
||||
void stop() override;
|
||||
ClockLatency getClockLatency() override;
|
||||
|
||||
/* engine interfaces */
|
||||
SLObjectItf mEngineObj{nullptr};
|
||||
SLEngineItf mEngine{nullptr};
|
||||
|
||||
/* output mix interfaces */
|
||||
SLObjectItf mOutputMix{nullptr};
|
||||
|
||||
/* buffer queue player interfaces */
|
||||
SLObjectItf mBufferQueueObj{nullptr};
|
||||
|
||||
RingBufferPtr mRing{nullptr};
|
||||
al::semaphore mSem;
|
||||
|
||||
ALuint mFrameSize{0};
|
||||
|
||||
std::atomic<bool> mKillNow{true};
|
||||
std::thread mThread;
|
||||
|
||||
DEF_NEWDEL(OpenSLPlayback)
|
||||
};
|
||||
|
||||
OpenSLPlayback::~OpenSLPlayback()
|
||||
{
|
||||
if(mBufferQueueObj)
|
||||
VCALL0(mBufferQueueObj,Destroy)();
|
||||
mBufferQueueObj = nullptr;
|
||||
|
||||
if(mOutputMix)
|
||||
VCALL0(mOutputMix,Destroy)();
|
||||
mOutputMix = nullptr;
|
||||
|
||||
if(mEngineObj)
|
||||
VCALL0(mEngineObj,Destroy)();
|
||||
mEngineObj = nullptr;
|
||||
mEngine = nullptr;
|
||||
}
|
||||
|
||||
|
||||
/* this callback handler is called every time a buffer finishes playing */
|
||||
void OpenSLPlayback::process(SLAndroidSimpleBufferQueueItf) noexcept
|
||||
{
|
||||
/* A note on the ringbuffer usage: The buffer queue seems to hold on to the
|
||||
* pointer passed to the Enqueue method, rather than copying the audio.
|
||||
* Consequently, the ringbuffer contains the audio that is currently queued
|
||||
* and waiting to play. This process() callback is called when a buffer is
|
||||
* finished, so we simply move the read pointer up to indicate the space is
|
||||
* available for writing again, and wake up the mixer thread to mix and
|
||||
* queue more audio.
|
||||
*/
|
||||
mRing->readAdvance(1);
|
||||
|
||||
mSem.post();
|
||||
}
|
||||
|
||||
int OpenSLPlayback::mixerProc()
|
||||
{
|
||||
SetRTPriority();
|
||||
althrd_setname(MIXER_THREAD_NAME);
|
||||
|
||||
SLPlayItf player;
|
||||
SLAndroidSimpleBufferQueueItf bufferQueue;
|
||||
SLresult result{VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
|
||||
&bufferQueue)};
|
||||
PRINTERR(result, "bufferQueue->GetInterface SL_IID_ANDROIDSIMPLEBUFFERQUEUE");
|
||||
if(SL_RESULT_SUCCESS == result)
|
||||
{
|
||||
result = VCALL(mBufferQueueObj,GetInterface)(SL_IID_PLAY, &player);
|
||||
PRINTERR(result, "bufferQueue->GetInterface SL_IID_PLAY");
|
||||
}
|
||||
|
||||
const size_t frame_step{mDevice->channelsFromFmt()};
|
||||
|
||||
std::unique_lock<OpenSLPlayback> dlock{*this};
|
||||
if(SL_RESULT_SUCCESS != result)
|
||||
aluHandleDisconnect(mDevice, "Failed to get playback buffer: 0x%08x", result);
|
||||
|
||||
while(SL_RESULT_SUCCESS == result && !mKillNow.load(std::memory_order_acquire) &&
|
||||
mDevice->Connected.load(std::memory_order_acquire))
|
||||
{
|
||||
if(mRing->writeSpace() == 0)
|
||||
{
|
||||
SLuint32 state{0};
|
||||
|
||||
result = VCALL(player,GetPlayState)(&state);
|
||||
PRINTERR(result, "player->GetPlayState");
|
||||
if(SL_RESULT_SUCCESS == result && state != SL_PLAYSTATE_PLAYING)
|
||||
{
|
||||
result = VCALL(player,SetPlayState)(SL_PLAYSTATE_PLAYING);
|
||||
PRINTERR(result, "player->SetPlayState");
|
||||
}
|
||||
if(SL_RESULT_SUCCESS != result)
|
||||
{
|
||||
aluHandleDisconnect(mDevice, "Failed to start platback: 0x%08x", result);
|
||||
break;
|
||||
}
|
||||
|
||||
if(mRing->writeSpace() == 0)
|
||||
{
|
||||
dlock.unlock();
|
||||
mSem.wait();
|
||||
dlock.lock();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
auto data = mRing->getWriteVector();
|
||||
aluMixData(mDevice, data.first.buf,
|
||||
static_cast<ALuint>(data.first.len*mDevice->UpdateSize), frame_step);
|
||||
if(data.second.len > 0)
|
||||
aluMixData(mDevice, data.second.buf,
|
||||
static_cast<ALuint>(data.second.len*mDevice->UpdateSize), frame_step);
|
||||
|
||||
size_t todo{data.first.len + data.second.len};
|
||||
mRing->writeAdvance(todo);
|
||||
|
||||
for(size_t i{0};i < todo;i++)
|
||||
{
|
||||
if(!data.first.len)
|
||||
{
|
||||
data.first = data.second;
|
||||
data.second.buf = nullptr;
|
||||
data.second.len = 0;
|
||||
}
|
||||
|
||||
result = VCALL(bufferQueue,Enqueue)(data.first.buf, mDevice->UpdateSize*mFrameSize);
|
||||
PRINTERR(result, "bufferQueue->Enqueue");
|
||||
if(SL_RESULT_SUCCESS != result)
|
||||
{
|
||||
aluHandleDisconnect(mDevice, "Failed to queue audio: 0x%08x", result);
|
||||
break;
|
||||
}
|
||||
|
||||
data.first.len--;
|
||||
data.first.buf += mDevice->UpdateSize*mFrameSize;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void OpenSLPlayback::open(const ALCchar *name)
|
||||
{
|
||||
if(!name)
|
||||
name = opensl_device;
|
||||
else if(strcmp(name, opensl_device) != 0)
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
|
||||
|
||||
// create engine
|
||||
SLresult result{slCreateEngine(&mEngineObj, 0, nullptr, 0, nullptr, nullptr)};
|
||||
PRINTERR(result, "slCreateEngine");
|
||||
if(SL_RESULT_SUCCESS == result)
|
||||
{
|
||||
result = VCALL(mEngineObj,Realize)(SL_BOOLEAN_FALSE);
|
||||
PRINTERR(result, "engine->Realize");
|
||||
}
|
||||
if(SL_RESULT_SUCCESS == result)
|
||||
{
|
||||
result = VCALL(mEngineObj,GetInterface)(SL_IID_ENGINE, &mEngine);
|
||||
PRINTERR(result, "engine->GetInterface");
|
||||
}
|
||||
if(SL_RESULT_SUCCESS == result)
|
||||
{
|
||||
result = VCALL(mEngine,CreateOutputMix)(&mOutputMix, 0, nullptr, nullptr);
|
||||
PRINTERR(result, "engine->CreateOutputMix");
|
||||
}
|
||||
if(SL_RESULT_SUCCESS == result)
|
||||
{
|
||||
result = VCALL(mOutputMix,Realize)(SL_BOOLEAN_FALSE);
|
||||
PRINTERR(result, "outputMix->Realize");
|
||||
}
|
||||
|
||||
if(SL_RESULT_SUCCESS != result)
|
||||
{
|
||||
if(mOutputMix)
|
||||
VCALL0(mOutputMix,Destroy)();
|
||||
mOutputMix = nullptr;
|
||||
|
||||
if(mEngineObj)
|
||||
VCALL0(mEngineObj,Destroy)();
|
||||
mEngineObj = nullptr;
|
||||
mEngine = nullptr;
|
||||
|
||||
throw al::backend_exception{ALC_INVALID_VALUE,
|
||||
"Failed to initialize OpenSL device: 0x%08x", result};
|
||||
}
|
||||
|
||||
mDevice->DeviceName = name;
|
||||
}
|
||||
|
||||
bool OpenSLPlayback::reset()
|
||||
{
|
||||
SLresult result;
|
||||
|
||||
if(mBufferQueueObj)
|
||||
VCALL0(mBufferQueueObj,Destroy)();
|
||||
mBufferQueueObj = nullptr;
|
||||
|
||||
mRing = nullptr;
|
||||
|
||||
#if 0
|
||||
if(!mDevice->Flags.get<FrequencyRequest>())
|
||||
{
|
||||
/* FIXME: Disabled until I figure out how to get the Context needed for
|
||||
* the getSystemService call.
|
||||
*/
|
||||
JNIEnv *env = Android_GetJNIEnv();
|
||||
jobject jctx = Android_GetContext();
|
||||
|
||||
/* Get necessary stuff for using java.lang.Integer,
|
||||
* android.content.Context, and android.media.AudioManager.
|
||||
*/
|
||||
jclass int_cls = JCALL(env,FindClass)("java/lang/Integer");
|
||||
jmethodID int_parseint = JCALL(env,GetStaticMethodID)(int_cls,
|
||||
"parseInt", "(Ljava/lang/String;)I"
|
||||
);
|
||||
TRACE("Integer: %p, parseInt: %p\n", int_cls, int_parseint);
|
||||
|
||||
jclass ctx_cls = JCALL(env,FindClass)("android/content/Context");
|
||||
jfieldID ctx_audsvc = JCALL(env,GetStaticFieldID)(ctx_cls,
|
||||
"AUDIO_SERVICE", "Ljava/lang/String;"
|
||||
);
|
||||
jmethodID ctx_getSysSvc = JCALL(env,GetMethodID)(ctx_cls,
|
||||
"getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;"
|
||||
);
|
||||
TRACE("Context: %p, AUDIO_SERVICE: %p, getSystemService: %p\n",
|
||||
ctx_cls, ctx_audsvc, ctx_getSysSvc);
|
||||
|
||||
jclass audmgr_cls = JCALL(env,FindClass)("android/media/AudioManager");
|
||||
jfieldID audmgr_prop_out_srate = JCALL(env,GetStaticFieldID)(audmgr_cls,
|
||||
"PROPERTY_OUTPUT_SAMPLE_RATE", "Ljava/lang/String;"
|
||||
);
|
||||
jmethodID audmgr_getproperty = JCALL(env,GetMethodID)(audmgr_cls,
|
||||
"getProperty", "(Ljava/lang/String;)Ljava/lang/String;"
|
||||
);
|
||||
TRACE("AudioManager: %p, PROPERTY_OUTPUT_SAMPLE_RATE: %p, getProperty: %p\n",
|
||||
audmgr_cls, audmgr_prop_out_srate, audmgr_getproperty);
|
||||
|
||||
const char *strchars;
|
||||
jstring strobj;
|
||||
|
||||
/* Now make the calls. */
|
||||
//AudioManager audMgr = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
|
||||
strobj = JCALL(env,GetStaticObjectField)(ctx_cls, ctx_audsvc);
|
||||
jobject audMgr = JCALL(env,CallObjectMethod)(jctx, ctx_getSysSvc, strobj);
|
||||
strchars = JCALL(env,GetStringUTFChars)(strobj, nullptr);
|
||||
TRACE("Context.getSystemService(%s) = %p\n", strchars, audMgr);
|
||||
JCALL(env,ReleaseStringUTFChars)(strobj, strchars);
|
||||
|
||||
//String srateStr = audMgr.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE);
|
||||
strobj = JCALL(env,GetStaticObjectField)(audmgr_cls, audmgr_prop_out_srate);
|
||||
jstring srateStr = JCALL(env,CallObjectMethod)(audMgr, audmgr_getproperty, strobj);
|
||||
strchars = JCALL(env,GetStringUTFChars)(strobj, nullptr);
|
||||
TRACE("audMgr.getProperty(%s) = %p\n", strchars, srateStr);
|
||||
JCALL(env,ReleaseStringUTFChars)(strobj, strchars);
|
||||
|
||||
//int sampleRate = Integer.parseInt(srateStr);
|
||||
sampleRate = JCALL(env,CallStaticIntMethod)(int_cls, int_parseint, srateStr);
|
||||
|
||||
strchars = JCALL(env,GetStringUTFChars)(srateStr, nullptr);
|
||||
TRACE("Got system sample rate %uhz (%s)\n", sampleRate, strchars);
|
||||
JCALL(env,ReleaseStringUTFChars)(srateStr, strchars);
|
||||
|
||||
if(!sampleRate) sampleRate = device->Frequency;
|
||||
else sampleRate = maxu(sampleRate, MIN_OUTPUT_RATE);
|
||||
}
|
||||
#endif
|
||||
|
||||
mDevice->FmtChans = DevFmtStereo;
|
||||
mDevice->FmtType = DevFmtShort;
|
||||
|
||||
SetDefaultWFXChannelOrder(mDevice);
|
||||
mFrameSize = mDevice->frameSizeFromFmt();
|
||||
|
||||
|
||||
const std::array<SLInterfaceID,2> ids{{ SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_ANDROIDCONFIGURATION }};
|
||||
const std::array<SLboolean,2> reqs{{ SL_BOOLEAN_TRUE, SL_BOOLEAN_FALSE }};
|
||||
|
||||
SLDataLocator_OutputMix loc_outmix{};
|
||||
loc_outmix.locatorType = SL_DATALOCATOR_OUTPUTMIX;
|
||||
loc_outmix.outputMix = mOutputMix;
|
||||
|
||||
SLDataSink audioSnk{};
|
||||
audioSnk.pLocator = &loc_outmix;
|
||||
audioSnk.pFormat = nullptr;
|
||||
|
||||
SLDataLocator_AndroidSimpleBufferQueue loc_bufq{};
|
||||
loc_bufq.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE;
|
||||
loc_bufq.numBuffers = mDevice->BufferSize / mDevice->UpdateSize;
|
||||
|
||||
SLDataSource audioSrc{};
|
||||
#ifdef SL_ANDROID_DATAFORMAT_PCM_EX
|
||||
SLAndroidDataFormat_PCM_EX format_pcm_ex{};
|
||||
format_pcm_ex.formatType = SL_ANDROID_DATAFORMAT_PCM_EX;
|
||||
format_pcm_ex.numChannels = mDevice->channelsFromFmt();
|
||||
format_pcm_ex.sampleRate = mDevice->Frequency * 1000;
|
||||
format_pcm_ex.bitsPerSample = mDevice->bytesFromFmt() * 8;
|
||||
format_pcm_ex.containerSize = format_pcm_ex.bitsPerSample;
|
||||
format_pcm_ex.channelMask = GetChannelMask(mDevice->FmtChans);
|
||||
format_pcm_ex.endianness = IS_LITTLE_ENDIAN ? SL_BYTEORDER_LITTLEENDIAN : SL_BYTEORDER_BIGENDIAN;
|
||||
format_pcm_ex.representation = GetTypeRepresentation(mDevice->FmtType);
|
||||
|
||||
audioSrc.pLocator = &loc_bufq;
|
||||
audioSrc.pFormat = &format_pcm_ex;
|
||||
|
||||
result = VCALL(mEngine,CreateAudioPlayer)(&mBufferQueueObj, &audioSrc, &audioSnk, ids.size(),
|
||||
ids.data(), reqs.data());
|
||||
if(SL_RESULT_SUCCESS != result)
|
||||
#endif
|
||||
{
|
||||
/* Alter sample type according to what SLDataFormat_PCM can support. */
|
||||
switch(mDevice->FmtType)
|
||||
{
|
||||
case DevFmtByte: mDevice->FmtType = DevFmtUByte; break;
|
||||
case DevFmtUInt: mDevice->FmtType = DevFmtInt; break;
|
||||
case DevFmtFloat:
|
||||
case DevFmtUShort: mDevice->FmtType = DevFmtShort; break;
|
||||
case DevFmtUByte:
|
||||
case DevFmtShort:
|
||||
case DevFmtInt:
|
||||
break;
|
||||
}
|
||||
|
||||
SLDataFormat_PCM format_pcm{};
|
||||
format_pcm.formatType = SL_DATAFORMAT_PCM;
|
||||
format_pcm.numChannels = mDevice->channelsFromFmt();
|
||||
format_pcm.samplesPerSec = mDevice->Frequency * 1000;
|
||||
format_pcm.bitsPerSample = mDevice->bytesFromFmt() * 8;
|
||||
format_pcm.containerSize = format_pcm.bitsPerSample;
|
||||
format_pcm.channelMask = GetChannelMask(mDevice->FmtChans);
|
||||
format_pcm.endianness = IS_LITTLE_ENDIAN ? SL_BYTEORDER_LITTLEENDIAN :
|
||||
SL_BYTEORDER_BIGENDIAN;
|
||||
|
||||
audioSrc.pLocator = &loc_bufq;
|
||||
audioSrc.pFormat = &format_pcm;
|
||||
|
||||
result = VCALL(mEngine,CreateAudioPlayer)(&mBufferQueueObj, &audioSrc, &audioSnk, ids.size(),
|
||||
ids.data(), reqs.data());
|
||||
PRINTERR(result, "engine->CreateAudioPlayer");
|
||||
}
|
||||
if(SL_RESULT_SUCCESS == result)
|
||||
{
|
||||
/* Set the stream type to "media" (games, music, etc), if possible. */
|
||||
SLAndroidConfigurationItf config;
|
||||
result = VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDCONFIGURATION, &config);
|
||||
PRINTERR(result, "bufferQueue->GetInterface SL_IID_ANDROIDCONFIGURATION");
|
||||
if(SL_RESULT_SUCCESS == result)
|
||||
{
|
||||
SLint32 streamType = SL_ANDROID_STREAM_MEDIA;
|
||||
result = VCALL(config,SetConfiguration)(SL_ANDROID_KEY_STREAM_TYPE, &streamType,
|
||||
sizeof(streamType));
|
||||
PRINTERR(result, "config->SetConfiguration");
|
||||
}
|
||||
|
||||
/* Clear any error since this was optional. */
|
||||
result = SL_RESULT_SUCCESS;
|
||||
}
|
||||
if(SL_RESULT_SUCCESS == result)
|
||||
{
|
||||
result = VCALL(mBufferQueueObj,Realize)(SL_BOOLEAN_FALSE);
|
||||
PRINTERR(result, "bufferQueue->Realize");
|
||||
}
|
||||
if(SL_RESULT_SUCCESS == result)
|
||||
{
|
||||
const ALuint num_updates{mDevice->BufferSize / mDevice->UpdateSize};
|
||||
mRing = RingBuffer::Create(num_updates, mFrameSize*mDevice->UpdateSize, true);
|
||||
}
|
||||
|
||||
if(SL_RESULT_SUCCESS != result)
|
||||
{
|
||||
if(mBufferQueueObj)
|
||||
VCALL0(mBufferQueueObj,Destroy)();
|
||||
mBufferQueueObj = nullptr;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OpenSLPlayback::start()
|
||||
{
|
||||
mRing->reset();
|
||||
|
||||
SLAndroidSimpleBufferQueueItf bufferQueue;
|
||||
SLresult result{VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
|
||||
&bufferQueue)};
|
||||
PRINTERR(result, "bufferQueue->GetInterface");
|
||||
if(SL_RESULT_SUCCESS != result)
|
||||
return false;
|
||||
|
||||
result = VCALL(bufferQueue,RegisterCallback)(&OpenSLPlayback::processC, this);
|
||||
PRINTERR(result, "bufferQueue->RegisterCallback");
|
||||
if(SL_RESULT_SUCCESS != result) return false;
|
||||
|
||||
try {
|
||||
mKillNow.store(false, std::memory_order_release);
|
||||
mThread = std::thread(std::mem_fn(&OpenSLPlayback::mixerProc), this);
|
||||
return true;
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
ERR("Could not create playback thread: %s\n", e.what());
|
||||
}
|
||||
catch(...) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void OpenSLPlayback::stop()
|
||||
{
|
||||
if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
|
||||
return;
|
||||
|
||||
mSem.post();
|
||||
mThread.join();
|
||||
|
||||
SLPlayItf player;
|
||||
SLresult result{VCALL(mBufferQueueObj,GetInterface)(SL_IID_PLAY, &player)};
|
||||
PRINTERR(result, "bufferQueue->GetInterface");
|
||||
if(SL_RESULT_SUCCESS == result)
|
||||
{
|
||||
result = VCALL(player,SetPlayState)(SL_PLAYSTATE_STOPPED);
|
||||
PRINTERR(result, "player->SetPlayState");
|
||||
}
|
||||
|
||||
SLAndroidSimpleBufferQueueItf bufferQueue;
|
||||
result = VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &bufferQueue);
|
||||
PRINTERR(result, "bufferQueue->GetInterface");
|
||||
if(SL_RESULT_SUCCESS == result)
|
||||
{
|
||||
result = VCALL0(bufferQueue,Clear)();
|
||||
PRINTERR(result, "bufferQueue->Clear");
|
||||
}
|
||||
if(SL_RESULT_SUCCESS == result)
|
||||
{
|
||||
result = VCALL(bufferQueue,RegisterCallback)(nullptr, nullptr);
|
||||
PRINTERR(result, "bufferQueue->RegisterCallback");
|
||||
}
|
||||
if(SL_RESULT_SUCCESS == result)
|
||||
{
|
||||
SLAndroidSimpleBufferQueueState state;
|
||||
do {
|
||||
std::this_thread::yield();
|
||||
result = VCALL(bufferQueue,GetState)(&state);
|
||||
} while(SL_RESULT_SUCCESS == result && state.count > 0);
|
||||
PRINTERR(result, "bufferQueue->GetState");
|
||||
}
|
||||
}
|
||||
|
||||
ClockLatency OpenSLPlayback::getClockLatency()
|
||||
{
|
||||
ClockLatency ret;
|
||||
|
||||
std::lock_guard<OpenSLPlayback> _{*this};
|
||||
ret.ClockTime = GetDeviceClockTime(mDevice);
|
||||
ret.Latency = std::chrono::seconds{mRing->readSpace() * mDevice->UpdateSize};
|
||||
ret.Latency /= mDevice->Frequency;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
struct OpenSLCapture final : public BackendBase {
|
||||
OpenSLCapture(ALCdevice *device) noexcept : BackendBase{device} { }
|
||||
~OpenSLCapture() override;
|
||||
|
||||
void process(SLAndroidSimpleBufferQueueItf bq) noexcept;
|
||||
static void processC(SLAndroidSimpleBufferQueueItf bq, void *context) noexcept
|
||||
{ static_cast<OpenSLCapture*>(context)->process(bq); }
|
||||
|
||||
void open(const ALCchar *name) override;
|
||||
bool start() override;
|
||||
void stop() override;
|
||||
ALCenum captureSamples(al::byte *buffer, ALCuint samples) override;
|
||||
ALCuint availableSamples() override;
|
||||
|
||||
/* engine interfaces */
|
||||
SLObjectItf mEngineObj{nullptr};
|
||||
SLEngineItf mEngine;
|
||||
|
||||
/* recording interfaces */
|
||||
SLObjectItf mRecordObj{nullptr};
|
||||
|
||||
RingBufferPtr mRing{nullptr};
|
||||
ALCuint mSplOffset{0u};
|
||||
|
||||
ALuint mFrameSize{0};
|
||||
|
||||
DEF_NEWDEL(OpenSLCapture)
|
||||
};
|
||||
|
||||
OpenSLCapture::~OpenSLCapture()
|
||||
{
|
||||
if(mRecordObj)
|
||||
VCALL0(mRecordObj,Destroy)();
|
||||
mRecordObj = nullptr;
|
||||
|
||||
if(mEngineObj)
|
||||
VCALL0(mEngineObj,Destroy)();
|
||||
mEngineObj = nullptr;
|
||||
mEngine = nullptr;
|
||||
}
|
||||
|
||||
|
||||
void OpenSLCapture::process(SLAndroidSimpleBufferQueueItf) noexcept
|
||||
{
|
||||
/* A new chunk has been written into the ring buffer, advance it. */
|
||||
mRing->writeAdvance(1);
|
||||
}
|
||||
|
||||
|
||||
void OpenSLCapture::open(const ALCchar* name)
|
||||
{
|
||||
if(!name)
|
||||
name = opensl_device;
|
||||
else if(strcmp(name, opensl_device) != 0)
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
|
||||
|
||||
SLresult result{slCreateEngine(&mEngineObj, 0, nullptr, 0, nullptr, nullptr)};
|
||||
PRINTERR(result, "slCreateEngine");
|
||||
if(SL_RESULT_SUCCESS == result)
|
||||
{
|
||||
result = VCALL(mEngineObj,Realize)(SL_BOOLEAN_FALSE);
|
||||
PRINTERR(result, "engine->Realize");
|
||||
}
|
||||
if(SL_RESULT_SUCCESS == result)
|
||||
{
|
||||
result = VCALL(mEngineObj,GetInterface)(SL_IID_ENGINE, &mEngine);
|
||||
PRINTERR(result, "engine->GetInterface");
|
||||
}
|
||||
if(SL_RESULT_SUCCESS == result)
|
||||
{
|
||||
mFrameSize = mDevice->frameSizeFromFmt();
|
||||
/* Ensure the total length is at least 100ms */
|
||||
ALuint length{maxu(mDevice->BufferSize, mDevice->Frequency/10)};
|
||||
/* Ensure the per-chunk length is at least 10ms, and no more than 50ms. */
|
||||
ALuint update_len{clampu(mDevice->BufferSize/3, mDevice->Frequency/100,
|
||||
mDevice->Frequency/100*5)};
|
||||
ALuint num_updates{(length+update_len-1) / update_len};
|
||||
|
||||
mRing = RingBuffer::Create(num_updates, update_len*mFrameSize, false);
|
||||
|
||||
mDevice->UpdateSize = update_len;
|
||||
mDevice->BufferSize = static_cast<ALuint>(mRing->writeSpace() * update_len);
|
||||
}
|
||||
if(SL_RESULT_SUCCESS == result)
|
||||
{
|
||||
const std::array<SLInterfaceID,2> ids{{ SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_ANDROIDCONFIGURATION }};
|
||||
const std::array<SLboolean,2> reqs{{ SL_BOOLEAN_TRUE, SL_BOOLEAN_FALSE }};
|
||||
|
||||
SLDataLocator_IODevice loc_dev{};
|
||||
loc_dev.locatorType = SL_DATALOCATOR_IODEVICE;
|
||||
loc_dev.deviceType = SL_IODEVICE_AUDIOINPUT;
|
||||
loc_dev.deviceID = SL_DEFAULTDEVICEID_AUDIOINPUT;
|
||||
loc_dev.device = nullptr;
|
||||
|
||||
SLDataSource audioSrc{};
|
||||
audioSrc.pLocator = &loc_dev;
|
||||
audioSrc.pFormat = nullptr;
|
||||
|
||||
SLDataLocator_AndroidSimpleBufferQueue loc_bq{};
|
||||
loc_bq.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE;
|
||||
loc_bq.numBuffers = mDevice->BufferSize / mDevice->UpdateSize;
|
||||
|
||||
SLDataSink audioSnk{};
|
||||
#ifdef SL_ANDROID_DATAFORMAT_PCM_EX
|
||||
SLAndroidDataFormat_PCM_EX format_pcm_ex{};
|
||||
format_pcm_ex.formatType = SL_ANDROID_DATAFORMAT_PCM_EX;
|
||||
format_pcm_ex.numChannels = mDevice->channelsFromFmt();
|
||||
format_pcm_ex.sampleRate = mDevice->Frequency * 1000;
|
||||
format_pcm_ex.bitsPerSample = mDevice->bytesFromFmt() * 8;
|
||||
format_pcm_ex.containerSize = format_pcm_ex.bitsPerSample;
|
||||
format_pcm_ex.channelMask = GetChannelMask(mDevice->FmtChans);
|
||||
format_pcm_ex.endianness = IS_LITTLE_ENDIAN ? SL_BYTEORDER_LITTLEENDIAN :
|
||||
SL_BYTEORDER_BIGENDIAN;
|
||||
format_pcm_ex.representation = GetTypeRepresentation(mDevice->FmtType);
|
||||
|
||||
audioSnk.pLocator = &loc_bq;
|
||||
audioSnk.pFormat = &format_pcm_ex;
|
||||
result = VCALL(mEngine,CreateAudioRecorder)(&mRecordObj, &audioSrc, &audioSnk,
|
||||
ids.size(), ids.data(), reqs.data());
|
||||
if(SL_RESULT_SUCCESS != result)
|
||||
#endif
|
||||
{
|
||||
/* Fallback to SLDataFormat_PCM only if it supports the desired
|
||||
* sample type.
|
||||
*/
|
||||
if(mDevice->FmtType == DevFmtUByte || mDevice->FmtType == DevFmtShort
|
||||
|| mDevice->FmtType == DevFmtInt)
|
||||
{
|
||||
SLDataFormat_PCM format_pcm{};
|
||||
format_pcm.formatType = SL_DATAFORMAT_PCM;
|
||||
format_pcm.numChannels = mDevice->channelsFromFmt();
|
||||
format_pcm.samplesPerSec = mDevice->Frequency * 1000;
|
||||
format_pcm.bitsPerSample = mDevice->bytesFromFmt() * 8;
|
||||
format_pcm.containerSize = format_pcm.bitsPerSample;
|
||||
format_pcm.channelMask = GetChannelMask(mDevice->FmtChans);
|
||||
format_pcm.endianness = IS_LITTLE_ENDIAN ? SL_BYTEORDER_LITTLEENDIAN :
|
||||
SL_BYTEORDER_BIGENDIAN;
|
||||
|
||||
audioSnk.pLocator = &loc_bq;
|
||||
audioSnk.pFormat = &format_pcm;
|
||||
result = VCALL(mEngine,CreateAudioRecorder)(&mRecordObj, &audioSrc, &audioSnk,
|
||||
ids.size(), ids.data(), reqs.data());
|
||||
}
|
||||
PRINTERR(result, "engine->CreateAudioRecorder");
|
||||
}
|
||||
}
|
||||
if(SL_RESULT_SUCCESS == result)
|
||||
{
|
||||
/* Set the record preset to "generic", if possible. */
|
||||
SLAndroidConfigurationItf config;
|
||||
result = VCALL(mRecordObj,GetInterface)(SL_IID_ANDROIDCONFIGURATION, &config);
|
||||
PRINTERR(result, "recordObj->GetInterface SL_IID_ANDROIDCONFIGURATION");
|
||||
if(SL_RESULT_SUCCESS == result)
|
||||
{
|
||||
SLuint32 preset = SL_ANDROID_RECORDING_PRESET_GENERIC;
|
||||
result = VCALL(config,SetConfiguration)(SL_ANDROID_KEY_RECORDING_PRESET, &preset,
|
||||
sizeof(preset));
|
||||
PRINTERR(result, "config->SetConfiguration");
|
||||
}
|
||||
|
||||
/* Clear any error since this was optional. */
|
||||
result = SL_RESULT_SUCCESS;
|
||||
}
|
||||
if(SL_RESULT_SUCCESS == result)
|
||||
{
|
||||
result = VCALL(mRecordObj,Realize)(SL_BOOLEAN_FALSE);
|
||||
PRINTERR(result, "recordObj->Realize");
|
||||
}
|
||||
|
||||
SLAndroidSimpleBufferQueueItf bufferQueue;
|
||||
if(SL_RESULT_SUCCESS == result)
|
||||
{
|
||||
result = VCALL(mRecordObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &bufferQueue);
|
||||
PRINTERR(result, "recordObj->GetInterface");
|
||||
}
|
||||
if(SL_RESULT_SUCCESS == result)
|
||||
{
|
||||
result = VCALL(bufferQueue,RegisterCallback)(&OpenSLCapture::processC, this);
|
||||
PRINTERR(result, "bufferQueue->RegisterCallback");
|
||||
}
|
||||
if(SL_RESULT_SUCCESS == result)
|
||||
{
|
||||
const ALuint chunk_size{mDevice->UpdateSize * mFrameSize};
|
||||
|
||||
auto data = mRing->getWriteVector();
|
||||
for(size_t i{0u};i < data.first.len && SL_RESULT_SUCCESS == result;i++)
|
||||
{
|
||||
result = VCALL(bufferQueue,Enqueue)(data.first.buf + chunk_size*i, chunk_size);
|
||||
PRINTERR(result, "bufferQueue->Enqueue");
|
||||
}
|
||||
for(size_t i{0u};i < data.second.len && SL_RESULT_SUCCESS == result;i++)
|
||||
{
|
||||
result = VCALL(bufferQueue,Enqueue)(data.second.buf + chunk_size*i, chunk_size);
|
||||
PRINTERR(result, "bufferQueue->Enqueue");
|
||||
}
|
||||
}
|
||||
|
||||
if(SL_RESULT_SUCCESS != result)
|
||||
{
|
||||
if(mRecordObj)
|
||||
VCALL0(mRecordObj,Destroy)();
|
||||
mRecordObj = nullptr;
|
||||
|
||||
if(mEngineObj)
|
||||
VCALL0(mEngineObj,Destroy)();
|
||||
mEngineObj = nullptr;
|
||||
mEngine = nullptr;
|
||||
|
||||
throw al::backend_exception{ALC_INVALID_VALUE,
|
||||
"Failed to initialize OpenSL device: 0x%08x", result};
|
||||
}
|
||||
|
||||
mDevice->DeviceName = name;
|
||||
}
|
||||
|
||||
bool OpenSLCapture::start()
|
||||
{
|
||||
SLRecordItf record;
|
||||
SLresult result{VCALL(mRecordObj,GetInterface)(SL_IID_RECORD, &record)};
|
||||
PRINTERR(result, "recordObj->GetInterface");
|
||||
|
||||
if(SL_RESULT_SUCCESS == result)
|
||||
{
|
||||
result = VCALL(record,SetRecordState)(SL_RECORDSTATE_RECORDING);
|
||||
PRINTERR(result, "record->SetRecordState");
|
||||
}
|
||||
|
||||
if(SL_RESULT_SUCCESS != result)
|
||||
{
|
||||
aluHandleDisconnect(mDevice, "Failed to start capture: 0x%08x", result);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void OpenSLCapture::stop()
|
||||
{
|
||||
SLRecordItf record;
|
||||
SLresult result{VCALL(mRecordObj,GetInterface)(SL_IID_RECORD, &record)};
|
||||
PRINTERR(result, "recordObj->GetInterface");
|
||||
|
||||
if(SL_RESULT_SUCCESS == result)
|
||||
{
|
||||
result = VCALL(record,SetRecordState)(SL_RECORDSTATE_PAUSED);
|
||||
PRINTERR(result, "record->SetRecordState");
|
||||
}
|
||||
}
|
||||
|
||||
ALCenum OpenSLCapture::captureSamples(al::byte *buffer, ALCuint samples)
|
||||
{
|
||||
SLAndroidSimpleBufferQueueItf bufferQueue{};
|
||||
if LIKELY(mDevice->Connected.load(std::memory_order_acquire))
|
||||
{
|
||||
const SLresult result{VCALL(mRecordObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
|
||||
&bufferQueue)};
|
||||
PRINTERR(result, "recordObj->GetInterface");
|
||||
if UNLIKELY(SL_RESULT_SUCCESS != result)
|
||||
{
|
||||
aluHandleDisconnect(mDevice, "Failed to get capture buffer queue: 0x%08x", result);
|
||||
bufferQueue = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
const ALuint update_size{mDevice->UpdateSize};
|
||||
const ALuint chunk_size{update_size * mFrameSize};
|
||||
|
||||
/* Read the desired samples from the ring buffer then advance its read
|
||||
* pointer.
|
||||
*/
|
||||
auto data = mRing->getReadVector();
|
||||
for(ALCuint i{0};i < samples;)
|
||||
{
|
||||
const ALCuint rem{minu(samples - i, update_size - mSplOffset)};
|
||||
std::copy_n(data.first.buf + mSplOffset*mFrameSize, rem*mFrameSize, buffer + i*mFrameSize);
|
||||
|
||||
mSplOffset += rem;
|
||||
if(mSplOffset == update_size)
|
||||
{
|
||||
/* Finished a chunk, reset the offset and advance the read pointer. */
|
||||
mSplOffset = 0;
|
||||
mRing->readAdvance(1);
|
||||
|
||||
if LIKELY(bufferQueue)
|
||||
{
|
||||
const SLresult result{VCALL(bufferQueue,Enqueue)(data.first.buf, chunk_size)};
|
||||
PRINTERR(result, "bufferQueue->Enqueue");
|
||||
if UNLIKELY(SL_RESULT_SUCCESS != result)
|
||||
{
|
||||
aluHandleDisconnect(mDevice, "Failed to update capture buffer: 0x%08x",
|
||||
result);
|
||||
bufferQueue = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
data.first.len--;
|
||||
if(!data.first.len)
|
||||
data.first = data.second;
|
||||
else
|
||||
data.first.buf += chunk_size;
|
||||
}
|
||||
|
||||
i += rem;
|
||||
}
|
||||
|
||||
return ALC_NO_ERROR;
|
||||
}
|
||||
|
||||
ALCuint OpenSLCapture::availableSamples()
|
||||
{ return static_cast<ALuint>(mRing->readSpace()*mDevice->UpdateSize - mSplOffset); }
|
||||
|
||||
} // namespace
|
||||
|
||||
bool OSLBackendFactory::init() { return true; }
|
||||
|
||||
bool OSLBackendFactory::querySupport(BackendType type)
|
||||
{ return (type == BackendType::Playback || type == BackendType::Capture); }
|
||||
|
||||
void OSLBackendFactory::probe(DevProbe type, std::string *outnames)
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
case DevProbe::Playback:
|
||||
case DevProbe::Capture:
|
||||
/* Includes null char. */
|
||||
outnames->append(opensl_device, sizeof(opensl_device));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
BackendPtr OSLBackendFactory::createBackend(ALCdevice *device, BackendType type)
|
||||
{
|
||||
if(type == BackendType::Playback)
|
||||
return BackendPtr{new OpenSLPlayback{device}};
|
||||
if(type == BackendType::Capture)
|
||||
return BackendPtr{new OpenSLCapture{device}};
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
BackendFactory &OSLBackendFactory::getFactory()
|
||||
{
|
||||
static OSLBackendFactory factory{};
|
||||
return factory;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
#ifndef BACKENDS_OSL_H
|
||||
#define BACKENDS_OSL_H
|
||||
|
||||
#include "backends/base.h"
|
||||
|
||||
struct OSLBackendFactory final : public BackendFactory {
|
||||
public:
|
||||
bool init() override;
|
||||
|
||||
bool querySupport(BackendType type) override;
|
||||
|
||||
void probe(DevProbe type, std::string *outnames) override;
|
||||
|
||||
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
|
||||
|
||||
static BackendFactory &getFactory();
|
||||
};
|
||||
|
||||
#endif /* BACKENDS_OSL_H */
|
||||
@@ -1,713 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 1999-2007 by authors.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "backends/oss.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cerrno>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <new>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
#include "AL/al.h"
|
||||
|
||||
#include "alcmain.h"
|
||||
#include "alconfig.h"
|
||||
#include "alexcpt.h"
|
||||
#include "almalloc.h"
|
||||
#include "alnumeric.h"
|
||||
#include "aloptional.h"
|
||||
#include "alu.h"
|
||||
#include "logging.h"
|
||||
#include "ringbuffer.h"
|
||||
#include "threads.h"
|
||||
#include "vector.h"
|
||||
|
||||
#include <sys/soundcard.h>
|
||||
|
||||
/*
|
||||
* The OSS documentation talks about SOUND_MIXER_READ, but the header
|
||||
* only contains MIXER_READ. Play safe. Same for WRITE.
|
||||
*/
|
||||
#ifndef SOUND_MIXER_READ
|
||||
#define SOUND_MIXER_READ MIXER_READ
|
||||
#endif
|
||||
#ifndef SOUND_MIXER_WRITE
|
||||
#define SOUND_MIXER_WRITE MIXER_WRITE
|
||||
#endif
|
||||
|
||||
#if defined(SOUND_VERSION) && (SOUND_VERSION < 0x040000)
|
||||
#define ALC_OSS_COMPAT
|
||||
#endif
|
||||
#ifndef SNDCTL_AUDIOINFO
|
||||
#define ALC_OSS_COMPAT
|
||||
#endif
|
||||
|
||||
/*
|
||||
* FreeBSD strongly discourages the use of specific devices,
|
||||
* such as those returned in oss_audioinfo.devnode
|
||||
*/
|
||||
#ifdef __FreeBSD__
|
||||
#define ALC_OSS_DEVNODE_TRUC
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr char DefaultName[] = "OSS Default";
|
||||
std::string DefaultPlayback{"/dev/dsp"};
|
||||
std::string DefaultCapture{"/dev/dsp"};
|
||||
|
||||
struct DevMap {
|
||||
std::string name;
|
||||
std::string device_name;
|
||||
};
|
||||
|
||||
bool checkName(const al::vector<DevMap> &list, const std::string &name)
|
||||
{
|
||||
return std::find_if(list.cbegin(), list.cend(),
|
||||
[&name](const DevMap &entry) -> bool
|
||||
{ return entry.name == name; }
|
||||
) != list.cend();
|
||||
}
|
||||
|
||||
al::vector<DevMap> PlaybackDevices;
|
||||
al::vector<DevMap> CaptureDevices;
|
||||
|
||||
|
||||
#ifdef ALC_OSS_COMPAT
|
||||
|
||||
#define DSP_CAP_OUTPUT 0x00020000
|
||||
#define DSP_CAP_INPUT 0x00010000
|
||||
void ALCossListPopulate(al::vector<DevMap> *devlist, int type)
|
||||
{
|
||||
devlist->emplace_back(DevMap{DefaultName, (type==DSP_CAP_INPUT) ? DefaultCapture : DefaultPlayback});
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void ALCossListAppend(al::vector<DevMap> *list, const char *handle, size_t hlen, const char *path, size_t plen)
|
||||
{
|
||||
#ifdef ALC_OSS_DEVNODE_TRUC
|
||||
for(size_t i{0};i < plen;i++)
|
||||
{
|
||||
if(path[i] == '.')
|
||||
{
|
||||
if(strncmp(path + i, handle + hlen + i - plen, plen - i) == 0)
|
||||
hlen = hlen + i - plen;
|
||||
plen = i;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if(handle[0] == '\0')
|
||||
{
|
||||
handle = path;
|
||||
hlen = plen;
|
||||
}
|
||||
|
||||
std::string basename{handle, hlen};
|
||||
basename.erase(std::find(basename.begin(), basename.end(), '\0'), basename.end());
|
||||
std::string devname{path, plen};
|
||||
devname.erase(std::find(devname.begin(), devname.end(), '\0'), devname.end());
|
||||
|
||||
auto iter = std::find_if(list->cbegin(), list->cend(),
|
||||
[&devname](const DevMap &entry) -> bool
|
||||
{ return entry.device_name == devname; }
|
||||
);
|
||||
if(iter != list->cend())
|
||||
return;
|
||||
|
||||
int count{1};
|
||||
std::string newname{basename};
|
||||
while(checkName(PlaybackDevices, newname))
|
||||
{
|
||||
newname = basename;
|
||||
newname += " #";
|
||||
newname += std::to_string(++count);
|
||||
}
|
||||
|
||||
list->emplace_back(DevMap{std::move(newname), std::move(devname)});
|
||||
const DevMap &entry = list->back();
|
||||
|
||||
TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str());
|
||||
}
|
||||
|
||||
void ALCossListPopulate(al::vector<DevMap> *devlist, int type_flag)
|
||||
{
|
||||
int fd{open("/dev/mixer", O_RDONLY)};
|
||||
if(fd < 0)
|
||||
{
|
||||
TRACE("Could not open /dev/mixer: %s\n", strerror(errno));
|
||||
goto done;
|
||||
}
|
||||
|
||||
oss_sysinfo si;
|
||||
if(ioctl(fd, SNDCTL_SYSINFO, &si) == -1)
|
||||
{
|
||||
TRACE("SNDCTL_SYSINFO failed: %s\n", strerror(errno));
|
||||
goto done;
|
||||
}
|
||||
|
||||
for(int i{0};i < si.numaudios;i++)
|
||||
{
|
||||
oss_audioinfo ai;
|
||||
ai.dev = i;
|
||||
if(ioctl(fd, SNDCTL_AUDIOINFO, &ai) == -1)
|
||||
{
|
||||
ERR("SNDCTL_AUDIOINFO (%d) failed: %s\n", i, strerror(errno));
|
||||
continue;
|
||||
}
|
||||
if(!(ai.caps&type_flag) || ai.devnode[0] == '\0')
|
||||
continue;
|
||||
|
||||
const char *handle;
|
||||
size_t len;
|
||||
if(ai.handle[0] != '\0')
|
||||
{
|
||||
len = strnlen(ai.handle, sizeof(ai.handle));
|
||||
handle = ai.handle;
|
||||
}
|
||||
else
|
||||
{
|
||||
len = strnlen(ai.name, sizeof(ai.name));
|
||||
handle = ai.name;
|
||||
}
|
||||
|
||||
ALCossListAppend(devlist, handle, len, ai.devnode,
|
||||
strnlen(ai.devnode, sizeof(ai.devnode)));
|
||||
}
|
||||
|
||||
done:
|
||||
if(fd >= 0)
|
||||
close(fd);
|
||||
fd = -1;
|
||||
|
||||
const char *defdev{((type_flag==DSP_CAP_INPUT) ? DefaultCapture : DefaultPlayback).c_str()};
|
||||
auto iter = std::find_if(devlist->cbegin(), devlist->cend(),
|
||||
[defdev](const DevMap &entry) -> bool
|
||||
{ return entry.device_name == defdev; }
|
||||
);
|
||||
if(iter == devlist->cend())
|
||||
devlist->insert(devlist->begin(), DevMap{DefaultName, defdev});
|
||||
else
|
||||
{
|
||||
DevMap entry{std::move(*iter)};
|
||||
devlist->erase(iter);
|
||||
devlist->insert(devlist->begin(), std::move(entry));
|
||||
}
|
||||
devlist->shrink_to_fit();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
ALCuint log2i(ALCuint x)
|
||||
{
|
||||
ALCuint y{0};
|
||||
while(x > 1)
|
||||
{
|
||||
x >>= 1;
|
||||
y++;
|
||||
}
|
||||
return y;
|
||||
}
|
||||
|
||||
|
||||
struct OSSPlayback final : public BackendBase {
|
||||
OSSPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
|
||||
~OSSPlayback() override;
|
||||
|
||||
int mixerProc();
|
||||
|
||||
void open(const ALCchar *name) override;
|
||||
bool reset() override;
|
||||
bool start() override;
|
||||
void stop() override;
|
||||
|
||||
int mFd{-1};
|
||||
|
||||
al::vector<ALubyte> mMixData;
|
||||
|
||||
std::atomic<bool> mKillNow{true};
|
||||
std::thread mThread;
|
||||
|
||||
DEF_NEWDEL(OSSPlayback)
|
||||
};
|
||||
|
||||
OSSPlayback::~OSSPlayback()
|
||||
{
|
||||
if(mFd != -1)
|
||||
close(mFd);
|
||||
mFd = -1;
|
||||
}
|
||||
|
||||
|
||||
int OSSPlayback::mixerProc()
|
||||
{
|
||||
SetRTPriority();
|
||||
althrd_setname(MIXER_THREAD_NAME);
|
||||
|
||||
const size_t frame_step{mDevice->channelsFromFmt()};
|
||||
const ALuint frame_size{mDevice->frameSizeFromFmt()};
|
||||
|
||||
std::unique_lock<OSSPlayback> dlock{*this};
|
||||
while(!mKillNow.load(std::memory_order_acquire) &&
|
||||
mDevice->Connected.load(std::memory_order_acquire))
|
||||
{
|
||||
pollfd pollitem{};
|
||||
pollitem.fd = mFd;
|
||||
pollitem.events = POLLOUT;
|
||||
|
||||
dlock.unlock();
|
||||
int pret{poll(&pollitem, 1, 1000)};
|
||||
dlock.lock();
|
||||
if(pret < 0)
|
||||
{
|
||||
if(errno == EINTR || errno == EAGAIN)
|
||||
continue;
|
||||
ERR("poll failed: %s\n", strerror(errno));
|
||||
aluHandleDisconnect(mDevice, "Failed waiting for playback buffer: %s", strerror(errno));
|
||||
break;
|
||||
}
|
||||
else if(pret == 0)
|
||||
{
|
||||
WARN("poll timeout\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
ALubyte *write_ptr{mMixData.data()};
|
||||
size_t to_write{mMixData.size()};
|
||||
aluMixData(mDevice, write_ptr, static_cast<ALuint>(to_write/frame_size), frame_step);
|
||||
while(to_write > 0 && !mKillNow.load(std::memory_order_acquire))
|
||||
{
|
||||
ssize_t wrote{write(mFd, write_ptr, to_write)};
|
||||
if(wrote < 0)
|
||||
{
|
||||
if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
|
||||
continue;
|
||||
ERR("write failed: %s\n", strerror(errno));
|
||||
aluHandleDisconnect(mDevice, "Failed writing playback samples: %s",
|
||||
strerror(errno));
|
||||
break;
|
||||
}
|
||||
|
||||
to_write -= static_cast<size_t>(wrote);
|
||||
write_ptr += wrote;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void OSSPlayback::open(const ALCchar *name)
|
||||
{
|
||||
const char *devname{DefaultPlayback.c_str()};
|
||||
if(!name)
|
||||
name = DefaultName;
|
||||
else
|
||||
{
|
||||
if(PlaybackDevices.empty())
|
||||
ALCossListPopulate(&PlaybackDevices, DSP_CAP_OUTPUT);
|
||||
|
||||
auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(),
|
||||
[&name](const DevMap &entry) -> bool
|
||||
{ return entry.name == name; }
|
||||
);
|
||||
if(iter == PlaybackDevices.cend())
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
|
||||
devname = iter->device_name.c_str();
|
||||
}
|
||||
|
||||
mFd = ::open(devname, O_WRONLY);
|
||||
if(mFd == -1)
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "Could not open %s: %s", devname,
|
||||
strerror(errno)};
|
||||
|
||||
mDevice->DeviceName = name;
|
||||
}
|
||||
|
||||
bool OSSPlayback::reset()
|
||||
{
|
||||
int ossFormat{};
|
||||
switch(mDevice->FmtType)
|
||||
{
|
||||
case DevFmtByte:
|
||||
ossFormat = AFMT_S8;
|
||||
break;
|
||||
case DevFmtUByte:
|
||||
ossFormat = AFMT_U8;
|
||||
break;
|
||||
case DevFmtUShort:
|
||||
case DevFmtInt:
|
||||
case DevFmtUInt:
|
||||
case DevFmtFloat:
|
||||
mDevice->FmtType = DevFmtShort;
|
||||
/* fall-through */
|
||||
case DevFmtShort:
|
||||
ossFormat = AFMT_S16_NE;
|
||||
break;
|
||||
}
|
||||
|
||||
ALuint periods{mDevice->BufferSize / mDevice->UpdateSize};
|
||||
ALuint numChannels{mDevice->channelsFromFmt()};
|
||||
ALuint ossSpeed{mDevice->Frequency};
|
||||
ALuint frameSize{numChannels * mDevice->bytesFromFmt()};
|
||||
/* According to the OSS spec, 16 bytes (log2(16)) is the minimum. */
|
||||
ALuint log2FragmentSize{maxu(log2i(mDevice->UpdateSize*frameSize), 4)};
|
||||
ALuint numFragmentsLogSize{(periods << 16) | log2FragmentSize};
|
||||
|
||||
audio_buf_info info{};
|
||||
const char *err;
|
||||
#define CHECKERR(func) if((func) < 0) { \
|
||||
err = #func; \
|
||||
goto err; \
|
||||
}
|
||||
/* Don't fail if SETFRAGMENT fails. We can handle just about anything
|
||||
* that's reported back via GETOSPACE */
|
||||
ioctl(mFd, SNDCTL_DSP_SETFRAGMENT, &numFragmentsLogSize);
|
||||
CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFMT, &ossFormat));
|
||||
CHECKERR(ioctl(mFd, SNDCTL_DSP_CHANNELS, &numChannels));
|
||||
CHECKERR(ioctl(mFd, SNDCTL_DSP_SPEED, &ossSpeed));
|
||||
CHECKERR(ioctl(mFd, SNDCTL_DSP_GETOSPACE, &info));
|
||||
if(0)
|
||||
{
|
||||
err:
|
||||
ERR("%s failed: %s\n", err, strerror(errno));
|
||||
return false;
|
||||
}
|
||||
#undef CHECKERR
|
||||
|
||||
if(mDevice->channelsFromFmt() != numChannels)
|
||||
{
|
||||
ERR("Failed to set %s, got %d channels instead\n", DevFmtChannelsString(mDevice->FmtChans),
|
||||
numChannels);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!((ossFormat == AFMT_S8 && mDevice->FmtType == DevFmtByte) ||
|
||||
(ossFormat == AFMT_U8 && mDevice->FmtType == DevFmtUByte) ||
|
||||
(ossFormat == AFMT_S16_NE && mDevice->FmtType == DevFmtShort)))
|
||||
{
|
||||
ERR("Failed to set %s samples, got OSS format %#x\n", DevFmtTypeString(mDevice->FmtType),
|
||||
ossFormat);
|
||||
return false;
|
||||
}
|
||||
|
||||
mDevice->Frequency = ossSpeed;
|
||||
mDevice->UpdateSize = static_cast<ALuint>(info.fragsize) / frameSize;
|
||||
mDevice->BufferSize = static_cast<ALuint>(info.fragments) * mDevice->UpdateSize;
|
||||
|
||||
SetDefaultChannelOrder(mDevice);
|
||||
|
||||
mMixData.resize(mDevice->UpdateSize * mDevice->frameSizeFromFmt());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OSSPlayback::start()
|
||||
{
|
||||
try {
|
||||
mKillNow.store(false, std::memory_order_release);
|
||||
mThread = std::thread{std::mem_fn(&OSSPlayback::mixerProc), this};
|
||||
return true;
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
ERR("Could not create playback thread: %s\n", e.what());
|
||||
}
|
||||
catch(...) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void OSSPlayback::stop()
|
||||
{
|
||||
if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
|
||||
return;
|
||||
mThread.join();
|
||||
|
||||
if(ioctl(mFd, SNDCTL_DSP_RESET) != 0)
|
||||
ERR("Error resetting device: %s\n", strerror(errno));
|
||||
}
|
||||
|
||||
|
||||
struct OSScapture final : public BackendBase {
|
||||
OSScapture(ALCdevice *device) noexcept : BackendBase{device} { }
|
||||
~OSScapture() override;
|
||||
|
||||
int recordProc();
|
||||
|
||||
void open(const ALCchar *name) override;
|
||||
bool start() override;
|
||||
void stop() override;
|
||||
ALCenum captureSamples(al::byte *buffer, ALCuint samples) override;
|
||||
ALCuint availableSamples() override;
|
||||
|
||||
int mFd{-1};
|
||||
|
||||
RingBufferPtr mRing{nullptr};
|
||||
|
||||
std::atomic<bool> mKillNow{true};
|
||||
std::thread mThread;
|
||||
|
||||
DEF_NEWDEL(OSScapture)
|
||||
};
|
||||
|
||||
OSScapture::~OSScapture()
|
||||
{
|
||||
if(mFd != -1)
|
||||
close(mFd);
|
||||
mFd = -1;
|
||||
}
|
||||
|
||||
|
||||
int OSScapture::recordProc()
|
||||
{
|
||||
SetRTPriority();
|
||||
althrd_setname(RECORD_THREAD_NAME);
|
||||
|
||||
const ALuint frame_size{mDevice->frameSizeFromFmt()};
|
||||
while(!mKillNow.load(std::memory_order_acquire))
|
||||
{
|
||||
pollfd pollitem{};
|
||||
pollitem.fd = mFd;
|
||||
pollitem.events = POLLIN;
|
||||
|
||||
int sret{poll(&pollitem, 1, 1000)};
|
||||
if(sret < 0)
|
||||
{
|
||||
if(errno == EINTR || errno == EAGAIN)
|
||||
continue;
|
||||
ERR("poll failed: %s\n", strerror(errno));
|
||||
aluHandleDisconnect(mDevice, "Failed to check capture samples: %s", strerror(errno));
|
||||
break;
|
||||
}
|
||||
else if(sret == 0)
|
||||
{
|
||||
WARN("poll timeout\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
auto vec = mRing->getWriteVector();
|
||||
if(vec.first.len > 0)
|
||||
{
|
||||
ssize_t amt{read(mFd, vec.first.buf, vec.first.len*frame_size)};
|
||||
if(amt < 0)
|
||||
{
|
||||
ERR("read failed: %s\n", strerror(errno));
|
||||
aluHandleDisconnect(mDevice, "Failed reading capture samples: %s",
|
||||
strerror(errno));
|
||||
break;
|
||||
}
|
||||
mRing->writeAdvance(static_cast<ALuint>(amt)/frame_size);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void OSScapture::open(const ALCchar *name)
|
||||
{
|
||||
const char *devname{DefaultCapture.c_str()};
|
||||
if(!name)
|
||||
name = DefaultName;
|
||||
else
|
||||
{
|
||||
if(CaptureDevices.empty())
|
||||
ALCossListPopulate(&CaptureDevices, DSP_CAP_INPUT);
|
||||
|
||||
auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(),
|
||||
[&name](const DevMap &entry) -> bool
|
||||
{ return entry.name == name; }
|
||||
);
|
||||
if(iter == CaptureDevices.cend())
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
|
||||
devname = iter->device_name.c_str();
|
||||
}
|
||||
|
||||
mFd = ::open(devname, O_RDONLY);
|
||||
if(mFd == -1)
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "Could not open %s: %s", devname,
|
||||
strerror(errno)};
|
||||
|
||||
int ossFormat{};
|
||||
switch(mDevice->FmtType)
|
||||
{
|
||||
case DevFmtByte:
|
||||
ossFormat = AFMT_S8;
|
||||
break;
|
||||
case DevFmtUByte:
|
||||
ossFormat = AFMT_U8;
|
||||
break;
|
||||
case DevFmtShort:
|
||||
ossFormat = AFMT_S16_NE;
|
||||
break;
|
||||
case DevFmtUShort:
|
||||
case DevFmtInt:
|
||||
case DevFmtUInt:
|
||||
case DevFmtFloat:
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "%s capture samples not supported",
|
||||
DevFmtTypeString(mDevice->FmtType)};
|
||||
}
|
||||
|
||||
ALuint periods{4};
|
||||
ALuint numChannels{mDevice->channelsFromFmt()};
|
||||
ALuint frameSize{numChannels * mDevice->bytesFromFmt()};
|
||||
ALuint ossSpeed{mDevice->Frequency};
|
||||
/* according to the OSS spec, 16 bytes are the minimum */
|
||||
ALuint log2FragmentSize{maxu(log2i(mDevice->BufferSize * frameSize / periods), 4)};
|
||||
ALuint numFragmentsLogSize{(periods << 16) | log2FragmentSize};
|
||||
|
||||
audio_buf_info info{};
|
||||
#define CHECKERR(func) if((func) < 0) { \
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, #func " failed: %s", strerror(errno)}; \
|
||||
}
|
||||
CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFRAGMENT, &numFragmentsLogSize));
|
||||
CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFMT, &ossFormat));
|
||||
CHECKERR(ioctl(mFd, SNDCTL_DSP_CHANNELS, &numChannels));
|
||||
CHECKERR(ioctl(mFd, SNDCTL_DSP_SPEED, &ossSpeed));
|
||||
CHECKERR(ioctl(mFd, SNDCTL_DSP_GETISPACE, &info));
|
||||
#undef CHECKERR
|
||||
|
||||
if(mDevice->channelsFromFmt() != numChannels)
|
||||
throw al::backend_exception{ALC_INVALID_VALUE,
|
||||
"Failed to set %s, got %d channels instead", DevFmtChannelsString(mDevice->FmtChans),
|
||||
numChannels};
|
||||
|
||||
if(!((ossFormat == AFMT_S8 && mDevice->FmtType == DevFmtByte)
|
||||
|| (ossFormat == AFMT_U8 && mDevice->FmtType == DevFmtUByte)
|
||||
|| (ossFormat == AFMT_S16_NE && mDevice->FmtType == DevFmtShort)))
|
||||
throw al::backend_exception{ALC_INVALID_VALUE,
|
||||
"Failed to set %s samples, got OSS format %#x", DevFmtTypeString(mDevice->FmtType),
|
||||
ossFormat};
|
||||
|
||||
mRing = RingBuffer::Create(mDevice->BufferSize, frameSize, false);
|
||||
|
||||
mDevice->DeviceName = name;
|
||||
}
|
||||
|
||||
bool OSScapture::start()
|
||||
{
|
||||
try {
|
||||
mKillNow.store(false, std::memory_order_release);
|
||||
mThread = std::thread{std::mem_fn(&OSScapture::recordProc), this};
|
||||
return true;
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
ERR("Could not create record thread: %s\n", e.what());
|
||||
}
|
||||
catch(...) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void OSScapture::stop()
|
||||
{
|
||||
if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
|
||||
return;
|
||||
mThread.join();
|
||||
|
||||
if(ioctl(mFd, SNDCTL_DSP_RESET) != 0)
|
||||
ERR("Error resetting device: %s\n", strerror(errno));
|
||||
}
|
||||
|
||||
ALCenum OSScapture::captureSamples(al::byte *buffer, ALCuint samples)
|
||||
{
|
||||
mRing->read(buffer, samples);
|
||||
return ALC_NO_ERROR;
|
||||
}
|
||||
|
||||
ALCuint OSScapture::availableSamples()
|
||||
{ return static_cast<ALCuint>(mRing->readSpace()); }
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
BackendFactory &OSSBackendFactory::getFactory()
|
||||
{
|
||||
static OSSBackendFactory factory{};
|
||||
return factory;
|
||||
}
|
||||
|
||||
bool OSSBackendFactory::init()
|
||||
{
|
||||
if(auto devopt = ConfigValueStr(nullptr, "oss", "device"))
|
||||
DefaultPlayback = std::move(*devopt);
|
||||
if(auto capopt = ConfigValueStr(nullptr, "oss", "capture"))
|
||||
DefaultCapture = std::move(*capopt);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OSSBackendFactory::querySupport(BackendType type)
|
||||
{ return (type == BackendType::Playback || type == BackendType::Capture); }
|
||||
|
||||
void OSSBackendFactory::probe(DevProbe type, std::string *outnames)
|
||||
{
|
||||
auto add_device = [outnames](const DevMap &entry) -> void
|
||||
{
|
||||
#ifdef HAVE_STAT
|
||||
struct stat buf;
|
||||
if(stat(entry.device_name.c_str(), &buf) == 0)
|
||||
#endif
|
||||
{
|
||||
/* Includes null char. */
|
||||
outnames->append(entry.name.c_str(), entry.name.length()+1);
|
||||
}
|
||||
};
|
||||
|
||||
switch(type)
|
||||
{
|
||||
case DevProbe::Playback:
|
||||
PlaybackDevices.clear();
|
||||
ALCossListPopulate(&PlaybackDevices, DSP_CAP_OUTPUT);
|
||||
std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
|
||||
break;
|
||||
|
||||
case DevProbe::Capture:
|
||||
CaptureDevices.clear();
|
||||
ALCossListPopulate(&CaptureDevices, DSP_CAP_INPUT);
|
||||
std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
BackendPtr OSSBackendFactory::createBackend(ALCdevice *device, BackendType type)
|
||||
{
|
||||
if(type == BackendType::Playback)
|
||||
return BackendPtr{new OSSPlayback{device}};
|
||||
if(type == BackendType::Capture)
|
||||
return BackendPtr{new OSScapture{device}};
|
||||
return nullptr;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
#ifndef BACKENDS_OSS_H
|
||||
#define BACKENDS_OSS_H
|
||||
|
||||
#include "backends/base.h"
|
||||
|
||||
struct OSSBackendFactory final : public BackendFactory {
|
||||
public:
|
||||
bool init() override;
|
||||
|
||||
bool querySupport(BackendType type) override;
|
||||
|
||||
void probe(DevProbe type, std::string *outnames) override;
|
||||
|
||||
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
|
||||
|
||||
static BackendFactory &getFactory();
|
||||
};
|
||||
|
||||
#endif /* BACKENDS_OSS_H */
|
||||
@@ -1,448 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 1999-2007 by authors.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "backends/portaudio.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#include "alcmain.h"
|
||||
#include "alexcpt.h"
|
||||
#include "alu.h"
|
||||
#include "alconfig.h"
|
||||
#include "dynload.h"
|
||||
#include "ringbuffer.h"
|
||||
|
||||
#include <portaudio.h>
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr ALCchar pa_device[] = "PortAudio Default";
|
||||
|
||||
|
||||
#ifdef HAVE_DYNLOAD
|
||||
void *pa_handle;
|
||||
#define MAKE_FUNC(x) decltype(x) * p##x
|
||||
MAKE_FUNC(Pa_Initialize);
|
||||
MAKE_FUNC(Pa_Terminate);
|
||||
MAKE_FUNC(Pa_GetErrorText);
|
||||
MAKE_FUNC(Pa_StartStream);
|
||||
MAKE_FUNC(Pa_StopStream);
|
||||
MAKE_FUNC(Pa_OpenStream);
|
||||
MAKE_FUNC(Pa_CloseStream);
|
||||
MAKE_FUNC(Pa_GetDefaultOutputDevice);
|
||||
MAKE_FUNC(Pa_GetDefaultInputDevice);
|
||||
MAKE_FUNC(Pa_GetStreamInfo);
|
||||
#undef MAKE_FUNC
|
||||
|
||||
#ifndef IN_IDE_PARSER
|
||||
#define Pa_Initialize pPa_Initialize
|
||||
#define Pa_Terminate pPa_Terminate
|
||||
#define Pa_GetErrorText pPa_GetErrorText
|
||||
#define Pa_StartStream pPa_StartStream
|
||||
#define Pa_StopStream pPa_StopStream
|
||||
#define Pa_OpenStream pPa_OpenStream
|
||||
#define Pa_CloseStream pPa_CloseStream
|
||||
#define Pa_GetDefaultOutputDevice pPa_GetDefaultOutputDevice
|
||||
#define Pa_GetDefaultInputDevice pPa_GetDefaultInputDevice
|
||||
#define Pa_GetStreamInfo pPa_GetStreamInfo
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
struct PortPlayback final : public BackendBase {
|
||||
PortPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
|
||||
~PortPlayback() override;
|
||||
|
||||
int writeCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer,
|
||||
const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags) noexcept;
|
||||
static int writeCallbackC(const void *inputBuffer, void *outputBuffer,
|
||||
unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo,
|
||||
const PaStreamCallbackFlags statusFlags, void *userData) noexcept
|
||||
{
|
||||
return static_cast<PortPlayback*>(userData)->writeCallback(inputBuffer, outputBuffer,
|
||||
framesPerBuffer, timeInfo, statusFlags);
|
||||
}
|
||||
|
||||
void open(const ALCchar *name) override;
|
||||
bool reset() override;
|
||||
bool start() override;
|
||||
void stop() override;
|
||||
|
||||
PaStream *mStream{nullptr};
|
||||
PaStreamParameters mParams{};
|
||||
ALuint mUpdateSize{0u};
|
||||
|
||||
DEF_NEWDEL(PortPlayback)
|
||||
};
|
||||
|
||||
PortPlayback::~PortPlayback()
|
||||
{
|
||||
PaError err{mStream ? Pa_CloseStream(mStream) : paNoError};
|
||||
if(err != paNoError)
|
||||
ERR("Error closing stream: %s\n", Pa_GetErrorText(err));
|
||||
mStream = nullptr;
|
||||
}
|
||||
|
||||
|
||||
int PortPlayback::writeCallback(const void*, void *outputBuffer, unsigned long framesPerBuffer,
|
||||
const PaStreamCallbackTimeInfo*, const PaStreamCallbackFlags) noexcept
|
||||
{
|
||||
std::lock_guard<PortPlayback> _{*this};
|
||||
aluMixData(mDevice, outputBuffer, static_cast<ALuint>(framesPerBuffer),
|
||||
mDevice->channelsFromFmt());
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void PortPlayback::open(const ALCchar *name)
|
||||
{
|
||||
if(!name)
|
||||
name = pa_device;
|
||||
else if(strcmp(name, pa_device) != 0)
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
|
||||
|
||||
mUpdateSize = mDevice->UpdateSize;
|
||||
|
||||
auto devidopt = ConfigValueInt(nullptr, "port", "device");
|
||||
if(devidopt && *devidopt >= 0) mParams.device = *devidopt;
|
||||
else mParams.device = Pa_GetDefaultOutputDevice();
|
||||
mParams.suggestedLatency = mDevice->BufferSize / static_cast<double>(mDevice->Frequency);
|
||||
mParams.hostApiSpecificStreamInfo = nullptr;
|
||||
|
||||
mParams.channelCount = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2);
|
||||
|
||||
switch(mDevice->FmtType)
|
||||
{
|
||||
case DevFmtByte:
|
||||
mParams.sampleFormat = paInt8;
|
||||
break;
|
||||
case DevFmtUByte:
|
||||
mParams.sampleFormat = paUInt8;
|
||||
break;
|
||||
case DevFmtUShort:
|
||||
/* fall-through */
|
||||
case DevFmtShort:
|
||||
mParams.sampleFormat = paInt16;
|
||||
break;
|
||||
case DevFmtUInt:
|
||||
/* fall-through */
|
||||
case DevFmtInt:
|
||||
mParams.sampleFormat = paInt32;
|
||||
break;
|
||||
case DevFmtFloat:
|
||||
mParams.sampleFormat = paFloat32;
|
||||
break;
|
||||
}
|
||||
|
||||
retry_open:
|
||||
PaError err{Pa_OpenStream(&mStream, nullptr, &mParams, mDevice->Frequency, mDevice->UpdateSize,
|
||||
paNoFlag, &PortPlayback::writeCallbackC, this)};
|
||||
if(err != paNoError)
|
||||
{
|
||||
if(mParams.sampleFormat == paFloat32)
|
||||
{
|
||||
mParams.sampleFormat = paInt16;
|
||||
goto retry_open;
|
||||
}
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "Failed to open stream: %s",
|
||||
Pa_GetErrorText(err)};
|
||||
}
|
||||
|
||||
mDevice->DeviceName = name;
|
||||
}
|
||||
|
||||
bool PortPlayback::reset()
|
||||
{
|
||||
const PaStreamInfo *streamInfo{Pa_GetStreamInfo(mStream)};
|
||||
mDevice->Frequency = static_cast<ALuint>(streamInfo->sampleRate);
|
||||
mDevice->UpdateSize = mUpdateSize;
|
||||
|
||||
if(mParams.sampleFormat == paInt8)
|
||||
mDevice->FmtType = DevFmtByte;
|
||||
else if(mParams.sampleFormat == paUInt8)
|
||||
mDevice->FmtType = DevFmtUByte;
|
||||
else if(mParams.sampleFormat == paInt16)
|
||||
mDevice->FmtType = DevFmtShort;
|
||||
else if(mParams.sampleFormat == paInt32)
|
||||
mDevice->FmtType = DevFmtInt;
|
||||
else if(mParams.sampleFormat == paFloat32)
|
||||
mDevice->FmtType = DevFmtFloat;
|
||||
else
|
||||
{
|
||||
ERR("Unexpected sample format: 0x%lx\n", mParams.sampleFormat);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(mParams.channelCount == 2)
|
||||
mDevice->FmtChans = DevFmtStereo;
|
||||
else if(mParams.channelCount == 1)
|
||||
mDevice->FmtChans = DevFmtMono;
|
||||
else
|
||||
{
|
||||
ERR("Unexpected channel count: %u\n", mParams.channelCount);
|
||||
return false;
|
||||
}
|
||||
SetDefaultChannelOrder(mDevice);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PortPlayback::start()
|
||||
{
|
||||
PaError err{Pa_StartStream(mStream)};
|
||||
if(err != paNoError)
|
||||
{
|
||||
ERR("Pa_StartStream() returned an error: %s\n", Pa_GetErrorText(err));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void PortPlayback::stop()
|
||||
{
|
||||
PaError err{Pa_StopStream(mStream)};
|
||||
if(err != paNoError)
|
||||
ERR("Error stopping stream: %s\n", Pa_GetErrorText(err));
|
||||
}
|
||||
|
||||
|
||||
struct PortCapture final : public BackendBase {
|
||||
PortCapture(ALCdevice *device) noexcept : BackendBase{device} { }
|
||||
~PortCapture() override;
|
||||
|
||||
int readCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer,
|
||||
const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags) noexcept;
|
||||
static int readCallbackC(const void *inputBuffer, void *outputBuffer,
|
||||
unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo,
|
||||
const PaStreamCallbackFlags statusFlags, void *userData) noexcept
|
||||
{
|
||||
return static_cast<PortCapture*>(userData)->readCallback(inputBuffer, outputBuffer,
|
||||
framesPerBuffer, timeInfo, statusFlags);
|
||||
}
|
||||
|
||||
void open(const ALCchar *name) override;
|
||||
bool start() override;
|
||||
void stop() override;
|
||||
ALCenum captureSamples(al::byte *buffer, ALCuint samples) override;
|
||||
ALCuint availableSamples() override;
|
||||
|
||||
PaStream *mStream{nullptr};
|
||||
PaStreamParameters mParams;
|
||||
|
||||
RingBufferPtr mRing{nullptr};
|
||||
|
||||
DEF_NEWDEL(PortCapture)
|
||||
};
|
||||
|
||||
PortCapture::~PortCapture()
|
||||
{
|
||||
PaError err{mStream ? Pa_CloseStream(mStream) : paNoError};
|
||||
if(err != paNoError)
|
||||
ERR("Error closing stream: %s\n", Pa_GetErrorText(err));
|
||||
mStream = nullptr;
|
||||
}
|
||||
|
||||
|
||||
int PortCapture::readCallback(const void *inputBuffer, void*, unsigned long framesPerBuffer,
|
||||
const PaStreamCallbackTimeInfo*, const PaStreamCallbackFlags) noexcept
|
||||
{
|
||||
mRing->write(inputBuffer, framesPerBuffer);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void PortCapture::open(const ALCchar *name)
|
||||
{
|
||||
if(!name)
|
||||
name = pa_device;
|
||||
else if(strcmp(name, pa_device) != 0)
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
|
||||
|
||||
ALuint samples{mDevice->BufferSize};
|
||||
samples = maxu(samples, 100 * mDevice->Frequency / 1000);
|
||||
ALuint frame_size{mDevice->frameSizeFromFmt()};
|
||||
|
||||
mRing = RingBuffer::Create(samples, frame_size, false);
|
||||
|
||||
auto devidopt = ConfigValueInt(nullptr, "port", "capture");
|
||||
if(devidopt && *devidopt >= 0) mParams.device = *devidopt;
|
||||
else mParams.device = Pa_GetDefaultOutputDevice();
|
||||
mParams.suggestedLatency = 0.0f;
|
||||
mParams.hostApiSpecificStreamInfo = nullptr;
|
||||
|
||||
switch(mDevice->FmtType)
|
||||
{
|
||||
case DevFmtByte:
|
||||
mParams.sampleFormat = paInt8;
|
||||
break;
|
||||
case DevFmtUByte:
|
||||
mParams.sampleFormat = paUInt8;
|
||||
break;
|
||||
case DevFmtShort:
|
||||
mParams.sampleFormat = paInt16;
|
||||
break;
|
||||
case DevFmtInt:
|
||||
mParams.sampleFormat = paInt32;
|
||||
break;
|
||||
case DevFmtFloat:
|
||||
mParams.sampleFormat = paFloat32;
|
||||
break;
|
||||
case DevFmtUInt:
|
||||
case DevFmtUShort:
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "%s samples not supported",
|
||||
DevFmtTypeString(mDevice->FmtType)};
|
||||
}
|
||||
mParams.channelCount = static_cast<int>(mDevice->channelsFromFmt());
|
||||
|
||||
PaError err{Pa_OpenStream(&mStream, &mParams, nullptr, mDevice->Frequency,
|
||||
paFramesPerBufferUnspecified, paNoFlag, &PortCapture::readCallbackC, this)};
|
||||
if(err != paNoError)
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "Failed to open stream: %s",
|
||||
Pa_GetErrorText(err)};
|
||||
|
||||
mDevice->DeviceName = name;
|
||||
}
|
||||
|
||||
|
||||
bool PortCapture::start()
|
||||
{
|
||||
PaError err{Pa_StartStream(mStream)};
|
||||
if(err != paNoError)
|
||||
{
|
||||
ERR("Error starting stream: %s\n", Pa_GetErrorText(err));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void PortCapture::stop()
|
||||
{
|
||||
PaError err{Pa_StopStream(mStream)};
|
||||
if(err != paNoError)
|
||||
ERR("Error stopping stream: %s\n", Pa_GetErrorText(err));
|
||||
}
|
||||
|
||||
|
||||
ALCuint PortCapture::availableSamples()
|
||||
{ return static_cast<ALCuint>(mRing->readSpace()); }
|
||||
|
||||
ALCenum PortCapture::captureSamples(al::byte *buffer, ALCuint samples)
|
||||
{
|
||||
mRing->read(buffer, samples);
|
||||
return ALC_NO_ERROR;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
bool PortBackendFactory::init()
|
||||
{
|
||||
PaError err;
|
||||
|
||||
#ifdef HAVE_DYNLOAD
|
||||
if(!pa_handle)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
# define PALIB "portaudio.dll"
|
||||
#elif defined(__APPLE__) && defined(__MACH__)
|
||||
# define PALIB "libportaudio.2.dylib"
|
||||
#elif defined(__OpenBSD__)
|
||||
# define PALIB "libportaudio.so"
|
||||
#else
|
||||
# define PALIB "libportaudio.so.2"
|
||||
#endif
|
||||
|
||||
pa_handle = LoadLib(PALIB);
|
||||
if(!pa_handle)
|
||||
return false;
|
||||
|
||||
#define LOAD_FUNC(f) do { \
|
||||
p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(pa_handle, #f)); \
|
||||
if(p##f == nullptr) \
|
||||
{ \
|
||||
CloseLib(pa_handle); \
|
||||
pa_handle = nullptr; \
|
||||
return false; \
|
||||
} \
|
||||
} while(0)
|
||||
LOAD_FUNC(Pa_Initialize);
|
||||
LOAD_FUNC(Pa_Terminate);
|
||||
LOAD_FUNC(Pa_GetErrorText);
|
||||
LOAD_FUNC(Pa_StartStream);
|
||||
LOAD_FUNC(Pa_StopStream);
|
||||
LOAD_FUNC(Pa_OpenStream);
|
||||
LOAD_FUNC(Pa_CloseStream);
|
||||
LOAD_FUNC(Pa_GetDefaultOutputDevice);
|
||||
LOAD_FUNC(Pa_GetDefaultInputDevice);
|
||||
LOAD_FUNC(Pa_GetStreamInfo);
|
||||
#undef LOAD_FUNC
|
||||
|
||||
if((err=Pa_Initialize()) != paNoError)
|
||||
{
|
||||
ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err));
|
||||
CloseLib(pa_handle);
|
||||
pa_handle = nullptr;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#else
|
||||
if((err=Pa_Initialize()) != paNoError)
|
||||
{
|
||||
ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err));
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PortBackendFactory::querySupport(BackendType type)
|
||||
{ return (type == BackendType::Playback || type == BackendType::Capture); }
|
||||
|
||||
void PortBackendFactory::probe(DevProbe type, std::string *outnames)
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
case DevProbe::Playback:
|
||||
case DevProbe::Capture:
|
||||
/* Includes null char. */
|
||||
outnames->append(pa_device, sizeof(pa_device));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
BackendPtr PortBackendFactory::createBackend(ALCdevice *device, BackendType type)
|
||||
{
|
||||
if(type == BackendType::Playback)
|
||||
return BackendPtr{new PortPlayback{device}};
|
||||
if(type == BackendType::Capture)
|
||||
return BackendPtr{new PortCapture{device}};
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
BackendFactory &PortBackendFactory::getFactory()
|
||||
{
|
||||
static PortBackendFactory factory{};
|
||||
return factory;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
#ifndef BACKENDS_PORTAUDIO_H
|
||||
#define BACKENDS_PORTAUDIO_H
|
||||
|
||||
#include "backends/base.h"
|
||||
|
||||
struct PortBackendFactory final : public BackendFactory {
|
||||
public:
|
||||
bool init() override;
|
||||
|
||||
bool querySupport(BackendType type) override;
|
||||
|
||||
void probe(DevProbe type, std::string *outnames) override;
|
||||
|
||||
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
|
||||
|
||||
static BackendFactory &getFactory();
|
||||
};
|
||||
|
||||
#endif /* BACKENDS_PORTAUDIO_H */
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,19 +0,0 @@
|
||||
#ifndef BACKENDS_PULSEAUDIO_H
|
||||
#define BACKENDS_PULSEAUDIO_H
|
||||
|
||||
#include "backends/base.h"
|
||||
|
||||
class PulseBackendFactory final : public BackendFactory {
|
||||
public:
|
||||
bool init() override;
|
||||
|
||||
bool querySupport(BackendType type) override;
|
||||
|
||||
void probe(DevProbe type, std::string *outnames) override;
|
||||
|
||||
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
|
||||
|
||||
static BackendFactory &getFactory();
|
||||
};
|
||||
|
||||
#endif /* BACKENDS_PULSEAUDIO_H */
|
||||
@@ -1,963 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 2011-2013 by authors.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "backends/qsa.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <sched.h>
|
||||
#include <errno.h>
|
||||
#include <memory.h>
|
||||
#include <poll.h>
|
||||
|
||||
#include <thread>
|
||||
#include <memory>
|
||||
#include <algorithm>
|
||||
|
||||
#include "alcmain.h"
|
||||
#include "alexcpt.h"
|
||||
#include "alu.h"
|
||||
#include "threads.h"
|
||||
|
||||
#include <sys/asoundlib.h>
|
||||
#include <sys/neutrino.h>
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
struct qsa_data {
|
||||
snd_pcm_t* pcmHandle{nullptr};
|
||||
int audio_fd{-1};
|
||||
|
||||
snd_pcm_channel_setup_t csetup{};
|
||||
snd_pcm_channel_params_t cparams{};
|
||||
|
||||
ALvoid* buffer{nullptr};
|
||||
ALsizei size{0};
|
||||
|
||||
std::atomic<ALenum> mKillNow{AL_TRUE};
|
||||
std::thread mThread;
|
||||
};
|
||||
|
||||
struct DevMap {
|
||||
ALCchar* name;
|
||||
int card;
|
||||
int dev;
|
||||
};
|
||||
|
||||
al::vector<DevMap> DeviceNameMap;
|
||||
al::vector<DevMap> CaptureNameMap;
|
||||
|
||||
constexpr ALCchar qsaDevice[] = "QSA Default";
|
||||
|
||||
constexpr struct {
|
||||
int32_t format;
|
||||
} formatlist[] = {
|
||||
{SND_PCM_SFMT_FLOAT_LE},
|
||||
{SND_PCM_SFMT_S32_LE},
|
||||
{SND_PCM_SFMT_U32_LE},
|
||||
{SND_PCM_SFMT_S16_LE},
|
||||
{SND_PCM_SFMT_U16_LE},
|
||||
{SND_PCM_SFMT_S8},
|
||||
{SND_PCM_SFMT_U8},
|
||||
{0},
|
||||
};
|
||||
|
||||
constexpr struct {
|
||||
int32_t rate;
|
||||
} ratelist[] = {
|
||||
{192000},
|
||||
{176400},
|
||||
{96000},
|
||||
{88200},
|
||||
{48000},
|
||||
{44100},
|
||||
{32000},
|
||||
{24000},
|
||||
{22050},
|
||||
{16000},
|
||||
{12000},
|
||||
{11025},
|
||||
{8000},
|
||||
{0},
|
||||
};
|
||||
|
||||
constexpr struct {
|
||||
int32_t channels;
|
||||
} channellist[] = {
|
||||
{8},
|
||||
{7},
|
||||
{6},
|
||||
{4},
|
||||
{2},
|
||||
{1},
|
||||
{0},
|
||||
};
|
||||
|
||||
void deviceList(int type, al::vector<DevMap> *devmap)
|
||||
{
|
||||
snd_ctl_t* handle;
|
||||
snd_pcm_info_t pcminfo;
|
||||
int max_cards, card, err, dev;
|
||||
DevMap entry;
|
||||
char name[1024];
|
||||
snd_ctl_hw_info info;
|
||||
|
||||
max_cards = snd_cards();
|
||||
if(max_cards < 0)
|
||||
return;
|
||||
|
||||
std::for_each(devmap->begin(), devmap->end(),
|
||||
[](const DevMap &entry) -> void
|
||||
{ free(entry.name); }
|
||||
);
|
||||
devmap->clear();
|
||||
|
||||
entry.name = strdup(qsaDevice);
|
||||
entry.card = 0;
|
||||
entry.dev = 0;
|
||||
devmap->push_back(entry);
|
||||
|
||||
for(card = 0;card < max_cards;card++)
|
||||
{
|
||||
if((err=snd_ctl_open(&handle, card)) < 0)
|
||||
continue;
|
||||
|
||||
if((err=snd_ctl_hw_info(handle, &info)) < 0)
|
||||
{
|
||||
snd_ctl_close(handle);
|
||||
continue;
|
||||
}
|
||||
|
||||
for(dev = 0;dev < (int)info.pcmdevs;dev++)
|
||||
{
|
||||
if((err=snd_ctl_pcm_info(handle, dev, &pcminfo)) < 0)
|
||||
continue;
|
||||
|
||||
if((type==SND_PCM_CHANNEL_PLAYBACK && (pcminfo.flags&SND_PCM_INFO_PLAYBACK)) ||
|
||||
(type==SND_PCM_CHANNEL_CAPTURE && (pcminfo.flags&SND_PCM_INFO_CAPTURE)))
|
||||
{
|
||||
snprintf(name, sizeof(name), "%s [%s] (hw:%d,%d)", info.name, pcminfo.name, card, dev);
|
||||
entry.name = strdup(name);
|
||||
entry.card = card;
|
||||
entry.dev = dev;
|
||||
|
||||
devmap->push_back(entry);
|
||||
TRACE("Got device \"%s\", card %d, dev %d\n", name, card, dev);
|
||||
}
|
||||
}
|
||||
snd_ctl_close(handle);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Wrappers to use an old-style backend with the new interface. */
|
||||
struct PlaybackWrapper final : public BackendBase {
|
||||
PlaybackWrapper(ALCdevice *device) noexcept : BackendBase{device} { }
|
||||
~PlaybackWrapper() override;
|
||||
|
||||
void open(const ALCchar *name) override;
|
||||
bool reset() override;
|
||||
bool start() override;
|
||||
void stop() override;
|
||||
|
||||
std::unique_ptr<qsa_data> mExtraData;
|
||||
|
||||
DEF_NEWDEL(PlaybackWrapper)
|
||||
};
|
||||
|
||||
|
||||
FORCE_ALIGN static int qsa_proc_playback(void *ptr)
|
||||
{
|
||||
PlaybackWrapper *self = static_cast<PlaybackWrapper*>(ptr);
|
||||
ALCdevice *device = self->mDevice;
|
||||
qsa_data *data = self->mExtraData.get();
|
||||
snd_pcm_channel_status_t status;
|
||||
sched_param param;
|
||||
char* write_ptr;
|
||||
ALint len;
|
||||
int sret;
|
||||
|
||||
SetRTPriority();
|
||||
althrd_setname(MIXER_THREAD_NAME);
|
||||
|
||||
/* Increase default 10 priority to 11 to avoid jerky sound */
|
||||
SchedGet(0, 0, ¶m);
|
||||
param.sched_priority=param.sched_curpriority+1;
|
||||
SchedSet(0, 0, SCHED_NOCHANGE, ¶m);
|
||||
|
||||
const ALint frame_size = device->frameSizeFromFmt();
|
||||
|
||||
std::unique_lock<PlaybackWrapper> dlock{*self};
|
||||
while(!data->mKillNow.load(std::memory_order_acquire))
|
||||
{
|
||||
pollfd pollitem{};
|
||||
pollitem.fd = data->audio_fd;
|
||||
pollitem.events = POLLOUT;
|
||||
|
||||
/* Select also works like time slice to OS */
|
||||
dlock.unlock();
|
||||
sret = poll(&pollitem, 1, 2000);
|
||||
dlock.lock();
|
||||
if(sret == -1)
|
||||
{
|
||||
if(errno == EINTR || errno == EAGAIN)
|
||||
continue;
|
||||
ERR("poll error: %s\n", strerror(errno));
|
||||
aluHandleDisconnect(device, "Failed waiting for playback buffer: %s", strerror(errno));
|
||||
break;
|
||||
}
|
||||
if(sret == 0)
|
||||
{
|
||||
ERR("poll timeout\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
len = data->size;
|
||||
write_ptr = static_cast<char*>(data->buffer);
|
||||
aluMixData(device, write_ptr, len/frame_size);
|
||||
while(len>0 && !data->mKillNow.load(std::memory_order_acquire))
|
||||
{
|
||||
int wrote = snd_pcm_plugin_write(data->pcmHandle, write_ptr, len);
|
||||
if(wrote <= 0)
|
||||
{
|
||||
if(errno==EAGAIN || errno==EWOULDBLOCK)
|
||||
continue;
|
||||
|
||||
memset(&status, 0, sizeof(status));
|
||||
status.channel = SND_PCM_CHANNEL_PLAYBACK;
|
||||
|
||||
snd_pcm_plugin_status(data->pcmHandle, &status);
|
||||
|
||||
/* we need to reinitialize the sound channel if we've underrun the buffer */
|
||||
if(status.status == SND_PCM_STATUS_UNDERRUN ||
|
||||
status.status == SND_PCM_STATUS_READY)
|
||||
{
|
||||
if(snd_pcm_plugin_prepare(data->pcmHandle, SND_PCM_CHANNEL_PLAYBACK) < 0)
|
||||
{
|
||||
aluHandleDisconnect(device, "Playback recovery failed");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
write_ptr += wrote;
|
||||
len -= wrote;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/************/
|
||||
/* Playback */
|
||||
/************/
|
||||
|
||||
static ALCenum qsa_open_playback(PlaybackWrapper *self, const ALCchar* deviceName)
|
||||
{
|
||||
ALCdevice *device = self->mDevice;
|
||||
int card, dev;
|
||||
int status;
|
||||
|
||||
std::unique_ptr<qsa_data> data{new qsa_data{}};
|
||||
data->mKillNow.store(AL_TRUE, std::memory_order_relaxed);
|
||||
|
||||
if(!deviceName)
|
||||
deviceName = qsaDevice;
|
||||
|
||||
if(strcmp(deviceName, qsaDevice) == 0)
|
||||
status = snd_pcm_open_preferred(&data->pcmHandle, &card, &dev, SND_PCM_OPEN_PLAYBACK);
|
||||
else
|
||||
{
|
||||
if(DeviceNameMap.empty())
|
||||
deviceList(SND_PCM_CHANNEL_PLAYBACK, &DeviceNameMap);
|
||||
|
||||
auto iter = std::find_if(DeviceNameMap.begin(), DeviceNameMap.end(),
|
||||
[deviceName](const DevMap &entry) -> bool
|
||||
{ return entry.name && strcmp(deviceName, entry.name) == 0; }
|
||||
);
|
||||
if(iter == DeviceNameMap.cend())
|
||||
return ALC_INVALID_DEVICE;
|
||||
|
||||
status = snd_pcm_open(&data->pcmHandle, iter->card, iter->dev, SND_PCM_OPEN_PLAYBACK);
|
||||
}
|
||||
|
||||
if(status < 0)
|
||||
return ALC_INVALID_DEVICE;
|
||||
|
||||
data->audio_fd = snd_pcm_file_descriptor(data->pcmHandle, SND_PCM_CHANNEL_PLAYBACK);
|
||||
if(data->audio_fd < 0)
|
||||
{
|
||||
snd_pcm_close(data->pcmHandle);
|
||||
return ALC_INVALID_DEVICE;
|
||||
}
|
||||
|
||||
device->DeviceName = deviceName;
|
||||
self->mExtraData = std::move(data);
|
||||
|
||||
return ALC_NO_ERROR;
|
||||
}
|
||||
|
||||
static void qsa_close_playback(PlaybackWrapper *self)
|
||||
{
|
||||
qsa_data *data = self->mExtraData.get();
|
||||
|
||||
if (data->buffer!=NULL)
|
||||
{
|
||||
free(data->buffer);
|
||||
data->buffer=NULL;
|
||||
}
|
||||
|
||||
snd_pcm_close(data->pcmHandle);
|
||||
|
||||
self->mExtraData = nullptr;
|
||||
}
|
||||
|
||||
static ALCboolean qsa_reset_playback(PlaybackWrapper *self)
|
||||
{
|
||||
ALCdevice *device = self->mDevice;
|
||||
qsa_data *data = self->mExtraData.get();
|
||||
int32_t format=-1;
|
||||
|
||||
switch(device->FmtType)
|
||||
{
|
||||
case DevFmtByte:
|
||||
format=SND_PCM_SFMT_S8;
|
||||
break;
|
||||
case DevFmtUByte:
|
||||
format=SND_PCM_SFMT_U8;
|
||||
break;
|
||||
case DevFmtShort:
|
||||
format=SND_PCM_SFMT_S16_LE;
|
||||
break;
|
||||
case DevFmtUShort:
|
||||
format=SND_PCM_SFMT_U16_LE;
|
||||
break;
|
||||
case DevFmtInt:
|
||||
format=SND_PCM_SFMT_S32_LE;
|
||||
break;
|
||||
case DevFmtUInt:
|
||||
format=SND_PCM_SFMT_U32_LE;
|
||||
break;
|
||||
case DevFmtFloat:
|
||||
format=SND_PCM_SFMT_FLOAT_LE;
|
||||
break;
|
||||
}
|
||||
|
||||
/* we actually don't want to block on writes */
|
||||
snd_pcm_nonblock_mode(data->pcmHandle, 1);
|
||||
/* Disable mmap to control data transfer to the audio device */
|
||||
snd_pcm_plugin_set_disable(data->pcmHandle, PLUGIN_DISABLE_MMAP);
|
||||
snd_pcm_plugin_set_disable(data->pcmHandle, PLUGIN_DISABLE_BUFFER_PARTIAL_BLOCKS);
|
||||
|
||||
// configure a sound channel
|
||||
memset(&data->cparams, 0, sizeof(data->cparams));
|
||||
data->cparams.channel=SND_PCM_CHANNEL_PLAYBACK;
|
||||
data->cparams.mode=SND_PCM_MODE_BLOCK;
|
||||
data->cparams.start_mode=SND_PCM_START_FULL;
|
||||
data->cparams.stop_mode=SND_PCM_STOP_STOP;
|
||||
|
||||
data->cparams.buf.block.frag_size=device->UpdateSize * device->frameSizeFromFmt();
|
||||
data->cparams.buf.block.frags_max=device->BufferSize / device->UpdateSize;
|
||||
data->cparams.buf.block.frags_min=data->cparams.buf.block.frags_max;
|
||||
|
||||
data->cparams.format.interleave=1;
|
||||
data->cparams.format.rate=device->Frequency;
|
||||
data->cparams.format.voices=device->channelsFromFmt();
|
||||
data->cparams.format.format=format;
|
||||
|
||||
if ((snd_pcm_plugin_params(data->pcmHandle, &data->cparams))<0)
|
||||
{
|
||||
int original_rate=data->cparams.format.rate;
|
||||
int original_voices=data->cparams.format.voices;
|
||||
int original_format=data->cparams.format.format;
|
||||
int it;
|
||||
int jt;
|
||||
|
||||
for (it=0; it<1; it++)
|
||||
{
|
||||
/* Check for second pass */
|
||||
if (it==1)
|
||||
{
|
||||
original_rate=ratelist[0].rate;
|
||||
original_voices=channellist[0].channels;
|
||||
original_format=formatlist[0].format;
|
||||
}
|
||||
|
||||
do {
|
||||
/* At first downgrade sample format */
|
||||
jt=0;
|
||||
do {
|
||||
if (formatlist[jt].format==data->cparams.format.format)
|
||||
{
|
||||
data->cparams.format.format=formatlist[jt+1].format;
|
||||
break;
|
||||
}
|
||||
if (formatlist[jt].format==0)
|
||||
{
|
||||
data->cparams.format.format=0;
|
||||
break;
|
||||
}
|
||||
jt++;
|
||||
} while(1);
|
||||
|
||||
if (data->cparams.format.format==0)
|
||||
{
|
||||
data->cparams.format.format=original_format;
|
||||
|
||||
/* At secod downgrade sample rate */
|
||||
jt=0;
|
||||
do {
|
||||
if (ratelist[jt].rate==data->cparams.format.rate)
|
||||
{
|
||||
data->cparams.format.rate=ratelist[jt+1].rate;
|
||||
break;
|
||||
}
|
||||
if (ratelist[jt].rate==0)
|
||||
{
|
||||
data->cparams.format.rate=0;
|
||||
break;
|
||||
}
|
||||
jt++;
|
||||
} while(1);
|
||||
|
||||
if (data->cparams.format.rate==0)
|
||||
{
|
||||
data->cparams.format.rate=original_rate;
|
||||
data->cparams.format.format=original_format;
|
||||
|
||||
/* At third downgrade channels number */
|
||||
jt=0;
|
||||
do {
|
||||
if(channellist[jt].channels==data->cparams.format.voices)
|
||||
{
|
||||
data->cparams.format.voices=channellist[jt+1].channels;
|
||||
break;
|
||||
}
|
||||
if (channellist[jt].channels==0)
|
||||
{
|
||||
data->cparams.format.voices=0;
|
||||
break;
|
||||
}
|
||||
jt++;
|
||||
} while(1);
|
||||
}
|
||||
|
||||
if (data->cparams.format.voices==0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
data->cparams.buf.block.frag_size=device->UpdateSize*
|
||||
data->cparams.format.voices*
|
||||
snd_pcm_format_width(data->cparams.format.format)/8;
|
||||
data->cparams.buf.block.frags_max=device->NumUpdates;
|
||||
data->cparams.buf.block.frags_min=device->NumUpdates;
|
||||
if ((snd_pcm_plugin_params(data->pcmHandle, &data->cparams))<0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
} while(1);
|
||||
|
||||
if (data->cparams.format.voices!=0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (data->cparams.format.voices==0)
|
||||
{
|
||||
return ALC_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
if ((snd_pcm_plugin_prepare(data->pcmHandle, SND_PCM_CHANNEL_PLAYBACK))<0)
|
||||
{
|
||||
return ALC_FALSE;
|
||||
}
|
||||
|
||||
memset(&data->csetup, 0, sizeof(data->csetup));
|
||||
data->csetup.channel=SND_PCM_CHANNEL_PLAYBACK;
|
||||
if (snd_pcm_plugin_setup(data->pcmHandle, &data->csetup)<0)
|
||||
{
|
||||
return ALC_FALSE;
|
||||
}
|
||||
|
||||
/* now fill back to the our AL device */
|
||||
device->Frequency=data->cparams.format.rate;
|
||||
|
||||
switch (data->cparams.format.voices)
|
||||
{
|
||||
case 1:
|
||||
device->FmtChans=DevFmtMono;
|
||||
break;
|
||||
case 2:
|
||||
device->FmtChans=DevFmtStereo;
|
||||
break;
|
||||
case 4:
|
||||
device->FmtChans=DevFmtQuad;
|
||||
break;
|
||||
case 6:
|
||||
device->FmtChans=DevFmtX51;
|
||||
break;
|
||||
case 7:
|
||||
device->FmtChans=DevFmtX61;
|
||||
break;
|
||||
case 8:
|
||||
device->FmtChans=DevFmtX71;
|
||||
break;
|
||||
default:
|
||||
device->FmtChans=DevFmtMono;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (data->cparams.format.format)
|
||||
{
|
||||
case SND_PCM_SFMT_S8:
|
||||
device->FmtType=DevFmtByte;
|
||||
break;
|
||||
case SND_PCM_SFMT_U8:
|
||||
device->FmtType=DevFmtUByte;
|
||||
break;
|
||||
case SND_PCM_SFMT_S16_LE:
|
||||
device->FmtType=DevFmtShort;
|
||||
break;
|
||||
case SND_PCM_SFMT_U16_LE:
|
||||
device->FmtType=DevFmtUShort;
|
||||
break;
|
||||
case SND_PCM_SFMT_S32_LE:
|
||||
device->FmtType=DevFmtInt;
|
||||
break;
|
||||
case SND_PCM_SFMT_U32_LE:
|
||||
device->FmtType=DevFmtUInt;
|
||||
break;
|
||||
case SND_PCM_SFMT_FLOAT_LE:
|
||||
device->FmtType=DevFmtFloat;
|
||||
break;
|
||||
default:
|
||||
device->FmtType=DevFmtShort;
|
||||
break;
|
||||
}
|
||||
|
||||
SetDefaultChannelOrder(device);
|
||||
|
||||
device->UpdateSize=data->csetup.buf.block.frag_size / device->frameSizeFromFmt();
|
||||
device->NumUpdates=data->csetup.buf.block.frags;
|
||||
|
||||
data->size=data->csetup.buf.block.frag_size;
|
||||
data->buffer=malloc(data->size);
|
||||
if (!data->buffer)
|
||||
{
|
||||
return ALC_FALSE;
|
||||
}
|
||||
|
||||
return ALC_TRUE;
|
||||
}
|
||||
|
||||
static ALCboolean qsa_start_playback(PlaybackWrapper *self)
|
||||
{
|
||||
qsa_data *data = self->mExtraData.get();
|
||||
|
||||
try {
|
||||
data->mKillNow.store(AL_FALSE, std::memory_order_release);
|
||||
data->mThread = std::thread(qsa_proc_playback, self);
|
||||
return ALC_TRUE;
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
ERR("Could not create playback thread: %s\n", e.what());
|
||||
}
|
||||
catch(...) {
|
||||
}
|
||||
return ALC_FALSE;
|
||||
}
|
||||
|
||||
static void qsa_stop_playback(PlaybackWrapper *self)
|
||||
{
|
||||
qsa_data *data = self->mExtraData.get();
|
||||
|
||||
if(data->mKillNow.exchange(AL_TRUE, std::memory_order_acq_rel) || !data->mThread.joinable())
|
||||
return;
|
||||
data->mThread.join();
|
||||
}
|
||||
|
||||
|
||||
PlaybackWrapper::~PlaybackWrapper()
|
||||
{
|
||||
if(mExtraData)
|
||||
qsa_close_playback(this);
|
||||
}
|
||||
|
||||
void PlaybackWrapper::open(const ALCchar *name)
|
||||
{
|
||||
if(auto err = qsa_open_playback(this, name))
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "%d", err};
|
||||
}
|
||||
|
||||
bool PlaybackWrapper::reset()
|
||||
{
|
||||
if(!qsa_reset_playback(this))
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, ""};
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PlaybackWrapper::start()
|
||||
{ return qsa_start_playback(this); }
|
||||
|
||||
void PlaybackWrapper::stop()
|
||||
{ qsa_stop_playback(this); }
|
||||
|
||||
|
||||
/***********/
|
||||
/* Capture */
|
||||
/***********/
|
||||
|
||||
struct CaptureWrapper final : public BackendBase {
|
||||
CaptureWrapper(ALCdevice *device) noexcept : BackendBase{device} { }
|
||||
~CaptureWrapper() override;
|
||||
|
||||
void open(const ALCchar *name) override;
|
||||
bool start() override;
|
||||
void stop() override;
|
||||
ALCenum captureSamples(al::byte *buffer, ALCuint samples) override;
|
||||
ALCuint availableSamples() override;
|
||||
|
||||
std::unique_ptr<qsa_data> mExtraData;
|
||||
|
||||
DEF_NEWDEL(CaptureWrapper)
|
||||
};
|
||||
|
||||
static ALCenum qsa_open_capture(CaptureWrapper *self, const ALCchar *deviceName)
|
||||
{
|
||||
ALCdevice *device = self->mDevice;
|
||||
int card, dev;
|
||||
int format=-1;
|
||||
int status;
|
||||
|
||||
std::unique_ptr<qsa_data> data{new qsa_data{}};
|
||||
|
||||
if(!deviceName)
|
||||
deviceName = qsaDevice;
|
||||
|
||||
if(strcmp(deviceName, qsaDevice) == 0)
|
||||
status = snd_pcm_open_preferred(&data->pcmHandle, &card, &dev, SND_PCM_OPEN_CAPTURE);
|
||||
else
|
||||
{
|
||||
if(CaptureNameMap.empty())
|
||||
deviceList(SND_PCM_CHANNEL_CAPTURE, &CaptureNameMap);
|
||||
|
||||
auto iter = std::find_if(CaptureNameMap.cbegin(), CaptureNameMap.cend(),
|
||||
[deviceName](const DevMap &entry) -> bool
|
||||
{ return entry.name && strcmp(deviceName, entry.name) == 0; }
|
||||
);
|
||||
if(iter == CaptureNameMap.cend())
|
||||
return ALC_INVALID_DEVICE;
|
||||
|
||||
status = snd_pcm_open(&data->pcmHandle, iter->card, iter->dev, SND_PCM_OPEN_CAPTURE);
|
||||
}
|
||||
|
||||
if(status < 0)
|
||||
return ALC_INVALID_DEVICE;
|
||||
|
||||
data->audio_fd = snd_pcm_file_descriptor(data->pcmHandle, SND_PCM_CHANNEL_CAPTURE);
|
||||
if(data->audio_fd < 0)
|
||||
{
|
||||
snd_pcm_close(data->pcmHandle);
|
||||
return ALC_INVALID_DEVICE;
|
||||
}
|
||||
|
||||
device->DeviceName = deviceName;
|
||||
|
||||
switch (device->FmtType)
|
||||
{
|
||||
case DevFmtByte:
|
||||
format=SND_PCM_SFMT_S8;
|
||||
break;
|
||||
case DevFmtUByte:
|
||||
format=SND_PCM_SFMT_U8;
|
||||
break;
|
||||
case DevFmtShort:
|
||||
format=SND_PCM_SFMT_S16_LE;
|
||||
break;
|
||||
case DevFmtUShort:
|
||||
format=SND_PCM_SFMT_U16_LE;
|
||||
break;
|
||||
case DevFmtInt:
|
||||
format=SND_PCM_SFMT_S32_LE;
|
||||
break;
|
||||
case DevFmtUInt:
|
||||
format=SND_PCM_SFMT_U32_LE;
|
||||
break;
|
||||
case DevFmtFloat:
|
||||
format=SND_PCM_SFMT_FLOAT_LE;
|
||||
break;
|
||||
}
|
||||
|
||||
/* we actually don't want to block on reads */
|
||||
snd_pcm_nonblock_mode(data->pcmHandle, 1);
|
||||
/* Disable mmap to control data transfer to the audio device */
|
||||
snd_pcm_plugin_set_disable(data->pcmHandle, PLUGIN_DISABLE_MMAP);
|
||||
|
||||
/* configure a sound channel */
|
||||
memset(&data->cparams, 0, sizeof(data->cparams));
|
||||
data->cparams.mode=SND_PCM_MODE_BLOCK;
|
||||
data->cparams.channel=SND_PCM_CHANNEL_CAPTURE;
|
||||
data->cparams.start_mode=SND_PCM_START_GO;
|
||||
data->cparams.stop_mode=SND_PCM_STOP_STOP;
|
||||
|
||||
data->cparams.buf.block.frag_size=device->UpdateSize * device->frameSizeFromFmt();
|
||||
data->cparams.buf.block.frags_max=device->NumUpdates;
|
||||
data->cparams.buf.block.frags_min=device->NumUpdates;
|
||||
|
||||
data->cparams.format.interleave=1;
|
||||
data->cparams.format.rate=device->Frequency;
|
||||
data->cparams.format.voices=device->channelsFromFmt();
|
||||
data->cparams.format.format=format;
|
||||
|
||||
if(snd_pcm_plugin_params(data->pcmHandle, &data->cparams) < 0)
|
||||
{
|
||||
snd_pcm_close(data->pcmHandle);
|
||||
return ALC_INVALID_VALUE;
|
||||
}
|
||||
|
||||
self->mExtraData = std::move(data);
|
||||
|
||||
return ALC_NO_ERROR;
|
||||
}
|
||||
|
||||
static void qsa_close_capture(CaptureWrapper *self)
|
||||
{
|
||||
qsa_data *data = self->mExtraData.get();
|
||||
|
||||
if (data->pcmHandle!=nullptr)
|
||||
snd_pcm_close(data->pcmHandle);
|
||||
data->pcmHandle = nullptr;
|
||||
|
||||
self->mExtraData = nullptr;
|
||||
}
|
||||
|
||||
static void qsa_start_capture(CaptureWrapper *self)
|
||||
{
|
||||
qsa_data *data = self->mExtraData.get();
|
||||
int rstatus;
|
||||
|
||||
if ((rstatus=snd_pcm_plugin_prepare(data->pcmHandle, SND_PCM_CHANNEL_CAPTURE))<0)
|
||||
{
|
||||
ERR("capture prepare failed: %s\n", snd_strerror(rstatus));
|
||||
return;
|
||||
}
|
||||
|
||||
memset(&data->csetup, 0, sizeof(data->csetup));
|
||||
data->csetup.channel=SND_PCM_CHANNEL_CAPTURE;
|
||||
if ((rstatus=snd_pcm_plugin_setup(data->pcmHandle, &data->csetup))<0)
|
||||
{
|
||||
ERR("capture setup failed: %s\n", snd_strerror(rstatus));
|
||||
return;
|
||||
}
|
||||
|
||||
snd_pcm_capture_go(data->pcmHandle);
|
||||
}
|
||||
|
||||
static void qsa_stop_capture(CaptureWrapper *self)
|
||||
{
|
||||
qsa_data *data = self->mExtraData.get();
|
||||
snd_pcm_capture_flush(data->pcmHandle);
|
||||
}
|
||||
|
||||
static ALCuint qsa_available_samples(CaptureWrapper *self)
|
||||
{
|
||||
ALCdevice *device = self->mDevice;
|
||||
qsa_data *data = self->mExtraData.get();
|
||||
snd_pcm_channel_status_t status;
|
||||
ALint frame_size = device->frameSizeFromFmt();
|
||||
ALint free_size;
|
||||
int rstatus;
|
||||
|
||||
memset(&status, 0, sizeof (status));
|
||||
status.channel=SND_PCM_CHANNEL_CAPTURE;
|
||||
snd_pcm_plugin_status(data->pcmHandle, &status);
|
||||
if ((status.status==SND_PCM_STATUS_OVERRUN) ||
|
||||
(status.status==SND_PCM_STATUS_READY))
|
||||
{
|
||||
if ((rstatus=snd_pcm_plugin_prepare(data->pcmHandle, SND_PCM_CHANNEL_CAPTURE))<0)
|
||||
{
|
||||
ERR("capture prepare failed: %s\n", snd_strerror(rstatus));
|
||||
aluHandleDisconnect(device, "Failed capture recovery: %s", snd_strerror(rstatus));
|
||||
return 0;
|
||||
}
|
||||
|
||||
snd_pcm_capture_go(data->pcmHandle);
|
||||
return 0;
|
||||
}
|
||||
|
||||
free_size=data->csetup.buf.block.frag_size*data->csetup.buf.block.frags;
|
||||
free_size-=status.free;
|
||||
|
||||
return free_size/frame_size;
|
||||
}
|
||||
|
||||
static ALCenum qsa_capture_samples(CaptureWrapper *self, ALCvoid *buffer, ALCuint samples)
|
||||
{
|
||||
ALCdevice *device = self->mDevice;
|
||||
qsa_data *data = self->mExtraData.get();
|
||||
char* read_ptr;
|
||||
snd_pcm_channel_status_t status;
|
||||
int selectret;
|
||||
int bytes_read;
|
||||
ALint frame_size=device->frameSizeFromFmt();
|
||||
ALint len=samples*frame_size;
|
||||
int rstatus;
|
||||
|
||||
read_ptr = static_cast<char*>(buffer);
|
||||
|
||||
while (len>0)
|
||||
{
|
||||
pollfd pollitem{};
|
||||
pollitem.fd = data->audio_fd;
|
||||
pollitem.events = POLLOUT;
|
||||
|
||||
/* Select also works like time slice to OS */
|
||||
bytes_read=0;
|
||||
selectret = poll(&pollitem, 1, 2000);
|
||||
switch (selectret)
|
||||
{
|
||||
case -1:
|
||||
aluHandleDisconnect(device, "Failed to check capture samples");
|
||||
return ALC_INVALID_DEVICE;
|
||||
case 0:
|
||||
break;
|
||||
default:
|
||||
bytes_read=snd_pcm_plugin_read(data->pcmHandle, read_ptr, len);
|
||||
break;
|
||||
}
|
||||
|
||||
if (bytes_read<=0)
|
||||
{
|
||||
if ((errno==EAGAIN) || (errno==EWOULDBLOCK))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
memset(&status, 0, sizeof (status));
|
||||
status.channel=SND_PCM_CHANNEL_CAPTURE;
|
||||
snd_pcm_plugin_status(data->pcmHandle, &status);
|
||||
|
||||
/* we need to reinitialize the sound channel if we've overrun the buffer */
|
||||
if ((status.status==SND_PCM_STATUS_OVERRUN) ||
|
||||
(status.status==SND_PCM_STATUS_READY))
|
||||
{
|
||||
if ((rstatus=snd_pcm_plugin_prepare(data->pcmHandle, SND_PCM_CHANNEL_CAPTURE))<0)
|
||||
{
|
||||
ERR("capture prepare failed: %s\n", snd_strerror(rstatus));
|
||||
aluHandleDisconnect(device, "Failed capture recovery: %s",
|
||||
snd_strerror(rstatus));
|
||||
return ALC_INVALID_DEVICE;
|
||||
}
|
||||
snd_pcm_capture_go(data->pcmHandle);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
read_ptr+=bytes_read;
|
||||
len-=bytes_read;
|
||||
}
|
||||
}
|
||||
|
||||
return ALC_NO_ERROR;
|
||||
}
|
||||
|
||||
|
||||
CaptureWrapper::~CaptureWrapper()
|
||||
{
|
||||
if(mExtraData)
|
||||
qsa_close_capture(this);
|
||||
}
|
||||
|
||||
void CaptureWrapper::open(const ALCchar *name)
|
||||
{
|
||||
if(auto err = qsa_open_capture(this, name))
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "%d", err};
|
||||
}
|
||||
|
||||
bool CaptureWrapper::start()
|
||||
{ qsa_start_capture(this); return true; }
|
||||
|
||||
void CaptureWrapper::stop()
|
||||
{ qsa_stop_capture(this); }
|
||||
|
||||
ALCenum CaptureWrapper::captureSamples(al::byte *buffer, ALCuint samples)
|
||||
{ return qsa_capture_samples(this, buffer, samples); }
|
||||
|
||||
ALCuint CaptureWrapper::availableSamples()
|
||||
{ return qsa_available_samples(this); }
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
bool QSABackendFactory::init()
|
||||
{ return true; }
|
||||
|
||||
bool QSABackendFactory::querySupport(BackendType type)
|
||||
{ return (type == BackendType::Playback || type == BackendType::Capture); }
|
||||
|
||||
void QSABackendFactory::probe(DevProbe type, std::string *outnames)
|
||||
{
|
||||
auto add_device = [outnames](const DevMap &entry) -> void
|
||||
{
|
||||
const char *n = entry.name;
|
||||
if(n && n[0])
|
||||
outnames->append(n, strlen(n)+1);
|
||||
};
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case DevProbe::Playback:
|
||||
deviceList(SND_PCM_CHANNEL_PLAYBACK, &DeviceNameMap);
|
||||
std::for_each(DeviceNameMap.cbegin(), DeviceNameMap.cend(), add_device);
|
||||
break;
|
||||
case DevProbe::Capture:
|
||||
deviceList(SND_PCM_CHANNEL_CAPTURE, &CaptureNameMap);
|
||||
std::for_each(CaptureNameMap.cbegin(), CaptureNameMap.cend(), add_device);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
BackendPtr QSABackendFactory::createBackend(ALCdevice *device, BackendType type)
|
||||
{
|
||||
if(type == BackendType::Playback)
|
||||
return BackendPtr{new PlaybackWrapper{device}};
|
||||
if(type == BackendType::Capture)
|
||||
return BackendPtr{new CaptureWrapper{device}};
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
BackendFactory &QSABackendFactory::getFactory()
|
||||
{
|
||||
static QSABackendFactory factory{};
|
||||
return factory;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
#ifndef BACKENDS_QSA_H
|
||||
#define BACKENDS_QSA_H
|
||||
|
||||
#include "backends/base.h"
|
||||
|
||||
struct QSABackendFactory final : public BackendFactory {
|
||||
public:
|
||||
bool init() override;
|
||||
|
||||
bool querySupport(BackendType type) override;
|
||||
|
||||
void probe(DevProbe type, std::string *outnames) override;
|
||||
|
||||
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
|
||||
|
||||
static BackendFactory &getFactory();
|
||||
};
|
||||
|
||||
#endif /* BACKENDS_QSA_H */
|
||||
@@ -1,227 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 2018 by authors.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "backends/sdl2.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#include "AL/al.h"
|
||||
|
||||
#include "alcmain.h"
|
||||
#include "alexcpt.h"
|
||||
#include "almalloc.h"
|
||||
#include "alu.h"
|
||||
#include "logging.h"
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
#ifdef _WIN32
|
||||
#define DEVNAME_PREFIX "OpenAL Soft on "
|
||||
#else
|
||||
#define DEVNAME_PREFIX ""
|
||||
#endif
|
||||
|
||||
constexpr ALCchar defaultDeviceName[] = DEVNAME_PREFIX "Default Device";
|
||||
|
||||
struct Sdl2Backend final : public BackendBase {
|
||||
Sdl2Backend(ALCdevice *device) noexcept : BackendBase{device} { }
|
||||
~Sdl2Backend() override;
|
||||
|
||||
void audioCallback(Uint8 *stream, int len) noexcept;
|
||||
static void audioCallbackC(void *ptr, Uint8 *stream, int len) noexcept
|
||||
{ static_cast<Sdl2Backend*>(ptr)->audioCallback(stream, len); }
|
||||
|
||||
void open(const ALCchar *name) override;
|
||||
bool reset() override;
|
||||
bool start() override;
|
||||
void stop() override;
|
||||
void lock() override;
|
||||
void unlock() override;
|
||||
|
||||
SDL_AudioDeviceID mDeviceID{0u};
|
||||
ALuint mFrameSize{0};
|
||||
|
||||
ALuint mFrequency{0u};
|
||||
DevFmtChannels mFmtChans{};
|
||||
DevFmtType mFmtType{};
|
||||
ALuint mUpdateSize{0u};
|
||||
|
||||
DEF_NEWDEL(Sdl2Backend)
|
||||
};
|
||||
|
||||
Sdl2Backend::~Sdl2Backend()
|
||||
{
|
||||
if(mDeviceID)
|
||||
SDL_CloseAudioDevice(mDeviceID);
|
||||
mDeviceID = 0;
|
||||
}
|
||||
|
||||
void Sdl2Backend::audioCallback(Uint8 *stream, int len) noexcept
|
||||
{
|
||||
const auto ulen = static_cast<unsigned int>(len);
|
||||
assert((ulen % mFrameSize) == 0);
|
||||
aluMixData(mDevice, stream, ulen / mFrameSize, mDevice->channelsFromFmt());
|
||||
}
|
||||
|
||||
void Sdl2Backend::open(const ALCchar *name)
|
||||
{
|
||||
SDL_AudioSpec want{}, have{};
|
||||
|
||||
want.freq = static_cast<int>(mDevice->Frequency);
|
||||
switch(mDevice->FmtType)
|
||||
{
|
||||
case DevFmtUByte: want.format = AUDIO_U8; break;
|
||||
case DevFmtByte: want.format = AUDIO_S8; break;
|
||||
case DevFmtUShort: want.format = AUDIO_U16SYS; break;
|
||||
case DevFmtShort: want.format = AUDIO_S16SYS; break;
|
||||
case DevFmtUInt: /* fall-through */
|
||||
case DevFmtInt: want.format = AUDIO_S32SYS; break;
|
||||
case DevFmtFloat: want.format = AUDIO_F32; break;
|
||||
}
|
||||
want.channels = (mDevice->FmtChans == DevFmtMono) ? 1 : 2;
|
||||
want.samples = static_cast<Uint16>(mDevice->UpdateSize);
|
||||
want.callback = &Sdl2Backend::audioCallbackC;
|
||||
want.userdata = this;
|
||||
|
||||
/* Passing nullptr to SDL_OpenAudioDevice opens a default, which isn't
|
||||
* necessarily the first in the list.
|
||||
*/
|
||||
if(!name || strcmp(name, defaultDeviceName) == 0)
|
||||
mDeviceID = SDL_OpenAudioDevice(nullptr, SDL_FALSE, &want, &have,
|
||||
SDL_AUDIO_ALLOW_ANY_CHANGE);
|
||||
else
|
||||
{
|
||||
const size_t prefix_len = strlen(DEVNAME_PREFIX);
|
||||
if(strncmp(name, DEVNAME_PREFIX, prefix_len) == 0)
|
||||
mDeviceID = SDL_OpenAudioDevice(name+prefix_len, SDL_FALSE, &want, &have,
|
||||
SDL_AUDIO_ALLOW_ANY_CHANGE);
|
||||
else
|
||||
mDeviceID = SDL_OpenAudioDevice(name, SDL_FALSE, &want, &have,
|
||||
SDL_AUDIO_ALLOW_ANY_CHANGE);
|
||||
}
|
||||
if(mDeviceID == 0)
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "%s", SDL_GetError()};
|
||||
|
||||
mDevice->Frequency = static_cast<ALuint>(have.freq);
|
||||
|
||||
if(have.channels == 1)
|
||||
mDevice->FmtChans = DevFmtMono;
|
||||
else if(have.channels == 2)
|
||||
mDevice->FmtChans = DevFmtStereo;
|
||||
else
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "Unhandled SDL channel count: %d",
|
||||
int{have.channels}};
|
||||
|
||||
switch(have.format)
|
||||
{
|
||||
case AUDIO_U8: mDevice->FmtType = DevFmtUByte; break;
|
||||
case AUDIO_S8: mDevice->FmtType = DevFmtByte; break;
|
||||
case AUDIO_U16SYS: mDevice->FmtType = DevFmtUShort; break;
|
||||
case AUDIO_S16SYS: mDevice->FmtType = DevFmtShort; break;
|
||||
case AUDIO_S32SYS: mDevice->FmtType = DevFmtInt; break;
|
||||
case AUDIO_F32SYS: mDevice->FmtType = DevFmtFloat; break;
|
||||
default:
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "Unhandled SDL format: 0x%04x",
|
||||
have.format};
|
||||
}
|
||||
mDevice->UpdateSize = have.samples;
|
||||
mDevice->BufferSize = have.samples * 2; /* SDL always (tries to) use two periods. */
|
||||
|
||||
mFrameSize = mDevice->frameSizeFromFmt();
|
||||
mFrequency = mDevice->Frequency;
|
||||
mFmtChans = mDevice->FmtChans;
|
||||
mFmtType = mDevice->FmtType;
|
||||
mUpdateSize = mDevice->UpdateSize;
|
||||
|
||||
mDevice->DeviceName = name ? name : defaultDeviceName;
|
||||
}
|
||||
|
||||
bool Sdl2Backend::reset()
|
||||
{
|
||||
mDevice->Frequency = mFrequency;
|
||||
mDevice->FmtChans = mFmtChans;
|
||||
mDevice->FmtType = mFmtType;
|
||||
mDevice->UpdateSize = mUpdateSize;
|
||||
mDevice->BufferSize = mUpdateSize * 2;
|
||||
SetDefaultWFXChannelOrder(mDevice);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Sdl2Backend::start()
|
||||
{
|
||||
SDL_PauseAudioDevice(mDeviceID, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Sdl2Backend::stop()
|
||||
{ SDL_PauseAudioDevice(mDeviceID, 1); }
|
||||
|
||||
void Sdl2Backend::lock()
|
||||
{ SDL_LockAudioDevice(mDeviceID); }
|
||||
|
||||
void Sdl2Backend::unlock()
|
||||
{ SDL_UnlockAudioDevice(mDeviceID); }
|
||||
|
||||
} // namespace
|
||||
|
||||
BackendFactory &SDL2BackendFactory::getFactory()
|
||||
{
|
||||
static SDL2BackendFactory factory{};
|
||||
return factory;
|
||||
}
|
||||
|
||||
bool SDL2BackendFactory::init()
|
||||
{ return (SDL_InitSubSystem(SDL_INIT_AUDIO) == 0); }
|
||||
|
||||
bool SDL2BackendFactory::querySupport(BackendType type)
|
||||
{ return type == BackendType::Playback; }
|
||||
|
||||
void SDL2BackendFactory::probe(DevProbe type, std::string *outnames)
|
||||
{
|
||||
if(type != DevProbe::Playback)
|
||||
return;
|
||||
|
||||
int num_devices{SDL_GetNumAudioDevices(SDL_FALSE)};
|
||||
|
||||
/* Includes null char. */
|
||||
outnames->append(defaultDeviceName, sizeof(defaultDeviceName));
|
||||
for(int i{0};i < num_devices;++i)
|
||||
{
|
||||
std::string name{DEVNAME_PREFIX};
|
||||
name += SDL_GetAudioDeviceName(i, SDL_FALSE);
|
||||
if(!name.empty())
|
||||
outnames->append(name.c_str(), name.length()+1);
|
||||
}
|
||||
}
|
||||
|
||||
BackendPtr SDL2BackendFactory::createBackend(ALCdevice *device, BackendType type)
|
||||
{
|
||||
if(type == BackendType::Playback)
|
||||
return BackendPtr{new Sdl2Backend{device}};
|
||||
return nullptr;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
#ifndef BACKENDS_SDL2_H
|
||||
#define BACKENDS_SDL2_H
|
||||
|
||||
#include "backends/base.h"
|
||||
|
||||
struct SDL2BackendFactory final : public BackendFactory {
|
||||
public:
|
||||
bool init() override;
|
||||
|
||||
bool querySupport(BackendType type) override;
|
||||
|
||||
void probe(DevProbe type, std::string *outnames) override;
|
||||
|
||||
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
|
||||
|
||||
static BackendFactory &getFactory();
|
||||
};
|
||||
|
||||
#endif /* BACKENDS_SDL2_H */
|
||||
@@ -1,477 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 1999-2007 by authors.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "backends/sndio.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <thread>
|
||||
#include <functional>
|
||||
|
||||
#include "alcmain.h"
|
||||
#include "alexcpt.h"
|
||||
#include "alu.h"
|
||||
#include "threads.h"
|
||||
#include "vector.h"
|
||||
#include "ringbuffer.h"
|
||||
|
||||
#include <sndio.h>
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
static const ALCchar sndio_device[] = "SndIO Default";
|
||||
|
||||
|
||||
struct SndioPlayback final : public BackendBase {
|
||||
SndioPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
|
||||
~SndioPlayback() override;
|
||||
|
||||
int mixerProc();
|
||||
|
||||
void open(const ALCchar *name) override;
|
||||
bool reset() override;
|
||||
bool start() override;
|
||||
void stop() override;
|
||||
|
||||
sio_hdl *mSndHandle{nullptr};
|
||||
|
||||
al::vector<ALubyte> mBuffer;
|
||||
|
||||
std::atomic<bool> mKillNow{true};
|
||||
std::thread mThread;
|
||||
|
||||
DEF_NEWDEL(SndioPlayback)
|
||||
};
|
||||
|
||||
SndioPlayback::~SndioPlayback()
|
||||
{
|
||||
if(mSndHandle)
|
||||
sio_close(mSndHandle);
|
||||
mSndHandle = nullptr;
|
||||
}
|
||||
|
||||
int SndioPlayback::mixerProc()
|
||||
{
|
||||
SetRTPriority();
|
||||
althrd_setname(MIXER_THREAD_NAME);
|
||||
|
||||
const size_t frameStep{mDevice->channelsFromFmt()};
|
||||
const ALuint frameSize{mDevice->frameSizeFromFmt()};
|
||||
|
||||
while(!mKillNow.load(std::memory_order_acquire) &&
|
||||
mDevice->Connected.load(std::memory_order_acquire))
|
||||
{
|
||||
ALubyte *WritePtr{mBuffer.data()};
|
||||
size_t len{mBuffer.size()};
|
||||
|
||||
{
|
||||
std::lock_guard<SndioPlayback> _{*this};
|
||||
aluMixData(mDevice, WritePtr, static_cast<ALuint>(len/frameSize), frameStep);
|
||||
}
|
||||
while(len > 0 && !mKillNow.load(std::memory_order_acquire))
|
||||
{
|
||||
size_t wrote{sio_write(mSndHandle, WritePtr, len)};
|
||||
if(wrote == 0)
|
||||
{
|
||||
ERR("sio_write failed\n");
|
||||
aluHandleDisconnect(mDevice, "Failed to write playback samples");
|
||||
break;
|
||||
}
|
||||
|
||||
len -= wrote;
|
||||
WritePtr += wrote;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void SndioPlayback::open(const ALCchar *name)
|
||||
{
|
||||
if(!name)
|
||||
name = sndio_device;
|
||||
else if(strcmp(name, sndio_device) != 0)
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
|
||||
|
||||
mSndHandle = sio_open(nullptr, SIO_PLAY, 0);
|
||||
if(mSndHandle == nullptr)
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "Could not open backend device"};
|
||||
|
||||
mDevice->DeviceName = name;
|
||||
}
|
||||
|
||||
bool SndioPlayback::reset()
|
||||
{
|
||||
sio_par par;
|
||||
sio_initpar(&par);
|
||||
|
||||
par.rate = mDevice->Frequency;
|
||||
par.pchan = ((mDevice->FmtChans != DevFmtMono) ? 2 : 1);
|
||||
|
||||
switch(mDevice->FmtType)
|
||||
{
|
||||
case DevFmtByte:
|
||||
par.bits = 8;
|
||||
par.sig = 1;
|
||||
break;
|
||||
case DevFmtUByte:
|
||||
par.bits = 8;
|
||||
par.sig = 0;
|
||||
break;
|
||||
case DevFmtFloat:
|
||||
case DevFmtShort:
|
||||
par.bits = 16;
|
||||
par.sig = 1;
|
||||
break;
|
||||
case DevFmtUShort:
|
||||
par.bits = 16;
|
||||
par.sig = 0;
|
||||
break;
|
||||
case DevFmtInt:
|
||||
par.bits = 32;
|
||||
par.sig = 1;
|
||||
break;
|
||||
case DevFmtUInt:
|
||||
par.bits = 32;
|
||||
par.sig = 0;
|
||||
break;
|
||||
}
|
||||
par.le = SIO_LE_NATIVE;
|
||||
|
||||
par.round = mDevice->UpdateSize;
|
||||
par.appbufsz = mDevice->BufferSize - mDevice->UpdateSize;
|
||||
if(!par.appbufsz) par.appbufsz = mDevice->UpdateSize;
|
||||
|
||||
if(!sio_setpar(mSndHandle, &par) || !sio_getpar(mSndHandle, &par))
|
||||
{
|
||||
ERR("Failed to set device parameters\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if(par.bits != par.bps*8)
|
||||
{
|
||||
ERR("Padded samples not supported (%u of %u bits)\n", par.bits, par.bps*8);
|
||||
return true;
|
||||
}
|
||||
|
||||
mDevice->Frequency = par.rate;
|
||||
mDevice->FmtChans = ((par.pchan==1) ? DevFmtMono : DevFmtStereo);
|
||||
|
||||
if(par.bits == 8 && par.sig == 1)
|
||||
mDevice->FmtType = DevFmtByte;
|
||||
else if(par.bits == 8 && par.sig == 0)
|
||||
mDevice->FmtType = DevFmtUByte;
|
||||
else if(par.bits == 16 && par.sig == 1)
|
||||
mDevice->FmtType = DevFmtShort;
|
||||
else if(par.bits == 16 && par.sig == 0)
|
||||
mDevice->FmtType = DevFmtUShort;
|
||||
else if(par.bits == 32 && par.sig == 1)
|
||||
mDevice->FmtType = DevFmtInt;
|
||||
else if(par.bits == 32 && par.sig == 0)
|
||||
mDevice->FmtType = DevFmtUInt;
|
||||
else
|
||||
{
|
||||
ERR("Unhandled sample format: %s %u-bit\n", (par.sig?"signed":"unsigned"), par.bits);
|
||||
return false;
|
||||
}
|
||||
|
||||
SetDefaultChannelOrder(mDevice);
|
||||
|
||||
mDevice->UpdateSize = par.round;
|
||||
mDevice->BufferSize = par.bufsz + par.round;
|
||||
|
||||
mBuffer.resize(mDevice->UpdateSize * mDevice->frameSizeFromFmt());
|
||||
std::fill(mBuffer.begin(), mBuffer.end(), 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SndioPlayback::start()
|
||||
{
|
||||
if(!sio_start(mSndHandle))
|
||||
{
|
||||
ERR("Error starting playback\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
mKillNow.store(false, std::memory_order_release);
|
||||
mThread = std::thread{std::mem_fn(&SndioPlayback::mixerProc), this};
|
||||
return true;
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
ERR("Could not create playback thread: %s\n", e.what());
|
||||
}
|
||||
catch(...) {
|
||||
}
|
||||
sio_stop(mSndHandle);
|
||||
return false;
|
||||
}
|
||||
|
||||
void SndioPlayback::stop()
|
||||
{
|
||||
if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
|
||||
return;
|
||||
mThread.join();
|
||||
|
||||
if(!sio_stop(mSndHandle))
|
||||
ERR("Error stopping device\n");
|
||||
}
|
||||
|
||||
|
||||
struct SndioCapture final : public BackendBase {
|
||||
SndioCapture(ALCdevice *device) noexcept : BackendBase{device} { }
|
||||
~SndioCapture() override;
|
||||
|
||||
int recordProc();
|
||||
|
||||
void open(const ALCchar *name) override;
|
||||
bool start() override;
|
||||
void stop() override;
|
||||
ALCenum captureSamples(al::byte *buffer, ALCuint samples) override;
|
||||
ALCuint availableSamples() override;
|
||||
|
||||
sio_hdl *mSndHandle{nullptr};
|
||||
|
||||
RingBufferPtr mRing;
|
||||
|
||||
std::atomic<bool> mKillNow{true};
|
||||
std::thread mThread;
|
||||
|
||||
DEF_NEWDEL(SndioCapture)
|
||||
};
|
||||
|
||||
SndioCapture::~SndioCapture()
|
||||
{
|
||||
if(mSndHandle)
|
||||
sio_close(mSndHandle);
|
||||
mSndHandle = nullptr;
|
||||
}
|
||||
|
||||
int SndioCapture::recordProc()
|
||||
{
|
||||
SetRTPriority();
|
||||
althrd_setname(RECORD_THREAD_NAME);
|
||||
|
||||
const ALuint frameSize{mDevice->frameSizeFromFmt()};
|
||||
|
||||
while(!mKillNow.load(std::memory_order_acquire) &&
|
||||
mDevice->Connected.load(std::memory_order_acquire))
|
||||
{
|
||||
auto data = mRing->getWriteVector();
|
||||
size_t todo{data.first.len + data.second.len};
|
||||
if(todo == 0)
|
||||
{
|
||||
static char junk[4096];
|
||||
sio_read(mSndHandle, junk,
|
||||
minz(sizeof(junk)/frameSize, mDevice->UpdateSize)*frameSize);
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t total{0u};
|
||||
data.first.len *= frameSize;
|
||||
data.second.len *= frameSize;
|
||||
todo = minz(todo, mDevice->UpdateSize) * frameSize;
|
||||
while(total < todo)
|
||||
{
|
||||
if(!data.first.len)
|
||||
data.first = data.second;
|
||||
|
||||
size_t got{sio_read(mSndHandle, data.first.buf, minz(todo-total, data.first.len))};
|
||||
if(!got)
|
||||
{
|
||||
aluHandleDisconnect(mDevice, "Failed to read capture samples");
|
||||
break;
|
||||
}
|
||||
|
||||
data.first.buf += got;
|
||||
data.first.len -= got;
|
||||
total += got;
|
||||
}
|
||||
mRing->writeAdvance(total / frameSize);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void SndioCapture::open(const ALCchar *name)
|
||||
{
|
||||
if(!name)
|
||||
name = sndio_device;
|
||||
else if(strcmp(name, sndio_device) != 0)
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
|
||||
|
||||
mSndHandle = sio_open(nullptr, SIO_REC, 0);
|
||||
if(mSndHandle == nullptr)
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "Could not open backend device"};
|
||||
|
||||
sio_par par;
|
||||
sio_initpar(&par);
|
||||
|
||||
switch(mDevice->FmtType)
|
||||
{
|
||||
case DevFmtByte:
|
||||
par.bps = 1;
|
||||
par.sig = 1;
|
||||
break;
|
||||
case DevFmtUByte:
|
||||
par.bps = 1;
|
||||
par.sig = 0;
|
||||
break;
|
||||
case DevFmtShort:
|
||||
par.bps = 2;
|
||||
par.sig = 1;
|
||||
break;
|
||||
case DevFmtUShort:
|
||||
par.bps = 2;
|
||||
par.sig = 0;
|
||||
break;
|
||||
case DevFmtInt:
|
||||
par.bps = 4;
|
||||
par.sig = 1;
|
||||
break;
|
||||
case DevFmtUInt:
|
||||
par.bps = 4;
|
||||
par.sig = 0;
|
||||
break;
|
||||
case DevFmtFloat:
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "%s capture samples not supported",
|
||||
DevFmtTypeString(mDevice->FmtType)};
|
||||
}
|
||||
par.bits = par.bps * 8;
|
||||
par.le = SIO_LE_NATIVE;
|
||||
par.msb = SIO_LE_NATIVE ? 0 : 1;
|
||||
par.rchan = mDevice->channelsFromFmt();
|
||||
par.rate = mDevice->Frequency;
|
||||
|
||||
par.appbufsz = maxu(mDevice->BufferSize, mDevice->Frequency/10);
|
||||
par.round = minu(par.appbufsz, mDevice->Frequency/40);
|
||||
|
||||
mDevice->UpdateSize = par.round;
|
||||
mDevice->BufferSize = par.appbufsz;
|
||||
|
||||
if(!sio_setpar(mSndHandle, &par) || !sio_getpar(mSndHandle, &par))
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "Failed to set device praameters"};
|
||||
|
||||
if(par.bits != par.bps*8)
|
||||
throw al::backend_exception{ALC_INVALID_VALUE,
|
||||
"Padded samples not supported (got %u of %u bits)", par.bits, par.bps*8};
|
||||
|
||||
if(!((mDevice->FmtType == DevFmtByte && par.bits == 8 && par.sig != 0)
|
||||
|| (mDevice->FmtType == DevFmtUByte && par.bits == 8 && par.sig == 0)
|
||||
|| (mDevice->FmtType == DevFmtShort && par.bits == 16 && par.sig != 0)
|
||||
|| (mDevice->FmtType == DevFmtUShort && par.bits == 16 && par.sig == 0)
|
||||
|| (mDevice->FmtType == DevFmtInt && par.bits == 32 && par.sig != 0)
|
||||
|| (mDevice->FmtType == DevFmtUInt && par.bits == 32 && par.sig == 0))
|
||||
|| mDevice->channelsFromFmt() != par.rchan || mDevice->Frequency != par.rate)
|
||||
throw al::backend_exception{ALC_INVALID_VALUE,
|
||||
"Failed to set format %s %s %uhz, got %c%u %u-channel %uhz instead",
|
||||
DevFmtTypeString(mDevice->FmtType), DevFmtChannelsString(mDevice->FmtChans),
|
||||
mDevice->Frequency, par.sig?'s':'u', par.bits, par.rchan, par.rate};
|
||||
|
||||
mRing = RingBuffer::Create(mDevice->BufferSize, par.bps*par.rchan, false);
|
||||
|
||||
SetDefaultChannelOrder(mDevice);
|
||||
|
||||
mDevice->DeviceName = name;
|
||||
}
|
||||
|
||||
bool SndioCapture::start()
|
||||
{
|
||||
if(!sio_start(mSndHandle))
|
||||
{
|
||||
ERR("Error starting playback\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
mKillNow.store(false, std::memory_order_release);
|
||||
mThread = std::thread{std::mem_fn(&SndioCapture::recordProc), this};
|
||||
return true;
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
ERR("Could not create record thread: %s\n", e.what());
|
||||
}
|
||||
catch(...) {
|
||||
}
|
||||
sio_stop(mSndHandle);
|
||||
return false;
|
||||
}
|
||||
|
||||
void SndioCapture::stop()
|
||||
{
|
||||
if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
|
||||
return;
|
||||
mThread.join();
|
||||
|
||||
if(!sio_stop(mSndHandle))
|
||||
ERR("Error stopping device\n");
|
||||
}
|
||||
|
||||
ALCenum SndioCapture::captureSamples(al::byte *buffer, ALCuint samples)
|
||||
{
|
||||
mRing->read(buffer, samples);
|
||||
return ALC_NO_ERROR;
|
||||
}
|
||||
|
||||
ALCuint SndioCapture::availableSamples()
|
||||
{ return static_cast<ALCuint>(mRing->readSpace()); }
|
||||
|
||||
} // namespace
|
||||
|
||||
BackendFactory &SndIOBackendFactory::getFactory()
|
||||
{
|
||||
static SndIOBackendFactory factory{};
|
||||
return factory;
|
||||
}
|
||||
|
||||
bool SndIOBackendFactory::init()
|
||||
{ return true; }
|
||||
|
||||
bool SndIOBackendFactory::querySupport(BackendType type)
|
||||
{ return (type == BackendType::Playback || type == BackendType::Capture); }
|
||||
|
||||
void SndIOBackendFactory::probe(DevProbe type, std::string *outnames)
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
case DevProbe::Playback:
|
||||
case DevProbe::Capture:
|
||||
/* Includes null char. */
|
||||
outnames->append(sndio_device, sizeof(sndio_device));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
BackendPtr SndIOBackendFactory::createBackend(ALCdevice *device, BackendType type)
|
||||
{
|
||||
if(type == BackendType::Playback)
|
||||
return BackendPtr{new SndioPlayback{device}};
|
||||
if(type == BackendType::Capture)
|
||||
return BackendPtr{new SndioCapture{device}};
|
||||
return nullptr;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
#ifndef BACKENDS_SNDIO_H
|
||||
#define BACKENDS_SNDIO_H
|
||||
|
||||
#include "backends/base.h"
|
||||
|
||||
struct SndIOBackendFactory final : public BackendFactory {
|
||||
public:
|
||||
bool init() override;
|
||||
|
||||
bool querySupport(BackendType type) override;
|
||||
|
||||
void probe(DevProbe type, std::string *outnames) override;
|
||||
|
||||
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
|
||||
|
||||
static BackendFactory &getFactory();
|
||||
};
|
||||
|
||||
#endif /* BACKENDS_SNDIO_H */
|
||||
@@ -1,300 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 1999-2007 by authors.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "backends/solaris.h"
|
||||
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <memory.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <poll.h>
|
||||
#include <math.h>
|
||||
|
||||
#include <thread>
|
||||
#include <functional>
|
||||
|
||||
#include "alcmain.h"
|
||||
#include "alexcpt.h"
|
||||
#include "alu.h"
|
||||
#include "alconfig.h"
|
||||
#include "threads.h"
|
||||
#include "vector.h"
|
||||
#include "compat.h"
|
||||
|
||||
#include <sys/audioio.h>
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr ALCchar solaris_device[] = "Solaris Default";
|
||||
|
||||
std::string solaris_driver{"/dev/audio"};
|
||||
|
||||
|
||||
struct SolarisBackend final : public BackendBase {
|
||||
SolarisBackend(ALCdevice *device) noexcept : BackendBase{device} { }
|
||||
~SolarisBackend() override;
|
||||
|
||||
int mixerProc();
|
||||
|
||||
void open(const ALCchar *name) override;
|
||||
bool reset() override;
|
||||
bool start() override;
|
||||
void stop() override;
|
||||
|
||||
int mFd{-1};
|
||||
|
||||
al::vector<ALubyte> mBuffer;
|
||||
|
||||
std::atomic<bool> mKillNow{true};
|
||||
std::thread mThread;
|
||||
|
||||
DEF_NEWDEL(SolarisBackend)
|
||||
};
|
||||
|
||||
SolarisBackend::~SolarisBackend()
|
||||
{
|
||||
if(mFd != -1)
|
||||
close(mFd);
|
||||
mFd = -1;
|
||||
}
|
||||
|
||||
int SolarisBackend::mixerProc()
|
||||
{
|
||||
SetRTPriority();
|
||||
althrd_setname(MIXER_THREAD_NAME);
|
||||
|
||||
const size_t frame_step{mDevice->channelsFromFmt()};
|
||||
const ALuint frame_size{mDevice->frameSizeFromFmt()};
|
||||
|
||||
std::unique_lock<SolarisBackend> dlock{*this};
|
||||
while(!mKillNow.load(std::memory_order_acquire) &&
|
||||
mDevice->Connected.load(std::memory_order_acquire))
|
||||
{
|
||||
pollfd pollitem{};
|
||||
pollitem.fd = mFd;
|
||||
pollitem.events = POLLOUT;
|
||||
|
||||
dlock.unlock();
|
||||
int pret{poll(&pollitem, 1, 1000)};
|
||||
dlock.lock();
|
||||
if(pret < 0)
|
||||
{
|
||||
if(errno == EINTR || errno == EAGAIN)
|
||||
continue;
|
||||
ERR("poll failed: %s\n", strerror(errno));
|
||||
aluHandleDisconnect(mDevice, "Failed to wait for playback buffer: %s",
|
||||
strerror(errno));
|
||||
break;
|
||||
}
|
||||
else if(pret == 0)
|
||||
{
|
||||
WARN("poll timeout\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
ALubyte *write_ptr{mBuffer.data()};
|
||||
size_t to_write{mBuffer.size()};
|
||||
aluMixData(mDevice, write_ptr, to_write/frame_size, frame_step);
|
||||
while(to_write > 0 && !mKillNow.load(std::memory_order_acquire))
|
||||
{
|
||||
ssize_t wrote{write(mFd, write_ptr, to_write)};
|
||||
if(wrote < 0)
|
||||
{
|
||||
if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
|
||||
continue;
|
||||
ERR("write failed: %s\n", strerror(errno));
|
||||
aluHandleDisconnect(mDevice, "Failed to write playback samples: %s",
|
||||
strerror(errno));
|
||||
break;
|
||||
}
|
||||
|
||||
to_write -= wrote;
|
||||
write_ptr += wrote;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void SolarisBackend::open(const ALCchar *name)
|
||||
{
|
||||
if(!name)
|
||||
name = solaris_device;
|
||||
else if(strcmp(name, solaris_device) != 0)
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
|
||||
|
||||
mFd = ::open(solaris_driver.c_str(), O_WRONLY);
|
||||
if(mFd == -1)
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "Could not open %s: %s",
|
||||
solaris_driver.c_str(), strerror(errno)};
|
||||
|
||||
mDevice->DeviceName = name;
|
||||
}
|
||||
|
||||
bool SolarisBackend::reset()
|
||||
{
|
||||
audio_info_t info;
|
||||
AUDIO_INITINFO(&info);
|
||||
|
||||
info.play.sample_rate = mDevice->Frequency;
|
||||
|
||||
if(mDevice->FmtChans != DevFmtMono)
|
||||
mDevice->FmtChans = DevFmtStereo;
|
||||
ALuint numChannels{mDevice->channelsFromFmt()};
|
||||
info.play.channels = numChannels;
|
||||
|
||||
switch(mDevice->FmtType)
|
||||
{
|
||||
case DevFmtByte:
|
||||
info.play.precision = 8;
|
||||
info.play.encoding = AUDIO_ENCODING_LINEAR;
|
||||
break;
|
||||
case DevFmtUByte:
|
||||
info.play.precision = 8;
|
||||
info.play.encoding = AUDIO_ENCODING_LINEAR8;
|
||||
break;
|
||||
case DevFmtUShort:
|
||||
case DevFmtInt:
|
||||
case DevFmtUInt:
|
||||
case DevFmtFloat:
|
||||
mDevice->FmtType = DevFmtShort;
|
||||
/* fall-through */
|
||||
case DevFmtShort:
|
||||
info.play.precision = 16;
|
||||
info.play.encoding = AUDIO_ENCODING_LINEAR;
|
||||
break;
|
||||
}
|
||||
|
||||
ALuint frameSize{numChannels * mDevice->bytesFromFmt()};
|
||||
info.play.buffer_size = mDevice->BufferSize * frameSize;
|
||||
|
||||
if(ioctl(mFd, AUDIO_SETINFO, &info) < 0)
|
||||
{
|
||||
ERR("ioctl failed: %s\n", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
if(mDevice->channelsFromFmt() != info.play.channels)
|
||||
{
|
||||
ERR("Failed to set %s, got %u channels instead\n", DevFmtChannelsString(mDevice->FmtChans),
|
||||
info.play.channels);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!((info.play.precision == 8 && info.play.encoding == AUDIO_ENCODING_LINEAR8 && mDevice->FmtType == DevFmtUByte) ||
|
||||
(info.play.precision == 8 && info.play.encoding == AUDIO_ENCODING_LINEAR && mDevice->FmtType == DevFmtByte) ||
|
||||
(info.play.precision == 16 && info.play.encoding == AUDIO_ENCODING_LINEAR && mDevice->FmtType == DevFmtShort) ||
|
||||
(info.play.precision == 32 && info.play.encoding == AUDIO_ENCODING_LINEAR && mDevice->FmtType == DevFmtInt)))
|
||||
{
|
||||
ERR("Could not set %s samples, got %d (0x%x)\n", DevFmtTypeString(mDevice->FmtType),
|
||||
info.play.precision, info.play.encoding);
|
||||
return false;
|
||||
}
|
||||
|
||||
mDevice->Frequency = info.play.sample_rate;
|
||||
mDevice->BufferSize = info.play.buffer_size / frameSize;
|
||||
mDevice->UpdateSize = mDevice->BufferSize / 2;
|
||||
|
||||
SetDefaultChannelOrder(mDevice);
|
||||
|
||||
mBuffer.resize(mDevice->UpdateSize * mDevice->frameSizeFromFmt());
|
||||
std::fill(mBuffer.begin(), mBuffer.end(), 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SolarisBackend::start()
|
||||
{
|
||||
try {
|
||||
mKillNow.store(false, std::memory_order_release);
|
||||
mThread = std::thread{std::mem_fn(&SolarisBackend::mixerProc), this};
|
||||
return true;
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
ERR("Could not create playback thread: %s\n", e.what());
|
||||
}
|
||||
catch(...) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void SolarisBackend::stop()
|
||||
{
|
||||
if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
|
||||
return;
|
||||
mThread.join();
|
||||
|
||||
if(ioctl(mFd, AUDIO_DRAIN) < 0)
|
||||
ERR("Error draining device: %s\n", strerror(errno));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
BackendFactory &SolarisBackendFactory::getFactory()
|
||||
{
|
||||
static SolarisBackendFactory factory{};
|
||||
return factory;
|
||||
}
|
||||
|
||||
bool SolarisBackendFactory::init()
|
||||
{
|
||||
if(auto devopt = ConfigValueStr(nullptr, "solaris", "device"))
|
||||
solaris_driver = std::move(*devopt);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SolarisBackendFactory::querySupport(BackendType type)
|
||||
{ return type == BackendType::Playback; }
|
||||
|
||||
void SolarisBackendFactory::probe(DevProbe type, std::string *outnames)
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
case DevProbe::Playback:
|
||||
{
|
||||
#ifdef HAVE_STAT
|
||||
struct stat buf;
|
||||
if(stat(solaris_driver.c_str(), &buf) == 0)
|
||||
#endif
|
||||
outnames->append(solaris_device, sizeof(solaris_device));
|
||||
}
|
||||
break;
|
||||
|
||||
case DevProbe::Capture:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
BackendPtr SolarisBackendFactory::createBackend(ALCdevice *device, BackendType type)
|
||||
{
|
||||
if(type == BackendType::Playback)
|
||||
return BackendPtr{new SolarisBackend{device}};
|
||||
return nullptr;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
#ifndef BACKENDS_SOLARIS_H
|
||||
#define BACKENDS_SOLARIS_H
|
||||
|
||||
#include "backends/base.h"
|
||||
|
||||
struct SolarisBackendFactory final : public BackendFactory {
|
||||
public:
|
||||
bool init() override;
|
||||
|
||||
bool querySupport(BackendType type) override;
|
||||
|
||||
void probe(DevProbe type, std::string *outnames) override;
|
||||
|
||||
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
|
||||
|
||||
static BackendFactory &getFactory();
|
||||
};
|
||||
|
||||
#endif /* BACKENDS_SOLARIS_H */
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,19 +0,0 @@
|
||||
#ifndef BACKENDS_WASAPI_H
|
||||
#define BACKENDS_WASAPI_H
|
||||
|
||||
#include "backends/base.h"
|
||||
|
||||
struct WasapiBackendFactory final : public BackendFactory {
|
||||
public:
|
||||
bool init() override;
|
||||
|
||||
bool querySupport(BackendType type) override;
|
||||
|
||||
void probe(DevProbe type, std::string *outnames) override;
|
||||
|
||||
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
|
||||
|
||||
static BackendFactory &getFactory();
|
||||
};
|
||||
|
||||
#endif /* BACKENDS_WASAPI_H */
|
||||
@@ -1,403 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 1999-2007 by authors.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "backends/wave.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cerrno>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
#include <functional>
|
||||
#include <thread>
|
||||
|
||||
#include "AL/al.h"
|
||||
|
||||
#include "albyte.h"
|
||||
#include "alcmain.h"
|
||||
#include "alconfig.h"
|
||||
#include "alexcpt.h"
|
||||
#include "almalloc.h"
|
||||
#include "alnumeric.h"
|
||||
#include "alu.h"
|
||||
#include "compat.h"
|
||||
#include "endiantest.h"
|
||||
#include "logging.h"
|
||||
#include "strutils.h"
|
||||
#include "threads.h"
|
||||
#include "vector.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
using std::chrono::seconds;
|
||||
using std::chrono::milliseconds;
|
||||
using std::chrono::nanoseconds;
|
||||
|
||||
constexpr ALCchar waveDevice[] = "Wave File Writer";
|
||||
|
||||
constexpr ALubyte SUBTYPE_PCM[]{
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa,
|
||||
0x00, 0x38, 0x9b, 0x71
|
||||
};
|
||||
constexpr ALubyte SUBTYPE_FLOAT[]{
|
||||
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa,
|
||||
0x00, 0x38, 0x9b, 0x71
|
||||
};
|
||||
|
||||
constexpr ALubyte SUBTYPE_BFORMAT_PCM[]{
|
||||
0x01, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, 0x86, 0x44, 0xc8, 0xc1,
|
||||
0xca, 0x00, 0x00, 0x00
|
||||
};
|
||||
|
||||
constexpr ALubyte SUBTYPE_BFORMAT_FLOAT[]{
|
||||
0x03, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, 0x86, 0x44, 0xc8, 0xc1,
|
||||
0xca, 0x00, 0x00, 0x00
|
||||
};
|
||||
|
||||
void fwrite16le(ALushort val, FILE *f)
|
||||
{
|
||||
ALubyte data[2]{ static_cast<ALubyte>(val&0xff), static_cast<ALubyte>((val>>8)&0xff) };
|
||||
fwrite(data, 1, 2, f);
|
||||
}
|
||||
|
||||
void fwrite32le(ALuint val, FILE *f)
|
||||
{
|
||||
ALubyte data[4]{ static_cast<ALubyte>(val&0xff), static_cast<ALubyte>((val>>8)&0xff),
|
||||
static_cast<ALubyte>((val>>16)&0xff), static_cast<ALubyte>((val>>24)&0xff) };
|
||||
fwrite(data, 1, 4, f);
|
||||
}
|
||||
|
||||
|
||||
struct WaveBackend final : public BackendBase {
|
||||
WaveBackend(ALCdevice *device) noexcept : BackendBase{device} { }
|
||||
~WaveBackend() override;
|
||||
|
||||
int mixerProc();
|
||||
|
||||
void open(const ALCchar *name) override;
|
||||
bool reset() override;
|
||||
bool start() override;
|
||||
void stop() override;
|
||||
|
||||
FILE *mFile{nullptr};
|
||||
long mDataStart{-1};
|
||||
|
||||
al::vector<al::byte> mBuffer;
|
||||
|
||||
std::atomic<bool> mKillNow{true};
|
||||
std::thread mThread;
|
||||
|
||||
DEF_NEWDEL(WaveBackend)
|
||||
};
|
||||
|
||||
WaveBackend::~WaveBackend()
|
||||
{
|
||||
if(mFile)
|
||||
fclose(mFile);
|
||||
mFile = nullptr;
|
||||
}
|
||||
|
||||
int WaveBackend::mixerProc()
|
||||
{
|
||||
const milliseconds restTime{mDevice->UpdateSize*1000/mDevice->Frequency / 2};
|
||||
|
||||
althrd_setname(MIXER_THREAD_NAME);
|
||||
|
||||
const size_t frameStep{mDevice->channelsFromFmt()};
|
||||
const ALuint frameSize{mDevice->frameSizeFromFmt()};
|
||||
|
||||
int64_t done{0};
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
while(!mKillNow.load(std::memory_order_acquire) &&
|
||||
mDevice->Connected.load(std::memory_order_acquire))
|
||||
{
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
|
||||
/* This converts from nanoseconds to nanosamples, then to samples. */
|
||||
int64_t avail{std::chrono::duration_cast<seconds>((now-start) *
|
||||
mDevice->Frequency).count()};
|
||||
if(avail-done < mDevice->UpdateSize)
|
||||
{
|
||||
std::this_thread::sleep_for(restTime);
|
||||
continue;
|
||||
}
|
||||
while(avail-done >= mDevice->UpdateSize)
|
||||
{
|
||||
{
|
||||
std::lock_guard<WaveBackend> _{*this};
|
||||
aluMixData(mDevice, mBuffer.data(), mDevice->UpdateSize, frameStep);
|
||||
}
|
||||
done += mDevice->UpdateSize;
|
||||
|
||||
if(!IS_LITTLE_ENDIAN)
|
||||
{
|
||||
const ALuint bytesize{mDevice->bytesFromFmt()};
|
||||
|
||||
if(bytesize == 2)
|
||||
{
|
||||
ALushort *samples = reinterpret_cast<ALushort*>(mBuffer.data());
|
||||
const size_t len{mBuffer.size() / 2};
|
||||
for(size_t i{0};i < len;i++)
|
||||
{
|
||||
const ALushort samp{samples[i]};
|
||||
samples[i] = static_cast<ALushort>((samp>>8) | (samp<<8));
|
||||
}
|
||||
}
|
||||
else if(bytesize == 4)
|
||||
{
|
||||
ALuint *samples = reinterpret_cast<ALuint*>(mBuffer.data());
|
||||
const size_t len{mBuffer.size() / 4};
|
||||
for(size_t i{0};i < len;i++)
|
||||
{
|
||||
const ALuint samp{samples[i]};
|
||||
samples[i] = (samp>>24) | ((samp>>8)&0x0000ff00) |
|
||||
((samp<<8)&0x00ff0000) | (samp<<24);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t fs{fwrite(mBuffer.data(), frameSize, mDevice->UpdateSize, mFile)};
|
||||
(void)fs;
|
||||
if(ferror(mFile))
|
||||
{
|
||||
ERR("Error writing to file\n");
|
||||
aluHandleDisconnect(mDevice, "Failed to write playback samples");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* For every completed second, increment the start time and reduce the
|
||||
* samples done. This prevents the difference between the start time
|
||||
* and current time from growing too large, while maintaining the
|
||||
* correct number of samples to render.
|
||||
*/
|
||||
if(done >= mDevice->Frequency)
|
||||
{
|
||||
seconds s{done/mDevice->Frequency};
|
||||
start += s;
|
||||
done -= mDevice->Frequency*s.count();
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void WaveBackend::open(const ALCchar *name)
|
||||
{
|
||||
const char *fname{GetConfigValue(nullptr, "wave", "file", "")};
|
||||
if(!fname[0]) throw al::backend_exception{ALC_INVALID_VALUE, "No wave output filename"};
|
||||
|
||||
if(!name)
|
||||
name = waveDevice;
|
||||
else if(strcmp(name, waveDevice) != 0)
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
|
||||
|
||||
#ifdef _WIN32
|
||||
{
|
||||
std::wstring wname = utf8_to_wstr(fname);
|
||||
mFile = _wfopen(wname.c_str(), L"wb");
|
||||
}
|
||||
#else
|
||||
mFile = fopen(fname, "wb");
|
||||
#endif
|
||||
if(!mFile)
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "Could not open file '%s': %s", fname,
|
||||
strerror(errno)};
|
||||
|
||||
mDevice->DeviceName = name;
|
||||
}
|
||||
|
||||
bool WaveBackend::reset()
|
||||
{
|
||||
ALuint channels=0, bytes=0, chanmask=0;
|
||||
int isbformat = 0;
|
||||
size_t val;
|
||||
|
||||
fseek(mFile, 0, SEEK_SET);
|
||||
clearerr(mFile);
|
||||
|
||||
if(GetConfigValueBool(nullptr, "wave", "bformat", 0))
|
||||
{
|
||||
mDevice->FmtChans = DevFmtAmbi3D;
|
||||
mDevice->mAmbiOrder = 1;
|
||||
}
|
||||
|
||||
switch(mDevice->FmtType)
|
||||
{
|
||||
case DevFmtByte:
|
||||
mDevice->FmtType = DevFmtUByte;
|
||||
break;
|
||||
case DevFmtUShort:
|
||||
mDevice->FmtType = DevFmtShort;
|
||||
break;
|
||||
case DevFmtUInt:
|
||||
mDevice->FmtType = DevFmtInt;
|
||||
break;
|
||||
case DevFmtUByte:
|
||||
case DevFmtShort:
|
||||
case DevFmtInt:
|
||||
case DevFmtFloat:
|
||||
break;
|
||||
}
|
||||
switch(mDevice->FmtChans)
|
||||
{
|
||||
case DevFmtMono: chanmask = 0x04; break;
|
||||
case DevFmtStereo: chanmask = 0x01 | 0x02; break;
|
||||
case DevFmtQuad: chanmask = 0x01 | 0x02 | 0x10 | 0x20; break;
|
||||
case DevFmtX51: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x200 | 0x400; break;
|
||||
case DevFmtX51Rear: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020; break;
|
||||
case DevFmtX61: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x100 | 0x200 | 0x400; break;
|
||||
case DevFmtX71: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020 | 0x200 | 0x400; break;
|
||||
case DevFmtAmbi3D:
|
||||
/* .amb output requires FuMa */
|
||||
mDevice->mAmbiOrder = minu(mDevice->mAmbiOrder, 3);
|
||||
mDevice->mAmbiLayout = AmbiLayout::FuMa;
|
||||
mDevice->mAmbiScale = AmbiNorm::FuMa;
|
||||
isbformat = 1;
|
||||
chanmask = 0;
|
||||
break;
|
||||
}
|
||||
bytes = mDevice->bytesFromFmt();
|
||||
channels = mDevice->channelsFromFmt();
|
||||
|
||||
rewind(mFile);
|
||||
|
||||
fputs("RIFF", mFile);
|
||||
fwrite32le(0xFFFFFFFF, mFile); // 'RIFF' header len; filled in at close
|
||||
|
||||
fputs("WAVE", mFile);
|
||||
|
||||
fputs("fmt ", mFile);
|
||||
fwrite32le(40, mFile); // 'fmt ' header len; 40 bytes for EXTENSIBLE
|
||||
|
||||
// 16-bit val, format type id (extensible: 0xFFFE)
|
||||
fwrite16le(0xFFFE, mFile);
|
||||
// 16-bit val, channel count
|
||||
fwrite16le(static_cast<ALushort>(channels), mFile);
|
||||
// 32-bit val, frequency
|
||||
fwrite32le(mDevice->Frequency, mFile);
|
||||
// 32-bit val, bytes per second
|
||||
fwrite32le(mDevice->Frequency * channels * bytes, mFile);
|
||||
// 16-bit val, frame size
|
||||
fwrite16le(static_cast<ALushort>(channels * bytes), mFile);
|
||||
// 16-bit val, bits per sample
|
||||
fwrite16le(static_cast<ALushort>(bytes * 8), mFile);
|
||||
// 16-bit val, extra byte count
|
||||
fwrite16le(22, mFile);
|
||||
// 16-bit val, valid bits per sample
|
||||
fwrite16le(static_cast<ALushort>(bytes * 8), mFile);
|
||||
// 32-bit val, channel mask
|
||||
fwrite32le(chanmask, mFile);
|
||||
// 16 byte GUID, sub-type format
|
||||
val = fwrite((mDevice->FmtType == DevFmtFloat) ?
|
||||
(isbformat ? SUBTYPE_BFORMAT_FLOAT : SUBTYPE_FLOAT) :
|
||||
(isbformat ? SUBTYPE_BFORMAT_PCM : SUBTYPE_PCM), 1, 16, mFile);
|
||||
(void)val;
|
||||
|
||||
fputs("data", mFile);
|
||||
fwrite32le(0xFFFFFFFF, mFile); // 'data' header len; filled in at close
|
||||
|
||||
if(ferror(mFile))
|
||||
{
|
||||
ERR("Error writing header: %s\n", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
mDataStart = ftell(mFile);
|
||||
|
||||
SetDefaultWFXChannelOrder(mDevice);
|
||||
|
||||
const ALuint bufsize{mDevice->frameSizeFromFmt() * mDevice->UpdateSize};
|
||||
mBuffer.resize(bufsize);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WaveBackend::start()
|
||||
{
|
||||
try {
|
||||
mKillNow.store(false, std::memory_order_release);
|
||||
mThread = std::thread{std::mem_fn(&WaveBackend::mixerProc), this};
|
||||
return true;
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
ERR("Failed to start mixing thread: %s\n", e.what());
|
||||
}
|
||||
catch(...) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void WaveBackend::stop()
|
||||
{
|
||||
if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
|
||||
return;
|
||||
mThread.join();
|
||||
|
||||
long size{ftell(mFile)};
|
||||
if(size > 0)
|
||||
{
|
||||
long dataLen{size - mDataStart};
|
||||
if(fseek(mFile, mDataStart-4, SEEK_SET) == 0)
|
||||
fwrite32le(static_cast<ALuint>(dataLen), mFile); // 'data' header len
|
||||
if(fseek(mFile, 4, SEEK_SET) == 0)
|
||||
fwrite32le(static_cast<ALuint>(size-8), mFile); // 'WAVE' header len
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
bool WaveBackendFactory::init()
|
||||
{ return true; }
|
||||
|
||||
bool WaveBackendFactory::querySupport(BackendType type)
|
||||
{ return type == BackendType::Playback; }
|
||||
|
||||
void WaveBackendFactory::probe(DevProbe type, std::string *outnames)
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
case DevProbe::Playback:
|
||||
/* Includes null char. */
|
||||
outnames->append(waveDevice, sizeof(waveDevice));
|
||||
break;
|
||||
case DevProbe::Capture:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
BackendPtr WaveBackendFactory::createBackend(ALCdevice *device, BackendType type)
|
||||
{
|
||||
if(type == BackendType::Playback)
|
||||
return BackendPtr{new WaveBackend{device}};
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
BackendFactory &WaveBackendFactory::getFactory()
|
||||
{
|
||||
static WaveBackendFactory factory{};
|
||||
return factory;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
#ifndef BACKENDS_WAVE_H
|
||||
#define BACKENDS_WAVE_H
|
||||
|
||||
#include "backends/base.h"
|
||||
|
||||
struct WaveBackendFactory final : public BackendFactory {
|
||||
public:
|
||||
bool init() override;
|
||||
|
||||
bool querySupport(BackendType type) override;
|
||||
|
||||
void probe(DevProbe type, std::string *outnames) override;
|
||||
|
||||
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
|
||||
|
||||
static BackendFactory &getFactory();
|
||||
};
|
||||
|
||||
#endif /* BACKENDS_WAVE_H */
|
||||
@@ -1,636 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 1999-2007 by authors.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "backends/winmm.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <memory.h>
|
||||
|
||||
#include <windows.h>
|
||||
#include <mmsystem.h>
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
|
||||
#include "alcmain.h"
|
||||
#include "alexcpt.h"
|
||||
#include "alu.h"
|
||||
#include "ringbuffer.h"
|
||||
#include "strutils.h"
|
||||
#include "threads.h"
|
||||
#include "compat.h"
|
||||
|
||||
#ifndef WAVE_FORMAT_IEEE_FLOAT
|
||||
#define WAVE_FORMAT_IEEE_FLOAT 0x0003
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
#define DEVNAME_HEAD "OpenAL Soft on "
|
||||
|
||||
|
||||
al::vector<std::string> PlaybackDevices;
|
||||
al::vector<std::string> CaptureDevices;
|
||||
|
||||
bool checkName(const al::vector<std::string> &list, const std::string &name)
|
||||
{ return std::find(list.cbegin(), list.cend(), name) != list.cend(); }
|
||||
|
||||
void ProbePlaybackDevices(void)
|
||||
{
|
||||
PlaybackDevices.clear();
|
||||
|
||||
ALuint numdevs{waveOutGetNumDevs()};
|
||||
PlaybackDevices.reserve(numdevs);
|
||||
for(ALuint i{0};i < numdevs;i++)
|
||||
{
|
||||
std::string dname;
|
||||
|
||||
WAVEOUTCAPSW WaveCaps{};
|
||||
if(waveOutGetDevCapsW(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR)
|
||||
{
|
||||
const std::string basename{DEVNAME_HEAD + wstr_to_utf8(WaveCaps.szPname)};
|
||||
|
||||
int count{1};
|
||||
std::string newname{basename};
|
||||
while(checkName(PlaybackDevices, newname))
|
||||
{
|
||||
newname = basename;
|
||||
newname += " #";
|
||||
newname += std::to_string(++count);
|
||||
}
|
||||
dname = std::move(newname);
|
||||
|
||||
TRACE("Got device \"%s\", ID %u\n", dname.c_str(), i);
|
||||
}
|
||||
PlaybackDevices.emplace_back(std::move(dname));
|
||||
}
|
||||
}
|
||||
|
||||
void ProbeCaptureDevices(void)
|
||||
{
|
||||
CaptureDevices.clear();
|
||||
|
||||
ALuint numdevs{waveInGetNumDevs()};
|
||||
CaptureDevices.reserve(numdevs);
|
||||
for(ALuint i{0};i < numdevs;i++)
|
||||
{
|
||||
std::string dname;
|
||||
|
||||
WAVEINCAPSW WaveCaps{};
|
||||
if(waveInGetDevCapsW(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR)
|
||||
{
|
||||
const std::string basename{DEVNAME_HEAD + wstr_to_utf8(WaveCaps.szPname)};
|
||||
|
||||
int count{1};
|
||||
std::string newname{basename};
|
||||
while(checkName(CaptureDevices, newname))
|
||||
{
|
||||
newname = basename;
|
||||
newname += " #";
|
||||
newname += std::to_string(++count);
|
||||
}
|
||||
dname = std::move(newname);
|
||||
|
||||
TRACE("Got device \"%s\", ID %u\n", dname.c_str(), i);
|
||||
}
|
||||
CaptureDevices.emplace_back(std::move(dname));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct WinMMPlayback final : public BackendBase {
|
||||
WinMMPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
|
||||
~WinMMPlayback() override;
|
||||
|
||||
void CALLBACK waveOutProc(HWAVEOUT device, UINT msg, DWORD_PTR param1, DWORD_PTR param2) noexcept;
|
||||
static void CALLBACK waveOutProcC(HWAVEOUT device, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2) noexcept
|
||||
{ reinterpret_cast<WinMMPlayback*>(instance)->waveOutProc(device, msg, param1, param2); }
|
||||
|
||||
int mixerProc();
|
||||
|
||||
void open(const ALCchar *name) override;
|
||||
bool reset() override;
|
||||
bool start() override;
|
||||
void stop() override;
|
||||
|
||||
std::atomic<ALuint> mWritable{0u};
|
||||
al::semaphore mSem;
|
||||
ALuint mIdx{0u};
|
||||
std::array<WAVEHDR,4> mWaveBuffer{};
|
||||
|
||||
HWAVEOUT mOutHdl{nullptr};
|
||||
|
||||
WAVEFORMATEX mFormat{};
|
||||
|
||||
std::atomic<bool> mKillNow{true};
|
||||
std::thread mThread;
|
||||
|
||||
DEF_NEWDEL(WinMMPlayback)
|
||||
};
|
||||
|
||||
WinMMPlayback::~WinMMPlayback()
|
||||
{
|
||||
if(mOutHdl)
|
||||
waveOutClose(mOutHdl);
|
||||
mOutHdl = nullptr;
|
||||
|
||||
al_free(mWaveBuffer[0].lpData);
|
||||
std::fill(mWaveBuffer.begin(), mWaveBuffer.end(), WAVEHDR{});
|
||||
}
|
||||
|
||||
/* WinMMPlayback::waveOutProc
|
||||
*
|
||||
* Posts a message to 'WinMMPlayback::mixerProc' everytime a WaveOut Buffer is
|
||||
* completed and returns to the application (for more data)
|
||||
*/
|
||||
void CALLBACK WinMMPlayback::waveOutProc(HWAVEOUT, UINT msg, DWORD_PTR, DWORD_PTR) noexcept
|
||||
{
|
||||
if(msg != WOM_DONE) return;
|
||||
mWritable.fetch_add(1, std::memory_order_acq_rel);
|
||||
mSem.post();
|
||||
}
|
||||
|
||||
FORCE_ALIGN int WinMMPlayback::mixerProc()
|
||||
{
|
||||
SetRTPriority();
|
||||
althrd_setname(MIXER_THREAD_NAME);
|
||||
|
||||
const size_t frame_step{mDevice->channelsFromFmt()};
|
||||
|
||||
std::unique_lock<WinMMPlayback> dlock{*this};
|
||||
while(!mKillNow.load(std::memory_order_acquire) &&
|
||||
mDevice->Connected.load(std::memory_order_acquire))
|
||||
{
|
||||
ALsizei todo = mWritable.load(std::memory_order_acquire);
|
||||
if(todo < 1)
|
||||
{
|
||||
dlock.unlock();
|
||||
mSem.wait();
|
||||
dlock.lock();
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t widx{mIdx};
|
||||
do {
|
||||
WAVEHDR &waveHdr = mWaveBuffer[widx];
|
||||
widx = (widx+1) % mWaveBuffer.size();
|
||||
|
||||
aluMixData(mDevice, waveHdr.lpData, mDevice->UpdateSize, frame_step);
|
||||
mWritable.fetch_sub(1, std::memory_order_acq_rel);
|
||||
waveOutWrite(mOutHdl, &waveHdr, sizeof(WAVEHDR));
|
||||
} while(--todo);
|
||||
mIdx = static_cast<ALuint>(widx);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void WinMMPlayback::open(const ALCchar *name)
|
||||
{
|
||||
if(PlaybackDevices.empty())
|
||||
ProbePlaybackDevices();
|
||||
|
||||
// Find the Device ID matching the deviceName if valid
|
||||
auto iter = name ?
|
||||
std::find(PlaybackDevices.cbegin(), PlaybackDevices.cend(), name) :
|
||||
PlaybackDevices.cbegin();
|
||||
if(iter == PlaybackDevices.cend())
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
|
||||
auto DeviceID = static_cast<UINT>(std::distance(PlaybackDevices.cbegin(), iter));
|
||||
|
||||
retry_open:
|
||||
mFormat = WAVEFORMATEX{};
|
||||
if(mDevice->FmtType == DevFmtFloat)
|
||||
{
|
||||
mFormat.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
|
||||
mFormat.wBitsPerSample = 32;
|
||||
}
|
||||
else
|
||||
{
|
||||
mFormat.wFormatTag = WAVE_FORMAT_PCM;
|
||||
if(mDevice->FmtType == DevFmtUByte || mDevice->FmtType == DevFmtByte)
|
||||
mFormat.wBitsPerSample = 8;
|
||||
else
|
||||
mFormat.wBitsPerSample = 16;
|
||||
}
|
||||
mFormat.nChannels = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2);
|
||||
mFormat.nBlockAlign = static_cast<WORD>(mFormat.wBitsPerSample * mFormat.nChannels / 8);
|
||||
mFormat.nSamplesPerSec = mDevice->Frequency;
|
||||
mFormat.nAvgBytesPerSec = mFormat.nSamplesPerSec * mFormat.nBlockAlign;
|
||||
mFormat.cbSize = 0;
|
||||
|
||||
MMRESULT res{waveOutOpen(&mOutHdl, DeviceID, &mFormat,
|
||||
reinterpret_cast<DWORD_PTR>(&WinMMPlayback::waveOutProcC),
|
||||
reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION)};
|
||||
if(res != MMSYSERR_NOERROR)
|
||||
{
|
||||
if(mDevice->FmtType == DevFmtFloat)
|
||||
{
|
||||
mDevice->FmtType = DevFmtShort;
|
||||
goto retry_open;
|
||||
}
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "waveOutOpen failed: %u", res};
|
||||
}
|
||||
|
||||
mDevice->DeviceName = PlaybackDevices[DeviceID];
|
||||
}
|
||||
|
||||
bool WinMMPlayback::reset()
|
||||
{
|
||||
mDevice->BufferSize = static_cast<ALuint>(uint64_t{mDevice->BufferSize} *
|
||||
mFormat.nSamplesPerSec / mDevice->Frequency);
|
||||
mDevice->BufferSize = (mDevice->BufferSize+3) & ~0x3;
|
||||
mDevice->UpdateSize = mDevice->BufferSize / 4;
|
||||
mDevice->Frequency = mFormat.nSamplesPerSec;
|
||||
|
||||
if(mFormat.wFormatTag == WAVE_FORMAT_IEEE_FLOAT)
|
||||
{
|
||||
if(mFormat.wBitsPerSample == 32)
|
||||
mDevice->FmtType = DevFmtFloat;
|
||||
else
|
||||
{
|
||||
ERR("Unhandled IEEE float sample depth: %d\n", mFormat.wBitsPerSample);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if(mFormat.wFormatTag == WAVE_FORMAT_PCM)
|
||||
{
|
||||
if(mFormat.wBitsPerSample == 16)
|
||||
mDevice->FmtType = DevFmtShort;
|
||||
else if(mFormat.wBitsPerSample == 8)
|
||||
mDevice->FmtType = DevFmtUByte;
|
||||
else
|
||||
{
|
||||
ERR("Unhandled PCM sample depth: %d\n", mFormat.wBitsPerSample);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ERR("Unhandled format tag: 0x%04x\n", mFormat.wFormatTag);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(mFormat.nChannels == 2)
|
||||
mDevice->FmtChans = DevFmtStereo;
|
||||
else if(mFormat.nChannels == 1)
|
||||
mDevice->FmtChans = DevFmtMono;
|
||||
else
|
||||
{
|
||||
ERR("Unhandled channel count: %d\n", mFormat.nChannels);
|
||||
return false;
|
||||
}
|
||||
SetDefaultWFXChannelOrder(mDevice);
|
||||
|
||||
ALuint BufferSize{mDevice->UpdateSize * mDevice->frameSizeFromFmt()};
|
||||
|
||||
al_free(mWaveBuffer[0].lpData);
|
||||
mWaveBuffer[0] = WAVEHDR{};
|
||||
mWaveBuffer[0].lpData = static_cast<char*>(al_calloc(16, BufferSize * mWaveBuffer.size()));
|
||||
mWaveBuffer[0].dwBufferLength = BufferSize;
|
||||
for(size_t i{1};i < mWaveBuffer.size();i++)
|
||||
{
|
||||
mWaveBuffer[i] = WAVEHDR{};
|
||||
mWaveBuffer[i].lpData = mWaveBuffer[i-1].lpData + mWaveBuffer[i-1].dwBufferLength;
|
||||
mWaveBuffer[i].dwBufferLength = BufferSize;
|
||||
}
|
||||
mIdx = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WinMMPlayback::start()
|
||||
{
|
||||
try {
|
||||
std::for_each(mWaveBuffer.begin(), mWaveBuffer.end(),
|
||||
[this](WAVEHDR &waveHdr) -> void
|
||||
{ waveOutPrepareHeader(mOutHdl, &waveHdr, static_cast<UINT>(sizeof(WAVEHDR))); }
|
||||
);
|
||||
mWritable.store(static_cast<ALuint>(mWaveBuffer.size()), std::memory_order_release);
|
||||
|
||||
mKillNow.store(false, std::memory_order_release);
|
||||
mThread = std::thread{std::mem_fn(&WinMMPlayback::mixerProc), this};
|
||||
return true;
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
ERR("Failed to start mixing thread: %s\n", e.what());
|
||||
}
|
||||
catch(...) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void WinMMPlayback::stop()
|
||||
{
|
||||
if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
|
||||
return;
|
||||
mThread.join();
|
||||
|
||||
while(mWritable.load(std::memory_order_acquire) < mWaveBuffer.size())
|
||||
mSem.wait();
|
||||
std::for_each(mWaveBuffer.begin(), mWaveBuffer.end(),
|
||||
[this](WAVEHDR &waveHdr) -> void
|
||||
{ waveOutUnprepareHeader(mOutHdl, &waveHdr, sizeof(WAVEHDR)); }
|
||||
);
|
||||
mWritable.store(0, std::memory_order_release);
|
||||
}
|
||||
|
||||
|
||||
struct WinMMCapture final : public BackendBase {
|
||||
WinMMCapture(ALCdevice *device) noexcept : BackendBase{device} { }
|
||||
~WinMMCapture() override;
|
||||
|
||||
void CALLBACK waveInProc(HWAVEIN device, UINT msg, DWORD_PTR param1, DWORD_PTR param2) noexcept;
|
||||
static void CALLBACK waveInProcC(HWAVEIN device, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2) noexcept
|
||||
{ reinterpret_cast<WinMMCapture*>(instance)->waveInProc(device, msg, param1, param2); }
|
||||
|
||||
int captureProc();
|
||||
|
||||
void open(const ALCchar *name) override;
|
||||
bool start() override;
|
||||
void stop() override;
|
||||
ALCenum captureSamples(al::byte *buffer, ALCuint samples) override;
|
||||
ALCuint availableSamples() override;
|
||||
|
||||
std::atomic<ALuint> mReadable{0u};
|
||||
al::semaphore mSem;
|
||||
ALuint mIdx{0};
|
||||
std::array<WAVEHDR,4> mWaveBuffer{};
|
||||
|
||||
HWAVEIN mInHdl{nullptr};
|
||||
|
||||
RingBufferPtr mRing{nullptr};
|
||||
|
||||
WAVEFORMATEX mFormat{};
|
||||
|
||||
std::atomic<bool> mKillNow{true};
|
||||
std::thread mThread;
|
||||
|
||||
DEF_NEWDEL(WinMMCapture)
|
||||
};
|
||||
|
||||
WinMMCapture::~WinMMCapture()
|
||||
{
|
||||
// Close the Wave device
|
||||
if(mInHdl)
|
||||
waveInClose(mInHdl);
|
||||
mInHdl = nullptr;
|
||||
|
||||
al_free(mWaveBuffer[0].lpData);
|
||||
std::fill(mWaveBuffer.begin(), mWaveBuffer.end(), WAVEHDR{});
|
||||
}
|
||||
|
||||
/* WinMMCapture::waveInProc
|
||||
*
|
||||
* Posts a message to 'WinMMCapture::captureProc' everytime a WaveIn Buffer is
|
||||
* completed and returns to the application (with more data).
|
||||
*/
|
||||
void CALLBACK WinMMCapture::waveInProc(HWAVEIN, UINT msg, DWORD_PTR, DWORD_PTR) noexcept
|
||||
{
|
||||
if(msg != WIM_DATA) return;
|
||||
mReadable.fetch_add(1, std::memory_order_acq_rel);
|
||||
mSem.post();
|
||||
}
|
||||
|
||||
int WinMMCapture::captureProc()
|
||||
{
|
||||
althrd_setname(RECORD_THREAD_NAME);
|
||||
|
||||
std::unique_lock<WinMMCapture> dlock{*this};
|
||||
while(!mKillNow.load(std::memory_order_acquire) &&
|
||||
mDevice->Connected.load(std::memory_order_acquire))
|
||||
{
|
||||
ALuint todo{mReadable.load(std::memory_order_acquire)};
|
||||
if(todo < 1)
|
||||
{
|
||||
dlock.unlock();
|
||||
mSem.wait();
|
||||
dlock.lock();
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t widx{mIdx};
|
||||
do {
|
||||
WAVEHDR &waveHdr = mWaveBuffer[widx];
|
||||
widx = (widx+1) % mWaveBuffer.size();
|
||||
|
||||
mRing->write(waveHdr.lpData, waveHdr.dwBytesRecorded / mFormat.nBlockAlign);
|
||||
mReadable.fetch_sub(1, std::memory_order_acq_rel);
|
||||
waveInAddBuffer(mInHdl, &waveHdr, sizeof(WAVEHDR));
|
||||
} while(--todo);
|
||||
mIdx = static_cast<ALuint>(widx);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void WinMMCapture::open(const ALCchar *name)
|
||||
{
|
||||
if(CaptureDevices.empty())
|
||||
ProbeCaptureDevices();
|
||||
|
||||
// Find the Device ID matching the deviceName if valid
|
||||
auto iter = name ?
|
||||
std::find(CaptureDevices.cbegin(), CaptureDevices.cend(), name) :
|
||||
CaptureDevices.cbegin();
|
||||
if(iter == CaptureDevices.cend())
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
|
||||
auto DeviceID = static_cast<UINT>(std::distance(CaptureDevices.cbegin(), iter));
|
||||
|
||||
switch(mDevice->FmtChans)
|
||||
{
|
||||
case DevFmtMono:
|
||||
case DevFmtStereo:
|
||||
break;
|
||||
|
||||
case DevFmtQuad:
|
||||
case DevFmtX51:
|
||||
case DevFmtX51Rear:
|
||||
case DevFmtX61:
|
||||
case DevFmtX71:
|
||||
case DevFmtAmbi3D:
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "%s capture not supported",
|
||||
DevFmtChannelsString(mDevice->FmtChans)};
|
||||
}
|
||||
|
||||
switch(mDevice->FmtType)
|
||||
{
|
||||
case DevFmtUByte:
|
||||
case DevFmtShort:
|
||||
case DevFmtInt:
|
||||
case DevFmtFloat:
|
||||
break;
|
||||
|
||||
case DevFmtByte:
|
||||
case DevFmtUShort:
|
||||
case DevFmtUInt:
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "%s samples not supported",
|
||||
DevFmtTypeString(mDevice->FmtType)};
|
||||
}
|
||||
|
||||
mFormat = WAVEFORMATEX{};
|
||||
mFormat.wFormatTag = (mDevice->FmtType == DevFmtFloat) ?
|
||||
WAVE_FORMAT_IEEE_FLOAT : WAVE_FORMAT_PCM;
|
||||
mFormat.nChannels = static_cast<WORD>(mDevice->channelsFromFmt());
|
||||
mFormat.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8);
|
||||
mFormat.nBlockAlign = static_cast<WORD>(mFormat.wBitsPerSample * mFormat.nChannels / 8);
|
||||
mFormat.nSamplesPerSec = mDevice->Frequency;
|
||||
mFormat.nAvgBytesPerSec = mFormat.nSamplesPerSec * mFormat.nBlockAlign;
|
||||
mFormat.cbSize = 0;
|
||||
|
||||
MMRESULT res{waveInOpen(&mInHdl, DeviceID, &mFormat,
|
||||
reinterpret_cast<DWORD_PTR>(&WinMMCapture::waveInProcC),
|
||||
reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION)};
|
||||
if(res != MMSYSERR_NOERROR)
|
||||
throw al::backend_exception{ALC_INVALID_VALUE, "waveInOpen failed: %u", res};
|
||||
|
||||
// Ensure each buffer is 50ms each
|
||||
DWORD BufferSize{mFormat.nAvgBytesPerSec / 20u};
|
||||
BufferSize -= (BufferSize % mFormat.nBlockAlign);
|
||||
|
||||
// Allocate circular memory buffer for the captured audio
|
||||
// Make sure circular buffer is at least 100ms in size
|
||||
ALuint CapturedDataSize{mDevice->BufferSize};
|
||||
CapturedDataSize = static_cast<ALuint>(maxz(CapturedDataSize, BufferSize*mWaveBuffer.size()));
|
||||
|
||||
mRing = RingBuffer::Create(CapturedDataSize, mFormat.nBlockAlign, false);
|
||||
|
||||
al_free(mWaveBuffer[0].lpData);
|
||||
mWaveBuffer[0] = WAVEHDR{};
|
||||
mWaveBuffer[0].lpData = static_cast<char*>(al_calloc(16, BufferSize * mWaveBuffer.size()));
|
||||
mWaveBuffer[0].dwBufferLength = BufferSize;
|
||||
for(size_t i{1};i < mWaveBuffer.size();++i)
|
||||
{
|
||||
mWaveBuffer[i] = WAVEHDR{};
|
||||
mWaveBuffer[i].lpData = mWaveBuffer[i-1].lpData + mWaveBuffer[i-1].dwBufferLength;
|
||||
mWaveBuffer[i].dwBufferLength = mWaveBuffer[i-1].dwBufferLength;
|
||||
}
|
||||
|
||||
mDevice->DeviceName = CaptureDevices[DeviceID];
|
||||
}
|
||||
|
||||
bool WinMMCapture::start()
|
||||
{
|
||||
try {
|
||||
for(size_t i{0};i < mWaveBuffer.size();++i)
|
||||
{
|
||||
waveInPrepareHeader(mInHdl, &mWaveBuffer[i], sizeof(WAVEHDR));
|
||||
waveInAddBuffer(mInHdl, &mWaveBuffer[i], sizeof(WAVEHDR));
|
||||
}
|
||||
|
||||
mKillNow.store(false, std::memory_order_release);
|
||||
mThread = std::thread{std::mem_fn(&WinMMCapture::captureProc), this};
|
||||
|
||||
waveInStart(mInHdl);
|
||||
return true;
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
ERR("Failed to start mixing thread: %s\n", e.what());
|
||||
}
|
||||
catch(...) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void WinMMCapture::stop()
|
||||
{
|
||||
waveInStop(mInHdl);
|
||||
|
||||
mKillNow.store(true, std::memory_order_release);
|
||||
if(mThread.joinable())
|
||||
{
|
||||
mSem.post();
|
||||
mThread.join();
|
||||
}
|
||||
|
||||
waveInReset(mInHdl);
|
||||
for(size_t i{0};i < mWaveBuffer.size();++i)
|
||||
waveInUnprepareHeader(mInHdl, &mWaveBuffer[i], sizeof(WAVEHDR));
|
||||
|
||||
mReadable.store(0, std::memory_order_release);
|
||||
mIdx = 0;
|
||||
}
|
||||
|
||||
ALCenum WinMMCapture::captureSamples(al::byte *buffer, ALCuint samples)
|
||||
{
|
||||
mRing->read(buffer, samples);
|
||||
return ALC_NO_ERROR;
|
||||
}
|
||||
|
||||
ALCuint WinMMCapture::availableSamples()
|
||||
{ return static_cast<ALCuint>(mRing->readSpace()); }
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
bool WinMMBackendFactory::init()
|
||||
{ return true; }
|
||||
|
||||
bool WinMMBackendFactory::querySupport(BackendType type)
|
||||
{ return type == BackendType::Playback || type == BackendType::Capture; }
|
||||
|
||||
void WinMMBackendFactory::probe(DevProbe type, std::string *outnames)
|
||||
{
|
||||
auto add_device = [outnames](const std::string &dname) -> void
|
||||
{
|
||||
/* +1 to also append the null char (to ensure a null-separated list and
|
||||
* double-null terminated list).
|
||||
*/
|
||||
if(!dname.empty())
|
||||
outnames->append(dname.c_str(), dname.length()+1);
|
||||
};
|
||||
switch(type)
|
||||
{
|
||||
case DevProbe::Playback:
|
||||
ProbePlaybackDevices();
|
||||
std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
|
||||
break;
|
||||
|
||||
case DevProbe::Capture:
|
||||
ProbeCaptureDevices();
|
||||
std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
BackendPtr WinMMBackendFactory::createBackend(ALCdevice *device, BackendType type)
|
||||
{
|
||||
if(type == BackendType::Playback)
|
||||
return BackendPtr{new WinMMPlayback{device}};
|
||||
if(type == BackendType::Capture)
|
||||
return BackendPtr{new WinMMCapture{device}};
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
BackendFactory &WinMMBackendFactory::getFactory()
|
||||
{
|
||||
static WinMMBackendFactory factory{};
|
||||
return factory;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
#ifndef BACKENDS_WINMM_H
|
||||
#define BACKENDS_WINMM_H
|
||||
|
||||
#include "backends/base.h"
|
||||
|
||||
struct WinMMBackendFactory final : public BackendFactory {
|
||||
public:
|
||||
bool init() override;
|
||||
|
||||
bool querySupport(BackendType type) override;
|
||||
|
||||
void probe(DevProbe type, std::string *outnames) override;
|
||||
|
||||
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
|
||||
|
||||
static BackendFactory &getFactory();
|
||||
};
|
||||
|
||||
#endif /* BACKENDS_WINMM_H */
|
||||
@@ -1,159 +0,0 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "bformatdec.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <iterator>
|
||||
#include <numeric>
|
||||
|
||||
#include "AL/al.h"
|
||||
|
||||
#include "almalloc.h"
|
||||
#include "alu.h"
|
||||
#include "ambdec.h"
|
||||
#include "filters/splitter.h"
|
||||
#include "opthelpers.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::array<float,MAX_AMBI_ORDER+1> Ambi3DDecoderHFScale{{
|
||||
1.00000000e+00f, 1.00000000e+00f
|
||||
}};
|
||||
constexpr std::array<float,MAX_AMBI_ORDER+1> Ambi3DDecoderHFScale2O{{
|
||||
7.45355990e-01f, 1.00000000e+00f, 1.00000000e+00f
|
||||
}};
|
||||
constexpr std::array<float,MAX_AMBI_ORDER+1> Ambi3DDecoderHFScale3O{{
|
||||
5.89792205e-01f, 8.79693856e-01f, 1.00000000e+00f, 1.00000000e+00f
|
||||
}};
|
||||
|
||||
inline auto GetDecoderHFScales(ALuint order) noexcept -> const std::array<float,MAX_AMBI_ORDER+1>&
|
||||
{
|
||||
if(order >= 3) return Ambi3DDecoderHFScale3O;
|
||||
if(order == 2) return Ambi3DDecoderHFScale2O;
|
||||
return Ambi3DDecoderHFScale;
|
||||
}
|
||||
|
||||
inline auto GetAmbiScales(AmbDecScale scaletype) noexcept
|
||||
-> const std::array<float,MAX_AMBI_CHANNELS>&
|
||||
{
|
||||
if(scaletype == AmbDecScale::FuMa) return AmbiScale::FromFuMa;
|
||||
if(scaletype == AmbDecScale::SN3D) return AmbiScale::FromSN3D;
|
||||
return AmbiScale::FromN3D;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
BFormatDec::BFormatDec(const AmbDecConf *conf, const bool allow_2band, const ALuint inchans,
|
||||
const ALuint srate, const ALuint (&chanmap)[MAX_OUTPUT_CHANNELS]) : mChannelDec{inchans}
|
||||
{
|
||||
mDualBand = allow_2band && (conf->FreqBands == 2);
|
||||
|
||||
const bool periphonic{(conf->ChanMask&AMBI_PERIPHONIC_MASK) != 0};
|
||||
const std::array<float,MAX_AMBI_CHANNELS> &coeff_scale = GetAmbiScales(conf->CoeffScale);
|
||||
|
||||
if(!mDualBand)
|
||||
{
|
||||
for(size_t j{0},k{0};j < mChannelDec.size();++j)
|
||||
{
|
||||
const size_t acn{periphonic ? j : AmbiIndex::From2D[j]};
|
||||
if(!(conf->ChanMask&(1u<<acn))) continue;
|
||||
const size_t order{AmbiIndex::OrderFromChannel[acn]};
|
||||
const float gain{conf->HFOrderGain[order] / coeff_scale[acn]};
|
||||
for(size_t i{0u};i < conf->Speakers.size();++i)
|
||||
{
|
||||
const size_t chanidx{chanmap[i]};
|
||||
mChannelDec[j].mGains.Single[chanidx] = conf->HFMatrix[i][k] * gain;
|
||||
}
|
||||
++k;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mChannelDec[0].mXOver.init(conf->XOverFreq / static_cast<float>(srate));
|
||||
for(size_t j{1};j < mChannelDec.size();++j)
|
||||
mChannelDec[j].mXOver = mChannelDec[0].mXOver;
|
||||
|
||||
const float ratio{std::pow(10.0f, conf->XOverRatio / 40.0f)};
|
||||
for(size_t j{0},k{0};j < mChannelDec.size();++j)
|
||||
{
|
||||
const size_t acn{periphonic ? j : AmbiIndex::From2D[j]};
|
||||
if(!(conf->ChanMask&(1u<<acn))) continue;
|
||||
const size_t order{AmbiIndex::OrderFromChannel[acn]};
|
||||
const float hfGain{conf->HFOrderGain[order] * ratio / coeff_scale[acn]};
|
||||
const float lfGain{conf->LFOrderGain[order] / ratio / coeff_scale[acn]};
|
||||
for(size_t i{0u};i < conf->Speakers.size();++i)
|
||||
{
|
||||
const size_t chanidx{chanmap[i]};
|
||||
mChannelDec[j].mGains.Dual[sHFBand][chanidx] = conf->HFMatrix[i][k] * hfGain;
|
||||
mChannelDec[j].mGains.Dual[sLFBand][chanidx] = conf->LFMatrix[i][k] * lfGain;
|
||||
}
|
||||
++k;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BFormatDec::BFormatDec(const ALuint inchans, const al::span<const ChannelDec> chancoeffs)
|
||||
: mChannelDec{inchans}
|
||||
{
|
||||
for(size_t j{0};j < mChannelDec.size();++j)
|
||||
{
|
||||
float *outcoeffs{mChannelDec[j].mGains.Single};
|
||||
for(const ChannelDec &incoeffs : chancoeffs)
|
||||
*(outcoeffs++) = incoeffs[j];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void BFormatDec::process(const al::span<FloatBufferLine> OutBuffer,
|
||||
const FloatBufferLine *InSamples, const size_t SamplesToDo)
|
||||
{
|
||||
ASSUME(SamplesToDo > 0);
|
||||
|
||||
if(mDualBand)
|
||||
{
|
||||
const al::span<const float> hfSamples{mSamples[sHFBand].data(), SamplesToDo};
|
||||
const al::span<const float> lfSamples{mSamples[sLFBand].data(), SamplesToDo};
|
||||
for(auto &chandec : mChannelDec)
|
||||
{
|
||||
chandec.mXOver.process({InSamples->data(), SamplesToDo}, mSamples[sHFBand].data(),
|
||||
mSamples[sLFBand].data());
|
||||
MixSamples(hfSamples, OutBuffer, chandec.mGains.Dual[sHFBand],
|
||||
chandec.mGains.Dual[sHFBand], 0, 0);
|
||||
MixSamples(hfSamples, OutBuffer, chandec.mGains.Dual[sLFBand],
|
||||
chandec.mGains.Dual[sLFBand], 0, 0);
|
||||
++InSamples;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for(auto &chandec : mChannelDec)
|
||||
{
|
||||
MixSamples({InSamples->data(), SamplesToDo}, OutBuffer, chandec.mGains.Single,
|
||||
chandec.mGains.Single, 0, 0);
|
||||
++InSamples;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
auto BFormatDec::GetHFOrderScales(const ALuint in_order, const ALuint out_order) noexcept
|
||||
-> std::array<float,MAX_AMBI_ORDER+1>
|
||||
{
|
||||
std::array<float,MAX_AMBI_ORDER+1> ret{};
|
||||
|
||||
assert(out_order >= in_order);
|
||||
|
||||
const auto &target = GetDecoderHFScales(out_order);
|
||||
const auto &input = GetDecoderHFScales(in_order);
|
||||
|
||||
for(size_t i{0};i < in_order+1;++i)
|
||||
ret[i] = input[i] / target[i];
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
#ifndef BFORMATDEC_H
|
||||
#define BFORMATDEC_H
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
|
||||
#include "AL/al.h"
|
||||
|
||||
#include "alcmain.h"
|
||||
#include "almalloc.h"
|
||||
#include "alspan.h"
|
||||
#include "ambidefs.h"
|
||||
#include "devformat.h"
|
||||
#include "filters/splitter.h"
|
||||
|
||||
struct AmbDecConf;
|
||||
|
||||
|
||||
using ChannelDec = float[MAX_AMBI_CHANNELS];
|
||||
|
||||
class BFormatDec {
|
||||
static constexpr size_t sHFBand{0};
|
||||
static constexpr size_t sLFBand{1};
|
||||
static constexpr size_t sNumBands{2};
|
||||
|
||||
struct ChannelDecoder {
|
||||
union MatrixU {
|
||||
float Dual[sNumBands][MAX_OUTPUT_CHANNELS];
|
||||
float Single[MAX_OUTPUT_CHANNELS];
|
||||
} mGains{};
|
||||
|
||||
/* NOTE: BandSplitter filter is unused with single-band decoding. */
|
||||
BandSplitter mXOver;
|
||||
};
|
||||
|
||||
alignas(16) std::array<FloatBufferLine,2> mSamples;
|
||||
|
||||
bool mDualBand{false};
|
||||
|
||||
al::FlexArray<ChannelDecoder> mChannelDec;
|
||||
|
||||
public:
|
||||
BFormatDec(const AmbDecConf *conf, const bool allow_2band, const ALuint inchans,
|
||||
const ALuint srate, const ALuint (&chanmap)[MAX_OUTPUT_CHANNELS]);
|
||||
BFormatDec(const ALuint inchans, const al::span<const ChannelDec> chancoeffs);
|
||||
|
||||
/* Decodes the ambisonic input to the given output channels. */
|
||||
void process(const al::span<FloatBufferLine> OutBuffer, const FloatBufferLine *InSamples,
|
||||
const size_t SamplesToDo);
|
||||
|
||||
/* Retrieves per-order HF scaling factors for "upsampling" ambisonic data. */
|
||||
static std::array<float,MAX_AMBI_ORDER+1> GetHFOrderScales(const ALuint in_order,
|
||||
const ALuint out_order) noexcept;
|
||||
|
||||
static std::unique_ptr<BFormatDec> Create(const AmbDecConf *conf, const bool allow_2band,
|
||||
const ALuint inchans, const ALuint srate, const ALuint (&chanmap)[MAX_OUTPUT_CHANNELS])
|
||||
{
|
||||
return std::unique_ptr<BFormatDec>{new(FamCount{inchans})
|
||||
BFormatDec{conf, allow_2band, inchans, srate, chanmap}};
|
||||
}
|
||||
static std::unique_ptr<BFormatDec> Create(const ALuint inchans,
|
||||
const al::span<const ChannelDec> chancoeffs)
|
||||
{
|
||||
return std::unique_ptr<BFormatDec>{new(FamCount{inchans}) BFormatDec{inchans, chancoeffs}};
|
||||
}
|
||||
|
||||
DEF_FAM_NEWDEL(BFormatDec, mChannelDec)
|
||||
};
|
||||
|
||||
#endif /* BFORMATDEC_H */
|
||||
-183
@@ -1,183 +0,0 @@
|
||||
/*-
|
||||
* Copyright (c) 2005 Boris Mikhaylov
|
||||
*
|
||||
* 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 the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <iterator>
|
||||
|
||||
#include "bs2b.h"
|
||||
#include "math_defs.h"
|
||||
|
||||
|
||||
/* Set up all data. */
|
||||
static void init(struct bs2b *bs2b)
|
||||
{
|
||||
float Fc_lo, Fc_hi;
|
||||
float G_lo, G_hi;
|
||||
float x, g;
|
||||
|
||||
switch(bs2b->level)
|
||||
{
|
||||
case BS2B_LOW_CLEVEL: /* Low crossfeed level */
|
||||
Fc_lo = 360.0f;
|
||||
Fc_hi = 501.0f;
|
||||
G_lo = 0.398107170553497f;
|
||||
G_hi = 0.205671765275719f;
|
||||
break;
|
||||
|
||||
case BS2B_MIDDLE_CLEVEL: /* Middle crossfeed level */
|
||||
Fc_lo = 500.0f;
|
||||
Fc_hi = 711.0f;
|
||||
G_lo = 0.459726988530872f;
|
||||
G_hi = 0.228208484414988f;
|
||||
break;
|
||||
|
||||
case BS2B_HIGH_CLEVEL: /* High crossfeed level (virtual speakers are closer to itself) */
|
||||
Fc_lo = 700.0f;
|
||||
Fc_hi = 1021.0f;
|
||||
G_lo = 0.530884444230988f;
|
||||
G_hi = 0.250105790667544f;
|
||||
break;
|
||||
|
||||
case BS2B_LOW_ECLEVEL: /* Low easy crossfeed level */
|
||||
Fc_lo = 360.0f;
|
||||
Fc_hi = 494.0f;
|
||||
G_lo = 0.316227766016838f;
|
||||
G_hi = 0.168236228897329f;
|
||||
break;
|
||||
|
||||
case BS2B_MIDDLE_ECLEVEL: /* Middle easy crossfeed level */
|
||||
Fc_lo = 500.0f;
|
||||
Fc_hi = 689.0f;
|
||||
G_lo = 0.354813389233575f;
|
||||
G_hi = 0.187169483835901f;
|
||||
break;
|
||||
|
||||
default: /* High easy crossfeed level */
|
||||
bs2b->level = BS2B_HIGH_ECLEVEL;
|
||||
|
||||
Fc_lo = 700.0f;
|
||||
Fc_hi = 975.0f;
|
||||
G_lo = 0.398107170553497f;
|
||||
G_hi = 0.205671765275719f;
|
||||
break;
|
||||
} /* switch */
|
||||
|
||||
g = 1.0f / (1.0f - G_hi + G_lo);
|
||||
|
||||
/* $fc = $Fc / $s;
|
||||
* $d = 1 / 2 / pi / $fc;
|
||||
* $x = exp(-1 / $d);
|
||||
*/
|
||||
x = std::exp(-al::MathDefs<float>::Tau() * Fc_lo / static_cast<float>(bs2b->srate));
|
||||
bs2b->b1_lo = x;
|
||||
bs2b->a0_lo = G_lo * (1.0f - x) * g;
|
||||
|
||||
x = std::exp(-al::MathDefs<float>::Tau() * Fc_hi / static_cast<float>(bs2b->srate));
|
||||
bs2b->b1_hi = x;
|
||||
bs2b->a0_hi = (1.0f - G_hi * (1.0f - x)) * g;
|
||||
bs2b->a1_hi = -x * g;
|
||||
} /* init */
|
||||
|
||||
|
||||
/* Exported functions.
|
||||
* See descriptions in "bs2b.h"
|
||||
*/
|
||||
|
||||
void bs2b_set_params(struct bs2b *bs2b, int level, int srate)
|
||||
{
|
||||
if(srate <= 0) srate = 1;
|
||||
|
||||
bs2b->level = level;
|
||||
bs2b->srate = srate;
|
||||
init(bs2b);
|
||||
} /* bs2b_set_params */
|
||||
|
||||
int bs2b_get_level(struct bs2b *bs2b)
|
||||
{
|
||||
return bs2b->level;
|
||||
} /* bs2b_get_level */
|
||||
|
||||
int bs2b_get_srate(struct bs2b *bs2b)
|
||||
{
|
||||
return bs2b->srate;
|
||||
} /* bs2b_get_srate */
|
||||
|
||||
void bs2b_clear(struct bs2b *bs2b)
|
||||
{
|
||||
std::fill(std::begin(bs2b->history), std::end(bs2b->history), bs2b::t_last_sample{});
|
||||
} /* bs2b_clear */
|
||||
|
||||
void bs2b_cross_feed(struct bs2b *bs2b, float *Left, float *Right, size_t SamplesToDo)
|
||||
{
|
||||
const float a0_lo{bs2b->a0_lo};
|
||||
const float b1_lo{bs2b->b1_lo};
|
||||
const float a0_hi{bs2b->a0_hi};
|
||||
const float a1_hi{bs2b->a1_hi};
|
||||
const float b1_hi{bs2b->b1_hi};
|
||||
float lsamples[128][2];
|
||||
float rsamples[128][2];
|
||||
|
||||
for(size_t base{0};base < SamplesToDo;)
|
||||
{
|
||||
const size_t todo{std::min<size_t>(128, SamplesToDo-base)};
|
||||
|
||||
/* Process left input */
|
||||
float z_lo{bs2b->history[0].lo};
|
||||
float z_hi{bs2b->history[0].hi};
|
||||
for(size_t i{0};i < todo;i++)
|
||||
{
|
||||
lsamples[i][0] = a0_lo*Left[i] + z_lo;
|
||||
z_lo = b1_lo*lsamples[i][0];
|
||||
|
||||
lsamples[i][1] = a0_hi*Left[i] + z_hi;
|
||||
z_hi = a1_hi*Left[i] + b1_hi*lsamples[i][1];
|
||||
}
|
||||
bs2b->history[0].lo = z_lo;
|
||||
bs2b->history[0].hi = z_hi;
|
||||
|
||||
/* Process right input */
|
||||
z_lo = bs2b->history[1].lo;
|
||||
z_hi = bs2b->history[1].hi;
|
||||
for(size_t i{0};i < todo;i++)
|
||||
{
|
||||
rsamples[i][0] = a0_lo*Right[i] + z_lo;
|
||||
z_lo = b1_lo*rsamples[i][0];
|
||||
|
||||
rsamples[i][1] = a0_hi*Right[i] + z_hi;
|
||||
z_hi = a1_hi*Right[i] + b1_hi*rsamples[i][1];
|
||||
}
|
||||
bs2b->history[1].lo = z_lo;
|
||||
bs2b->history[1].hi = z_hi;
|
||||
|
||||
/* Crossfeed */
|
||||
for(size_t i{0};i < todo;i++)
|
||||
*(Left++) = lsamples[i][1] + rsamples[i][0];
|
||||
for(size_t i{0};i < todo;i++)
|
||||
*(Right++) = rsamples[i][1] + lsamples[i][0];
|
||||
|
||||
base += todo;
|
||||
}
|
||||
} /* bs2b_cross_feed */
|
||||
-89
@@ -1,89 +0,0 @@
|
||||
/*-
|
||||
* Copyright (c) 2005 Boris Mikhaylov
|
||||
*
|
||||
* 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 the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef BS2B_H
|
||||
#define BS2B_H
|
||||
|
||||
#include "almalloc.h"
|
||||
|
||||
/* Number of crossfeed levels */
|
||||
#define BS2B_CLEVELS 3
|
||||
|
||||
/* Normal crossfeed levels */
|
||||
#define BS2B_HIGH_CLEVEL 3
|
||||
#define BS2B_MIDDLE_CLEVEL 2
|
||||
#define BS2B_LOW_CLEVEL 1
|
||||
|
||||
/* Easy crossfeed levels */
|
||||
#define BS2B_HIGH_ECLEVEL BS2B_HIGH_CLEVEL + BS2B_CLEVELS
|
||||
#define BS2B_MIDDLE_ECLEVEL BS2B_MIDDLE_CLEVEL + BS2B_CLEVELS
|
||||
#define BS2B_LOW_ECLEVEL BS2B_LOW_CLEVEL + BS2B_CLEVELS
|
||||
|
||||
/* Default crossfeed levels */
|
||||
#define BS2B_DEFAULT_CLEVEL BS2B_HIGH_ECLEVEL
|
||||
/* Default sample rate (Hz) */
|
||||
#define BS2B_DEFAULT_SRATE 44100
|
||||
|
||||
struct bs2b {
|
||||
int level; /* Crossfeed level */
|
||||
int srate; /* Sample rate (Hz) */
|
||||
|
||||
/* Lowpass IIR filter coefficients */
|
||||
float a0_lo;
|
||||
float b1_lo;
|
||||
|
||||
/* Highboost IIR filter coefficients */
|
||||
float a0_hi;
|
||||
float a1_hi;
|
||||
float b1_hi;
|
||||
|
||||
/* Buffer of filter history
|
||||
* [0] - first channel, [1] - second channel
|
||||
*/
|
||||
struct t_last_sample {
|
||||
float lo;
|
||||
float hi;
|
||||
} history[2];
|
||||
|
||||
DEF_NEWDEL(bs2b)
|
||||
};
|
||||
|
||||
/* Clear buffers and set new coefficients with new crossfeed level and sample
|
||||
* rate values.
|
||||
* level - crossfeed level of *LEVEL values.
|
||||
* srate - sample rate by Hz.
|
||||
*/
|
||||
void bs2b_set_params(bs2b *bs2b, int level, int srate);
|
||||
|
||||
/* Return current crossfeed level value */
|
||||
int bs2b_get_level(bs2b *bs2b);
|
||||
|
||||
/* Return current sample rate value */
|
||||
int bs2b_get_srate(bs2b *bs2b);
|
||||
|
||||
/* Clear buffer */
|
||||
void bs2b_clear(bs2b *bs2b);
|
||||
|
||||
void bs2b_cross_feed(bs2b *bs2b, float *Left, float *Right, size_t SamplesToDo);
|
||||
|
||||
#endif /* BS2B_H */
|
||||
@@ -1,9 +0,0 @@
|
||||
#ifndef AL_COMPAT_H
|
||||
#define AL_COMPAT_H
|
||||
|
||||
#include <string>
|
||||
|
||||
struct PathNamePair { std::string path, fname; };
|
||||
const PathNamePair &GetProcBinary(void);
|
||||
|
||||
#endif /* AL_COMPAT_H */
|
||||
@@ -1,360 +0,0 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "converter.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
|
||||
#include "AL/al.h"
|
||||
|
||||
#include "albyte.h"
|
||||
#include "alu.h"
|
||||
#include "fpu_modes.h"
|
||||
#include "mixer/defs.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
/* Base template left undefined. Should be marked =delete, but Clang 3.8.1
|
||||
* chokes on that given the inline specializations.
|
||||
*/
|
||||
template<DevFmtType T>
|
||||
inline ALfloat LoadSample(typename DevFmtTypeTraits<T>::Type val) noexcept;
|
||||
|
||||
template<> inline ALfloat LoadSample<DevFmtByte>(DevFmtTypeTraits<DevFmtByte>::Type val) noexcept
|
||||
{ return val * (1.0f/128.0f); }
|
||||
template<> inline ALfloat LoadSample<DevFmtShort>(DevFmtTypeTraits<DevFmtShort>::Type val) noexcept
|
||||
{ return val * (1.0f/32768.0f); }
|
||||
template<> inline ALfloat LoadSample<DevFmtInt>(DevFmtTypeTraits<DevFmtInt>::Type val) noexcept
|
||||
{ return static_cast<float>(val) * (1.0f/2147483648.0f); }
|
||||
template<> inline ALfloat LoadSample<DevFmtFloat>(DevFmtTypeTraits<DevFmtFloat>::Type val) noexcept
|
||||
{ return val; }
|
||||
|
||||
template<> inline ALfloat LoadSample<DevFmtUByte>(DevFmtTypeTraits<DevFmtUByte>::Type val) noexcept
|
||||
{ return LoadSample<DevFmtByte>(static_cast<ALbyte>(val - 128)); }
|
||||
template<> inline ALfloat LoadSample<DevFmtUShort>(DevFmtTypeTraits<DevFmtUShort>::Type val) noexcept
|
||||
{ return LoadSample<DevFmtShort>(static_cast<ALshort>(val - 32768)); }
|
||||
template<> inline ALfloat LoadSample<DevFmtUInt>(DevFmtTypeTraits<DevFmtUInt>::Type val) noexcept
|
||||
{ return LoadSample<DevFmtInt>(static_cast<ALint>(val - 2147483648u)); }
|
||||
|
||||
|
||||
template<DevFmtType T>
|
||||
inline void LoadSampleArray(ALfloat *RESTRICT dst, const void *src, const size_t srcstep,
|
||||
const size_t samples) noexcept
|
||||
{
|
||||
using SampleType = typename DevFmtTypeTraits<T>::Type;
|
||||
|
||||
const SampleType *ssrc = static_cast<const SampleType*>(src);
|
||||
for(size_t i{0u};i < samples;i++)
|
||||
dst[i] = LoadSample<T>(ssrc[i*srcstep]);
|
||||
}
|
||||
|
||||
void LoadSamples(ALfloat *dst, const ALvoid *src, const size_t srcstep, const DevFmtType srctype,
|
||||
const size_t samples) noexcept
|
||||
{
|
||||
#define HANDLE_FMT(T) \
|
||||
case T: LoadSampleArray<T>(dst, src, srcstep, samples); break
|
||||
switch(srctype)
|
||||
{
|
||||
HANDLE_FMT(DevFmtByte);
|
||||
HANDLE_FMT(DevFmtUByte);
|
||||
HANDLE_FMT(DevFmtShort);
|
||||
HANDLE_FMT(DevFmtUShort);
|
||||
HANDLE_FMT(DevFmtInt);
|
||||
HANDLE_FMT(DevFmtUInt);
|
||||
HANDLE_FMT(DevFmtFloat);
|
||||
}
|
||||
#undef HANDLE_FMT
|
||||
}
|
||||
|
||||
|
||||
template<DevFmtType T>
|
||||
inline typename DevFmtTypeTraits<T>::Type StoreSample(ALfloat) noexcept;
|
||||
|
||||
template<> inline ALfloat StoreSample<DevFmtFloat>(ALfloat val) noexcept
|
||||
{ return val; }
|
||||
template<> inline ALint StoreSample<DevFmtInt>(ALfloat val) noexcept
|
||||
{ return fastf2i(clampf(val*2147483648.0f, -2147483648.0f, 2147483520.0f)); }
|
||||
template<> inline ALshort StoreSample<DevFmtShort>(ALfloat val) noexcept
|
||||
{ return static_cast<ALshort>(fastf2i(clampf(val*32768.0f, -32768.0f, 32767.0f))); }
|
||||
template<> inline ALbyte StoreSample<DevFmtByte>(ALfloat val) noexcept
|
||||
{ return static_cast<ALbyte>(fastf2i(clampf(val*128.0f, -128.0f, 127.0f))); }
|
||||
|
||||
/* Define unsigned output variations. */
|
||||
template<> inline ALuint StoreSample<DevFmtUInt>(ALfloat val) noexcept
|
||||
{ return static_cast<ALuint>(StoreSample<DevFmtInt>(val)) + 2147483648u; }
|
||||
template<> inline ALushort StoreSample<DevFmtUShort>(ALfloat val) noexcept
|
||||
{ return static_cast<ALushort>(StoreSample<DevFmtShort>(val) + 32768); }
|
||||
template<> inline ALubyte StoreSample<DevFmtUByte>(ALfloat val) noexcept
|
||||
{ return static_cast<ALubyte>(StoreSample<DevFmtByte>(val) + 128); }
|
||||
|
||||
template<DevFmtType T>
|
||||
inline void StoreSampleArray(void *dst, const ALfloat *RESTRICT src, const size_t dststep,
|
||||
const size_t samples) noexcept
|
||||
{
|
||||
using SampleType = typename DevFmtTypeTraits<T>::Type;
|
||||
|
||||
SampleType *sdst = static_cast<SampleType*>(dst);
|
||||
for(size_t i{0u};i < samples;i++)
|
||||
sdst[i*dststep] = StoreSample<T>(src[i]);
|
||||
}
|
||||
|
||||
|
||||
void StoreSamples(ALvoid *dst, const ALfloat *src, const size_t dststep, const DevFmtType dsttype,
|
||||
const size_t samples) noexcept
|
||||
{
|
||||
#define HANDLE_FMT(T) \
|
||||
case T: StoreSampleArray<T>(dst, src, dststep, samples); break
|
||||
switch(dsttype)
|
||||
{
|
||||
HANDLE_FMT(DevFmtByte);
|
||||
HANDLE_FMT(DevFmtUByte);
|
||||
HANDLE_FMT(DevFmtShort);
|
||||
HANDLE_FMT(DevFmtUShort);
|
||||
HANDLE_FMT(DevFmtInt);
|
||||
HANDLE_FMT(DevFmtUInt);
|
||||
HANDLE_FMT(DevFmtFloat);
|
||||
}
|
||||
#undef HANDLE_FMT
|
||||
}
|
||||
|
||||
|
||||
template<DevFmtType T>
|
||||
void Mono2Stereo(ALfloat *RESTRICT dst, const void *src, const size_t frames) noexcept
|
||||
{
|
||||
using SampleType = typename DevFmtTypeTraits<T>::Type;
|
||||
|
||||
const SampleType *ssrc = static_cast<const SampleType*>(src);
|
||||
for(size_t i{0u};i < frames;i++)
|
||||
dst[i*2 + 1] = dst[i*2 + 0] = LoadSample<T>(ssrc[i]) * 0.707106781187f;
|
||||
}
|
||||
|
||||
template<DevFmtType T>
|
||||
void Stereo2Mono(ALfloat *RESTRICT dst, const void *src, const size_t frames) noexcept
|
||||
{
|
||||
using SampleType = typename DevFmtTypeTraits<T>::Type;
|
||||
|
||||
const SampleType *ssrc = static_cast<const SampleType*>(src);
|
||||
for(size_t i{0u};i < frames;i++)
|
||||
dst[i] = (LoadSample<T>(ssrc[i*2 + 0])+LoadSample<T>(ssrc[i*2 + 1])) *
|
||||
0.707106781187f;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
SampleConverterPtr CreateSampleConverter(DevFmtType srcType, DevFmtType dstType, size_t numchans,
|
||||
ALuint srcRate, ALuint dstRate, Resampler resampler)
|
||||
{
|
||||
if(numchans < 1 || srcRate < 1 || dstRate < 1)
|
||||
return nullptr;
|
||||
|
||||
SampleConverterPtr converter{new (FamCount{numchans}) SampleConverter{numchans}};
|
||||
converter->mSrcType = srcType;
|
||||
converter->mDstType = dstType;
|
||||
converter->mSrcTypeSize = BytesFromDevFmt(srcType);
|
||||
converter->mDstTypeSize = BytesFromDevFmt(dstType);
|
||||
|
||||
converter->mSrcPrepCount = 0;
|
||||
converter->mFracOffset = 0;
|
||||
|
||||
/* Have to set the mixer FPU mode since that's what the resampler code expects. */
|
||||
FPUCtl mixer_mode{};
|
||||
auto step = static_cast<ALuint>(
|
||||
mind(srcRate*double{FRACTIONONE}/dstRate + 0.5, MAX_PITCH*FRACTIONONE));
|
||||
converter->mIncrement = maxu(step, 1);
|
||||
if(converter->mIncrement == FRACTIONONE)
|
||||
converter->mResample = Resample_<CopyTag,CTag>;
|
||||
else
|
||||
converter->mResample = PrepareResampler(resampler, converter->mIncrement,
|
||||
&converter->mState);
|
||||
|
||||
return converter;
|
||||
}
|
||||
|
||||
ALuint SampleConverter::availableOut(ALuint srcframes) const
|
||||
{
|
||||
ALint prepcount{mSrcPrepCount};
|
||||
if(prepcount < 0)
|
||||
{
|
||||
/* Negative prepcount means we need to skip that many input samples. */
|
||||
if(static_cast<ALuint>(-prepcount) >= srcframes)
|
||||
return 0;
|
||||
srcframes -= static_cast<ALuint>(-prepcount);
|
||||
prepcount = 0;
|
||||
}
|
||||
|
||||
if(srcframes < 1)
|
||||
{
|
||||
/* No output samples if there's no input samples. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(prepcount < MAX_RESAMPLER_PADDING
|
||||
&& static_cast<ALuint>(MAX_RESAMPLER_PADDING - prepcount) >= srcframes)
|
||||
{
|
||||
/* Not enough input samples to generate an output sample. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto DataSize64 = static_cast<uint64_t>(prepcount);
|
||||
DataSize64 += srcframes;
|
||||
DataSize64 -= MAX_RESAMPLER_PADDING;
|
||||
DataSize64 <<= FRACTIONBITS;
|
||||
DataSize64 -= mFracOffset;
|
||||
|
||||
/* If we have a full prep, we can generate at least one sample. */
|
||||
return static_cast<ALuint>(clampu64((DataSize64 + mIncrement-1)/mIncrement, 1, BUFFERSIZE));
|
||||
}
|
||||
|
||||
ALuint SampleConverter::convert(const ALvoid **src, ALuint *srcframes, ALvoid *dst, ALuint dstframes)
|
||||
{
|
||||
const ALuint SrcFrameSize{static_cast<ALuint>(mChan.size()) * mSrcTypeSize};
|
||||
const ALuint DstFrameSize{static_cast<ALuint>(mChan.size()) * mDstTypeSize};
|
||||
const ALuint increment{mIncrement};
|
||||
auto SamplesIn = static_cast<const al::byte*>(*src);
|
||||
ALuint NumSrcSamples{*srcframes};
|
||||
|
||||
FPUCtl mixer_mode{};
|
||||
ALuint pos{0};
|
||||
while(pos < dstframes && NumSrcSamples > 0)
|
||||
{
|
||||
ALint prepcount{mSrcPrepCount};
|
||||
if(prepcount < 0)
|
||||
{
|
||||
/* Negative prepcount means we need to skip that many input samples. */
|
||||
if(static_cast<ALuint>(-prepcount) >= NumSrcSamples)
|
||||
{
|
||||
mSrcPrepCount = static_cast<ALint>(NumSrcSamples) + prepcount;
|
||||
NumSrcSamples = 0;
|
||||
break;
|
||||
}
|
||||
SamplesIn += SrcFrameSize*static_cast<ALuint>(-prepcount);
|
||||
NumSrcSamples -= static_cast<ALuint>(-prepcount);
|
||||
mSrcPrepCount = 0;
|
||||
continue;
|
||||
}
|
||||
ALuint toread{minu(NumSrcSamples, BUFFERSIZE - MAX_RESAMPLER_PADDING)};
|
||||
|
||||
if(prepcount < MAX_RESAMPLER_PADDING
|
||||
&& static_cast<ALuint>(MAX_RESAMPLER_PADDING - prepcount) >= toread)
|
||||
{
|
||||
/* Not enough input samples to generate an output sample. Store
|
||||
* what we're given for later.
|
||||
*/
|
||||
for(size_t chan{0u};chan < mChan.size();chan++)
|
||||
LoadSamples(&mChan[chan].PrevSamples[prepcount], SamplesIn + mSrcTypeSize*chan,
|
||||
mChan.size(), mSrcType, toread);
|
||||
|
||||
mSrcPrepCount = prepcount + static_cast<ALint>(toread);
|
||||
NumSrcSamples = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
ALfloat *RESTRICT SrcData{mSrcSamples};
|
||||
ALfloat *RESTRICT DstData{mDstSamples};
|
||||
ALuint DataPosFrac{mFracOffset};
|
||||
auto DataSize64 = static_cast<uint64_t>(prepcount);
|
||||
DataSize64 += toread;
|
||||
DataSize64 -= MAX_RESAMPLER_PADDING;
|
||||
DataSize64 <<= FRACTIONBITS;
|
||||
DataSize64 -= DataPosFrac;
|
||||
|
||||
/* If we have a full prep, we can generate at least one sample. */
|
||||
auto DstSize = static_cast<ALuint>(
|
||||
clampu64((DataSize64 + increment-1)/increment, 1, BUFFERSIZE));
|
||||
DstSize = minu(DstSize, dstframes-pos);
|
||||
|
||||
for(size_t chan{0u};chan < mChan.size();chan++)
|
||||
{
|
||||
const al::byte *SrcSamples{SamplesIn + mSrcTypeSize*chan};
|
||||
al::byte *DstSamples = static_cast<al::byte*>(dst) + mDstTypeSize*chan;
|
||||
|
||||
/* Load the previous samples into the source data first, then the
|
||||
* new samples from the input buffer.
|
||||
*/
|
||||
std::copy_n(mChan[chan].PrevSamples, prepcount, SrcData);
|
||||
LoadSamples(SrcData + prepcount, SrcSamples, mChan.size(), mSrcType, toread);
|
||||
|
||||
/* Store as many prep samples for next time as possible, given the
|
||||
* number of output samples being generated.
|
||||
*/
|
||||
ALuint SrcDataEnd{(DstSize*increment + DataPosFrac)>>FRACTIONBITS};
|
||||
if(SrcDataEnd >= static_cast<ALuint>(prepcount)+toread)
|
||||
std::fill(std::begin(mChan[chan].PrevSamples),
|
||||
std::end(mChan[chan].PrevSamples), 0.0f);
|
||||
else
|
||||
{
|
||||
const size_t len{minz(al::size(mChan[chan].PrevSamples),
|
||||
static_cast<ALuint>(prepcount)+toread-SrcDataEnd)};
|
||||
std::copy_n(SrcData+SrcDataEnd, len, mChan[chan].PrevSamples);
|
||||
std::fill(std::begin(mChan[chan].PrevSamples)+len,
|
||||
std::end(mChan[chan].PrevSamples), 0.0f);
|
||||
}
|
||||
|
||||
/* Now resample, and store the result in the output buffer. */
|
||||
const ALfloat *ResampledData{mResample(&mState, SrcData+(MAX_RESAMPLER_PADDING>>1),
|
||||
DataPosFrac, increment, {DstData, DstSize})};
|
||||
|
||||
StoreSamples(DstSamples, ResampledData, mChan.size(), mDstType, DstSize);
|
||||
}
|
||||
|
||||
/* Update the number of prep samples still available, as well as the
|
||||
* fractional offset.
|
||||
*/
|
||||
DataPosFrac += increment*DstSize;
|
||||
mSrcPrepCount = mini(prepcount + static_cast<ALint>(toread - (DataPosFrac>>FRACTIONBITS)),
|
||||
MAX_RESAMPLER_PADDING);
|
||||
mFracOffset = DataPosFrac & FRACTIONMASK;
|
||||
|
||||
/* Update the src and dst pointers in case there's still more to do. */
|
||||
SamplesIn += SrcFrameSize*(DataPosFrac>>FRACTIONBITS);
|
||||
NumSrcSamples -= minu(NumSrcSamples, (DataPosFrac>>FRACTIONBITS));
|
||||
|
||||
dst = static_cast<al::byte*>(dst) + DstFrameSize*DstSize;
|
||||
pos += DstSize;
|
||||
}
|
||||
|
||||
*src = SamplesIn;
|
||||
*srcframes = NumSrcSamples;
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
|
||||
void ChannelConverter::convert(const ALvoid *src, ALfloat *dst, ALuint frames) const
|
||||
{
|
||||
if(mSrcChans == DevFmtStereo && mDstChans == DevFmtMono)
|
||||
{
|
||||
switch(mSrcType)
|
||||
{
|
||||
#define HANDLE_FMT(T) case T: Stereo2Mono<T>(dst, src, frames); break
|
||||
HANDLE_FMT(DevFmtByte);
|
||||
HANDLE_FMT(DevFmtUByte);
|
||||
HANDLE_FMT(DevFmtShort);
|
||||
HANDLE_FMT(DevFmtUShort);
|
||||
HANDLE_FMT(DevFmtInt);
|
||||
HANDLE_FMT(DevFmtUInt);
|
||||
HANDLE_FMT(DevFmtFloat);
|
||||
#undef HANDLE_FMT
|
||||
}
|
||||
}
|
||||
else if(mSrcChans == DevFmtMono && mDstChans == DevFmtStereo)
|
||||
{
|
||||
switch(mSrcType)
|
||||
{
|
||||
#define HANDLE_FMT(T) case T: Mono2Stereo<T>(dst, src, frames); break
|
||||
HANDLE_FMT(DevFmtByte);
|
||||
HANDLE_FMT(DevFmtUByte);
|
||||
HANDLE_FMT(DevFmtShort);
|
||||
HANDLE_FMT(DevFmtUShort);
|
||||
HANDLE_FMT(DevFmtInt);
|
||||
HANDLE_FMT(DevFmtUInt);
|
||||
HANDLE_FMT(DevFmtFloat);
|
||||
#undef HANDLE_FMT
|
||||
}
|
||||
}
|
||||
else
|
||||
LoadSamples(dst, src, 1u, mSrcType, frames * ChannelsFromDevFmt(mSrcChans, 0));
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
#ifndef CONVERTER_H
|
||||
#define CONVERTER_H
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
|
||||
#include "AL/al.h"
|
||||
|
||||
#include "alcmain.h"
|
||||
#include "almalloc.h"
|
||||
#include "alnumeric.h"
|
||||
#include "alu.h"
|
||||
#include "devformat.h"
|
||||
#include "voice.h"
|
||||
|
||||
|
||||
struct SampleConverter {
|
||||
DevFmtType mSrcType{};
|
||||
DevFmtType mDstType{};
|
||||
ALuint mSrcTypeSize{};
|
||||
ALuint mDstTypeSize{};
|
||||
|
||||
ALint mSrcPrepCount{};
|
||||
|
||||
ALuint mFracOffset{};
|
||||
ALuint mIncrement{};
|
||||
InterpState mState{};
|
||||
ResamplerFunc mResample{};
|
||||
|
||||
alignas(16) ALfloat mSrcSamples[BUFFERSIZE]{};
|
||||
alignas(16) ALfloat mDstSamples[BUFFERSIZE]{};
|
||||
|
||||
struct ChanSamples {
|
||||
alignas(16) ALfloat PrevSamples[MAX_RESAMPLER_PADDING];
|
||||
};
|
||||
al::FlexArray<ChanSamples> mChan;
|
||||
|
||||
SampleConverter(size_t numchans) : mChan{numchans} { }
|
||||
|
||||
ALuint convert(const ALvoid **src, ALuint *srcframes, ALvoid *dst, ALuint dstframes);
|
||||
ALuint availableOut(ALuint srcframes) const;
|
||||
|
||||
DEF_FAM_NEWDEL(SampleConverter, mChan)
|
||||
};
|
||||
using SampleConverterPtr = std::unique_ptr<SampleConverter>;
|
||||
|
||||
SampleConverterPtr CreateSampleConverter(DevFmtType srcType, DevFmtType dstType, size_t numchans,
|
||||
ALuint srcRate, ALuint dstRate, Resampler resampler);
|
||||
|
||||
|
||||
struct ChannelConverter {
|
||||
DevFmtType mSrcType;
|
||||
DevFmtChannels mSrcChans;
|
||||
DevFmtChannels mDstChans;
|
||||
|
||||
bool is_active() const noexcept { return mSrcChans != mDstChans; }
|
||||
|
||||
void convert(const ALvoid *src, ALfloat *dst, ALuint frames) const;
|
||||
};
|
||||
|
||||
#endif /* CONVERTER_H */
|
||||
@@ -1,16 +0,0 @@
|
||||
#ifndef CPU_CAPS_H
|
||||
#define CPU_CAPS_H
|
||||
|
||||
|
||||
extern int CPUCapFlags;
|
||||
enum {
|
||||
CPU_CAP_SSE = 1<<0,
|
||||
CPU_CAP_SSE2 = 1<<1,
|
||||
CPU_CAP_SSE3 = 1<<2,
|
||||
CPU_CAP_SSE4_1 = 1<<3,
|
||||
CPU_CAP_NEON = 1<<4,
|
||||
};
|
||||
|
||||
void FillCPUCaps(int capfilter);
|
||||
|
||||
#endif /* CPU_CAPS_H */
|
||||
-121
@@ -1,121 +0,0 @@
|
||||
#ifndef ALC_DEVFORMAT_H
|
||||
#define ALC_DEVFORMAT_H
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "AL/al.h"
|
||||
#include "AL/alext.h"
|
||||
|
||||
#include "inprogext.h"
|
||||
|
||||
|
||||
enum Channel {
|
||||
FrontLeft = 0,
|
||||
FrontRight,
|
||||
FrontCenter,
|
||||
LFE,
|
||||
BackLeft,
|
||||
BackRight,
|
||||
BackCenter,
|
||||
SideLeft,
|
||||
SideRight,
|
||||
|
||||
UpperFrontLeft,
|
||||
UpperFrontRight,
|
||||
UpperBackLeft,
|
||||
UpperBackRight,
|
||||
LowerFrontLeft,
|
||||
LowerFrontRight,
|
||||
LowerBackLeft,
|
||||
LowerBackRight,
|
||||
|
||||
Aux0,
|
||||
Aux1,
|
||||
Aux2,
|
||||
Aux3,
|
||||
Aux4,
|
||||
Aux5,
|
||||
Aux6,
|
||||
Aux7,
|
||||
Aux8,
|
||||
Aux9,
|
||||
Aux10,
|
||||
Aux11,
|
||||
Aux12,
|
||||
Aux13,
|
||||
Aux14,
|
||||
Aux15,
|
||||
|
||||
MaxChannels
|
||||
};
|
||||
|
||||
|
||||
/* Device formats */
|
||||
enum DevFmtType : ALenum {
|
||||
DevFmtByte = ALC_BYTE_SOFT,
|
||||
DevFmtUByte = ALC_UNSIGNED_BYTE_SOFT,
|
||||
DevFmtShort = ALC_SHORT_SOFT,
|
||||
DevFmtUShort = ALC_UNSIGNED_SHORT_SOFT,
|
||||
DevFmtInt = ALC_INT_SOFT,
|
||||
DevFmtUInt = ALC_UNSIGNED_INT_SOFT,
|
||||
DevFmtFloat = ALC_FLOAT_SOFT,
|
||||
|
||||
DevFmtTypeDefault = DevFmtFloat
|
||||
};
|
||||
enum DevFmtChannels : ALenum {
|
||||
DevFmtMono = ALC_MONO_SOFT,
|
||||
DevFmtStereo = ALC_STEREO_SOFT,
|
||||
DevFmtQuad = ALC_QUAD_SOFT,
|
||||
DevFmtX51 = ALC_5POINT1_SOFT,
|
||||
DevFmtX61 = ALC_6POINT1_SOFT,
|
||||
DevFmtX71 = ALC_7POINT1_SOFT,
|
||||
DevFmtAmbi3D = ALC_BFORMAT3D_SOFT,
|
||||
|
||||
/* Similar to 5.1, except using rear channels instead of sides */
|
||||
DevFmtX51Rear = 0x70000000,
|
||||
|
||||
DevFmtChannelsDefault = DevFmtStereo
|
||||
};
|
||||
#define MAX_OUTPUT_CHANNELS (16)
|
||||
|
||||
/* DevFmtType traits, providing the type, etc given a DevFmtType. */
|
||||
template<DevFmtType T>
|
||||
struct DevFmtTypeTraits { };
|
||||
|
||||
template<>
|
||||
struct DevFmtTypeTraits<DevFmtByte> { using Type = int8_t; };
|
||||
template<>
|
||||
struct DevFmtTypeTraits<DevFmtUByte> { using Type = uint8_t; };
|
||||
template<>
|
||||
struct DevFmtTypeTraits<DevFmtShort> { using Type = int16_t; };
|
||||
template<>
|
||||
struct DevFmtTypeTraits<DevFmtUShort> { using Type = uint16_t; };
|
||||
template<>
|
||||
struct DevFmtTypeTraits<DevFmtInt> { using Type = int32_t; };
|
||||
template<>
|
||||
struct DevFmtTypeTraits<DevFmtUInt> { using Type = uint32_t; };
|
||||
template<>
|
||||
struct DevFmtTypeTraits<DevFmtFloat> { using Type = float; };
|
||||
|
||||
|
||||
ALuint BytesFromDevFmt(DevFmtType type) noexcept;
|
||||
ALuint ChannelsFromDevFmt(DevFmtChannels chans, ALuint ambiorder) noexcept;
|
||||
inline ALuint FrameSizeFromDevFmt(DevFmtChannels chans, DevFmtType type, ALuint ambiorder) noexcept
|
||||
{ return ChannelsFromDevFmt(chans, ambiorder) * BytesFromDevFmt(type); }
|
||||
|
||||
enum class AmbiLayout {
|
||||
FuMa = ALC_FUMA_SOFT, /* FuMa channel order */
|
||||
ACN = ALC_ACN_SOFT, /* ACN channel order */
|
||||
|
||||
Default = ACN
|
||||
};
|
||||
|
||||
enum class AmbiNorm {
|
||||
FuMa = ALC_FUMA_SOFT, /* FuMa normalization */
|
||||
SN3D = ALC_SN3D_SOFT, /* SN3D normalization */
|
||||
N3D = ALC_N3D_SOFT, /* N3D normalization */
|
||||
|
||||
Default = SN3D
|
||||
};
|
||||
|
||||
#endif /* ALC_DEVFORMAT_H */
|
||||
@@ -1,299 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 2018 by Raul Herraiz.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "al/auxeffectslot.h"
|
||||
#include "alcmain.h"
|
||||
#include "alcontext.h"
|
||||
#include "alu.h"
|
||||
#include "filters/biquad.h"
|
||||
#include "vecmat.h"
|
||||
|
||||
namespace {
|
||||
|
||||
#define MIN_FREQ 20.0f
|
||||
#define MAX_FREQ 2500.0f
|
||||
#define Q_FACTOR 5.0f
|
||||
|
||||
struct AutowahState final : public EffectState {
|
||||
/* Effect parameters */
|
||||
ALfloat mAttackRate;
|
||||
ALfloat mReleaseRate;
|
||||
ALfloat mResonanceGain;
|
||||
ALfloat mPeakGain;
|
||||
ALfloat mFreqMinNorm;
|
||||
ALfloat mBandwidthNorm;
|
||||
ALfloat mEnvDelay;
|
||||
|
||||
/* Filter components derived from the envelope. */
|
||||
struct {
|
||||
ALfloat cos_w0;
|
||||
ALfloat alpha;
|
||||
} mEnv[BUFFERSIZE];
|
||||
|
||||
struct {
|
||||
/* Effect filters' history. */
|
||||
struct {
|
||||
ALfloat z1, z2;
|
||||
} Filter;
|
||||
|
||||
/* Effect gains for each output channel */
|
||||
ALfloat CurrentGains[MAX_OUTPUT_CHANNELS];
|
||||
ALfloat TargetGains[MAX_OUTPUT_CHANNELS];
|
||||
} mChans[MAX_AMBI_CHANNELS];
|
||||
|
||||
/* Effects buffers */
|
||||
alignas(16) ALfloat mBufferOut[BUFFERSIZE];
|
||||
|
||||
|
||||
ALboolean deviceUpdate(const ALCdevice *device) override;
|
||||
void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
|
||||
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) override;
|
||||
|
||||
DEF_NEWDEL(AutowahState)
|
||||
};
|
||||
|
||||
ALboolean AutowahState::deviceUpdate(const ALCdevice*)
|
||||
{
|
||||
/* (Re-)initializing parameters and clear the buffers. */
|
||||
|
||||
mAttackRate = 1.0f;
|
||||
mReleaseRate = 1.0f;
|
||||
mResonanceGain = 10.0f;
|
||||
mPeakGain = 4.5f;
|
||||
mFreqMinNorm = 4.5e-4f;
|
||||
mBandwidthNorm = 0.05f;
|
||||
mEnvDelay = 0.0f;
|
||||
|
||||
for(auto &e : mEnv)
|
||||
{
|
||||
e.cos_w0 = 0.0f;
|
||||
e.alpha = 0.0f;
|
||||
}
|
||||
|
||||
for(auto &chan : mChans)
|
||||
{
|
||||
std::fill(std::begin(chan.CurrentGains), std::end(chan.CurrentGains), 0.0f);
|
||||
chan.Filter.z1 = 0.0f;
|
||||
chan.Filter.z2 = 0.0f;
|
||||
}
|
||||
|
||||
return AL_TRUE;
|
||||
}
|
||||
|
||||
void AutowahState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target)
|
||||
{
|
||||
const ALCdevice *device{context->mDevice.get()};
|
||||
const auto frequency = static_cast<float>(device->Frequency);
|
||||
|
||||
const ALfloat ReleaseTime{clampf(props->Autowah.ReleaseTime, 0.001f, 1.0f)};
|
||||
|
||||
mAttackRate = std::exp(-1.0f / (props->Autowah.AttackTime*frequency));
|
||||
mReleaseRate = std::exp(-1.0f / (ReleaseTime*frequency));
|
||||
/* 0-20dB Resonance Peak gain */
|
||||
mResonanceGain = std::sqrt(std::log10(props->Autowah.Resonance)*10.0f / 3.0f);
|
||||
mPeakGain = 1.0f - std::log10(props->Autowah.PeakGain/AL_AUTOWAH_MAX_PEAK_GAIN);
|
||||
mFreqMinNorm = MIN_FREQ / frequency;
|
||||
mBandwidthNorm = (MAX_FREQ-MIN_FREQ) / frequency;
|
||||
|
||||
mOutTarget = target.Main->Buffer;
|
||||
for(size_t i{0u};i < slot->Wet.Buffer.size();++i)
|
||||
{
|
||||
auto coeffs = GetAmbiIdentityRow(i);
|
||||
ComputePanGains(target.Main, coeffs.data(), slot->Params.Gain, mChans[i].TargetGains);
|
||||
}
|
||||
}
|
||||
|
||||
void AutowahState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
|
||||
{
|
||||
const ALfloat attack_rate = mAttackRate;
|
||||
const ALfloat release_rate = mReleaseRate;
|
||||
const ALfloat res_gain = mResonanceGain;
|
||||
const ALfloat peak_gain = mPeakGain;
|
||||
const ALfloat freq_min = mFreqMinNorm;
|
||||
const ALfloat bandwidth = mBandwidthNorm;
|
||||
|
||||
ALfloat env_delay{mEnvDelay};
|
||||
for(size_t i{0u};i < samplesToDo;i++)
|
||||
{
|
||||
ALfloat w0, sample, a;
|
||||
|
||||
/* Envelope follower described on the book: Audio Effects, Theory,
|
||||
* Implementation and Application.
|
||||
*/
|
||||
sample = peak_gain * std::fabs(samplesIn[0][i]);
|
||||
a = (sample > env_delay) ? attack_rate : release_rate;
|
||||
env_delay = lerp(sample, env_delay, a);
|
||||
|
||||
/* Calculate the cos and alpha components for this sample's filter. */
|
||||
w0 = minf((bandwidth*env_delay + freq_min), 0.46f) * al::MathDefs<float>::Tau();
|
||||
mEnv[i].cos_w0 = cosf(w0);
|
||||
mEnv[i].alpha = sinf(w0)/(2.0f * Q_FACTOR);
|
||||
}
|
||||
mEnvDelay = env_delay;
|
||||
|
||||
auto chandata = std::addressof(mChans[0]);
|
||||
for(const auto &insamples : samplesIn)
|
||||
{
|
||||
/* This effectively inlines BiquadFilter_setParams for a peaking
|
||||
* filter and BiquadFilter_processC. The alpha and cosine components
|
||||
* for the filter coefficients were previously calculated with the
|
||||
* envelope. Because the filter changes for each sample, the
|
||||
* coefficients are transient and don't need to be held.
|
||||
*/
|
||||
ALfloat z1{chandata->Filter.z1};
|
||||
ALfloat z2{chandata->Filter.z2};
|
||||
|
||||
for(size_t i{0u};i < samplesToDo;i++)
|
||||
{
|
||||
const ALfloat alpha = mEnv[i].alpha;
|
||||
const ALfloat cos_w0 = mEnv[i].cos_w0;
|
||||
ALfloat input, output;
|
||||
ALfloat a[3], b[3];
|
||||
|
||||
b[0] = 1.0f + alpha*res_gain;
|
||||
b[1] = -2.0f * cos_w0;
|
||||
b[2] = 1.0f - alpha*res_gain;
|
||||
a[0] = 1.0f + alpha/res_gain;
|
||||
a[1] = -2.0f * cos_w0;
|
||||
a[2] = 1.0f - alpha/res_gain;
|
||||
|
||||
input = insamples[i];
|
||||
output = input*(b[0]/a[0]) + z1;
|
||||
z1 = input*(b[1]/a[0]) - output*(a[1]/a[0]) + z2;
|
||||
z2 = input*(b[2]/a[0]) - output*(a[2]/a[0]);
|
||||
mBufferOut[i] = output;
|
||||
}
|
||||
chandata->Filter.z1 = z1;
|
||||
chandata->Filter.z2 = z2;
|
||||
|
||||
/* Now, mix the processed sound data to the output. */
|
||||
MixSamples({mBufferOut, samplesToDo}, samplesOut, chandata->CurrentGains,
|
||||
chandata->TargetGains, samplesToDo, 0);
|
||||
++chandata;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Autowah_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_AUTOWAH_ATTACK_TIME:
|
||||
if(!(val >= AL_AUTOWAH_MIN_ATTACK_TIME && val <= AL_AUTOWAH_MAX_ATTACK_TIME))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,,"Autowah attack time out of range");
|
||||
props->Autowah.AttackTime = val;
|
||||
break;
|
||||
|
||||
case AL_AUTOWAH_RELEASE_TIME:
|
||||
if(!(val >= AL_AUTOWAH_MIN_RELEASE_TIME && val <= AL_AUTOWAH_MAX_RELEASE_TIME))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,,"Autowah release time out of range");
|
||||
props->Autowah.ReleaseTime = val;
|
||||
break;
|
||||
|
||||
case AL_AUTOWAH_RESONANCE:
|
||||
if(!(val >= AL_AUTOWAH_MIN_RESONANCE && val <= AL_AUTOWAH_MAX_RESONANCE))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,,"Autowah resonance out of range");
|
||||
props->Autowah.Resonance = val;
|
||||
break;
|
||||
|
||||
case AL_AUTOWAH_PEAK_GAIN:
|
||||
if(!(val >= AL_AUTOWAH_MIN_PEAK_GAIN && val <= AL_AUTOWAH_MAX_PEAK_GAIN))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,,"Autowah peak gain out of range");
|
||||
props->Autowah.PeakGain = val;
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid autowah float property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
void Autowah_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
|
||||
{ Autowah_setParamf(props, context, param, vals[0]); }
|
||||
|
||||
void Autowah_setParami(EffectProps*, ALCcontext *context, ALenum param, ALint)
|
||||
{ context->setError(AL_INVALID_ENUM, "Invalid autowah integer property 0x%04x", param); }
|
||||
void Autowah_setParamiv(EffectProps*, ALCcontext *context, ALenum param, const ALint*)
|
||||
{ context->setError(AL_INVALID_ENUM, "Invalid autowah integer vector property 0x%04x", param); }
|
||||
|
||||
void Autowah_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_AUTOWAH_ATTACK_TIME:
|
||||
*val = props->Autowah.AttackTime;
|
||||
break;
|
||||
|
||||
case AL_AUTOWAH_RELEASE_TIME:
|
||||
*val = props->Autowah.ReleaseTime;
|
||||
break;
|
||||
|
||||
case AL_AUTOWAH_RESONANCE:
|
||||
*val = props->Autowah.Resonance;
|
||||
break;
|
||||
|
||||
case AL_AUTOWAH_PEAK_GAIN:
|
||||
*val = props->Autowah.PeakGain;
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid autowah float property 0x%04x", param);
|
||||
}
|
||||
|
||||
}
|
||||
void Autowah_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
|
||||
{ Autowah_getParamf(props, context, param, vals); }
|
||||
|
||||
void Autowah_getParami(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
|
||||
{ context->setError(AL_INVALID_ENUM, "Invalid autowah integer property 0x%04x", param); }
|
||||
void Autowah_getParamiv(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
|
||||
{ context->setError(AL_INVALID_ENUM, "Invalid autowah integer vector property 0x%04x", param); }
|
||||
|
||||
DEFINE_ALEFFECT_VTABLE(Autowah);
|
||||
|
||||
|
||||
struct AutowahStateFactory final : public EffectStateFactory {
|
||||
EffectState *create() override { return new AutowahState{}; }
|
||||
EffectProps getDefaultProps() const noexcept override;
|
||||
const EffectVtable *getEffectVtable() const noexcept override { return &Autowah_vtable; }
|
||||
};
|
||||
|
||||
EffectProps AutowahStateFactory::getDefaultProps() const noexcept
|
||||
{
|
||||
EffectProps props{};
|
||||
props.Autowah.AttackTime = AL_AUTOWAH_DEFAULT_ATTACK_TIME;
|
||||
props.Autowah.ReleaseTime = AL_AUTOWAH_DEFAULT_RELEASE_TIME;
|
||||
props.Autowah.Resonance = AL_AUTOWAH_DEFAULT_RESONANCE;
|
||||
props.Autowah.PeakGain = AL_AUTOWAH_DEFAULT_PEAK_GAIN;
|
||||
return props;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
EffectStateFactory *AutowahStateFactory_getFactory()
|
||||
{
|
||||
static AutowahStateFactory AutowahFactory{};
|
||||
return &AutowahFactory;
|
||||
}
|
||||
@@ -1,193 +0,0 @@
|
||||
#ifndef EFFECTS_BASE_H
|
||||
#define EFFECTS_BASE_H
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include "alcmain.h"
|
||||
#include "almalloc.h"
|
||||
#include "alspan.h"
|
||||
#include "atomic.h"
|
||||
#include "intrusive_ptr.h"
|
||||
|
||||
struct ALeffectslot;
|
||||
|
||||
|
||||
union EffectProps {
|
||||
struct {
|
||||
// Shared Reverb Properties
|
||||
ALfloat Density;
|
||||
ALfloat Diffusion;
|
||||
ALfloat Gain;
|
||||
ALfloat GainHF;
|
||||
ALfloat DecayTime;
|
||||
ALfloat DecayHFRatio;
|
||||
ALfloat ReflectionsGain;
|
||||
ALfloat ReflectionsDelay;
|
||||
ALfloat LateReverbGain;
|
||||
ALfloat LateReverbDelay;
|
||||
ALfloat AirAbsorptionGainHF;
|
||||
ALfloat RoomRolloffFactor;
|
||||
bool DecayHFLimit;
|
||||
|
||||
// Additional EAX Reverb Properties
|
||||
ALfloat GainLF;
|
||||
ALfloat DecayLFRatio;
|
||||
ALfloat ReflectionsPan[3];
|
||||
ALfloat LateReverbPan[3];
|
||||
ALfloat EchoTime;
|
||||
ALfloat EchoDepth;
|
||||
ALfloat ModulationTime;
|
||||
ALfloat ModulationDepth;
|
||||
ALfloat HFReference;
|
||||
ALfloat LFReference;
|
||||
} Reverb;
|
||||
|
||||
struct {
|
||||
ALfloat AttackTime;
|
||||
ALfloat ReleaseTime;
|
||||
ALfloat Resonance;
|
||||
ALfloat PeakGain;
|
||||
} Autowah;
|
||||
|
||||
struct {
|
||||
ALint Waveform;
|
||||
ALint Phase;
|
||||
ALfloat Rate;
|
||||
ALfloat Depth;
|
||||
ALfloat Feedback;
|
||||
ALfloat Delay;
|
||||
} Chorus; /* Also Flanger */
|
||||
|
||||
struct {
|
||||
bool OnOff;
|
||||
} Compressor;
|
||||
|
||||
struct {
|
||||
ALfloat Edge;
|
||||
ALfloat Gain;
|
||||
ALfloat LowpassCutoff;
|
||||
ALfloat EQCenter;
|
||||
ALfloat EQBandwidth;
|
||||
} Distortion;
|
||||
|
||||
struct {
|
||||
ALfloat Delay;
|
||||
ALfloat LRDelay;
|
||||
|
||||
ALfloat Damping;
|
||||
ALfloat Feedback;
|
||||
|
||||
ALfloat Spread;
|
||||
} Echo;
|
||||
|
||||
struct {
|
||||
ALfloat LowCutoff;
|
||||
ALfloat LowGain;
|
||||
ALfloat Mid1Center;
|
||||
ALfloat Mid1Gain;
|
||||
ALfloat Mid1Width;
|
||||
ALfloat Mid2Center;
|
||||
ALfloat Mid2Gain;
|
||||
ALfloat Mid2Width;
|
||||
ALfloat HighCutoff;
|
||||
ALfloat HighGain;
|
||||
} Equalizer;
|
||||
|
||||
struct {
|
||||
ALfloat Frequency;
|
||||
ALint LeftDirection;
|
||||
ALint RightDirection;
|
||||
} Fshifter;
|
||||
|
||||
struct {
|
||||
ALfloat Frequency;
|
||||
ALfloat HighPassCutoff;
|
||||
ALint Waveform;
|
||||
} Modulator;
|
||||
|
||||
struct {
|
||||
ALint CoarseTune;
|
||||
ALint FineTune;
|
||||
} Pshifter;
|
||||
|
||||
struct {
|
||||
ALfloat Rate;
|
||||
ALint PhonemeA;
|
||||
ALint PhonemeB;
|
||||
ALint PhonemeACoarseTuning;
|
||||
ALint PhonemeBCoarseTuning;
|
||||
ALint Waveform;
|
||||
} Vmorpher;
|
||||
|
||||
struct {
|
||||
ALfloat Gain;
|
||||
} Dedicated;
|
||||
};
|
||||
|
||||
|
||||
struct EffectVtable {
|
||||
void (*const setParami)(EffectProps *props, ALCcontext *context, ALenum param, ALint val);
|
||||
void (*const setParamiv)(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals);
|
||||
void (*const setParamf)(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val);
|
||||
void (*const setParamfv)(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals);
|
||||
|
||||
void (*const getParami)(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val);
|
||||
void (*const getParamiv)(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals);
|
||||
void (*const getParamf)(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val);
|
||||
void (*const getParamfv)(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals);
|
||||
};
|
||||
|
||||
#define DEFINE_ALEFFECT_VTABLE(T) \
|
||||
const EffectVtable T##_vtable = { \
|
||||
T##_setParami, T##_setParamiv, \
|
||||
T##_setParamf, T##_setParamfv, \
|
||||
T##_getParami, T##_getParamiv, \
|
||||
T##_getParamf, T##_getParamfv, \
|
||||
}
|
||||
|
||||
|
||||
struct EffectTarget {
|
||||
MixParams *Main;
|
||||
RealMixParams *RealOut;
|
||||
};
|
||||
|
||||
struct EffectState : public al::intrusive_ref<EffectState> {
|
||||
al::span<FloatBufferLine> mOutTarget;
|
||||
|
||||
|
||||
virtual ~EffectState() = default;
|
||||
|
||||
virtual ALboolean deviceUpdate(const ALCdevice *device) = 0;
|
||||
virtual void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) = 0;
|
||||
virtual void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) = 0;
|
||||
};
|
||||
|
||||
|
||||
struct EffectStateFactory {
|
||||
virtual ~EffectStateFactory() { }
|
||||
|
||||
virtual EffectState *create() = 0;
|
||||
virtual EffectProps getDefaultProps() const noexcept = 0;
|
||||
virtual const EffectVtable *getEffectVtable() const noexcept = 0;
|
||||
};
|
||||
|
||||
|
||||
EffectStateFactory *NullStateFactory_getFactory(void);
|
||||
EffectStateFactory *ReverbStateFactory_getFactory(void);
|
||||
EffectStateFactory *StdReverbStateFactory_getFactory(void);
|
||||
EffectStateFactory *AutowahStateFactory_getFactory(void);
|
||||
EffectStateFactory *ChorusStateFactory_getFactory(void);
|
||||
EffectStateFactory *CompressorStateFactory_getFactory(void);
|
||||
EffectStateFactory *DistortionStateFactory_getFactory(void);
|
||||
EffectStateFactory *EchoStateFactory_getFactory(void);
|
||||
EffectStateFactory *EqualizerStateFactory_getFactory(void);
|
||||
EffectStateFactory *FlangerStateFactory_getFactory(void);
|
||||
EffectStateFactory *FshifterStateFactory_getFactory(void);
|
||||
EffectStateFactory *ModulatorStateFactory_getFactory(void);
|
||||
EffectStateFactory *PshifterStateFactory_getFactory(void);
|
||||
EffectStateFactory* VmorpherStateFactory_getFactory(void);
|
||||
|
||||
EffectStateFactory *DedicatedStateFactory_getFactory(void);
|
||||
|
||||
|
||||
#endif /* EFFECTS_BASE_H */
|
||||
@@ -1,534 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 2013 by Mike Gorchak
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <climits>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <iterator>
|
||||
|
||||
#include "AL/al.h"
|
||||
#include "AL/alc.h"
|
||||
#include "AL/efx.h"
|
||||
|
||||
#include "al/auxeffectslot.h"
|
||||
#include "alcmain.h"
|
||||
#include "alcontext.h"
|
||||
#include "almalloc.h"
|
||||
#include "alnumeric.h"
|
||||
#include "alspan.h"
|
||||
#include "alu.h"
|
||||
#include "ambidefs.h"
|
||||
#include "effects/base.h"
|
||||
#include "math_defs.h"
|
||||
#include "opthelpers.h"
|
||||
#include "vector.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
static_assert(AL_CHORUS_WAVEFORM_SINUSOID == AL_FLANGER_WAVEFORM_SINUSOID, "Chorus/Flanger waveform value mismatch");
|
||||
static_assert(AL_CHORUS_WAVEFORM_TRIANGLE == AL_FLANGER_WAVEFORM_TRIANGLE, "Chorus/Flanger waveform value mismatch");
|
||||
|
||||
enum class WaveForm {
|
||||
Sinusoid,
|
||||
Triangle
|
||||
};
|
||||
|
||||
void GetTriangleDelays(ALuint *delays, const ALuint start_offset, const ALuint lfo_range,
|
||||
const ALfloat lfo_scale, const ALfloat depth, const ALsizei delay, const size_t todo)
|
||||
{
|
||||
ASSUME(lfo_range > 0);
|
||||
ASSUME(todo > 0);
|
||||
|
||||
ALuint offset{start_offset};
|
||||
auto gen_lfo = [&offset,lfo_range,lfo_scale,depth,delay]() -> ALuint
|
||||
{
|
||||
offset = (offset+1)%lfo_range;
|
||||
const float offset_norm{static_cast<float>(offset) * lfo_scale};
|
||||
return static_cast<ALuint>(fastf2i((1.0f-std::abs(2.0f-offset_norm)) * depth) + delay);
|
||||
};
|
||||
std::generate_n(delays, todo, gen_lfo);
|
||||
}
|
||||
|
||||
void GetSinusoidDelays(ALuint *delays, const ALuint start_offset, const ALuint lfo_range,
|
||||
const ALfloat lfo_scale, const ALfloat depth, const ALsizei delay, const size_t todo)
|
||||
{
|
||||
ASSUME(lfo_range > 0);
|
||||
ASSUME(todo > 0);
|
||||
|
||||
ALuint offset{start_offset};
|
||||
auto gen_lfo = [&offset,lfo_range,lfo_scale,depth,delay]() -> ALuint
|
||||
{
|
||||
offset = (offset+1)%lfo_range;
|
||||
const float offset_norm{static_cast<float>(offset) * lfo_scale};
|
||||
return static_cast<ALuint>(fastf2i(std::sin(offset_norm)*depth) + delay);
|
||||
};
|
||||
std::generate_n(delays, todo, gen_lfo);
|
||||
}
|
||||
|
||||
struct ChorusState final : public EffectState {
|
||||
al::vector<ALfloat,16> mSampleBuffer;
|
||||
ALuint mOffset{0};
|
||||
|
||||
ALuint mLfoOffset{0};
|
||||
ALuint mLfoRange{1};
|
||||
ALfloat mLfoScale{0.0f};
|
||||
ALuint mLfoDisp{0};
|
||||
|
||||
/* Gains for left and right sides */
|
||||
struct {
|
||||
ALfloat Current[MAX_OUTPUT_CHANNELS]{};
|
||||
ALfloat Target[MAX_OUTPUT_CHANNELS]{};
|
||||
} mGains[2];
|
||||
|
||||
/* effect parameters */
|
||||
WaveForm mWaveform{};
|
||||
ALint mDelay{0};
|
||||
ALfloat mDepth{0.0f};
|
||||
ALfloat mFeedback{0.0f};
|
||||
|
||||
|
||||
ALboolean deviceUpdate(const ALCdevice *device) override;
|
||||
void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
|
||||
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) override;
|
||||
|
||||
DEF_NEWDEL(ChorusState)
|
||||
};
|
||||
|
||||
ALboolean ChorusState::deviceUpdate(const ALCdevice *Device)
|
||||
{
|
||||
constexpr ALfloat max_delay{maxf(AL_CHORUS_MAX_DELAY, AL_FLANGER_MAX_DELAY)};
|
||||
|
||||
const auto frequency = static_cast<float>(Device->Frequency);
|
||||
const size_t maxlen{NextPowerOf2(float2uint(max_delay*2.0f*frequency) + 1u)};
|
||||
if(maxlen != mSampleBuffer.size())
|
||||
{
|
||||
mSampleBuffer.resize(maxlen);
|
||||
mSampleBuffer.shrink_to_fit();
|
||||
}
|
||||
|
||||
std::fill(mSampleBuffer.begin(), mSampleBuffer.end(), 0.0f);
|
||||
for(auto &e : mGains)
|
||||
{
|
||||
std::fill(std::begin(e.Current), std::end(e.Current), 0.0f);
|
||||
std::fill(std::begin(e.Target), std::end(e.Target), 0.0f);
|
||||
}
|
||||
|
||||
return AL_TRUE;
|
||||
}
|
||||
|
||||
void ChorusState::update(const ALCcontext *Context, const ALeffectslot *Slot, const EffectProps *props, const EffectTarget target)
|
||||
{
|
||||
constexpr ALsizei mindelay{(MAX_RESAMPLER_PADDING>>1) << FRACTIONBITS};
|
||||
|
||||
switch(props->Chorus.Waveform)
|
||||
{
|
||||
case AL_CHORUS_WAVEFORM_TRIANGLE:
|
||||
mWaveform = WaveForm::Triangle;
|
||||
break;
|
||||
case AL_CHORUS_WAVEFORM_SINUSOID:
|
||||
mWaveform = WaveForm::Sinusoid;
|
||||
break;
|
||||
}
|
||||
|
||||
/* The LFO depth is scaled to be relative to the sample delay. Clamp the
|
||||
* delay and depth to allow enough padding for resampling.
|
||||
*/
|
||||
const ALCdevice *device{Context->mDevice.get()};
|
||||
const auto frequency = static_cast<float>(device->Frequency);
|
||||
|
||||
mDelay = maxi(float2int(props->Chorus.Delay*frequency*FRACTIONONE + 0.5f), mindelay);
|
||||
mDepth = minf(props->Chorus.Depth * static_cast<float>(mDelay),
|
||||
static_cast<float>(mDelay - mindelay));
|
||||
|
||||
mFeedback = props->Chorus.Feedback;
|
||||
|
||||
/* Gains for left and right sides */
|
||||
ALfloat coeffs[2][MAX_AMBI_CHANNELS];
|
||||
CalcDirectionCoeffs({-1.0f, 0.0f, 0.0f}, 0.0f, coeffs[0]);
|
||||
CalcDirectionCoeffs({ 1.0f, 0.0f, 0.0f}, 0.0f, coeffs[1]);
|
||||
|
||||
mOutTarget = target.Main->Buffer;
|
||||
ComputePanGains(target.Main, coeffs[0], Slot->Params.Gain, mGains[0].Target);
|
||||
ComputePanGains(target.Main, coeffs[1], Slot->Params.Gain, mGains[1].Target);
|
||||
|
||||
ALfloat rate{props->Chorus.Rate};
|
||||
if(!(rate > 0.0f))
|
||||
{
|
||||
mLfoOffset = 0;
|
||||
mLfoRange = 1;
|
||||
mLfoScale = 0.0f;
|
||||
mLfoDisp = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Calculate LFO coefficient (number of samples per cycle). Limit the
|
||||
* max range to avoid overflow when calculating the displacement.
|
||||
*/
|
||||
ALuint lfo_range{float2uint(minf(frequency/rate + 0.5f, ALfloat{INT_MAX/360 - 180}))};
|
||||
|
||||
mLfoOffset = mLfoOffset * lfo_range / mLfoRange;
|
||||
mLfoRange = lfo_range;
|
||||
switch(mWaveform)
|
||||
{
|
||||
case WaveForm::Triangle:
|
||||
mLfoScale = 4.0f / static_cast<float>(mLfoRange);
|
||||
break;
|
||||
case WaveForm::Sinusoid:
|
||||
mLfoScale = al::MathDefs<float>::Tau() / static_cast<float>(mLfoRange);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Calculate lfo phase displacement */
|
||||
ALint phase{props->Chorus.Phase};
|
||||
if(phase < 0) phase = 360 + phase;
|
||||
mLfoDisp = (mLfoRange*static_cast<ALuint>(phase) + 180) / 360;
|
||||
}
|
||||
}
|
||||
|
||||
void ChorusState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
|
||||
{
|
||||
const size_t bufmask{mSampleBuffer.size()-1};
|
||||
const ALfloat feedback{mFeedback};
|
||||
const ALuint avgdelay{(static_cast<ALuint>(mDelay) + (FRACTIONONE>>1)) >> FRACTIONBITS};
|
||||
ALfloat *RESTRICT delaybuf{mSampleBuffer.data()};
|
||||
ALuint offset{mOffset};
|
||||
|
||||
for(size_t base{0u};base < samplesToDo;)
|
||||
{
|
||||
const size_t todo{minz(256, samplesToDo-base)};
|
||||
|
||||
ALuint moddelays[2][256];
|
||||
if(mWaveform == WaveForm::Sinusoid)
|
||||
{
|
||||
GetSinusoidDelays(moddelays[0], mLfoOffset, mLfoRange, mLfoScale, mDepth, mDelay,
|
||||
todo);
|
||||
GetSinusoidDelays(moddelays[1], (mLfoOffset+mLfoDisp)%mLfoRange, mLfoRange, mLfoScale,
|
||||
mDepth, mDelay, todo);
|
||||
}
|
||||
else /*if(mWaveform == WaveForm::Triangle)*/
|
||||
{
|
||||
GetTriangleDelays(moddelays[0], mLfoOffset, mLfoRange, mLfoScale, mDepth, mDelay,
|
||||
todo);
|
||||
GetTriangleDelays(moddelays[1], (mLfoOffset+mLfoDisp)%mLfoRange, mLfoRange, mLfoScale,
|
||||
mDepth, mDelay, todo);
|
||||
}
|
||||
mLfoOffset = (mLfoOffset+static_cast<ALuint>(todo)) % mLfoRange;
|
||||
|
||||
alignas(16) ALfloat temps[2][256];
|
||||
for(size_t i{0u};i < todo;i++)
|
||||
{
|
||||
// Feed the buffer's input first (necessary for delays < 1).
|
||||
delaybuf[offset&bufmask] = samplesIn[0][base+i];
|
||||
|
||||
// Tap for the left output.
|
||||
ALuint delay{offset - (moddelays[0][i]>>FRACTIONBITS)};
|
||||
ALfloat mu{static_cast<float>(moddelays[0][i]&FRACTIONMASK) * (1.0f/FRACTIONONE)};
|
||||
temps[0][i] = cubic(delaybuf[(delay+1) & bufmask], delaybuf[(delay ) & bufmask],
|
||||
delaybuf[(delay-1) & bufmask], delaybuf[(delay-2) & bufmask], mu);
|
||||
|
||||
// Tap for the right output.
|
||||
delay = offset - (moddelays[1][i]>>FRACTIONBITS);
|
||||
mu = static_cast<float>(moddelays[1][i]&FRACTIONMASK) * (1.0f/FRACTIONONE);
|
||||
temps[1][i] = cubic(delaybuf[(delay+1) & bufmask], delaybuf[(delay ) & bufmask],
|
||||
delaybuf[(delay-1) & bufmask], delaybuf[(delay-2) & bufmask], mu);
|
||||
|
||||
// Accumulate feedback from the average delay of the taps.
|
||||
delaybuf[offset&bufmask] += delaybuf[(offset-avgdelay) & bufmask] * feedback;
|
||||
++offset;
|
||||
}
|
||||
|
||||
for(ALsizei c{0};c < 2;c++)
|
||||
MixSamples({temps[c], todo}, samplesOut, mGains[c].Current, mGains[c].Target,
|
||||
samplesToDo-base, base);
|
||||
|
||||
base += todo;
|
||||
}
|
||||
|
||||
mOffset = offset;
|
||||
}
|
||||
|
||||
|
||||
void Chorus_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_CHORUS_WAVEFORM:
|
||||
if(!(val >= AL_CHORUS_MIN_WAVEFORM && val <= AL_CHORUS_MAX_WAVEFORM))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Invalid chorus waveform");
|
||||
props->Chorus.Waveform = val;
|
||||
break;
|
||||
|
||||
case AL_CHORUS_PHASE:
|
||||
if(!(val >= AL_CHORUS_MIN_PHASE && val <= AL_CHORUS_MAX_PHASE))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Chorus phase out of range");
|
||||
props->Chorus.Phase = val;
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid chorus integer property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
void Chorus_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals)
|
||||
{ Chorus_setParami(props, context, param, vals[0]); }
|
||||
void Chorus_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_CHORUS_RATE:
|
||||
if(!(val >= AL_CHORUS_MIN_RATE && val <= AL_CHORUS_MAX_RATE))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Chorus rate out of range");
|
||||
props->Chorus.Rate = val;
|
||||
break;
|
||||
|
||||
case AL_CHORUS_DEPTH:
|
||||
if(!(val >= AL_CHORUS_MIN_DEPTH && val <= AL_CHORUS_MAX_DEPTH))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Chorus depth out of range");
|
||||
props->Chorus.Depth = val;
|
||||
break;
|
||||
|
||||
case AL_CHORUS_FEEDBACK:
|
||||
if(!(val >= AL_CHORUS_MIN_FEEDBACK && val <= AL_CHORUS_MAX_FEEDBACK))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Chorus feedback out of range");
|
||||
props->Chorus.Feedback = val;
|
||||
break;
|
||||
|
||||
case AL_CHORUS_DELAY:
|
||||
if(!(val >= AL_CHORUS_MIN_DELAY && val <= AL_CHORUS_MAX_DELAY))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Chorus delay out of range");
|
||||
props->Chorus.Delay = val;
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid chorus float property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
void Chorus_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
|
||||
{ Chorus_setParamf(props, context, param, vals[0]); }
|
||||
|
||||
void Chorus_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_CHORUS_WAVEFORM:
|
||||
*val = props->Chorus.Waveform;
|
||||
break;
|
||||
|
||||
case AL_CHORUS_PHASE:
|
||||
*val = props->Chorus.Phase;
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid chorus integer property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
void Chorus_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals)
|
||||
{ Chorus_getParami(props, context, param, vals); }
|
||||
void Chorus_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_CHORUS_RATE:
|
||||
*val = props->Chorus.Rate;
|
||||
break;
|
||||
|
||||
case AL_CHORUS_DEPTH:
|
||||
*val = props->Chorus.Depth;
|
||||
break;
|
||||
|
||||
case AL_CHORUS_FEEDBACK:
|
||||
*val = props->Chorus.Feedback;
|
||||
break;
|
||||
|
||||
case AL_CHORUS_DELAY:
|
||||
*val = props->Chorus.Delay;
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid chorus float property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
void Chorus_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
|
||||
{ Chorus_getParamf(props, context, param, vals); }
|
||||
|
||||
DEFINE_ALEFFECT_VTABLE(Chorus);
|
||||
|
||||
|
||||
struct ChorusStateFactory final : public EffectStateFactory {
|
||||
EffectState *create() override { return new ChorusState{}; }
|
||||
EffectProps getDefaultProps() const noexcept override;
|
||||
const EffectVtable *getEffectVtable() const noexcept override { return &Chorus_vtable; }
|
||||
};
|
||||
|
||||
EffectProps ChorusStateFactory::getDefaultProps() const noexcept
|
||||
{
|
||||
EffectProps props{};
|
||||
props.Chorus.Waveform = AL_CHORUS_DEFAULT_WAVEFORM;
|
||||
props.Chorus.Phase = AL_CHORUS_DEFAULT_PHASE;
|
||||
props.Chorus.Rate = AL_CHORUS_DEFAULT_RATE;
|
||||
props.Chorus.Depth = AL_CHORUS_DEFAULT_DEPTH;
|
||||
props.Chorus.Feedback = AL_CHORUS_DEFAULT_FEEDBACK;
|
||||
props.Chorus.Delay = AL_CHORUS_DEFAULT_DELAY;
|
||||
return props;
|
||||
}
|
||||
|
||||
|
||||
void Flanger_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_FLANGER_WAVEFORM:
|
||||
if(!(val >= AL_FLANGER_MIN_WAVEFORM && val <= AL_FLANGER_MAX_WAVEFORM))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Invalid flanger waveform");
|
||||
props->Chorus.Waveform = val;
|
||||
break;
|
||||
|
||||
case AL_FLANGER_PHASE:
|
||||
if(!(val >= AL_FLANGER_MIN_PHASE && val <= AL_FLANGER_MAX_PHASE))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Flanger phase out of range");
|
||||
props->Chorus.Phase = val;
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid flanger integer property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
void Flanger_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals)
|
||||
{ Flanger_setParami(props, context, param, vals[0]); }
|
||||
void Flanger_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_FLANGER_RATE:
|
||||
if(!(val >= AL_FLANGER_MIN_RATE && val <= AL_FLANGER_MAX_RATE))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Flanger rate out of range");
|
||||
props->Chorus.Rate = val;
|
||||
break;
|
||||
|
||||
case AL_FLANGER_DEPTH:
|
||||
if(!(val >= AL_FLANGER_MIN_DEPTH && val <= AL_FLANGER_MAX_DEPTH))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Flanger depth out of range");
|
||||
props->Chorus.Depth = val;
|
||||
break;
|
||||
|
||||
case AL_FLANGER_FEEDBACK:
|
||||
if(!(val >= AL_FLANGER_MIN_FEEDBACK && val <= AL_FLANGER_MAX_FEEDBACK))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Flanger feedback out of range");
|
||||
props->Chorus.Feedback = val;
|
||||
break;
|
||||
|
||||
case AL_FLANGER_DELAY:
|
||||
if(!(val >= AL_FLANGER_MIN_DELAY && val <= AL_FLANGER_MAX_DELAY))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Flanger delay out of range");
|
||||
props->Chorus.Delay = val;
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid flanger float property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
void Flanger_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
|
||||
{ Flanger_setParamf(props, context, param, vals[0]); }
|
||||
|
||||
void Flanger_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_FLANGER_WAVEFORM:
|
||||
*val = props->Chorus.Waveform;
|
||||
break;
|
||||
|
||||
case AL_FLANGER_PHASE:
|
||||
*val = props->Chorus.Phase;
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid flanger integer property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
void Flanger_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals)
|
||||
{ Flanger_getParami(props, context, param, vals); }
|
||||
void Flanger_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_FLANGER_RATE:
|
||||
*val = props->Chorus.Rate;
|
||||
break;
|
||||
|
||||
case AL_FLANGER_DEPTH:
|
||||
*val = props->Chorus.Depth;
|
||||
break;
|
||||
|
||||
case AL_FLANGER_FEEDBACK:
|
||||
*val = props->Chorus.Feedback;
|
||||
break;
|
||||
|
||||
case AL_FLANGER_DELAY:
|
||||
*val = props->Chorus.Delay;
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid flanger float property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
void Flanger_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
|
||||
{ Flanger_getParamf(props, context, param, vals); }
|
||||
|
||||
DEFINE_ALEFFECT_VTABLE(Flanger);
|
||||
|
||||
|
||||
/* Flanger is basically a chorus with a really short delay. They can both use
|
||||
* the same processing functions, so piggyback flanger on the chorus functions.
|
||||
*/
|
||||
struct FlangerStateFactory final : public EffectStateFactory {
|
||||
EffectState *create() override { return new ChorusState{}; }
|
||||
EffectProps getDefaultProps() const noexcept override;
|
||||
const EffectVtable *getEffectVtable() const noexcept override { return &Flanger_vtable; }
|
||||
};
|
||||
|
||||
EffectProps FlangerStateFactory::getDefaultProps() const noexcept
|
||||
{
|
||||
EffectProps props{};
|
||||
props.Chorus.Waveform = AL_FLANGER_DEFAULT_WAVEFORM;
|
||||
props.Chorus.Phase = AL_FLANGER_DEFAULT_PHASE;
|
||||
props.Chorus.Rate = AL_FLANGER_DEFAULT_RATE;
|
||||
props.Chorus.Depth = AL_FLANGER_DEFAULT_DEPTH;
|
||||
props.Chorus.Feedback = AL_FLANGER_DEFAULT_FEEDBACK;
|
||||
props.Chorus.Delay = AL_FLANGER_DEFAULT_DELAY;
|
||||
return props;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
EffectStateFactory *ChorusStateFactory_getFactory()
|
||||
{
|
||||
static ChorusStateFactory ChorusFactory{};
|
||||
return &ChorusFactory;
|
||||
}
|
||||
|
||||
EffectStateFactory *FlangerStateFactory_getFactory()
|
||||
{
|
||||
static FlangerStateFactory FlangerFactory{};
|
||||
return &FlangerFactory;
|
||||
}
|
||||
@@ -1,221 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 2013 by Anis A. Hireche
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#include "al/auxeffectslot.h"
|
||||
#include "alcmain.h"
|
||||
#include "alcontext.h"
|
||||
#include "alu.h"
|
||||
#include "vecmat.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
#define AMP_ENVELOPE_MIN 0.5f
|
||||
#define AMP_ENVELOPE_MAX 2.0f
|
||||
|
||||
#define ATTACK_TIME 0.1f /* 100ms to rise from min to max */
|
||||
#define RELEASE_TIME 0.2f /* 200ms to drop from max to min */
|
||||
|
||||
|
||||
struct CompressorState final : public EffectState {
|
||||
/* Effect gains for each channel */
|
||||
ALfloat mGain[MAX_AMBI_CHANNELS][MAX_OUTPUT_CHANNELS]{};
|
||||
|
||||
/* Effect parameters */
|
||||
ALboolean mEnabled{AL_TRUE};
|
||||
ALfloat mAttackMult{1.0f};
|
||||
ALfloat mReleaseMult{1.0f};
|
||||
ALfloat mEnvFollower{1.0f};
|
||||
|
||||
|
||||
ALboolean deviceUpdate(const ALCdevice *device) override;
|
||||
void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
|
||||
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) override;
|
||||
|
||||
DEF_NEWDEL(CompressorState)
|
||||
};
|
||||
|
||||
ALboolean CompressorState::deviceUpdate(const ALCdevice *device)
|
||||
{
|
||||
/* Number of samples to do a full attack and release (non-integer sample
|
||||
* counts are okay).
|
||||
*/
|
||||
const ALfloat attackCount = static_cast<ALfloat>(device->Frequency) * ATTACK_TIME;
|
||||
const ALfloat releaseCount = static_cast<ALfloat>(device->Frequency) * RELEASE_TIME;
|
||||
|
||||
/* Calculate per-sample multipliers to attack and release at the desired
|
||||
* rates.
|
||||
*/
|
||||
mAttackMult = std::pow(AMP_ENVELOPE_MAX/AMP_ENVELOPE_MIN, 1.0f/attackCount);
|
||||
mReleaseMult = std::pow(AMP_ENVELOPE_MIN/AMP_ENVELOPE_MAX, 1.0f/releaseCount);
|
||||
|
||||
return AL_TRUE;
|
||||
}
|
||||
|
||||
void CompressorState::update(const ALCcontext*, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target)
|
||||
{
|
||||
mEnabled = props->Compressor.OnOff;
|
||||
|
||||
mOutTarget = target.Main->Buffer;
|
||||
for(size_t i{0u};i < slot->Wet.Buffer.size();++i)
|
||||
{
|
||||
auto coeffs = GetAmbiIdentityRow(i);
|
||||
ComputePanGains(target.Main, coeffs.data(), slot->Params.Gain, mGain[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void CompressorState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
|
||||
{
|
||||
for(size_t base{0u};base < samplesToDo;)
|
||||
{
|
||||
ALfloat gains[256];
|
||||
const size_t td{minz(256, samplesToDo-base)};
|
||||
|
||||
/* Generate the per-sample gains from the signal envelope. */
|
||||
ALfloat env{mEnvFollower};
|
||||
if(mEnabled)
|
||||
{
|
||||
for(size_t i{0u};i < td;++i)
|
||||
{
|
||||
/* Clamp the absolute amplitude to the defined envelope limits,
|
||||
* then attack or release the envelope to reach it.
|
||||
*/
|
||||
const ALfloat amplitude{clampf(std::fabs(samplesIn[0][base+i]), AMP_ENVELOPE_MIN,
|
||||
AMP_ENVELOPE_MAX)};
|
||||
if(amplitude > env)
|
||||
env = minf(env*mAttackMult, amplitude);
|
||||
else if(amplitude < env)
|
||||
env = maxf(env*mReleaseMult, amplitude);
|
||||
|
||||
/* Apply the reciprocal of the envelope to normalize the volume
|
||||
* (compress the dynamic range).
|
||||
*/
|
||||
gains[i] = 1.0f / env;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Same as above, except the amplitude is forced to 1. This helps
|
||||
* ensure smooth gain changes when the compressor is turned on and
|
||||
* off.
|
||||
*/
|
||||
for(size_t i{0u};i < td;++i)
|
||||
{
|
||||
const ALfloat amplitude{1.0f};
|
||||
if(amplitude > env)
|
||||
env = minf(env*mAttackMult, amplitude);
|
||||
else if(amplitude < env)
|
||||
env = maxf(env*mReleaseMult, amplitude);
|
||||
|
||||
gains[i] = 1.0f / env;
|
||||
}
|
||||
}
|
||||
mEnvFollower = env;
|
||||
|
||||
/* Now compress the signal amplitude to output. */
|
||||
auto changains = std::addressof(mGain[0]);
|
||||
for(const auto &input : samplesIn)
|
||||
{
|
||||
const ALfloat *outgains{*(changains++)};
|
||||
for(FloatBufferLine &output : samplesOut)
|
||||
{
|
||||
const ALfloat gain{*(outgains++)};
|
||||
if(!(std::fabs(gain) > GAIN_SILENCE_THRESHOLD))
|
||||
continue;
|
||||
|
||||
for(size_t i{0u};i < td;i++)
|
||||
output[base+i] += input[base+i] * gains[i] * gain;
|
||||
}
|
||||
}
|
||||
|
||||
base += td;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Compressor_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_COMPRESSOR_ONOFF:
|
||||
if(!(val >= AL_COMPRESSOR_MIN_ONOFF && val <= AL_COMPRESSOR_MAX_ONOFF))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Compressor state out of range");
|
||||
props->Compressor.OnOff = val != AL_FALSE;
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid compressor integer property 0x%04x",
|
||||
param);
|
||||
}
|
||||
}
|
||||
void Compressor_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals)
|
||||
{ Compressor_setParami(props, context, param, vals[0]); }
|
||||
void Compressor_setParamf(EffectProps*, ALCcontext *context, ALenum param, ALfloat)
|
||||
{ context->setError(AL_INVALID_ENUM, "Invalid compressor float property 0x%04x", param); }
|
||||
void Compressor_setParamfv(EffectProps*, ALCcontext *context, ALenum param, const ALfloat*)
|
||||
{ context->setError(AL_INVALID_ENUM, "Invalid compressor float-vector property 0x%04x", param); }
|
||||
|
||||
void Compressor_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_COMPRESSOR_ONOFF:
|
||||
*val = props->Compressor.OnOff;
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid compressor integer property 0x%04x",
|
||||
param);
|
||||
}
|
||||
}
|
||||
void Compressor_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals)
|
||||
{ Compressor_getParami(props, context, param, vals); }
|
||||
void Compressor_getParamf(const EffectProps*, ALCcontext *context, ALenum param, ALfloat*)
|
||||
{ context->setError(AL_INVALID_ENUM, "Invalid compressor float property 0x%04x", param); }
|
||||
void Compressor_getParamfv(const EffectProps*, ALCcontext *context, ALenum param, ALfloat*)
|
||||
{ context->setError(AL_INVALID_ENUM, "Invalid compressor float-vector property 0x%04x", param); }
|
||||
|
||||
DEFINE_ALEFFECT_VTABLE(Compressor);
|
||||
|
||||
|
||||
struct CompressorStateFactory final : public EffectStateFactory {
|
||||
EffectState *create() override { return new CompressorState{}; }
|
||||
EffectProps getDefaultProps() const noexcept override;
|
||||
const EffectVtable *getEffectVtable() const noexcept override { return &Compressor_vtable; }
|
||||
};
|
||||
|
||||
EffectProps CompressorStateFactory::getDefaultProps() const noexcept
|
||||
{
|
||||
EffectProps props{};
|
||||
props.Compressor.OnOff = AL_COMPRESSOR_DEFAULT_ONOFF;
|
||||
return props;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
EffectStateFactory *CompressorStateFactory_getFactory()
|
||||
{
|
||||
static CompressorStateFactory CompressorFactory{};
|
||||
return &CompressorFactory;
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 2011 by Chris Robinson.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
|
||||
#include "al/auxeffectslot.h"
|
||||
#include "alcmain.h"
|
||||
#include "alcontext.h"
|
||||
#include "alu.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
struct DedicatedState final : public EffectState {
|
||||
ALfloat mCurrentGains[MAX_OUTPUT_CHANNELS];
|
||||
ALfloat mTargetGains[MAX_OUTPUT_CHANNELS];
|
||||
|
||||
|
||||
ALboolean deviceUpdate(const ALCdevice *device) override;
|
||||
void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
|
||||
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) override;
|
||||
|
||||
DEF_NEWDEL(DedicatedState)
|
||||
};
|
||||
|
||||
ALboolean DedicatedState::deviceUpdate(const ALCdevice*)
|
||||
{
|
||||
std::fill(std::begin(mCurrentGains), std::end(mCurrentGains), 0.0f);
|
||||
return AL_TRUE;
|
||||
}
|
||||
|
||||
void DedicatedState::update(const ALCcontext*, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target)
|
||||
{
|
||||
std::fill(std::begin(mTargetGains), std::end(mTargetGains), 0.0f);
|
||||
|
||||
const ALfloat Gain{slot->Params.Gain * props->Dedicated.Gain};
|
||||
|
||||
if(slot->Params.EffectType == AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT)
|
||||
{
|
||||
const ALuint idx{!target.RealOut ? INVALID_CHANNEL_INDEX :
|
||||
GetChannelIdxByName(*target.RealOut, LFE)};
|
||||
if(idx != INVALID_CHANNEL_INDEX)
|
||||
{
|
||||
mOutTarget = target.RealOut->Buffer;
|
||||
mTargetGains[idx] = Gain;
|
||||
}
|
||||
}
|
||||
else if(slot->Params.EffectType == AL_EFFECT_DEDICATED_DIALOGUE)
|
||||
{
|
||||
/* Dialog goes to the front-center speaker if it exists, otherwise it
|
||||
* plays from the front-center location. */
|
||||
const ALuint idx{!target.RealOut ? INVALID_CHANNEL_INDEX :
|
||||
GetChannelIdxByName(*target.RealOut, FrontCenter)};
|
||||
if(idx != INVALID_CHANNEL_INDEX)
|
||||
{
|
||||
mOutTarget = target.RealOut->Buffer;
|
||||
mTargetGains[idx] = Gain;
|
||||
}
|
||||
else
|
||||
{
|
||||
ALfloat coeffs[MAX_AMBI_CHANNELS];
|
||||
CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}, 0.0f, coeffs);
|
||||
|
||||
mOutTarget = target.Main->Buffer;
|
||||
ComputePanGains(target.Main, coeffs, Gain, mTargetGains);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DedicatedState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
|
||||
{
|
||||
MixSamples({samplesIn[0].data(), samplesToDo}, samplesOut, mCurrentGains, mTargetGains,
|
||||
samplesToDo, 0);
|
||||
}
|
||||
|
||||
|
||||
void Dedicated_setParami(EffectProps*, ALCcontext *context, ALenum param, ALint)
|
||||
{ context->setError(AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param); }
|
||||
void Dedicated_setParamiv(EffectProps*, ALCcontext *context, ALenum param, const ALint*)
|
||||
{ context->setError(AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x", param); }
|
||||
void Dedicated_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_DEDICATED_GAIN:
|
||||
if(!(val >= 0.0f && std::isfinite(val)))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Dedicated gain out of range");
|
||||
props->Dedicated.Gain = val;
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
void Dedicated_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
|
||||
{ Dedicated_setParamf(props, context, param, vals[0]); }
|
||||
|
||||
void Dedicated_getParami(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
|
||||
{ context->setError(AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param); }
|
||||
void Dedicated_getParamiv(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
|
||||
{ context->setError(AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x", param); }
|
||||
void Dedicated_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_DEDICATED_GAIN:
|
||||
*val = props->Dedicated.Gain;
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
void Dedicated_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
|
||||
{ Dedicated_getParamf(props, context, param, vals); }
|
||||
|
||||
DEFINE_ALEFFECT_VTABLE(Dedicated);
|
||||
|
||||
|
||||
struct DedicatedStateFactory final : public EffectStateFactory {
|
||||
EffectState *create() override { return new DedicatedState{}; }
|
||||
EffectProps getDefaultProps() const noexcept override;
|
||||
const EffectVtable *getEffectVtable() const noexcept override { return &Dedicated_vtable; }
|
||||
};
|
||||
|
||||
EffectProps DedicatedStateFactory::getDefaultProps() const noexcept
|
||||
{
|
||||
EffectProps props{};
|
||||
props.Dedicated.Gain = 1.0f;
|
||||
return props;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
EffectStateFactory *DedicatedStateFactory_getFactory()
|
||||
{
|
||||
static DedicatedStateFactory DedicatedFactory{};
|
||||
return &DedicatedFactory;
|
||||
}
|
||||
@@ -1,262 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 2013 by Mike Gorchak
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "al/auxeffectslot.h"
|
||||
#include "alcmain.h"
|
||||
#include "alcontext.h"
|
||||
#include "alu.h"
|
||||
#include "filters/biquad.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
struct DistortionState final : public EffectState {
|
||||
/* Effect gains for each channel */
|
||||
ALfloat mGain[MAX_OUTPUT_CHANNELS]{};
|
||||
|
||||
/* Effect parameters */
|
||||
BiquadFilter mLowpass;
|
||||
BiquadFilter mBandpass;
|
||||
ALfloat mAttenuation{};
|
||||
ALfloat mEdgeCoeff{};
|
||||
|
||||
ALfloat mBuffer[2][BUFFERSIZE]{};
|
||||
|
||||
|
||||
ALboolean deviceUpdate(const ALCdevice *device) override;
|
||||
void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
|
||||
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) override;
|
||||
|
||||
DEF_NEWDEL(DistortionState)
|
||||
};
|
||||
|
||||
ALboolean DistortionState::deviceUpdate(const ALCdevice*)
|
||||
{
|
||||
mLowpass.clear();
|
||||
mBandpass.clear();
|
||||
return AL_TRUE;
|
||||
}
|
||||
|
||||
void DistortionState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target)
|
||||
{
|
||||
const ALCdevice *device{context->mDevice.get()};
|
||||
|
||||
/* Store waveshaper edge settings. */
|
||||
const ALfloat edge{
|
||||
minf(std::sin(al::MathDefs<float>::Pi()*0.5f * props->Distortion.Edge), 0.99f)};
|
||||
mEdgeCoeff = 2.0f * edge / (1.0f-edge);
|
||||
|
||||
ALfloat cutoff{props->Distortion.LowpassCutoff};
|
||||
/* Bandwidth value is constant in octaves. */
|
||||
ALfloat bandwidth{(cutoff / 2.0f) / (cutoff * 0.67f)};
|
||||
/* Divide normalized frequency by the amount of oversampling done during
|
||||
* processing.
|
||||
*/
|
||||
auto frequency = static_cast<ALfloat>(device->Frequency);
|
||||
mLowpass.setParamsFromBandwidth(BiquadType::LowPass, cutoff/frequency/4.0f, 1.0f, bandwidth);
|
||||
|
||||
cutoff = props->Distortion.EQCenter;
|
||||
/* Convert bandwidth in Hz to octaves. */
|
||||
bandwidth = props->Distortion.EQBandwidth / (cutoff * 0.67f);
|
||||
mBandpass.setParamsFromBandwidth(BiquadType::BandPass, cutoff/frequency/4.0f, 1.0f, bandwidth);
|
||||
|
||||
ALfloat coeffs[MAX_AMBI_CHANNELS];
|
||||
CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}, 0.0f, coeffs);
|
||||
|
||||
mOutTarget = target.Main->Buffer;
|
||||
ComputePanGains(target.Main, coeffs, slot->Params.Gain*props->Distortion.Gain, mGain);
|
||||
}
|
||||
|
||||
void DistortionState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
|
||||
{
|
||||
const ALfloat fc{mEdgeCoeff};
|
||||
for(size_t base{0u};base < samplesToDo;)
|
||||
{
|
||||
/* Perform 4x oversampling to avoid aliasing. Oversampling greatly
|
||||
* improves distortion quality and allows to implement lowpass and
|
||||
* bandpass filters using high frequencies, at which classic IIR
|
||||
* filters became unstable.
|
||||
*/
|
||||
size_t todo{minz(BUFFERSIZE, (samplesToDo-base) * 4)};
|
||||
|
||||
/* Fill oversample buffer using zero stuffing. Multiply the sample by
|
||||
* the amount of oversampling to maintain the signal's power.
|
||||
*/
|
||||
for(size_t i{0u};i < todo;i++)
|
||||
mBuffer[0][i] = !(i&3) ? samplesIn[0][(i>>2)+base] * 4.0f : 0.0f;
|
||||
|
||||
/* First step, do lowpass filtering of original signal. Additionally
|
||||
* perform buffer interpolation and lowpass cutoff for oversampling
|
||||
* (which is fortunately first step of distortion). So combine three
|
||||
* operations into the one.
|
||||
*/
|
||||
mLowpass.process({mBuffer[0], todo}, mBuffer[1]);
|
||||
|
||||
/* Second step, do distortion using waveshaper function to emulate
|
||||
* signal processing during tube overdriving. Three steps of
|
||||
* waveshaping are intended to modify waveform without boost/clipping/
|
||||
* attenuation process.
|
||||
*/
|
||||
auto proc_sample = [fc](float smp) -> float
|
||||
{
|
||||
smp = (1.0f + fc) * smp/(1.0f + fc*std::abs(smp));
|
||||
smp = (1.0f + fc) * smp/(1.0f + fc*std::abs(smp)) * -1.0f;
|
||||
smp = (1.0f + fc) * smp/(1.0f + fc*std::abs(smp));
|
||||
return smp;
|
||||
};
|
||||
std::transform(std::begin(mBuffer[1]), std::begin(mBuffer[1])+todo, std::begin(mBuffer[0]),
|
||||
proc_sample);
|
||||
|
||||
/* Third step, do bandpass filtering of distorted signal. */
|
||||
mBandpass.process({mBuffer[0], todo}, mBuffer[1]);
|
||||
|
||||
todo >>= 2;
|
||||
const ALfloat *outgains{mGain};
|
||||
for(FloatBufferLine &output : samplesOut)
|
||||
{
|
||||
/* Fourth step, final, do attenuation and perform decimation,
|
||||
* storing only one sample out of four.
|
||||
*/
|
||||
const ALfloat gain{*(outgains++)};
|
||||
if(!(std::fabs(gain) > GAIN_SILENCE_THRESHOLD))
|
||||
continue;
|
||||
|
||||
for(size_t i{0u};i < todo;i++)
|
||||
output[base+i] += gain * mBuffer[1][i*4];
|
||||
}
|
||||
|
||||
base += todo;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Distortion_setParami(EffectProps*, ALCcontext *context, ALenum param, ALint)
|
||||
{ context->setError(AL_INVALID_ENUM, "Invalid distortion integer property 0x%04x", param); }
|
||||
void Distortion_setParamiv(EffectProps*, ALCcontext *context, ALenum param, const ALint*)
|
||||
{ context->setError(AL_INVALID_ENUM, "Invalid distortion integer-vector property 0x%04x", param); }
|
||||
void Distortion_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_DISTORTION_EDGE:
|
||||
if(!(val >= AL_DISTORTION_MIN_EDGE && val <= AL_DISTORTION_MAX_EDGE))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Distortion edge out of range");
|
||||
props->Distortion.Edge = val;
|
||||
break;
|
||||
|
||||
case AL_DISTORTION_GAIN:
|
||||
if(!(val >= AL_DISTORTION_MIN_GAIN && val <= AL_DISTORTION_MAX_GAIN))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Distortion gain out of range");
|
||||
props->Distortion.Gain = val;
|
||||
break;
|
||||
|
||||
case AL_DISTORTION_LOWPASS_CUTOFF:
|
||||
if(!(val >= AL_DISTORTION_MIN_LOWPASS_CUTOFF && val <= AL_DISTORTION_MAX_LOWPASS_CUTOFF))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Distortion low-pass cutoff out of range");
|
||||
props->Distortion.LowpassCutoff = val;
|
||||
break;
|
||||
|
||||
case AL_DISTORTION_EQCENTER:
|
||||
if(!(val >= AL_DISTORTION_MIN_EQCENTER && val <= AL_DISTORTION_MAX_EQCENTER))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Distortion EQ center out of range");
|
||||
props->Distortion.EQCenter = val;
|
||||
break;
|
||||
|
||||
case AL_DISTORTION_EQBANDWIDTH:
|
||||
if(!(val >= AL_DISTORTION_MIN_EQBANDWIDTH && val <= AL_DISTORTION_MAX_EQBANDWIDTH))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Distortion EQ bandwidth out of range");
|
||||
props->Distortion.EQBandwidth = val;
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid distortion float property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
void Distortion_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
|
||||
{ Distortion_setParamf(props, context, param, vals[0]); }
|
||||
|
||||
void Distortion_getParami(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
|
||||
{ context->setError(AL_INVALID_ENUM, "Invalid distortion integer property 0x%04x", param); }
|
||||
void Distortion_getParamiv(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
|
||||
{ context->setError(AL_INVALID_ENUM, "Invalid distortion integer-vector property 0x%04x", param); }
|
||||
void Distortion_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_DISTORTION_EDGE:
|
||||
*val = props->Distortion.Edge;
|
||||
break;
|
||||
|
||||
case AL_DISTORTION_GAIN:
|
||||
*val = props->Distortion.Gain;
|
||||
break;
|
||||
|
||||
case AL_DISTORTION_LOWPASS_CUTOFF:
|
||||
*val = props->Distortion.LowpassCutoff;
|
||||
break;
|
||||
|
||||
case AL_DISTORTION_EQCENTER:
|
||||
*val = props->Distortion.EQCenter;
|
||||
break;
|
||||
|
||||
case AL_DISTORTION_EQBANDWIDTH:
|
||||
*val = props->Distortion.EQBandwidth;
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid distortion float property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
void Distortion_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
|
||||
{ Distortion_getParamf(props, context, param, vals); }
|
||||
|
||||
DEFINE_ALEFFECT_VTABLE(Distortion);
|
||||
|
||||
|
||||
struct DistortionStateFactory final : public EffectStateFactory {
|
||||
EffectState *create() override { return new DistortionState{}; }
|
||||
EffectProps getDefaultProps() const noexcept override;
|
||||
const EffectVtable *getEffectVtable() const noexcept override { return &Distortion_vtable; }
|
||||
};
|
||||
|
||||
EffectProps DistortionStateFactory::getDefaultProps() const noexcept
|
||||
{
|
||||
EffectProps props{};
|
||||
props.Distortion.Edge = AL_DISTORTION_DEFAULT_EDGE;
|
||||
props.Distortion.Gain = AL_DISTORTION_DEFAULT_GAIN;
|
||||
props.Distortion.LowpassCutoff = AL_DISTORTION_DEFAULT_LOWPASS_CUTOFF;
|
||||
props.Distortion.EQCenter = AL_DISTORTION_DEFAULT_EQCENTER;
|
||||
props.Distortion.EQBandwidth = AL_DISTORTION_DEFAULT_EQBANDWIDTH;
|
||||
return props;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
EffectStateFactory *DistortionStateFactory_getFactory()
|
||||
{
|
||||
static DistortionStateFactory DistortionFactory{};
|
||||
return &DistortionFactory;
|
||||
}
|
||||
@@ -1,266 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 2009 by Chris Robinson.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "al/auxeffectslot.h"
|
||||
#include "al/filter.h"
|
||||
#include "alcmain.h"
|
||||
#include "alcontext.h"
|
||||
#include "alu.h"
|
||||
#include "filters/biquad.h"
|
||||
#include "vector.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
struct EchoState final : public EffectState {
|
||||
al::vector<ALfloat,16> mSampleBuffer;
|
||||
|
||||
// The echo is two tap. The delay is the number of samples from before the
|
||||
// current offset
|
||||
struct {
|
||||
size_t delay{0u};
|
||||
} mTap[2];
|
||||
size_t mOffset{0u};
|
||||
|
||||
/* The panning gains for the two taps */
|
||||
struct {
|
||||
ALfloat Current[MAX_OUTPUT_CHANNELS]{};
|
||||
ALfloat Target[MAX_OUTPUT_CHANNELS]{};
|
||||
} mGains[2];
|
||||
|
||||
BiquadFilter mFilter;
|
||||
ALfloat mFeedGain{0.0f};
|
||||
|
||||
alignas(16) ALfloat mTempBuffer[2][BUFFERSIZE];
|
||||
|
||||
ALboolean deviceUpdate(const ALCdevice *device) override;
|
||||
void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
|
||||
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) override;
|
||||
|
||||
DEF_NEWDEL(EchoState)
|
||||
};
|
||||
|
||||
ALboolean EchoState::deviceUpdate(const ALCdevice *Device)
|
||||
{
|
||||
const auto frequency = static_cast<float>(Device->Frequency);
|
||||
|
||||
// Use the next power of 2 for the buffer length, so the tap offsets can be
|
||||
// wrapped using a mask instead of a modulo
|
||||
const ALuint maxlen{NextPowerOf2(float2uint(AL_ECHO_MAX_DELAY*frequency + 0.5f) +
|
||||
float2uint(AL_ECHO_MAX_LRDELAY*frequency + 0.5f))};
|
||||
if(maxlen != mSampleBuffer.size())
|
||||
{
|
||||
mSampleBuffer.resize(maxlen);
|
||||
mSampleBuffer.shrink_to_fit();
|
||||
}
|
||||
|
||||
std::fill(mSampleBuffer.begin(), mSampleBuffer.end(), 0.0f);
|
||||
for(auto &e : mGains)
|
||||
{
|
||||
std::fill(std::begin(e.Current), std::end(e.Current), 0.0f);
|
||||
std::fill(std::begin(e.Target), std::end(e.Target), 0.0f);
|
||||
}
|
||||
|
||||
return AL_TRUE;
|
||||
}
|
||||
|
||||
void EchoState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target)
|
||||
{
|
||||
const ALCdevice *device{context->mDevice.get()};
|
||||
const auto frequency = static_cast<ALfloat>(device->Frequency);
|
||||
|
||||
mTap[0].delay = maxu(float2uint(props->Echo.Delay*frequency + 0.5f), 1);
|
||||
mTap[1].delay = float2uint(props->Echo.LRDelay*frequency + 0.5f) + mTap[0].delay;
|
||||
|
||||
const ALfloat gainhf{maxf(1.0f - props->Echo.Damping, 0.0625f)}; /* Limit -24dB */
|
||||
mFilter.setParamsFromSlope(BiquadType::HighShelf, LOWPASSFREQREF/frequency, gainhf, 1.0f);
|
||||
|
||||
mFeedGain = props->Echo.Feedback;
|
||||
|
||||
/* Convert echo spread (where 0 = center, +/-1 = sides) to angle. */
|
||||
const ALfloat angle{std::asin(props->Echo.Spread)};
|
||||
|
||||
ALfloat coeffs[2][MAX_AMBI_CHANNELS];
|
||||
CalcAngleCoeffs(-angle, 0.0f, 0.0f, coeffs[0]);
|
||||
CalcAngleCoeffs( angle, 0.0f, 0.0f, coeffs[1]);
|
||||
|
||||
mOutTarget = target.Main->Buffer;
|
||||
ComputePanGains(target.Main, coeffs[0], slot->Params.Gain, mGains[0].Target);
|
||||
ComputePanGains(target.Main, coeffs[1], slot->Params.Gain, mGains[1].Target);
|
||||
}
|
||||
|
||||
void EchoState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
|
||||
{
|
||||
const size_t mask{mSampleBuffer.size()-1};
|
||||
ALfloat *RESTRICT delaybuf{mSampleBuffer.data()};
|
||||
size_t offset{mOffset};
|
||||
size_t tap1{offset - mTap[0].delay};
|
||||
size_t tap2{offset - mTap[1].delay};
|
||||
ALfloat z1, z2;
|
||||
|
||||
ASSUME(samplesToDo > 0);
|
||||
|
||||
const BiquadFilter filter{mFilter};
|
||||
std::tie(z1, z2) = mFilter.getComponents();
|
||||
for(size_t i{0u};i < samplesToDo;)
|
||||
{
|
||||
offset &= mask;
|
||||
tap1 &= mask;
|
||||
tap2 &= mask;
|
||||
|
||||
size_t td{minz(mask+1 - maxz(offset, maxz(tap1, tap2)), samplesToDo-i)};
|
||||
do {
|
||||
/* Feed the delay buffer's input first. */
|
||||
delaybuf[offset] = samplesIn[0][i];
|
||||
|
||||
/* Get delayed output from the first and second taps. Use the
|
||||
* second tap for feedback.
|
||||
*/
|
||||
mTempBuffer[0][i] = delaybuf[tap1++];
|
||||
mTempBuffer[1][i] = delaybuf[tap2++];
|
||||
const float feedb{mTempBuffer[1][i++]};
|
||||
|
||||
/* Add feedback to the delay buffer with damping and attenuation. */
|
||||
delaybuf[offset++] += filter.processOne(feedb, z1, z2) * mFeedGain;
|
||||
} while(--td);
|
||||
}
|
||||
mFilter.setComponents(z1, z2);
|
||||
mOffset = offset;
|
||||
|
||||
for(ALsizei c{0};c < 2;c++)
|
||||
MixSamples({mTempBuffer[c], samplesToDo}, samplesOut, mGains[c].Current, mGains[c].Target,
|
||||
samplesToDo, 0);
|
||||
}
|
||||
|
||||
|
||||
void Echo_setParami(EffectProps*, ALCcontext *context, ALenum param, ALint)
|
||||
{ context->setError(AL_INVALID_ENUM, "Invalid echo integer property 0x%04x", param); }
|
||||
void Echo_setParamiv(EffectProps*, ALCcontext *context, ALenum param, const ALint*)
|
||||
{ context->setError(AL_INVALID_ENUM, "Invalid echo integer-vector property 0x%04x", param); }
|
||||
void Echo_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_ECHO_DELAY:
|
||||
if(!(val >= AL_ECHO_MIN_DELAY && val <= AL_ECHO_MAX_DELAY))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo delay out of range");
|
||||
props->Echo.Delay = val;
|
||||
break;
|
||||
|
||||
case AL_ECHO_LRDELAY:
|
||||
if(!(val >= AL_ECHO_MIN_LRDELAY && val <= AL_ECHO_MAX_LRDELAY))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo LR delay out of range");
|
||||
props->Echo.LRDelay = val;
|
||||
break;
|
||||
|
||||
case AL_ECHO_DAMPING:
|
||||
if(!(val >= AL_ECHO_MIN_DAMPING && val <= AL_ECHO_MAX_DAMPING))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo damping out of range");
|
||||
props->Echo.Damping = val;
|
||||
break;
|
||||
|
||||
case AL_ECHO_FEEDBACK:
|
||||
if(!(val >= AL_ECHO_MIN_FEEDBACK && val <= AL_ECHO_MAX_FEEDBACK))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo feedback out of range");
|
||||
props->Echo.Feedback = val;
|
||||
break;
|
||||
|
||||
case AL_ECHO_SPREAD:
|
||||
if(!(val >= AL_ECHO_MIN_SPREAD && val <= AL_ECHO_MAX_SPREAD))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo spread out of range");
|
||||
props->Echo.Spread = val;
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid echo float property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
void Echo_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
|
||||
{ Echo_setParamf(props, context, param, vals[0]); }
|
||||
|
||||
void Echo_getParami(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
|
||||
{ context->setError(AL_INVALID_ENUM, "Invalid echo integer property 0x%04x", param); }
|
||||
void Echo_getParamiv(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
|
||||
{ context->setError(AL_INVALID_ENUM, "Invalid echo integer-vector property 0x%04x", param); }
|
||||
void Echo_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_ECHO_DELAY:
|
||||
*val = props->Echo.Delay;
|
||||
break;
|
||||
|
||||
case AL_ECHO_LRDELAY:
|
||||
*val = props->Echo.LRDelay;
|
||||
break;
|
||||
|
||||
case AL_ECHO_DAMPING:
|
||||
*val = props->Echo.Damping;
|
||||
break;
|
||||
|
||||
case AL_ECHO_FEEDBACK:
|
||||
*val = props->Echo.Feedback;
|
||||
break;
|
||||
|
||||
case AL_ECHO_SPREAD:
|
||||
*val = props->Echo.Spread;
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid echo float property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
void Echo_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
|
||||
{ Echo_getParamf(props, context, param, vals); }
|
||||
|
||||
DEFINE_ALEFFECT_VTABLE(Echo);
|
||||
|
||||
|
||||
struct EchoStateFactory final : public EffectStateFactory {
|
||||
EffectState *create() override { return new EchoState{}; }
|
||||
EffectProps getDefaultProps() const noexcept override;
|
||||
const EffectVtable *getEffectVtable() const noexcept override { return &Echo_vtable; }
|
||||
};
|
||||
|
||||
EffectProps EchoStateFactory::getDefaultProps() const noexcept
|
||||
{
|
||||
EffectProps props{};
|
||||
props.Echo.Delay = AL_ECHO_DEFAULT_DELAY;
|
||||
props.Echo.LRDelay = AL_ECHO_DEFAULT_LRDELAY;
|
||||
props.Echo.Damping = AL_ECHO_DEFAULT_DAMPING;
|
||||
props.Echo.Feedback = AL_ECHO_DEFAULT_FEEDBACK;
|
||||
props.Echo.Spread = AL_ECHO_DEFAULT_SPREAD;
|
||||
return props;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
EffectStateFactory *EchoStateFactory_getFactory()
|
||||
{
|
||||
static EchoStateFactory EchoFactory{};
|
||||
return &EchoFactory;
|
||||
}
|
||||
@@ -1,338 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 2013 by Mike Gorchak
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
|
||||
#include "al/auxeffectslot.h"
|
||||
#include "alcmain.h"
|
||||
#include "alcontext.h"
|
||||
#include "alu.h"
|
||||
#include "filters/biquad.h"
|
||||
#include "vecmat.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
/* The document "Effects Extension Guide.pdf" says that low and high *
|
||||
* frequencies are cutoff frequencies. This is not fully correct, they *
|
||||
* are corner frequencies for low and high shelf filters. If they were *
|
||||
* just cutoff frequencies, there would be no need in cutoff frequency *
|
||||
* gains, which are present. Documentation for "Creative Proteus X2" *
|
||||
* software describes 4-band equalizer functionality in a much better *
|
||||
* way. This equalizer seems to be a predecessor of OpenAL 4-band *
|
||||
* equalizer. With low and high shelf filters we are able to cutoff *
|
||||
* frequencies below and/or above corner frequencies using attenuation *
|
||||
* gains (below 1.0) and amplify all low and/or high frequencies using *
|
||||
* gains above 1.0. *
|
||||
* *
|
||||
* Low-shelf Low Mid Band High Mid Band High-shelf *
|
||||
* corner center center corner *
|
||||
* frequency frequency frequency frequency *
|
||||
* 50Hz..800Hz 200Hz..3000Hz 1000Hz..8000Hz 4000Hz..16000Hz *
|
||||
* *
|
||||
* | | | | *
|
||||
* | | | | *
|
||||
* B -----+ /--+--\ /--+--\ +----- *
|
||||
* O |\ | | | | | | /| *
|
||||
* O | \ - | - - | - / | *
|
||||
* S + | \ | | | | | | / | *
|
||||
* T | | | | | | | | | | *
|
||||
* ---------+---------------+------------------+---------------+-------- *
|
||||
* C | | | | | | | | | | *
|
||||
* U - | / | | | | | | \ | *
|
||||
* T | / - | - - | - \ | *
|
||||
* O |/ | | | | | | \| *
|
||||
* F -----+ \--+--/ \--+--/ +----- *
|
||||
* F | | | | *
|
||||
* | | | | *
|
||||
* *
|
||||
* Gains vary from 0.126 up to 7.943, which means from -18dB attenuation *
|
||||
* up to +18dB amplification. Band width varies from 0.01 up to 1.0 in *
|
||||
* octaves for two mid bands. *
|
||||
* *
|
||||
* Implementation is based on the "Cookbook formulae for audio EQ biquad *
|
||||
* filter coefficients" by Robert Bristow-Johnson *
|
||||
* http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt */
|
||||
|
||||
|
||||
struct EqualizerState final : public EffectState {
|
||||
struct {
|
||||
/* Effect parameters */
|
||||
BiquadFilter filter[4];
|
||||
|
||||
/* Effect gains for each channel */
|
||||
ALfloat CurrentGains[MAX_OUTPUT_CHANNELS]{};
|
||||
ALfloat TargetGains[MAX_OUTPUT_CHANNELS]{};
|
||||
} mChans[MAX_AMBI_CHANNELS];
|
||||
|
||||
ALfloat mSampleBuffer[BUFFERSIZE]{};
|
||||
|
||||
|
||||
ALboolean deviceUpdate(const ALCdevice *device) override;
|
||||
void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
|
||||
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) override;
|
||||
|
||||
DEF_NEWDEL(EqualizerState)
|
||||
};
|
||||
|
||||
ALboolean EqualizerState::deviceUpdate(const ALCdevice*)
|
||||
{
|
||||
for(auto &e : mChans)
|
||||
{
|
||||
std::for_each(std::begin(e.filter), std::end(e.filter),
|
||||
std::mem_fn(&BiquadFilter::clear));
|
||||
std::fill(std::begin(e.CurrentGains), std::end(e.CurrentGains), 0.0f);
|
||||
}
|
||||
return AL_TRUE;
|
||||
}
|
||||
|
||||
void EqualizerState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target)
|
||||
{
|
||||
const ALCdevice *device{context->mDevice.get()};
|
||||
auto frequency = static_cast<ALfloat>(device->Frequency);
|
||||
ALfloat gain, f0norm;
|
||||
|
||||
/* Calculate coefficients for the each type of filter. Note that the shelf
|
||||
* and peaking filters' gain is for the centerpoint of the transition band,
|
||||
* while the effect property gains are for the shelf/peak itself. So the
|
||||
* property gains need their dB halved (sqrt of linear gain) for the
|
||||
* shelf/peak to reach the provided gain.
|
||||
*/
|
||||
gain = std::sqrt(props->Equalizer.LowGain);
|
||||
f0norm = props->Equalizer.LowCutoff / frequency;
|
||||
mChans[0].filter[0].setParamsFromSlope(BiquadType::LowShelf, f0norm, gain, 0.75f);
|
||||
|
||||
gain = std::sqrt(props->Equalizer.Mid1Gain);
|
||||
f0norm = props->Equalizer.Mid1Center / frequency;
|
||||
mChans[0].filter[1].setParamsFromBandwidth(BiquadType::Peaking, f0norm, gain,
|
||||
props->Equalizer.Mid1Width);
|
||||
|
||||
gain = std::sqrt(props->Equalizer.Mid2Gain);
|
||||
f0norm = props->Equalizer.Mid2Center / frequency;
|
||||
mChans[0].filter[2].setParamsFromBandwidth(BiquadType::Peaking, f0norm, gain,
|
||||
props->Equalizer.Mid2Width);
|
||||
|
||||
gain = std::sqrt(props->Equalizer.HighGain);
|
||||
f0norm = props->Equalizer.HighCutoff / frequency;
|
||||
mChans[0].filter[3].setParamsFromSlope(BiquadType::HighShelf, f0norm, gain, 0.75f);
|
||||
|
||||
/* Copy the filter coefficients for the other input channels. */
|
||||
for(size_t i{1u};i < slot->Wet.Buffer.size();++i)
|
||||
{
|
||||
mChans[i].filter[0].copyParamsFrom(mChans[0].filter[0]);
|
||||
mChans[i].filter[1].copyParamsFrom(mChans[0].filter[1]);
|
||||
mChans[i].filter[2].copyParamsFrom(mChans[0].filter[2]);
|
||||
mChans[i].filter[3].copyParamsFrom(mChans[0].filter[3]);
|
||||
}
|
||||
|
||||
mOutTarget = target.Main->Buffer;
|
||||
for(size_t i{0u};i < slot->Wet.Buffer.size();++i)
|
||||
{
|
||||
auto coeffs = GetAmbiIdentityRow(i);
|
||||
ComputePanGains(target.Main, coeffs.data(), slot->Params.Gain, mChans[i].TargetGains);
|
||||
}
|
||||
}
|
||||
|
||||
void EqualizerState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
|
||||
{
|
||||
const al::span<float> buffer{mSampleBuffer, samplesToDo};
|
||||
auto chandata = std::addressof(mChans[0]);
|
||||
for(const auto &input : samplesIn)
|
||||
{
|
||||
chandata->filter[0].process({input.data(), samplesToDo}, buffer.begin());
|
||||
chandata->filter[1].process(buffer, buffer.begin());
|
||||
chandata->filter[2].process(buffer, buffer.begin());
|
||||
chandata->filter[3].process(buffer, buffer.begin());
|
||||
|
||||
MixSamples(buffer, samplesOut, chandata->CurrentGains, chandata->TargetGains, samplesToDo,
|
||||
0u);
|
||||
++chandata;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Equalizer_setParami(EffectProps*, ALCcontext *context, ALenum param, ALint)
|
||||
{ context->setError(AL_INVALID_ENUM, "Invalid equalizer integer property 0x%04x", param); }
|
||||
void Equalizer_setParamiv(EffectProps*, ALCcontext *context, ALenum param, const ALint*)
|
||||
{ context->setError(AL_INVALID_ENUM, "Invalid equalizer integer-vector property 0x%04x", param); }
|
||||
void Equalizer_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_EQUALIZER_LOW_GAIN:
|
||||
if(!(val >= AL_EQUALIZER_MIN_LOW_GAIN && val <= AL_EQUALIZER_MAX_LOW_GAIN))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer low-band gain out of range");
|
||||
props->Equalizer.LowGain = val;
|
||||
break;
|
||||
|
||||
case AL_EQUALIZER_LOW_CUTOFF:
|
||||
if(!(val >= AL_EQUALIZER_MIN_LOW_CUTOFF && val <= AL_EQUALIZER_MAX_LOW_CUTOFF))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer low-band cutoff out of range");
|
||||
props->Equalizer.LowCutoff = val;
|
||||
break;
|
||||
|
||||
case AL_EQUALIZER_MID1_GAIN:
|
||||
if(!(val >= AL_EQUALIZER_MIN_MID1_GAIN && val <= AL_EQUALIZER_MAX_MID1_GAIN))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer mid1-band gain out of range");
|
||||
props->Equalizer.Mid1Gain = val;
|
||||
break;
|
||||
|
||||
case AL_EQUALIZER_MID1_CENTER:
|
||||
if(!(val >= AL_EQUALIZER_MIN_MID1_CENTER && val <= AL_EQUALIZER_MAX_MID1_CENTER))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer mid1-band center out of range");
|
||||
props->Equalizer.Mid1Center = val;
|
||||
break;
|
||||
|
||||
case AL_EQUALIZER_MID1_WIDTH:
|
||||
if(!(val >= AL_EQUALIZER_MIN_MID1_WIDTH && val <= AL_EQUALIZER_MAX_MID1_WIDTH))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer mid1-band width out of range");
|
||||
props->Equalizer.Mid1Width = val;
|
||||
break;
|
||||
|
||||
case AL_EQUALIZER_MID2_GAIN:
|
||||
if(!(val >= AL_EQUALIZER_MIN_MID2_GAIN && val <= AL_EQUALIZER_MAX_MID2_GAIN))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer mid2-band gain out of range");
|
||||
props->Equalizer.Mid2Gain = val;
|
||||
break;
|
||||
|
||||
case AL_EQUALIZER_MID2_CENTER:
|
||||
if(!(val >= AL_EQUALIZER_MIN_MID2_CENTER && val <= AL_EQUALIZER_MAX_MID2_CENTER))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer mid2-band center out of range");
|
||||
props->Equalizer.Mid2Center = val;
|
||||
break;
|
||||
|
||||
case AL_EQUALIZER_MID2_WIDTH:
|
||||
if(!(val >= AL_EQUALIZER_MIN_MID2_WIDTH && val <= AL_EQUALIZER_MAX_MID2_WIDTH))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer mid2-band width out of range");
|
||||
props->Equalizer.Mid2Width = val;
|
||||
break;
|
||||
|
||||
case AL_EQUALIZER_HIGH_GAIN:
|
||||
if(!(val >= AL_EQUALIZER_MIN_HIGH_GAIN && val <= AL_EQUALIZER_MAX_HIGH_GAIN))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer high-band gain out of range");
|
||||
props->Equalizer.HighGain = val;
|
||||
break;
|
||||
|
||||
case AL_EQUALIZER_HIGH_CUTOFF:
|
||||
if(!(val >= AL_EQUALIZER_MIN_HIGH_CUTOFF && val <= AL_EQUALIZER_MAX_HIGH_CUTOFF))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer high-band cutoff out of range");
|
||||
props->Equalizer.HighCutoff = val;
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid equalizer float property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
void Equalizer_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
|
||||
{ Equalizer_setParamf(props, context, param, vals[0]); }
|
||||
|
||||
void Equalizer_getParami(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
|
||||
{ context->setError(AL_INVALID_ENUM, "Invalid equalizer integer property 0x%04x", param); }
|
||||
void Equalizer_getParamiv(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
|
||||
{ context->setError(AL_INVALID_ENUM, "Invalid equalizer integer-vector property 0x%04x", param); }
|
||||
void Equalizer_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_EQUALIZER_LOW_GAIN:
|
||||
*val = props->Equalizer.LowGain;
|
||||
break;
|
||||
|
||||
case AL_EQUALIZER_LOW_CUTOFF:
|
||||
*val = props->Equalizer.LowCutoff;
|
||||
break;
|
||||
|
||||
case AL_EQUALIZER_MID1_GAIN:
|
||||
*val = props->Equalizer.Mid1Gain;
|
||||
break;
|
||||
|
||||
case AL_EQUALIZER_MID1_CENTER:
|
||||
*val = props->Equalizer.Mid1Center;
|
||||
break;
|
||||
|
||||
case AL_EQUALIZER_MID1_WIDTH:
|
||||
*val = props->Equalizer.Mid1Width;
|
||||
break;
|
||||
|
||||
case AL_EQUALIZER_MID2_GAIN:
|
||||
*val = props->Equalizer.Mid2Gain;
|
||||
break;
|
||||
|
||||
case AL_EQUALIZER_MID2_CENTER:
|
||||
*val = props->Equalizer.Mid2Center;
|
||||
break;
|
||||
|
||||
case AL_EQUALIZER_MID2_WIDTH:
|
||||
*val = props->Equalizer.Mid2Width;
|
||||
break;
|
||||
|
||||
case AL_EQUALIZER_HIGH_GAIN:
|
||||
*val = props->Equalizer.HighGain;
|
||||
break;
|
||||
|
||||
case AL_EQUALIZER_HIGH_CUTOFF:
|
||||
*val = props->Equalizer.HighCutoff;
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid equalizer float property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
void Equalizer_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
|
||||
{ Equalizer_getParamf(props, context, param, vals); }
|
||||
|
||||
DEFINE_ALEFFECT_VTABLE(Equalizer);
|
||||
|
||||
|
||||
struct EqualizerStateFactory final : public EffectStateFactory {
|
||||
EffectState *create() override { return new EqualizerState{}; }
|
||||
EffectProps getDefaultProps() const noexcept override;
|
||||
const EffectVtable *getEffectVtable() const noexcept override { return &Equalizer_vtable; }
|
||||
};
|
||||
|
||||
EffectProps EqualizerStateFactory::getDefaultProps() const noexcept
|
||||
{
|
||||
EffectProps props{};
|
||||
props.Equalizer.LowCutoff = AL_EQUALIZER_DEFAULT_LOW_CUTOFF;
|
||||
props.Equalizer.LowGain = AL_EQUALIZER_DEFAULT_LOW_GAIN;
|
||||
props.Equalizer.Mid1Center = AL_EQUALIZER_DEFAULT_MID1_CENTER;
|
||||
props.Equalizer.Mid1Gain = AL_EQUALIZER_DEFAULT_MID1_GAIN;
|
||||
props.Equalizer.Mid1Width = AL_EQUALIZER_DEFAULT_MID1_WIDTH;
|
||||
props.Equalizer.Mid2Center = AL_EQUALIZER_DEFAULT_MID2_CENTER;
|
||||
props.Equalizer.Mid2Gain = AL_EQUALIZER_DEFAULT_MID2_GAIN;
|
||||
props.Equalizer.Mid2Width = AL_EQUALIZER_DEFAULT_MID2_WIDTH;
|
||||
props.Equalizer.HighCutoff = AL_EQUALIZER_DEFAULT_HIGH_CUTOFF;
|
||||
props.Equalizer.HighGain = AL_EQUALIZER_DEFAULT_HIGH_GAIN;
|
||||
return props;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
EffectStateFactory *EqualizerStateFactory_getFactory()
|
||||
{
|
||||
static EqualizerStateFactory EqualizerFactory{};
|
||||
return &EqualizerFactory;
|
||||
}
|
||||
@@ -1,332 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 2018 by Raul Herraiz.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <array>
|
||||
#include <complex>
|
||||
#include <algorithm>
|
||||
|
||||
#include "al/auxeffectslot.h"
|
||||
#include "alcmain.h"
|
||||
#include "alcontext.h"
|
||||
#include "alu.h"
|
||||
|
||||
#include "alcomplex.h"
|
||||
|
||||
namespace {
|
||||
|
||||
using complex_d = std::complex<double>;
|
||||
|
||||
#define HIL_SIZE 1024
|
||||
#define OVERSAMP (1<<2)
|
||||
|
||||
#define HIL_STEP (HIL_SIZE / OVERSAMP)
|
||||
#define FIFO_LATENCY (HIL_STEP * (OVERSAMP-1))
|
||||
|
||||
/* Define a Hann window, used to filter the HIL input and output. */
|
||||
/* Making this constexpr seems to require C++14. */
|
||||
std::array<ALdouble,HIL_SIZE> InitHannWindow()
|
||||
{
|
||||
std::array<ALdouble,HIL_SIZE> ret;
|
||||
/* Create lookup table of the Hann window for the desired size, i.e. HIL_SIZE */
|
||||
for(size_t i{0};i < HIL_SIZE>>1;i++)
|
||||
{
|
||||
constexpr double scale{al::MathDefs<double>::Pi() / double{HIL_SIZE-1}};
|
||||
const double val{std::sin(static_cast<double>(i) * scale)};
|
||||
ret[i] = ret[HIL_SIZE-1-i] = val * val;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
alignas(16) const std::array<ALdouble,HIL_SIZE> HannWindow = InitHannWindow();
|
||||
|
||||
|
||||
struct FshifterState final : public EffectState {
|
||||
/* Effect parameters */
|
||||
size_t mCount{};
|
||||
ALsizei mPhaseStep[2]{};
|
||||
ALsizei mPhase[2]{};
|
||||
ALdouble mSign[2]{};
|
||||
|
||||
|
||||
/*Effects buffers*/
|
||||
ALfloat mInFIFO[HIL_SIZE]{};
|
||||
complex_d mOutFIFO[HIL_SIZE]{};
|
||||
complex_d mOutputAccum[HIL_SIZE]{};
|
||||
complex_d mAnalytic[HIL_SIZE]{};
|
||||
complex_d mOutdata[BUFFERSIZE]{};
|
||||
|
||||
alignas(16) ALfloat mBufferOut[BUFFERSIZE]{};
|
||||
|
||||
/* Effect gains for each output channel */
|
||||
struct {
|
||||
ALfloat Current[MAX_OUTPUT_CHANNELS]{};
|
||||
ALfloat Target[MAX_OUTPUT_CHANNELS]{};
|
||||
} mGains[2];
|
||||
|
||||
|
||||
ALboolean deviceUpdate(const ALCdevice *device) override;
|
||||
void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
|
||||
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) override;
|
||||
|
||||
DEF_NEWDEL(FshifterState)
|
||||
};
|
||||
|
||||
ALboolean FshifterState::deviceUpdate(const ALCdevice*)
|
||||
{
|
||||
/* (Re-)initializing parameters and clear the buffers. */
|
||||
mCount = FIFO_LATENCY;
|
||||
|
||||
std::fill(std::begin(mPhaseStep), std::end(mPhaseStep), 0);
|
||||
std::fill(std::begin(mPhase), std::end(mPhase), 0);
|
||||
std::fill(std::begin(mSign), std::end(mSign), 1.0);
|
||||
std::fill(std::begin(mInFIFO), std::end(mInFIFO), 0.0f);
|
||||
std::fill(std::begin(mOutFIFO), std::end(mOutFIFO), complex_d{});
|
||||
std::fill(std::begin(mOutputAccum), std::end(mOutputAccum), complex_d{});
|
||||
std::fill(std::begin(mAnalytic), std::end(mAnalytic), complex_d{});
|
||||
|
||||
for(auto &gain : mGains)
|
||||
{
|
||||
std::fill(std::begin(gain.Current), std::end(gain.Current), 0.0f);
|
||||
std::fill(std::begin(gain.Target), std::end(gain.Target), 0.0f);
|
||||
}
|
||||
|
||||
return AL_TRUE;
|
||||
}
|
||||
|
||||
void FshifterState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target)
|
||||
{
|
||||
const ALCdevice *device{context->mDevice.get()};
|
||||
|
||||
ALfloat step{props->Fshifter.Frequency / static_cast<ALfloat>(device->Frequency)};
|
||||
mPhaseStep[0] = mPhaseStep[1] = fastf2i(minf(step, 0.5f) * FRACTIONONE);
|
||||
|
||||
switch(props->Fshifter.LeftDirection)
|
||||
{
|
||||
case AL_FREQUENCY_SHIFTER_DIRECTION_DOWN:
|
||||
mSign[0] = -1.0;
|
||||
break;
|
||||
|
||||
case AL_FREQUENCY_SHIFTER_DIRECTION_UP:
|
||||
mSign[0] = 1.0;
|
||||
break;
|
||||
|
||||
case AL_FREQUENCY_SHIFTER_DIRECTION_OFF:
|
||||
mPhase[0] = 0;
|
||||
mPhaseStep[0] = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (props->Fshifter.RightDirection)
|
||||
{
|
||||
case AL_FREQUENCY_SHIFTER_DIRECTION_DOWN:
|
||||
mSign[1] = -1.0;
|
||||
break;
|
||||
|
||||
case AL_FREQUENCY_SHIFTER_DIRECTION_UP:
|
||||
mSign[1] = 1.0;
|
||||
break;
|
||||
|
||||
case AL_FREQUENCY_SHIFTER_DIRECTION_OFF:
|
||||
mPhase[1] = 0;
|
||||
mPhaseStep[1] = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
ALfloat coeffs[2][MAX_AMBI_CHANNELS];
|
||||
CalcDirectionCoeffs({-1.0f, 0.0f, -1.0f}, 0.0f, coeffs[0]);
|
||||
CalcDirectionCoeffs({ 1.0f, 0.0f, -1.0f}, 0.0f, coeffs[1]);
|
||||
|
||||
mOutTarget = target.Main->Buffer;
|
||||
ComputePanGains(target.Main, coeffs[0], slot->Params.Gain, mGains[0].Target);
|
||||
ComputePanGains(target.Main, coeffs[1], slot->Params.Gain, mGains[1].Target);
|
||||
}
|
||||
|
||||
void FshifterState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
|
||||
{
|
||||
static constexpr complex_d complex_zero{0.0, 0.0};
|
||||
ALfloat *RESTRICT BufferOut = mBufferOut;
|
||||
size_t j, k;
|
||||
|
||||
for(size_t base{0u};base < samplesToDo;)
|
||||
{
|
||||
const size_t todo{minz(HIL_SIZE-mCount, samplesToDo-base)};
|
||||
|
||||
ASSUME(todo > 0);
|
||||
|
||||
/* Fill FIFO buffer with samples data */
|
||||
k = mCount;
|
||||
for(j = 0;j < todo;j++,k++)
|
||||
{
|
||||
mInFIFO[k] = samplesIn[0][base+j];
|
||||
mOutdata[base+j] = mOutFIFO[k-FIFO_LATENCY];
|
||||
}
|
||||
mCount += todo;
|
||||
base += todo;
|
||||
|
||||
/* Check whether FIFO buffer is filled */
|
||||
if(mCount < HIL_SIZE) continue;
|
||||
mCount = FIFO_LATENCY;
|
||||
|
||||
/* Real signal windowing and store in Analytic buffer */
|
||||
for(k = 0;k < HIL_SIZE;k++)
|
||||
{
|
||||
mAnalytic[k].real(mInFIFO[k] * HannWindow[k]);
|
||||
mAnalytic[k].imag(0.0);
|
||||
}
|
||||
|
||||
/* Processing signal by Discrete Hilbert Transform (analytical signal). */
|
||||
complex_hilbert(mAnalytic);
|
||||
|
||||
/* Windowing and add to output accumulator */
|
||||
for(k = 0;k < HIL_SIZE;k++)
|
||||
mOutputAccum[k] += 2.0/OVERSAMP*HannWindow[k]*mAnalytic[k];
|
||||
|
||||
/* Shift accumulator, input & output FIFO */
|
||||
for(k = 0;k < HIL_STEP;k++) mOutFIFO[k] = mOutputAccum[k];
|
||||
for(j = 0;k < HIL_SIZE;k++,j++) mOutputAccum[j] = mOutputAccum[k];
|
||||
for(;j < HIL_SIZE;j++) mOutputAccum[j] = complex_zero;
|
||||
for(k = 0;k < FIFO_LATENCY;k++)
|
||||
mInFIFO[k] = mInFIFO[k+HIL_STEP];
|
||||
}
|
||||
|
||||
/* Process frequency shifter using the analytic signal obtained. */
|
||||
for(ALsizei c{0};c < 2;++c)
|
||||
{
|
||||
for(k = 0;k < samplesToDo;++k)
|
||||
{
|
||||
double phase = mPhase[c] * ((1.0 / FRACTIONONE) * al::MathDefs<double>::Tau());
|
||||
BufferOut[k] = static_cast<float>(mOutdata[k].real()*std::cos(phase) +
|
||||
mOutdata[k].imag()*std::sin(phase)*mSign[c]);
|
||||
|
||||
mPhase[c] += mPhaseStep[c];
|
||||
mPhase[c] &= FRACTIONMASK;
|
||||
}
|
||||
|
||||
/* Now, mix the processed sound data to the output. */
|
||||
MixSamples({BufferOut, samplesToDo}, samplesOut, mGains[c].Current, mGains[c].Target,
|
||||
maxz(samplesToDo, 512), 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Fshifter_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_FREQUENCY_SHIFTER_FREQUENCY:
|
||||
if(!(val >= AL_FREQUENCY_SHIFTER_MIN_FREQUENCY && val <= AL_FREQUENCY_SHIFTER_MAX_FREQUENCY))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,,"Frequency shifter frequency out of range");
|
||||
props->Fshifter.Frequency = val;
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid frequency shifter float property 0x%04x",
|
||||
param);
|
||||
}
|
||||
}
|
||||
void Fshifter_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
|
||||
{ Fshifter_setParamf(props, context, param, vals[0]); }
|
||||
|
||||
void Fshifter_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_FREQUENCY_SHIFTER_LEFT_DIRECTION:
|
||||
if(!(val >= AL_FREQUENCY_SHIFTER_MIN_LEFT_DIRECTION && val <= AL_FREQUENCY_SHIFTER_MAX_LEFT_DIRECTION))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,,"Frequency shifter left direction out of range");
|
||||
props->Fshifter.LeftDirection = val;
|
||||
break;
|
||||
|
||||
case AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION:
|
||||
if(!(val >= AL_FREQUENCY_SHIFTER_MIN_RIGHT_DIRECTION && val <= AL_FREQUENCY_SHIFTER_MAX_RIGHT_DIRECTION))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,,"Frequency shifter right direction out of range");
|
||||
props->Fshifter.RightDirection = val;
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid frequency shifter integer property 0x%04x",
|
||||
param);
|
||||
}
|
||||
}
|
||||
void Fshifter_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals)
|
||||
{ Fshifter_setParami(props, context, param, vals[0]); }
|
||||
|
||||
void Fshifter_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_FREQUENCY_SHIFTER_LEFT_DIRECTION:
|
||||
*val = props->Fshifter.LeftDirection;
|
||||
break;
|
||||
case AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION:
|
||||
*val = props->Fshifter.RightDirection;
|
||||
break;
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid frequency shifter integer property 0x%04x",
|
||||
param);
|
||||
}
|
||||
}
|
||||
void Fshifter_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals)
|
||||
{ Fshifter_getParami(props, context, param, vals); }
|
||||
|
||||
void Fshifter_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_FREQUENCY_SHIFTER_FREQUENCY:
|
||||
*val = props->Fshifter.Frequency;
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid frequency shifter float property 0x%04x",
|
||||
param);
|
||||
}
|
||||
}
|
||||
void Fshifter_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
|
||||
{ Fshifter_getParamf(props, context, param, vals); }
|
||||
|
||||
DEFINE_ALEFFECT_VTABLE(Fshifter);
|
||||
|
||||
|
||||
struct FshifterStateFactory final : public EffectStateFactory {
|
||||
EffectState *create() override { return new FshifterState{}; }
|
||||
EffectProps getDefaultProps() const noexcept override;
|
||||
const EffectVtable *getEffectVtable() const noexcept override { return &Fshifter_vtable; }
|
||||
};
|
||||
|
||||
EffectProps FshifterStateFactory::getDefaultProps() const noexcept
|
||||
{
|
||||
EffectProps props{};
|
||||
props.Fshifter.Frequency = AL_FREQUENCY_SHIFTER_DEFAULT_FREQUENCY;
|
||||
props.Fshifter.LeftDirection = AL_FREQUENCY_SHIFTER_DEFAULT_LEFT_DIRECTION;
|
||||
props.Fshifter.RightDirection = AL_FREQUENCY_SHIFTER_DEFAULT_RIGHT_DIRECTION;
|
||||
return props;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
EffectStateFactory *FshifterStateFactory_getFactory()
|
||||
{
|
||||
static FshifterStateFactory FshifterFactory{};
|
||||
return &FshifterFactory;
|
||||
}
|
||||
@@ -1,269 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 2009 by Chris Robinson.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
|
||||
#include "al/auxeffectslot.h"
|
||||
#include "alcmain.h"
|
||||
#include "alcontext.h"
|
||||
#include "alu.h"
|
||||
#include "filters/biquad.h"
|
||||
#include "vecmat.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
#define MAX_UPDATE_SAMPLES 128
|
||||
|
||||
#define WAVEFORM_FRACBITS 24
|
||||
#define WAVEFORM_FRACONE (1<<WAVEFORM_FRACBITS)
|
||||
#define WAVEFORM_FRACMASK (WAVEFORM_FRACONE-1)
|
||||
|
||||
inline float Sin(ALuint index)
|
||||
{
|
||||
constexpr float scale{al::MathDefs<float>::Tau() / WAVEFORM_FRACONE};
|
||||
return std::sin(static_cast<float>(index) * scale);
|
||||
}
|
||||
|
||||
inline float Saw(ALuint index)
|
||||
{ return static_cast<float>(index)*(2.0f/WAVEFORM_FRACONE) - 1.0f; }
|
||||
|
||||
inline float Square(ALuint index)
|
||||
{ return static_cast<float>(static_cast<int>((index>>(WAVEFORM_FRACBITS-2))&2) - 1); }
|
||||
|
||||
inline float One(ALuint) { return 1.0f; }
|
||||
|
||||
template<float (&func)(ALuint)>
|
||||
void Modulate(float *RESTRICT dst, ALuint index, const ALuint step, size_t todo)
|
||||
{
|
||||
for(size_t i{0u};i < todo;i++)
|
||||
{
|
||||
index += step;
|
||||
index &= WAVEFORM_FRACMASK;
|
||||
dst[i] = func(index);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct ModulatorState final : public EffectState {
|
||||
void (*mGetSamples)(float*RESTRICT, ALuint, const ALuint, size_t){};
|
||||
|
||||
ALuint mIndex{0};
|
||||
ALuint mStep{1};
|
||||
|
||||
struct {
|
||||
BiquadFilter Filter;
|
||||
|
||||
ALfloat CurrentGains[MAX_OUTPUT_CHANNELS]{};
|
||||
ALfloat TargetGains[MAX_OUTPUT_CHANNELS]{};
|
||||
} mChans[MAX_AMBI_CHANNELS];
|
||||
|
||||
|
||||
ALboolean deviceUpdate(const ALCdevice *device) override;
|
||||
void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
|
||||
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) override;
|
||||
|
||||
DEF_NEWDEL(ModulatorState)
|
||||
};
|
||||
|
||||
ALboolean ModulatorState::deviceUpdate(const ALCdevice*)
|
||||
{
|
||||
for(auto &e : mChans)
|
||||
{
|
||||
e.Filter.clear();
|
||||
std::fill(std::begin(e.CurrentGains), std::end(e.CurrentGains), 0.0f);
|
||||
}
|
||||
return AL_TRUE;
|
||||
}
|
||||
|
||||
void ModulatorState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target)
|
||||
{
|
||||
const ALCdevice *device{context->mDevice.get()};
|
||||
|
||||
const float step{props->Modulator.Frequency / static_cast<ALfloat>(device->Frequency)};
|
||||
mStep = fastf2u(clampf(step*WAVEFORM_FRACONE, 0.0f, ALfloat{WAVEFORM_FRACONE-1}));
|
||||
|
||||
if(mStep == 0)
|
||||
mGetSamples = Modulate<One>;
|
||||
else if(props->Modulator.Waveform == AL_RING_MODULATOR_SINUSOID)
|
||||
mGetSamples = Modulate<Sin>;
|
||||
else if(props->Modulator.Waveform == AL_RING_MODULATOR_SAWTOOTH)
|
||||
mGetSamples = Modulate<Saw>;
|
||||
else /*if(props->Modulator.Waveform == AL_RING_MODULATOR_SQUARE)*/
|
||||
mGetSamples = Modulate<Square>;
|
||||
|
||||
float f0norm{props->Modulator.HighPassCutoff / static_cast<ALfloat>(device->Frequency)};
|
||||
f0norm = clampf(f0norm, 1.0f/512.0f, 0.49f);
|
||||
/* Bandwidth value is constant in octaves. */
|
||||
mChans[0].Filter.setParamsFromBandwidth(BiquadType::HighPass, f0norm, 1.0f, 0.75f);
|
||||
for(size_t i{1u};i < slot->Wet.Buffer.size();++i)
|
||||
mChans[i].Filter.copyParamsFrom(mChans[0].Filter);
|
||||
|
||||
mOutTarget = target.Main->Buffer;
|
||||
for(size_t i{0u};i < slot->Wet.Buffer.size();++i)
|
||||
{
|
||||
auto coeffs = GetAmbiIdentityRow(i);
|
||||
ComputePanGains(target.Main, coeffs.data(), slot->Params.Gain, mChans[i].TargetGains);
|
||||
}
|
||||
}
|
||||
|
||||
void ModulatorState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
|
||||
{
|
||||
for(size_t base{0u};base < samplesToDo;)
|
||||
{
|
||||
alignas(16) ALfloat modsamples[MAX_UPDATE_SAMPLES];
|
||||
size_t td{minz(MAX_UPDATE_SAMPLES, samplesToDo-base)};
|
||||
|
||||
mGetSamples(modsamples, mIndex, mStep, td);
|
||||
mIndex += static_cast<ALuint>(mStep * td);
|
||||
mIndex &= WAVEFORM_FRACMASK;
|
||||
|
||||
auto chandata = std::addressof(mChans[0]);
|
||||
for(const auto &input : samplesIn)
|
||||
{
|
||||
alignas(16) ALfloat temps[MAX_UPDATE_SAMPLES];
|
||||
|
||||
chandata->Filter.process({&input[base], td}, temps);
|
||||
for(size_t i{0u};i < td;i++)
|
||||
temps[i] *= modsamples[i];
|
||||
|
||||
MixSamples({temps, td}, samplesOut, chandata->CurrentGains, chandata->TargetGains,
|
||||
samplesToDo-base, base);
|
||||
++chandata;
|
||||
}
|
||||
|
||||
base += td;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Modulator_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_RING_MODULATOR_FREQUENCY:
|
||||
if(!(val >= AL_RING_MODULATOR_MIN_FREQUENCY && val <= AL_RING_MODULATOR_MAX_FREQUENCY))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Modulator frequency out of range");
|
||||
props->Modulator.Frequency = val;
|
||||
break;
|
||||
|
||||
case AL_RING_MODULATOR_HIGHPASS_CUTOFF:
|
||||
if(!(val >= AL_RING_MODULATOR_MIN_HIGHPASS_CUTOFF && val <= AL_RING_MODULATOR_MAX_HIGHPASS_CUTOFF))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Modulator high-pass cutoff out of range");
|
||||
props->Modulator.HighPassCutoff = val;
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid modulator float property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
void Modulator_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
|
||||
{ Modulator_setParamf(props, context, param, vals[0]); }
|
||||
void Modulator_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_RING_MODULATOR_FREQUENCY:
|
||||
case AL_RING_MODULATOR_HIGHPASS_CUTOFF:
|
||||
Modulator_setParamf(props, context, param, static_cast<ALfloat>(val));
|
||||
break;
|
||||
|
||||
case AL_RING_MODULATOR_WAVEFORM:
|
||||
if(!(val >= AL_RING_MODULATOR_MIN_WAVEFORM && val <= AL_RING_MODULATOR_MAX_WAVEFORM))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Invalid modulator waveform");
|
||||
props->Modulator.Waveform = val;
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid modulator integer property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
void Modulator_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals)
|
||||
{ Modulator_setParami(props, context, param, vals[0]); }
|
||||
|
||||
void Modulator_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_RING_MODULATOR_FREQUENCY:
|
||||
*val = static_cast<ALint>(props->Modulator.Frequency);
|
||||
break;
|
||||
case AL_RING_MODULATOR_HIGHPASS_CUTOFF:
|
||||
*val = static_cast<ALint>(props->Modulator.HighPassCutoff);
|
||||
break;
|
||||
case AL_RING_MODULATOR_WAVEFORM:
|
||||
*val = props->Modulator.Waveform;
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid modulator integer property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
void Modulator_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals)
|
||||
{ Modulator_getParami(props, context, param, vals); }
|
||||
void Modulator_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_RING_MODULATOR_FREQUENCY:
|
||||
*val = props->Modulator.Frequency;
|
||||
break;
|
||||
case AL_RING_MODULATOR_HIGHPASS_CUTOFF:
|
||||
*val = props->Modulator.HighPassCutoff;
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid modulator float property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
void Modulator_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
|
||||
{ Modulator_getParamf(props, context, param, vals); }
|
||||
|
||||
DEFINE_ALEFFECT_VTABLE(Modulator);
|
||||
|
||||
|
||||
struct ModulatorStateFactory final : public EffectStateFactory {
|
||||
EffectState *create() override { return new ModulatorState{}; }
|
||||
EffectProps getDefaultProps() const noexcept override;
|
||||
const EffectVtable *getEffectVtable() const noexcept override { return &Modulator_vtable; }
|
||||
};
|
||||
|
||||
EffectProps ModulatorStateFactory::getDefaultProps() const noexcept
|
||||
{
|
||||
EffectProps props{};
|
||||
props.Modulator.Frequency = AL_RING_MODULATOR_DEFAULT_FREQUENCY;
|
||||
props.Modulator.HighPassCutoff = AL_RING_MODULATOR_DEFAULT_HIGHPASS_CUTOFF;
|
||||
props.Modulator.Waveform = AL_RING_MODULATOR_DEFAULT_WAVEFORM;
|
||||
return props;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
EffectStateFactory *ModulatorStateFactory_getFactory()
|
||||
{
|
||||
static ModulatorStateFactory ModulatorFactory{};
|
||||
return &ModulatorFactory;
|
||||
}
|
||||
@@ -1,165 +0,0 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "AL/al.h"
|
||||
#include "AL/alc.h"
|
||||
|
||||
#include "al/auxeffectslot.h"
|
||||
#include "alcmain.h"
|
||||
#include "alcontext.h"
|
||||
#include "almalloc.h"
|
||||
#include "alspan.h"
|
||||
#include "effects/base.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
struct NullState final : public EffectState {
|
||||
NullState();
|
||||
~NullState() override;
|
||||
|
||||
ALboolean deviceUpdate(const ALCdevice *device) override;
|
||||
void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
|
||||
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) override;
|
||||
|
||||
DEF_NEWDEL(NullState)
|
||||
};
|
||||
|
||||
/* This constructs the effect state. It's called when the object is first
|
||||
* created.
|
||||
*/
|
||||
NullState::NullState() = default;
|
||||
|
||||
/* This destructs the effect state. It's called only when the effect instance
|
||||
* is no longer used.
|
||||
*/
|
||||
NullState::~NullState() = default;
|
||||
|
||||
/* This updates the device-dependant effect state. This is called on state
|
||||
* initialization and any time the device parameters (e.g. playback frequency,
|
||||
* format) have been changed. Will always be followed by a call to the update
|
||||
* method, if successful.
|
||||
*/
|
||||
ALboolean NullState::deviceUpdate(const ALCdevice* /*device*/)
|
||||
{
|
||||
return AL_TRUE;
|
||||
}
|
||||
|
||||
/* This updates the effect state with new properties. This is called any time
|
||||
* the effect is (re)loaded into a slot.
|
||||
*/
|
||||
void NullState::update(const ALCcontext* /*context*/, const ALeffectslot* /*slot*/,
|
||||
const EffectProps* /*props*/, const EffectTarget /*target*/)
|
||||
{
|
||||
}
|
||||
|
||||
/* This processes the effect state, for the given number of samples from the
|
||||
* input to the output buffer. The result should be added to the output buffer,
|
||||
* not replace it.
|
||||
*/
|
||||
void NullState::process(const size_t/*samplesToDo*/,
|
||||
const al::span<const FloatBufferLine> /*samplesIn*/,
|
||||
const al::span<FloatBufferLine> /*samplesOut*/)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void NullEffect_setParami(EffectProps* /*props*/, ALCcontext *context, ALenum param, ALint /*val*/)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
void NullEffect_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
default:
|
||||
NullEffect_setParami(props, context, param, vals[0]);
|
||||
}
|
||||
}
|
||||
void NullEffect_setParamf(EffectProps* /*props*/, ALCcontext *context, ALenum param, ALfloat /*val*/)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid null effect float property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
void NullEffect_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
default:
|
||||
NullEffect_setParamf(props, context, param, vals[0]);
|
||||
}
|
||||
}
|
||||
|
||||
void NullEffect_getParami(const EffectProps* /*props*/, ALCcontext *context, ALenum param, ALint* /*val*/)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
void NullEffect_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
default:
|
||||
NullEffect_getParami(props, context, param, vals);
|
||||
}
|
||||
}
|
||||
void NullEffect_getParamf(const EffectProps* /*props*/, ALCcontext *context, ALenum param, ALfloat* /*val*/)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid null effect float property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
void NullEffect_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
default:
|
||||
NullEffect_getParamf(props, context, param, vals);
|
||||
}
|
||||
}
|
||||
|
||||
DEFINE_ALEFFECT_VTABLE(NullEffect);
|
||||
|
||||
|
||||
struct NullStateFactory final : public EffectStateFactory {
|
||||
EffectState *create() override;
|
||||
EffectProps getDefaultProps() const noexcept override;
|
||||
const EffectVtable *getEffectVtable() const noexcept override;
|
||||
};
|
||||
|
||||
/* Creates EffectState objects of the appropriate type. */
|
||||
EffectState *NullStateFactory::create()
|
||||
{ return new NullState{}; }
|
||||
|
||||
/* Returns an ALeffectProps initialized with this effect type's default
|
||||
* property values.
|
||||
*/
|
||||
EffectProps NullStateFactory::getDefaultProps() const noexcept
|
||||
{
|
||||
EffectProps props{};
|
||||
return props;
|
||||
}
|
||||
|
||||
/* Returns a pointer to this effect type's global set/get vtable. */
|
||||
const EffectVtable *NullStateFactory::getEffectVtable() const noexcept
|
||||
{ return &NullEffect_vtable; }
|
||||
|
||||
} // namespace
|
||||
|
||||
EffectStateFactory *NullStateFactory_getFactory()
|
||||
{
|
||||
static NullStateFactory NullFactory{};
|
||||
return &NullFactory;
|
||||
}
|
||||
@@ -1,373 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 2018 by Raul Herraiz.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#ifdef HAVE_SSE_INTRINSICS
|
||||
#include <emmintrin.h>
|
||||
#endif
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <array>
|
||||
#include <complex>
|
||||
#include <algorithm>
|
||||
|
||||
#include "al/auxeffectslot.h"
|
||||
#include "alcmain.h"
|
||||
#include "alcomplex.h"
|
||||
#include "alcontext.h"
|
||||
#include "alnumeric.h"
|
||||
#include "alu.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
using complex_d = std::complex<double>;
|
||||
|
||||
#define STFT_SIZE 1024
|
||||
#define STFT_HALF_SIZE (STFT_SIZE>>1)
|
||||
#define OVERSAMP (1<<2)
|
||||
|
||||
#define STFT_STEP (STFT_SIZE / OVERSAMP)
|
||||
#define FIFO_LATENCY (STFT_STEP * (OVERSAMP-1))
|
||||
|
||||
/* Define a Hann window, used to filter the STFT input and output. */
|
||||
/* Making this constexpr seems to require C++14. */
|
||||
std::array<ALdouble,STFT_SIZE> InitHannWindow()
|
||||
{
|
||||
std::array<ALdouble,STFT_SIZE> ret;
|
||||
/* Create lookup table of the Hann window for the desired size, i.e. HIL_SIZE */
|
||||
for(size_t i{0};i < STFT_SIZE>>1;i++)
|
||||
{
|
||||
constexpr double scale{al::MathDefs<double>::Pi() / double{STFT_SIZE-1}};
|
||||
const double val{std::sin(static_cast<double>(i) * scale)};
|
||||
ret[i] = ret[STFT_SIZE-1-i] = val * val;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
alignas(16) const std::array<ALdouble,STFT_SIZE> HannWindow = InitHannWindow();
|
||||
|
||||
|
||||
struct ALphasor {
|
||||
ALdouble Amplitude;
|
||||
ALdouble Phase;
|
||||
};
|
||||
|
||||
struct ALfrequencyDomain {
|
||||
ALdouble Amplitude;
|
||||
ALdouble Frequency;
|
||||
};
|
||||
|
||||
|
||||
/* Converts complex to ALphasor */
|
||||
inline ALphasor rect2polar(const complex_d &number)
|
||||
{
|
||||
ALphasor polar;
|
||||
polar.Amplitude = std::abs(number);
|
||||
polar.Phase = std::arg(number);
|
||||
return polar;
|
||||
}
|
||||
|
||||
/* Converts ALphasor to complex */
|
||||
inline complex_d polar2rect(const ALphasor &number)
|
||||
{ return std::polar<double>(number.Amplitude, number.Phase); }
|
||||
|
||||
|
||||
struct PshifterState final : public EffectState {
|
||||
/* Effect parameters */
|
||||
size_t mCount;
|
||||
ALuint mPitchShiftI;
|
||||
ALfloat mPitchShift;
|
||||
ALfloat mFreqPerBin;
|
||||
|
||||
/* Effects buffers */
|
||||
ALfloat mInFIFO[STFT_SIZE];
|
||||
ALfloat mOutFIFO[STFT_STEP];
|
||||
ALdouble mLastPhase[STFT_HALF_SIZE+1];
|
||||
ALdouble mSumPhase[STFT_HALF_SIZE+1];
|
||||
ALdouble mOutputAccum[STFT_SIZE];
|
||||
|
||||
complex_d mFFTbuffer[STFT_SIZE];
|
||||
|
||||
ALfrequencyDomain mAnalysis_buffer[STFT_HALF_SIZE+1];
|
||||
ALfrequencyDomain mSyntesis_buffer[STFT_HALF_SIZE+1];
|
||||
|
||||
alignas(16) ALfloat mBufferOut[BUFFERSIZE];
|
||||
|
||||
/* Effect gains for each output channel */
|
||||
ALfloat mCurrentGains[MAX_OUTPUT_CHANNELS];
|
||||
ALfloat mTargetGains[MAX_OUTPUT_CHANNELS];
|
||||
|
||||
|
||||
ALboolean deviceUpdate(const ALCdevice *device) override;
|
||||
void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
|
||||
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) override;
|
||||
|
||||
DEF_NEWDEL(PshifterState)
|
||||
};
|
||||
|
||||
ALboolean PshifterState::deviceUpdate(const ALCdevice *device)
|
||||
{
|
||||
/* (Re-)initializing parameters and clear the buffers. */
|
||||
mCount = FIFO_LATENCY;
|
||||
mPitchShiftI = FRACTIONONE;
|
||||
mPitchShift = 1.0f;
|
||||
mFreqPerBin = static_cast<float>(device->Frequency) / float{STFT_SIZE};
|
||||
|
||||
std::fill(std::begin(mInFIFO), std::end(mInFIFO), 0.0f);
|
||||
std::fill(std::begin(mOutFIFO), std::end(mOutFIFO), 0.0f);
|
||||
std::fill(std::begin(mLastPhase), std::end(mLastPhase), 0.0);
|
||||
std::fill(std::begin(mSumPhase), std::end(mSumPhase), 0.0);
|
||||
std::fill(std::begin(mOutputAccum), std::end(mOutputAccum), 0.0);
|
||||
std::fill(std::begin(mFFTbuffer), std::end(mFFTbuffer), complex_d{});
|
||||
std::fill(std::begin(mAnalysis_buffer), std::end(mAnalysis_buffer), ALfrequencyDomain{});
|
||||
std::fill(std::begin(mSyntesis_buffer), std::end(mSyntesis_buffer), ALfrequencyDomain{});
|
||||
|
||||
std::fill(std::begin(mCurrentGains), std::end(mCurrentGains), 0.0f);
|
||||
std::fill(std::begin(mTargetGains), std::end(mTargetGains), 0.0f);
|
||||
|
||||
return AL_TRUE;
|
||||
}
|
||||
|
||||
void PshifterState::update(const ALCcontext*, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target)
|
||||
{
|
||||
const float pitch{std::pow(2.0f,
|
||||
static_cast<ALfloat>(props->Pshifter.CoarseTune*100 + props->Pshifter.FineTune) / 1200.0f
|
||||
)};
|
||||
mPitchShiftI = fastf2u(pitch*FRACTIONONE);
|
||||
mPitchShift = static_cast<float>(mPitchShiftI) * (1.0f/FRACTIONONE);
|
||||
|
||||
ALfloat coeffs[MAX_AMBI_CHANNELS];
|
||||
CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}, 0.0f, coeffs);
|
||||
|
||||
mOutTarget = target.Main->Buffer;
|
||||
ComputePanGains(target.Main, coeffs, slot->Params.Gain, mTargetGains);
|
||||
}
|
||||
|
||||
void PshifterState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
|
||||
{
|
||||
/* Pitch shifter engine based on the work of Stephan Bernsee.
|
||||
* http://blogs.zynaptiq.com/bernsee/pitch-shifting-using-the-ft/
|
||||
*/
|
||||
|
||||
static constexpr ALdouble expected{al::MathDefs<double>::Tau() / OVERSAMP};
|
||||
const ALdouble freq_per_bin{mFreqPerBin};
|
||||
ALfloat *RESTRICT bufferOut{mBufferOut};
|
||||
size_t count{mCount};
|
||||
|
||||
for(size_t i{0u};i < samplesToDo;)
|
||||
{
|
||||
do {
|
||||
/* Fill FIFO buffer with samples data */
|
||||
mInFIFO[count] = samplesIn[0][i];
|
||||
bufferOut[i] = mOutFIFO[count - FIFO_LATENCY];
|
||||
|
||||
count++;
|
||||
} while(++i < samplesToDo && count < STFT_SIZE);
|
||||
|
||||
/* Check whether FIFO buffer is filled */
|
||||
if(count < STFT_SIZE) break;
|
||||
count = FIFO_LATENCY;
|
||||
|
||||
/* Real signal windowing and store in FFTbuffer */
|
||||
for(ALuint k{0u};k < STFT_SIZE;k++)
|
||||
{
|
||||
mFFTbuffer[k].real(mInFIFO[k] * HannWindow[k]);
|
||||
mFFTbuffer[k].imag(0.0);
|
||||
}
|
||||
|
||||
/* ANALYSIS */
|
||||
/* Apply FFT to FFTbuffer data */
|
||||
complex_fft(mFFTbuffer, -1.0);
|
||||
|
||||
/* Analyze the obtained data. Since the real FFT is symmetric, only
|
||||
* STFT_HALF_SIZE+1 samples are needed.
|
||||
*/
|
||||
for(ALuint k{0u};k < STFT_HALF_SIZE+1;k++)
|
||||
{
|
||||
/* Compute amplitude and phase */
|
||||
ALphasor component{rect2polar(mFFTbuffer[k])};
|
||||
|
||||
/* Compute phase difference and subtract expected phase difference */
|
||||
double tmp{(component.Phase - mLastPhase[k]) - k*expected};
|
||||
|
||||
/* Map delta phase into +/- Pi interval */
|
||||
int qpd{double2int(tmp / al::MathDefs<double>::Pi())};
|
||||
tmp -= al::MathDefs<double>::Pi() * (qpd + (qpd%2));
|
||||
|
||||
/* Get deviation from bin frequency from the +/- Pi interval */
|
||||
tmp /= expected;
|
||||
|
||||
/* Compute the k-th partials' true frequency, twice the amplitude
|
||||
* for maintain the gain (because half of bins are used) and store
|
||||
* amplitude and true frequency in analysis buffer.
|
||||
*/
|
||||
mAnalysis_buffer[k].Amplitude = 2.0 * component.Amplitude;
|
||||
mAnalysis_buffer[k].Frequency = (k + tmp) * freq_per_bin;
|
||||
|
||||
/* Store actual phase[k] for the calculations in the next frame*/
|
||||
mLastPhase[k] = component.Phase;
|
||||
}
|
||||
|
||||
/* PROCESSING */
|
||||
/* pitch shifting */
|
||||
for(ALuint k{0u};k < STFT_HALF_SIZE+1;k++)
|
||||
{
|
||||
mSyntesis_buffer[k].Amplitude = 0.0;
|
||||
mSyntesis_buffer[k].Frequency = 0.0;
|
||||
}
|
||||
|
||||
for(size_t k{0u};k < STFT_HALF_SIZE+1;k++)
|
||||
{
|
||||
size_t j{(k*mPitchShiftI) >> FRACTIONBITS};
|
||||
if(j >= STFT_HALF_SIZE+1) break;
|
||||
|
||||
mSyntesis_buffer[j].Amplitude += mAnalysis_buffer[k].Amplitude;
|
||||
mSyntesis_buffer[j].Frequency = mAnalysis_buffer[k].Frequency * mPitchShift;
|
||||
}
|
||||
|
||||
/* SYNTHESIS */
|
||||
/* Synthesis the processing data */
|
||||
for(ALuint k{0u};k < STFT_HALF_SIZE+1;k++)
|
||||
{
|
||||
ALphasor component;
|
||||
ALdouble tmp;
|
||||
|
||||
/* Compute bin deviation from scaled freq */
|
||||
tmp = mSyntesis_buffer[k].Frequency/freq_per_bin - k;
|
||||
|
||||
/* Calculate actual delta phase and accumulate it to get bin phase */
|
||||
mSumPhase[k] += (k + tmp) * expected;
|
||||
|
||||
component.Amplitude = mSyntesis_buffer[k].Amplitude;
|
||||
component.Phase = mSumPhase[k];
|
||||
|
||||
/* Compute phasor component to cartesian complex number and storage it into FFTbuffer*/
|
||||
mFFTbuffer[k] = polar2rect(component);
|
||||
}
|
||||
/* zero negative frequencies for recontruct a real signal */
|
||||
for(ALuint k{STFT_HALF_SIZE+1};k < STFT_SIZE;k++)
|
||||
mFFTbuffer[k] = complex_d{};
|
||||
|
||||
/* Apply iFFT to buffer data */
|
||||
complex_fft(mFFTbuffer, 1.0);
|
||||
|
||||
/* Windowing and add to output */
|
||||
for(ALuint k{0u};k < STFT_SIZE;k++)
|
||||
mOutputAccum[k] += HannWindow[k] * mFFTbuffer[k].real() /
|
||||
(0.5 * STFT_HALF_SIZE * OVERSAMP);
|
||||
|
||||
/* Shift accumulator, input & output FIFO */
|
||||
size_t j, k;
|
||||
for(k = 0;k < STFT_STEP;k++) mOutFIFO[k] = static_cast<ALfloat>(mOutputAccum[k]);
|
||||
for(j = 0;k < STFT_SIZE;k++,j++) mOutputAccum[j] = mOutputAccum[k];
|
||||
for(;j < STFT_SIZE;j++) mOutputAccum[j] = 0.0;
|
||||
for(k = 0;k < FIFO_LATENCY;k++)
|
||||
mInFIFO[k] = mInFIFO[k+STFT_STEP];
|
||||
}
|
||||
mCount = count;
|
||||
|
||||
/* Now, mix the processed sound data to the output. */
|
||||
MixSamples({bufferOut, samplesToDo}, samplesOut, mCurrentGains, mTargetGains,
|
||||
maxz(samplesToDo, 512), 0);
|
||||
}
|
||||
|
||||
|
||||
void Pshifter_setParamf(EffectProps*, ALCcontext *context, ALenum param, ALfloat)
|
||||
{ context->setError(AL_INVALID_ENUM, "Invalid pitch shifter float property 0x%04x", param); }
|
||||
void Pshifter_setParamfv(EffectProps*, ALCcontext *context, ALenum param, const ALfloat*)
|
||||
{ context->setError(AL_INVALID_ENUM, "Invalid pitch shifter float-vector property 0x%04x", param); }
|
||||
|
||||
void Pshifter_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_PITCH_SHIFTER_COARSE_TUNE:
|
||||
if(!(val >= AL_PITCH_SHIFTER_MIN_COARSE_TUNE && val <= AL_PITCH_SHIFTER_MAX_COARSE_TUNE))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,,"Pitch shifter coarse tune out of range");
|
||||
props->Pshifter.CoarseTune = val;
|
||||
break;
|
||||
|
||||
case AL_PITCH_SHIFTER_FINE_TUNE:
|
||||
if(!(val >= AL_PITCH_SHIFTER_MIN_FINE_TUNE && val <= AL_PITCH_SHIFTER_MAX_FINE_TUNE))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,,"Pitch shifter fine tune out of range");
|
||||
props->Pshifter.FineTune = val;
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid pitch shifter integer property 0x%04x",
|
||||
param);
|
||||
}
|
||||
}
|
||||
void Pshifter_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals)
|
||||
{ Pshifter_setParami(props, context, param, vals[0]); }
|
||||
|
||||
void Pshifter_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_PITCH_SHIFTER_COARSE_TUNE:
|
||||
*val = props->Pshifter.CoarseTune;
|
||||
break;
|
||||
case AL_PITCH_SHIFTER_FINE_TUNE:
|
||||
*val = props->Pshifter.FineTune;
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid pitch shifter integer property 0x%04x",
|
||||
param);
|
||||
}
|
||||
}
|
||||
void Pshifter_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals)
|
||||
{ Pshifter_getParami(props, context, param, vals); }
|
||||
|
||||
void Pshifter_getParamf(const EffectProps*, ALCcontext *context, ALenum param, ALfloat*)
|
||||
{ context->setError(AL_INVALID_ENUM, "Invalid pitch shifter float property 0x%04x", param); }
|
||||
void Pshifter_getParamfv(const EffectProps*, ALCcontext *context, ALenum param, ALfloat*)
|
||||
{ context->setError(AL_INVALID_ENUM, "Invalid pitch shifter float vector-property 0x%04x", param); }
|
||||
|
||||
DEFINE_ALEFFECT_VTABLE(Pshifter);
|
||||
|
||||
|
||||
struct PshifterStateFactory final : public EffectStateFactory {
|
||||
EffectState *create() override;
|
||||
EffectProps getDefaultProps() const noexcept override;
|
||||
const EffectVtable *getEffectVtable() const noexcept override { return &Pshifter_vtable; }
|
||||
};
|
||||
|
||||
EffectState *PshifterStateFactory::create()
|
||||
{ return new PshifterState{}; }
|
||||
|
||||
EffectProps PshifterStateFactory::getDefaultProps() const noexcept
|
||||
{
|
||||
EffectProps props{};
|
||||
props.Pshifter.CoarseTune = AL_PITCH_SHIFTER_DEFAULT_COARSE_TUNE;
|
||||
props.Pshifter.FineTune = AL_PITCH_SHIFTER_DEFAULT_FINE_TUNE;
|
||||
return props;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
EffectStateFactory *PshifterStateFactory_getFactory()
|
||||
{
|
||||
static PshifterStateFactory PshifterFactory{};
|
||||
return &PshifterFactory;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,432 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 2019 by Anis A. Hireche
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
|
||||
#include "al/auxeffectslot.h"
|
||||
#include "alcmain.h"
|
||||
#include "alcontext.h"
|
||||
#include "alu.h"
|
||||
|
||||
namespace {
|
||||
|
||||
#define MAX_UPDATE_SAMPLES 128
|
||||
#define NUM_FORMANTS 4
|
||||
#define NUM_FILTERS 2
|
||||
#define Q_FACTOR 5.0f
|
||||
|
||||
#define VOWEL_A_INDEX 0
|
||||
#define VOWEL_B_INDEX 1
|
||||
|
||||
#define WAVEFORM_FRACBITS 24
|
||||
#define WAVEFORM_FRACONE (1<<WAVEFORM_FRACBITS)
|
||||
#define WAVEFORM_FRACMASK (WAVEFORM_FRACONE-1)
|
||||
|
||||
inline float Sin(ALuint index)
|
||||
{
|
||||
constexpr float scale{al::MathDefs<float>::Tau() / WAVEFORM_FRACONE};
|
||||
return std::sin(static_cast<float>(index) * scale)*0.5f + 0.5f;
|
||||
}
|
||||
|
||||
inline float Saw(ALuint index)
|
||||
{ return static_cast<float>(index) / float{WAVEFORM_FRACONE}; }
|
||||
|
||||
inline float Triangle(ALuint index)
|
||||
{ return std::fabs(static_cast<float>(index)*(2.0f/WAVEFORM_FRACONE) - 1.0f); }
|
||||
|
||||
inline float Half(ALuint) { return 0.5f; }
|
||||
|
||||
template<float (&func)(ALuint)>
|
||||
void Oscillate(float *RESTRICT dst, ALuint index, const ALuint step, size_t todo)
|
||||
{
|
||||
for(size_t i{0u};i < todo;i++)
|
||||
{
|
||||
index += step;
|
||||
index &= WAVEFORM_FRACMASK;
|
||||
dst[i] = func(index);
|
||||
}
|
||||
}
|
||||
|
||||
struct FormantFilter
|
||||
{
|
||||
ALfloat mCoeff{0.0f};
|
||||
ALfloat mGain{1.0f};
|
||||
ALfloat mS1{0.0f};
|
||||
ALfloat mS2{0.0f};
|
||||
|
||||
FormantFilter() = default;
|
||||
FormantFilter(ALfloat f0norm, ALfloat gain)
|
||||
: mCoeff{std::tan(al::MathDefs<float>::Pi() * f0norm)}, mGain{gain}
|
||||
{ }
|
||||
|
||||
inline void process(const ALfloat *samplesIn, ALfloat *samplesOut, const size_t numInput)
|
||||
{
|
||||
/* A state variable filter from a topology-preserving transform.
|
||||
* Based on a talk given by Ivan Cohen: https://www.youtube.com/watch?v=esjHXGPyrhg
|
||||
*/
|
||||
const ALfloat g{mCoeff};
|
||||
const ALfloat gain{mGain};
|
||||
const ALfloat h{1.0f / (1.0f + (g/Q_FACTOR) + (g*g))};
|
||||
ALfloat s1{mS1};
|
||||
ALfloat s2{mS2};
|
||||
|
||||
for(size_t i{0u};i < numInput;i++)
|
||||
{
|
||||
const ALfloat H{(samplesIn[i] - (1.0f/Q_FACTOR + g)*s1 - s2)*h};
|
||||
const ALfloat B{g*H + s1};
|
||||
const ALfloat L{g*B + s2};
|
||||
|
||||
s1 = g*H + B;
|
||||
s2 = g*B + L;
|
||||
|
||||
// Apply peak and accumulate samples.
|
||||
samplesOut[i] += B * gain;
|
||||
}
|
||||
mS1 = s1;
|
||||
mS2 = s2;
|
||||
}
|
||||
|
||||
inline void clear()
|
||||
{
|
||||
mS1 = 0.0f;
|
||||
mS2 = 0.0f;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
struct VmorpherState final : public EffectState {
|
||||
struct {
|
||||
/* Effect parameters */
|
||||
FormantFilter Formants[NUM_FILTERS][NUM_FORMANTS];
|
||||
|
||||
/* Effect gains for each channel */
|
||||
ALfloat CurrentGains[MAX_OUTPUT_CHANNELS]{};
|
||||
ALfloat TargetGains[MAX_OUTPUT_CHANNELS]{};
|
||||
} mChans[MAX_AMBI_CHANNELS];
|
||||
|
||||
void (*mGetSamples)(float*RESTRICT, ALuint, const ALuint, size_t){};
|
||||
|
||||
ALuint mIndex{0};
|
||||
ALuint mStep{1};
|
||||
|
||||
/* Effects buffers */
|
||||
ALfloat mSampleBufferA[MAX_UPDATE_SAMPLES]{};
|
||||
ALfloat mSampleBufferB[MAX_UPDATE_SAMPLES]{};
|
||||
|
||||
ALboolean deviceUpdate(const ALCdevice *device) override;
|
||||
void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
|
||||
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) override;
|
||||
|
||||
static std::array<FormantFilter,4> getFiltersByPhoneme(ALenum phoneme, ALfloat frequency, ALfloat pitch);
|
||||
|
||||
DEF_NEWDEL(VmorpherState)
|
||||
};
|
||||
|
||||
std::array<FormantFilter,4> VmorpherState::getFiltersByPhoneme(ALenum phoneme, ALfloat frequency, ALfloat pitch)
|
||||
{
|
||||
/* Using soprano formant set of values to
|
||||
* better match mid-range frequency space.
|
||||
*
|
||||
* See: https://www.classes.cs.uchicago.edu/archive/1999/spring/CS295/Computing_Resources/Csound/CsManual3.48b1.HTML/Appendices/table3.html
|
||||
*/
|
||||
switch(phoneme)
|
||||
{
|
||||
case AL_VOCAL_MORPHER_PHONEME_A:
|
||||
return {{
|
||||
{( 800 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */
|
||||
{(1150 * pitch) / frequency, 0.501187f}, /* std::pow(10.0f, -6 / 20.0f); */
|
||||
{(2900 * pitch) / frequency, 0.025118f}, /* std::pow(10.0f, -32 / 20.0f); */
|
||||
{(3900 * pitch) / frequency, 0.100000f} /* std::pow(10.0f, -20 / 20.0f); */
|
||||
}};
|
||||
case AL_VOCAL_MORPHER_PHONEME_E:
|
||||
return {{
|
||||
{( 350 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */
|
||||
{(2000 * pitch) / frequency, 0.100000f}, /* std::pow(10.0f, -20 / 20.0f); */
|
||||
{(2800 * pitch) / frequency, 0.177827f}, /* std::pow(10.0f, -15 / 20.0f); */
|
||||
{(3600 * pitch) / frequency, 0.009999f} /* std::pow(10.0f, -40 / 20.0f); */
|
||||
}};
|
||||
case AL_VOCAL_MORPHER_PHONEME_I:
|
||||
return {{
|
||||
{( 270 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */
|
||||
{(2140 * pitch) / frequency, 0.251188f}, /* std::pow(10.0f, -12 / 20.0f); */
|
||||
{(2950 * pitch) / frequency, 0.050118f}, /* std::pow(10.0f, -26 / 20.0f); */
|
||||
{(3900 * pitch) / frequency, 0.050118f} /* std::pow(10.0f, -26 / 20.0f); */
|
||||
}};
|
||||
case AL_VOCAL_MORPHER_PHONEME_O:
|
||||
return {{
|
||||
{( 450 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */
|
||||
{( 800 * pitch) / frequency, 0.281838f}, /* std::pow(10.0f, -11 / 20.0f); */
|
||||
{(2830 * pitch) / frequency, 0.079432f}, /* std::pow(10.0f, -22 / 20.0f); */
|
||||
{(3800 * pitch) / frequency, 0.079432f} /* std::pow(10.0f, -22 / 20.0f); */
|
||||
}};
|
||||
case AL_VOCAL_MORPHER_PHONEME_U:
|
||||
return {{
|
||||
{( 325 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */
|
||||
{( 700 * pitch) / frequency, 0.158489f}, /* std::pow(10.0f, -16 / 20.0f); */
|
||||
{(2700 * pitch) / frequency, 0.017782f}, /* std::pow(10.0f, -35 / 20.0f); */
|
||||
{(3800 * pitch) / frequency, 0.009999f} /* std::pow(10.0f, -40 / 20.0f); */
|
||||
}};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
ALboolean VmorpherState::deviceUpdate(const ALCdevice* /*device*/)
|
||||
{
|
||||
for(auto &e : mChans)
|
||||
{
|
||||
std::for_each(std::begin(e.Formants[VOWEL_A_INDEX]), std::end(e.Formants[VOWEL_A_INDEX]),
|
||||
std::mem_fn(&FormantFilter::clear));
|
||||
std::for_each(std::begin(e.Formants[VOWEL_B_INDEX]), std::end(e.Formants[VOWEL_B_INDEX]),
|
||||
std::mem_fn(&FormantFilter::clear));
|
||||
std::fill(std::begin(e.CurrentGains), std::end(e.CurrentGains), 0.0f);
|
||||
}
|
||||
|
||||
return AL_TRUE;
|
||||
}
|
||||
|
||||
void VmorpherState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target)
|
||||
{
|
||||
const ALCdevice *device{context->mDevice.get()};
|
||||
const ALfloat frequency{static_cast<ALfloat>(device->Frequency)};
|
||||
const ALfloat step{props->Vmorpher.Rate / frequency};
|
||||
mStep = fastf2u(clampf(step*WAVEFORM_FRACONE, 0.0f, ALfloat{WAVEFORM_FRACONE-1}));
|
||||
|
||||
if(mStep == 0)
|
||||
mGetSamples = Oscillate<Half>;
|
||||
else if(props->Vmorpher.Waveform == AL_VOCAL_MORPHER_WAVEFORM_SINUSOID)
|
||||
mGetSamples = Oscillate<Sin>;
|
||||
else if(props->Vmorpher.Waveform == AL_VOCAL_MORPHER_WAVEFORM_SAWTOOTH)
|
||||
mGetSamples = Oscillate<Saw>;
|
||||
else /*if(props->Vmorpher.Waveform == AL_VOCAL_MORPHER_WAVEFORM_TRIANGLE)*/
|
||||
mGetSamples = Oscillate<Triangle>;
|
||||
|
||||
const ALfloat pitchA{std::pow(2.0f,
|
||||
static_cast<float>(props->Vmorpher.PhonemeACoarseTuning) / 12.0f)};
|
||||
const ALfloat pitchB{std::pow(2.0f,
|
||||
static_cast<float>(props->Vmorpher.PhonemeBCoarseTuning) / 12.0f)};
|
||||
|
||||
auto vowelA = getFiltersByPhoneme(props->Vmorpher.PhonemeA, frequency, pitchA);
|
||||
auto vowelB = getFiltersByPhoneme(props->Vmorpher.PhonemeB, frequency, pitchB);
|
||||
|
||||
/* Copy the filter coefficients to the input channels. */
|
||||
for(size_t i{0u};i < slot->Wet.Buffer.size();++i)
|
||||
{
|
||||
std::copy(vowelA.begin(), vowelA.end(), std::begin(mChans[i].Formants[VOWEL_A_INDEX]));
|
||||
std::copy(vowelB.begin(), vowelB.end(), std::begin(mChans[i].Formants[VOWEL_B_INDEX]));
|
||||
}
|
||||
|
||||
mOutTarget = target.Main->Buffer;
|
||||
for(size_t i{0u};i < slot->Wet.Buffer.size();++i)
|
||||
{
|
||||
auto coeffs = GetAmbiIdentityRow(i);
|
||||
ComputePanGains(target.Main, coeffs.data(), slot->Params.Gain, mChans[i].TargetGains);
|
||||
}
|
||||
}
|
||||
|
||||
void VmorpherState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
|
||||
{
|
||||
/* Following the EFX specification for a conformant implementation which describes
|
||||
* the effect as a pair of 4-band formant filters blended together using an LFO.
|
||||
*/
|
||||
for(size_t base{0u};base < samplesToDo;)
|
||||
{
|
||||
alignas(16) ALfloat lfo[MAX_UPDATE_SAMPLES];
|
||||
const size_t td{minz(MAX_UPDATE_SAMPLES, samplesToDo-base)};
|
||||
|
||||
mGetSamples(lfo, mIndex, mStep, td);
|
||||
mIndex += static_cast<ALuint>(mStep * td);
|
||||
mIndex &= WAVEFORM_FRACMASK;
|
||||
|
||||
auto chandata = std::addressof(mChans[0]);
|
||||
for(const auto &input : samplesIn)
|
||||
{
|
||||
std::fill_n(std::begin(mSampleBufferA), td, 0.0f);
|
||||
std::fill_n(std::begin(mSampleBufferB), td, 0.0f);
|
||||
|
||||
auto& vowelA = chandata->Formants[VOWEL_A_INDEX];
|
||||
auto& vowelB = chandata->Formants[VOWEL_B_INDEX];
|
||||
|
||||
/* Process first vowel. */
|
||||
vowelA[0].process(&input[base], mSampleBufferA, td);
|
||||
vowelA[1].process(&input[base], mSampleBufferA, td);
|
||||
vowelA[2].process(&input[base], mSampleBufferA, td);
|
||||
vowelA[3].process(&input[base], mSampleBufferA, td);
|
||||
|
||||
/* Process second vowel. */
|
||||
vowelB[0].process(&input[base], mSampleBufferB, td);
|
||||
vowelB[1].process(&input[base], mSampleBufferB, td);
|
||||
vowelB[2].process(&input[base], mSampleBufferB, td);
|
||||
vowelB[3].process(&input[base], mSampleBufferB, td);
|
||||
|
||||
alignas(16) ALfloat blended[MAX_UPDATE_SAMPLES];
|
||||
for(size_t i{0u};i < td;i++)
|
||||
blended[i] = lerp(mSampleBufferA[i], mSampleBufferB[i], lfo[i]);
|
||||
|
||||
/* Now, mix the processed sound data to the output. */
|
||||
MixSamples({blended, td}, samplesOut, chandata->CurrentGains, chandata->TargetGains,
|
||||
samplesToDo-base, base);
|
||||
++chandata;
|
||||
}
|
||||
|
||||
base += td;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Vmorpher_setParami(EffectProps* props, ALCcontext *context, ALenum param, ALint val)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_VOCAL_MORPHER_WAVEFORM:
|
||||
if(!(val >= AL_VOCAL_MORPHER_MIN_WAVEFORM && val <= AL_VOCAL_MORPHER_MAX_WAVEFORM))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Vocal morpher waveform out of range");
|
||||
props->Vmorpher.Waveform = val;
|
||||
break;
|
||||
|
||||
case AL_VOCAL_MORPHER_PHONEMEA:
|
||||
if(!(val >= AL_VOCAL_MORPHER_MIN_PHONEMEA && val <= AL_VOCAL_MORPHER_MAX_PHONEMEA))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Vocal morpher phoneme-a out of range");
|
||||
props->Vmorpher.PhonemeA = val;
|
||||
break;
|
||||
|
||||
case AL_VOCAL_MORPHER_PHONEMEB:
|
||||
if(!(val >= AL_VOCAL_MORPHER_MIN_PHONEMEB && val <= AL_VOCAL_MORPHER_MAX_PHONEMEB))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Vocal morpher phoneme-b out of range");
|
||||
props->Vmorpher.PhonemeB = val;
|
||||
break;
|
||||
|
||||
case AL_VOCAL_MORPHER_PHONEMEA_COARSE_TUNING:
|
||||
if(!(val >= AL_VOCAL_MORPHER_MIN_PHONEMEA_COARSE_TUNING && val <= AL_VOCAL_MORPHER_MAX_PHONEMEA_COARSE_TUNING))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Vocal morpher phoneme-a coarse tuning out of range");
|
||||
props->Vmorpher.PhonemeACoarseTuning = val;
|
||||
break;
|
||||
|
||||
case AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING:
|
||||
if(!(val >= AL_VOCAL_MORPHER_MIN_PHONEMEB_COARSE_TUNING && val <= AL_VOCAL_MORPHER_MAX_PHONEMEB_COARSE_TUNING))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Vocal morpher phoneme-b coarse tuning out of range");
|
||||
props->Vmorpher.PhonemeBCoarseTuning = val;
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid vocal morpher integer property 0x%04x",
|
||||
param);
|
||||
}
|
||||
}
|
||||
void Vmorpher_setParamiv(EffectProps*, ALCcontext *context, ALenum param, const ALint*)
|
||||
{ context->setError(AL_INVALID_ENUM, "Invalid vocal morpher integer-vector property 0x%04x", param); }
|
||||
void Vmorpher_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_VOCAL_MORPHER_RATE:
|
||||
if(!(val >= AL_VOCAL_MORPHER_MIN_RATE && val <= AL_VOCAL_MORPHER_MAX_RATE))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Vocal morpher rate out of range");
|
||||
props->Vmorpher.Rate = val;
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid vocal morpher float property 0x%04x",
|
||||
param);
|
||||
}
|
||||
}
|
||||
void Vmorpher_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
|
||||
{ Vmorpher_setParamf(props, context, param, vals[0]); }
|
||||
|
||||
void Vmorpher_getParami(const EffectProps* props, ALCcontext *context, ALenum param, ALint* val)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_VOCAL_MORPHER_PHONEMEA:
|
||||
*val = props->Vmorpher.PhonemeA;
|
||||
break;
|
||||
|
||||
case AL_VOCAL_MORPHER_PHONEMEB:
|
||||
*val = props->Vmorpher.PhonemeB;
|
||||
break;
|
||||
|
||||
case AL_VOCAL_MORPHER_PHONEMEA_COARSE_TUNING:
|
||||
*val = props->Vmorpher.PhonemeACoarseTuning;
|
||||
break;
|
||||
|
||||
case AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING:
|
||||
*val = props->Vmorpher.PhonemeBCoarseTuning;
|
||||
break;
|
||||
|
||||
case AL_VOCAL_MORPHER_WAVEFORM:
|
||||
*val = props->Vmorpher.Waveform;
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid vocal morpher integer property 0x%04x",
|
||||
param);
|
||||
}
|
||||
}
|
||||
void Vmorpher_getParamiv(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
|
||||
{ context->setError(AL_INVALID_ENUM, "Invalid vocal morpher integer-vector property 0x%04x", param); }
|
||||
void Vmorpher_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_VOCAL_MORPHER_RATE:
|
||||
*val = props->Vmorpher.Rate;
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid vocal morpher float property 0x%04x",
|
||||
param);
|
||||
}
|
||||
}
|
||||
void Vmorpher_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
|
||||
{ Vmorpher_getParamf(props, context, param, vals); }
|
||||
|
||||
DEFINE_ALEFFECT_VTABLE(Vmorpher);
|
||||
|
||||
|
||||
struct VmorpherStateFactory final : public EffectStateFactory {
|
||||
EffectState *create() override { return new VmorpherState{}; }
|
||||
EffectProps getDefaultProps() const noexcept override;
|
||||
const EffectVtable *getEffectVtable() const noexcept override { return &Vmorpher_vtable; }
|
||||
};
|
||||
|
||||
EffectProps VmorpherStateFactory::getDefaultProps() const noexcept
|
||||
{
|
||||
EffectProps props{};
|
||||
props.Vmorpher.Rate = AL_VOCAL_MORPHER_DEFAULT_RATE;
|
||||
props.Vmorpher.PhonemeA = AL_VOCAL_MORPHER_DEFAULT_PHONEMEA;
|
||||
props.Vmorpher.PhonemeB = AL_VOCAL_MORPHER_DEFAULT_PHONEMEB;
|
||||
props.Vmorpher.PhonemeACoarseTuning = AL_VOCAL_MORPHER_DEFAULT_PHONEMEA_COARSE_TUNING;
|
||||
props.Vmorpher.PhonemeBCoarseTuning = AL_VOCAL_MORPHER_DEFAULT_PHONEMEB_COARSE_TUNING;
|
||||
props.Vmorpher.Waveform = AL_VOCAL_MORPHER_DEFAULT_WAVEFORM;
|
||||
return props;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
EffectStateFactory *VmorpherStateFactory_getFactory()
|
||||
{
|
||||
static VmorpherStateFactory VmorpherFactory{};
|
||||
return &VmorpherFactory;
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "biquad.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
|
||||
#include "opthelpers.h"
|
||||
|
||||
|
||||
template<typename Real>
|
||||
void BiquadFilterR<Real>::setParams(BiquadType type, Real f0norm, Real gain, Real rcpQ)
|
||||
{
|
||||
// Limit gain to -100dB
|
||||
assert(gain > 0.00001f);
|
||||
|
||||
const Real w0{al::MathDefs<Real>::Tau() * f0norm};
|
||||
const Real sin_w0{std::sin(w0)};
|
||||
const Real cos_w0{std::cos(w0)};
|
||||
const Real alpha{sin_w0/2.0f * rcpQ};
|
||||
|
||||
Real sqrtgain_alpha_2;
|
||||
Real a[3]{ 1.0f, 0.0f, 0.0f };
|
||||
Real b[3]{ 1.0f, 0.0f, 0.0f };
|
||||
|
||||
/* Calculate filter coefficients depending on filter type */
|
||||
switch(type)
|
||||
{
|
||||
case BiquadType::HighShelf:
|
||||
sqrtgain_alpha_2 = 2.0f * std::sqrt(gain) * alpha;
|
||||
b[0] = gain*((gain+1.0f) + (gain-1.0f)*cos_w0 + sqrtgain_alpha_2);
|
||||
b[1] = -2.0f*gain*((gain-1.0f) + (gain+1.0f)*cos_w0 );
|
||||
b[2] = gain*((gain+1.0f) + (gain-1.0f)*cos_w0 - sqrtgain_alpha_2);
|
||||
a[0] = (gain+1.0f) - (gain-1.0f)*cos_w0 + sqrtgain_alpha_2;
|
||||
a[1] = 2.0f* ((gain-1.0f) - (gain+1.0f)*cos_w0 );
|
||||
a[2] = (gain+1.0f) - (gain-1.0f)*cos_w0 - sqrtgain_alpha_2;
|
||||
break;
|
||||
case BiquadType::LowShelf:
|
||||
sqrtgain_alpha_2 = 2.0f * std::sqrt(gain) * alpha;
|
||||
b[0] = gain*((gain+1.0f) - (gain-1.0f)*cos_w0 + sqrtgain_alpha_2);
|
||||
b[1] = 2.0f*gain*((gain-1.0f) - (gain+1.0f)*cos_w0 );
|
||||
b[2] = gain*((gain+1.0f) - (gain-1.0f)*cos_w0 - sqrtgain_alpha_2);
|
||||
a[0] = (gain+1.0f) + (gain-1.0f)*cos_w0 + sqrtgain_alpha_2;
|
||||
a[1] = -2.0f* ((gain-1.0f) + (gain+1.0f)*cos_w0 );
|
||||
a[2] = (gain+1.0f) + (gain-1.0f)*cos_w0 - sqrtgain_alpha_2;
|
||||
break;
|
||||
case BiquadType::Peaking:
|
||||
b[0] = 1.0f + alpha * gain;
|
||||
b[1] = -2.0f * cos_w0;
|
||||
b[2] = 1.0f - alpha * gain;
|
||||
a[0] = 1.0f + alpha / gain;
|
||||
a[1] = -2.0f * cos_w0;
|
||||
a[2] = 1.0f - alpha / gain;
|
||||
break;
|
||||
|
||||
case BiquadType::LowPass:
|
||||
b[0] = (1.0f - cos_w0) / 2.0f;
|
||||
b[1] = 1.0f - cos_w0;
|
||||
b[2] = (1.0f - cos_w0) / 2.0f;
|
||||
a[0] = 1.0f + alpha;
|
||||
a[1] = -2.0f * cos_w0;
|
||||
a[2] = 1.0f - alpha;
|
||||
break;
|
||||
case BiquadType::HighPass:
|
||||
b[0] = (1.0f + cos_w0) / 2.0f;
|
||||
b[1] = -(1.0f + cos_w0);
|
||||
b[2] = (1.0f + cos_w0) / 2.0f;
|
||||
a[0] = 1.0f + alpha;
|
||||
a[1] = -2.0f * cos_w0;
|
||||
a[2] = 1.0f - alpha;
|
||||
break;
|
||||
case BiquadType::BandPass:
|
||||
b[0] = alpha;
|
||||
b[1] = 0.0f;
|
||||
b[2] = -alpha;
|
||||
a[0] = 1.0f + alpha;
|
||||
a[1] = -2.0f * cos_w0;
|
||||
a[2] = 1.0f - alpha;
|
||||
break;
|
||||
}
|
||||
|
||||
mA1 = a[1] / a[0];
|
||||
mA2 = a[2] / a[0];
|
||||
mB0 = b[0] / a[0];
|
||||
mB1 = b[1] / a[0];
|
||||
mB2 = b[2] / a[0];
|
||||
}
|
||||
|
||||
template<typename Real>
|
||||
void BiquadFilterR<Real>::process(const al::span<const Real> src, Real *dst)
|
||||
{
|
||||
const Real b0{mB0};
|
||||
const Real b1{mB1};
|
||||
const Real b2{mB2};
|
||||
const Real a1{mA1};
|
||||
const Real a2{mA2};
|
||||
Real z1{mZ1};
|
||||
Real z2{mZ2};
|
||||
|
||||
/* Processing loop is Transposed Direct Form II. This requires less storage
|
||||
* compared to Direct Form I (only two delay components, instead of a four-
|
||||
* sample history; the last two inputs and outputs), and works better for
|
||||
* floating-point which favors summing similarly-sized values while being
|
||||
* less bothered by overflow.
|
||||
*
|
||||
* See: http://www.earlevel.com/main/2003/02/28/biquads/
|
||||
*/
|
||||
auto proc_sample = [b0,b1,b2,a1,a2,&z1,&z2](Real input) noexcept -> Real
|
||||
{
|
||||
const Real output{input*b0 + z1};
|
||||
z1 = input*b1 - output*a1 + z2;
|
||||
z2 = input*b2 - output*a2;
|
||||
return output;
|
||||
};
|
||||
std::transform(src.cbegin(), src.cend(), dst, proc_sample);
|
||||
|
||||
mZ1 = z1;
|
||||
mZ2 = z2;
|
||||
}
|
||||
|
||||
template class BiquadFilterR<float>;
|
||||
template class BiquadFilterR<double>;
|
||||
@@ -1,134 +0,0 @@
|
||||
#ifndef FILTERS_BIQUAD_H
|
||||
#define FILTERS_BIQUAD_H
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
|
||||
#include "alspan.h"
|
||||
#include "math_defs.h"
|
||||
|
||||
|
||||
/* Filters implementation is based on the "Cookbook formulae for audio
|
||||
* EQ biquad filter coefficients" by Robert Bristow-Johnson
|
||||
* http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt
|
||||
*/
|
||||
/* Implementation note: For the shelf and peaking filters, the specified gain
|
||||
* is for the centerpoint of the transition band. This better fits EFX filter
|
||||
* behavior, which expects the shelf's reference frequency to reach the given
|
||||
* gain. To set the gain for the shelf or peak itself, use the square root of
|
||||
* the desired linear gain (or halve the dB gain).
|
||||
*/
|
||||
|
||||
enum class BiquadType {
|
||||
/** EFX-style low-pass filter, specifying a gain and reference frequency. */
|
||||
HighShelf,
|
||||
/** EFX-style high-pass filter, specifying a gain and reference frequency. */
|
||||
LowShelf,
|
||||
/** Peaking filter, specifying a gain and reference frequency. */
|
||||
Peaking,
|
||||
|
||||
/** Low-pass cut-off filter, specifying a cut-off frequency. */
|
||||
LowPass,
|
||||
/** High-pass cut-off filter, specifying a cut-off frequency. */
|
||||
HighPass,
|
||||
/** Band-pass filter, specifying a center frequency. */
|
||||
BandPass,
|
||||
};
|
||||
|
||||
template<typename Real>
|
||||
class BiquadFilterR {
|
||||
/* Last two delayed components for direct form II. */
|
||||
Real mZ1{0.0f}, mZ2{0.0f};
|
||||
/* Transfer function coefficients "b" (numerator) */
|
||||
Real mB0{1.0f}, mB1{0.0f}, mB2{0.0f};
|
||||
/* Transfer function coefficients "a" (denominator; a0 is pre-applied). */
|
||||
Real mA1{0.0f}, mA2{0.0f};
|
||||
|
||||
void setParams(BiquadType type, Real f0norm, Real gain, Real rcpQ);
|
||||
|
||||
/**
|
||||
* Calculates the rcpQ (i.e. 1/Q) coefficient for shelving filters, using
|
||||
* the reference gain and shelf slope parameter.
|
||||
* \param gain 0 < gain
|
||||
* \param slope 0 < slope <= 1
|
||||
*/
|
||||
static Real rcpQFromSlope(Real gain, Real slope)
|
||||
{ return std::sqrt((gain + 1.0f/gain)*(1.0f/slope - 1.0f) + 2.0f); }
|
||||
|
||||
/**
|
||||
* Calculates the rcpQ (i.e. 1/Q) coefficient for filters, using the
|
||||
* normalized reference frequency and bandwidth.
|
||||
* \param f0norm 0 < f0norm < 0.5.
|
||||
* \param bandwidth 0 < bandwidth
|
||||
*/
|
||||
static Real rcpQFromBandwidth(Real f0norm, Real bandwidth)
|
||||
{
|
||||
const Real w0{al::MathDefs<Real>::Tau() * f0norm};
|
||||
return 2.0f*std::sinh(std::log(Real{2.0f})/2.0f*bandwidth*w0/std::sin(w0));
|
||||
}
|
||||
|
||||
public:
|
||||
void clear() noexcept { mZ1 = mZ2 = 0.0f; }
|
||||
|
||||
/**
|
||||
* Sets the filter state for the specified filter type and its parameters.
|
||||
*
|
||||
* \param type The type of filter to apply.
|
||||
* \param f0norm The normalized reference frequency (ref / sample_rate).
|
||||
* This is the center point for the Shelf, Peaking, and BandPass filter
|
||||
* types, or the cutoff frequency for the LowPass and HighPass filter
|
||||
* types.
|
||||
* \param gain The gain for the reference frequency response. Only used by
|
||||
* the Shelf and Peaking filter types.
|
||||
* \param slope Slope steepness of the transition band.
|
||||
*/
|
||||
void setParamsFromSlope(BiquadType type, Real f0norm, Real gain, Real slope)
|
||||
{
|
||||
gain = std::max<Real>(gain, 0.001f); /* Limit -60dB */
|
||||
setParams(type, f0norm, gain, rcpQFromSlope(gain, slope));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the filter state for the specified filter type and its parameters.
|
||||
*
|
||||
* \param type The type of filter to apply.
|
||||
* \param f0norm The normalized reference frequency (ref / sample_rate).
|
||||
* This is the center point for the Shelf, Peaking, and BandPass filter
|
||||
* types, or the cutoff frequency for the LowPass and HighPass filter
|
||||
* types.
|
||||
* \param gain The gain for the reference frequency response. Only used by
|
||||
* the Shelf and Peaking filter types.
|
||||
* \param bandwidth Normalized bandwidth of the transition band.
|
||||
*/
|
||||
void setParamsFromBandwidth(BiquadType type, Real f0norm, Real gain, Real bandwidth)
|
||||
{ setParams(type, f0norm, gain, rcpQFromBandwidth(f0norm, bandwidth)); }
|
||||
|
||||
void copyParamsFrom(const BiquadFilterR &other)
|
||||
{
|
||||
mB0 = other.mB0;
|
||||
mB1 = other.mB1;
|
||||
mB2 = other.mB2;
|
||||
mA1 = other.mA1;
|
||||
mA2 = other.mA2;
|
||||
}
|
||||
|
||||
|
||||
void process(const al::span<const Real> src, Real *dst);
|
||||
|
||||
/* Rather hacky. It's just here to support "manual" processing. */
|
||||
std::pair<Real,Real> getComponents() const noexcept { return {mZ1, mZ2}; }
|
||||
void setComponents(Real z1, Real z2) noexcept { mZ1 = z1; mZ2 = z2; }
|
||||
Real processOne(const Real in, Real &z1, Real &z2) const noexcept
|
||||
{
|
||||
const Real out{in*mB0 + z1};
|
||||
z1 = in*mB1 - out*mA1 + z2;
|
||||
z2 = in*mB2 - out*mA2;
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
using BiquadFilter = BiquadFilterR<float>;
|
||||
|
||||
#endif /* FILTERS_BIQUAD_H */
|
||||
@@ -1,383 +0,0 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "nfc.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "opthelpers.h"
|
||||
|
||||
|
||||
/* Near-field control filters are the basis for handling the near-field effect.
|
||||
* The near-field effect is a bass-boost present in the directional components
|
||||
* of a recorded signal, created as a result of the wavefront curvature (itself
|
||||
* a function of sound distance). Proper reproduction dictates this be
|
||||
* compensated for using a bass-cut given the playback speaker distance, to
|
||||
* avoid excessive bass in the playback.
|
||||
*
|
||||
* For real-time rendered audio, emulating the near-field effect based on the
|
||||
* sound source's distance, and subsequently compensating for it at output
|
||||
* based on the speaker distances, can create a more realistic perception of
|
||||
* sound distance beyond a simple 1/r attenuation.
|
||||
*
|
||||
* These filters do just that. Each one applies a low-shelf filter, created as
|
||||
* the combination of a bass-boost for a given sound source distance (near-
|
||||
* field emulation) along with a bass-cut for a given control/speaker distance
|
||||
* (near-field compensation).
|
||||
*
|
||||
* Note that it is necessary to apply a cut along with the boost, since the
|
||||
* boost alone is unstable in higher-order ambisonics as it causes an infinite
|
||||
* DC gain (even first-order ambisonics requires there to be no DC offset for
|
||||
* the boost to work). Consequently, ambisonics requires a control parameter to
|
||||
* be used to avoid an unstable boost-only filter. NFC-HOA defines this control
|
||||
* as a reference delay, calculated with:
|
||||
*
|
||||
* reference_delay = control_distance / speed_of_sound
|
||||
*
|
||||
* This means w0 (for input) or w1 (for output) should be set to:
|
||||
*
|
||||
* wN = 1 / (reference_delay * sample_rate)
|
||||
*
|
||||
* when dealing with NFC-HOA content. For FOA input content, which does not
|
||||
* specify a reference_delay variable, w0 should be set to 0 to apply only
|
||||
* near-field compensation for output. It's important that w1 be a finite,
|
||||
* positive, non-0 value or else the bass-boost will become unstable again.
|
||||
* Also, w0 should not be too large compared to w1, to avoid excessively loud
|
||||
* low frequencies.
|
||||
*/
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr float B[5][4] = {
|
||||
{ 0.0f },
|
||||
{ 1.0f },
|
||||
{ 3.0f, 3.0f },
|
||||
{ 3.6778f, 6.4595f, 2.3222f },
|
||||
{ 4.2076f, 11.4877f, 5.7924f, 9.1401f }
|
||||
};
|
||||
|
||||
NfcFilter1 NfcFilterCreate1(const float w0, const float w1) noexcept
|
||||
{
|
||||
NfcFilter1 nfc{};
|
||||
float b_00, g_0;
|
||||
float r;
|
||||
|
||||
nfc.base_gain = 1.0f;
|
||||
nfc.gain = 1.0f;
|
||||
|
||||
/* Calculate bass-boost coefficients. */
|
||||
r = 0.5f * w0;
|
||||
b_00 = B[1][0] * r;
|
||||
g_0 = 1.0f + b_00;
|
||||
|
||||
nfc.gain *= g_0;
|
||||
nfc.b1 = 2.0f * b_00 / g_0;
|
||||
|
||||
/* Calculate bass-cut coefficients. */
|
||||
r = 0.5f * w1;
|
||||
b_00 = B[1][0] * r;
|
||||
g_0 = 1.0f + b_00;
|
||||
|
||||
nfc.base_gain /= g_0;
|
||||
nfc.gain /= g_0;
|
||||
nfc.a1 = 2.0f * b_00 / g_0;
|
||||
|
||||
return nfc;
|
||||
}
|
||||
|
||||
void NfcFilterAdjust1(NfcFilter1 *nfc, const float w0) noexcept
|
||||
{
|
||||
const float r{0.5f * w0};
|
||||
const float b_00{B[1][0] * r};
|
||||
const float g_0{1.0f + b_00};
|
||||
|
||||
nfc->gain = nfc->base_gain * g_0;
|
||||
nfc->b1 = 2.0f * b_00 / g_0;
|
||||
}
|
||||
|
||||
|
||||
NfcFilter2 NfcFilterCreate2(const float w0, const float w1) noexcept
|
||||
{
|
||||
NfcFilter2 nfc{};
|
||||
float b_10, b_11, g_1;
|
||||
float r;
|
||||
|
||||
nfc.base_gain = 1.0f;
|
||||
nfc.gain = 1.0f;
|
||||
|
||||
/* Calculate bass-boost coefficients. */
|
||||
r = 0.5f * w0;
|
||||
b_10 = B[2][0] * r;
|
||||
b_11 = B[2][1] * r * r;
|
||||
g_1 = 1.0f + b_10 + b_11;
|
||||
|
||||
nfc.gain *= g_1;
|
||||
nfc.b1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
|
||||
nfc.b2 = 4.0f * b_11 / g_1;
|
||||
|
||||
/* Calculate bass-cut coefficients. */
|
||||
r = 0.5f * w1;
|
||||
b_10 = B[2][0] * r;
|
||||
b_11 = B[2][1] * r * r;
|
||||
g_1 = 1.0f + b_10 + b_11;
|
||||
|
||||
nfc.base_gain /= g_1;
|
||||
nfc.gain /= g_1;
|
||||
nfc.a1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
|
||||
nfc.a2 = 4.0f * b_11 / g_1;
|
||||
|
||||
return nfc;
|
||||
}
|
||||
|
||||
void NfcFilterAdjust2(NfcFilter2 *nfc, const float w0) noexcept
|
||||
{
|
||||
const float r{0.5f * w0};
|
||||
const float b_10{B[2][0] * r};
|
||||
const float b_11{B[2][1] * r * r};
|
||||
const float g_1{1.0f + b_10 + b_11};
|
||||
|
||||
nfc->gain = nfc->base_gain * g_1;
|
||||
nfc->b1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
|
||||
nfc->b2 = 4.0f * b_11 / g_1;
|
||||
}
|
||||
|
||||
|
||||
NfcFilter3 NfcFilterCreate3(const float w0, const float w1) noexcept
|
||||
{
|
||||
NfcFilter3 nfc{};
|
||||
float b_10, b_11, g_1;
|
||||
float b_00, g_0;
|
||||
float r;
|
||||
|
||||
nfc.base_gain = 1.0f;
|
||||
nfc.gain = 1.0f;
|
||||
|
||||
/* Calculate bass-boost coefficients. */
|
||||
r = 0.5f * w0;
|
||||
b_10 = B[3][0] * r;
|
||||
b_11 = B[3][1] * r * r;
|
||||
b_00 = B[3][2] * r;
|
||||
g_1 = 1.0f + b_10 + b_11;
|
||||
g_0 = 1.0f + b_00;
|
||||
|
||||
nfc.gain *= g_1 * g_0;
|
||||
nfc.b1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
|
||||
nfc.b2 = 4.0f * b_11 / g_1;
|
||||
nfc.b3 = 2.0f * b_00 / g_0;
|
||||
|
||||
/* Calculate bass-cut coefficients. */
|
||||
r = 0.5f * w1;
|
||||
b_10 = B[3][0] * r;
|
||||
b_11 = B[3][1] * r * r;
|
||||
b_00 = B[3][2] * r;
|
||||
g_1 = 1.0f + b_10 + b_11;
|
||||
g_0 = 1.0f + b_00;
|
||||
|
||||
nfc.base_gain /= g_1 * g_0;
|
||||
nfc.gain /= g_1 * g_0;
|
||||
nfc.a1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
|
||||
nfc.a2 = 4.0f * b_11 / g_1;
|
||||
nfc.a3 = 2.0f * b_00 / g_0;
|
||||
|
||||
return nfc;
|
||||
}
|
||||
|
||||
void NfcFilterAdjust3(NfcFilter3 *nfc, const float w0) noexcept
|
||||
{
|
||||
const float r{0.5f * w0};
|
||||
const float b_10{B[3][0] * r};
|
||||
const float b_11{B[3][1] * r * r};
|
||||
const float b_00{B[3][2] * r};
|
||||
const float g_1{1.0f + b_10 + b_11};
|
||||
const float g_0{1.0f + b_00};
|
||||
|
||||
nfc->gain = nfc->base_gain * g_1 * g_0;
|
||||
nfc->b1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
|
||||
nfc->b2 = 4.0f * b_11 / g_1;
|
||||
nfc->b3 = 2.0f * b_00 / g_0;
|
||||
}
|
||||
|
||||
|
||||
NfcFilter4 NfcFilterCreate4(const float w0, const float w1) noexcept
|
||||
{
|
||||
NfcFilter4 nfc{};
|
||||
float b_10, b_11, g_1;
|
||||
float b_00, b_01, g_0;
|
||||
float r;
|
||||
|
||||
nfc.base_gain = 1.0f;
|
||||
nfc.gain = 1.0f;
|
||||
|
||||
/* Calculate bass-boost coefficients. */
|
||||
r = 0.5f * w0;
|
||||
b_10 = B[4][0] * r;
|
||||
b_11 = B[4][1] * r * r;
|
||||
b_00 = B[4][2] * r;
|
||||
b_01 = B[4][3] * r * r;
|
||||
g_1 = 1.0f + b_10 + b_11;
|
||||
g_0 = 1.0f + b_00 + b_01;
|
||||
|
||||
nfc.gain *= g_1 * g_0;
|
||||
nfc.b1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
|
||||
nfc.b2 = 4.0f * b_11 / g_1;
|
||||
nfc.b3 = (2.0f*b_00 + 4.0f*b_01) / g_0;
|
||||
nfc.b4 = 4.0f * b_01 / g_0;
|
||||
|
||||
/* Calculate bass-cut coefficients. */
|
||||
r = 0.5f * w1;
|
||||
b_10 = B[4][0] * r;
|
||||
b_11 = B[4][1] * r * r;
|
||||
b_00 = B[4][2] * r;
|
||||
b_01 = B[4][3] * r * r;
|
||||
g_1 = 1.0f + b_10 + b_11;
|
||||
g_0 = 1.0f + b_00 + b_01;
|
||||
|
||||
nfc.base_gain /= g_1 * g_0;
|
||||
nfc.gain /= g_1 * g_0;
|
||||
nfc.a1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
|
||||
nfc.a2 = 4.0f * b_11 / g_1;
|
||||
nfc.a3 = (2.0f*b_00 + 4.0f*b_01) / g_0;
|
||||
nfc.a4 = 4.0f * b_01 / g_0;
|
||||
|
||||
return nfc;
|
||||
}
|
||||
|
||||
void NfcFilterAdjust4(NfcFilter4 *nfc, const float w0) noexcept
|
||||
{
|
||||
const float r{0.5f * w0};
|
||||
const float b_10{B[4][0] * r};
|
||||
const float b_11{B[4][1] * r * r};
|
||||
const float b_00{B[4][2] * r};
|
||||
const float b_01{B[4][3] * r * r};
|
||||
const float g_1{1.0f + b_10 + b_11};
|
||||
const float g_0{1.0f + b_00 + b_01};
|
||||
|
||||
nfc->gain = nfc->base_gain * g_1 * g_0;
|
||||
nfc->b1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
|
||||
nfc->b2 = 4.0f * b_11 / g_1;
|
||||
nfc->b3 = (2.0f*b_00 + 4.0f*b_01) / g_0;
|
||||
nfc->b4 = 4.0f * b_01 / g_0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void NfcFilter::init(const float w1) noexcept
|
||||
{
|
||||
first = NfcFilterCreate1(0.0f, w1);
|
||||
second = NfcFilterCreate2(0.0f, w1);
|
||||
third = NfcFilterCreate3(0.0f, w1);
|
||||
fourth = NfcFilterCreate4(0.0f, w1);
|
||||
}
|
||||
|
||||
void NfcFilter::adjust(const float w0) noexcept
|
||||
{
|
||||
NfcFilterAdjust1(&first, w0);
|
||||
NfcFilterAdjust2(&second, w0);
|
||||
NfcFilterAdjust3(&third, w0);
|
||||
NfcFilterAdjust4(&fourth, w0);
|
||||
}
|
||||
|
||||
|
||||
void NfcFilter::process1(const al::span<const float> src, float *RESTRICT dst)
|
||||
{
|
||||
const float gain{first.gain};
|
||||
const float b1{first.b1};
|
||||
const float a1{first.a1};
|
||||
float z1{first.z[0]};
|
||||
auto proc_sample = [gain,b1,a1,&z1](const float in) noexcept -> float
|
||||
{
|
||||
const float y{in*gain - a1*z1};
|
||||
const float out{y + b1*z1};
|
||||
z1 += y;
|
||||
return out;
|
||||
};
|
||||
std::transform(src.cbegin(), src.cend(), dst, proc_sample);
|
||||
first.z[0] = z1;
|
||||
}
|
||||
|
||||
void NfcFilter::process2(const al::span<const float> src, float *RESTRICT dst)
|
||||
{
|
||||
const float gain{second.gain};
|
||||
const float b1{second.b1};
|
||||
const float b2{second.b2};
|
||||
const float a1{second.a1};
|
||||
const float a2{second.a2};
|
||||
float z1{second.z[0]};
|
||||
float z2{second.z[1]};
|
||||
auto proc_sample = [gain,b1,b2,a1,a2,&z1,&z2](const float in) noexcept -> float
|
||||
{
|
||||
const float y{in*gain - a1*z1 - a2*z2};
|
||||
const float out{y + b1*z1 + b2*z2};
|
||||
z2 += z1;
|
||||
z1 += y;
|
||||
return out;
|
||||
};
|
||||
std::transform(src.cbegin(), src.cend(), dst, proc_sample);
|
||||
second.z[0] = z1;
|
||||
second.z[1] = z2;
|
||||
}
|
||||
|
||||
void NfcFilter::process3(const al::span<const float> src, float *RESTRICT dst)
|
||||
{
|
||||
const float gain{third.gain};
|
||||
const float b1{third.b1};
|
||||
const float b2{third.b2};
|
||||
const float b3{third.b3};
|
||||
const float a1{third.a1};
|
||||
const float a2{third.a2};
|
||||
const float a3{third.a3};
|
||||
float z1{third.z[0]};
|
||||
float z2{third.z[1]};
|
||||
float z3{third.z[2]};
|
||||
auto proc_sample = [gain,b1,b2,b3,a1,a2,a3,&z1,&z2,&z3](const float in) noexcept -> float
|
||||
{
|
||||
float y{in*gain - a1*z1 - a2*z2};
|
||||
float out{y + b1*z1 + b2*z2};
|
||||
z2 += z1;
|
||||
z1 += y;
|
||||
|
||||
y = out - a3*z3;
|
||||
out = y + b3*z3;
|
||||
z3 += y;
|
||||
return out;
|
||||
};
|
||||
std::transform(src.cbegin(), src.cend(), dst, proc_sample);
|
||||
third.z[0] = z1;
|
||||
third.z[1] = z2;
|
||||
third.z[2] = z3;
|
||||
}
|
||||
|
||||
void NfcFilter::process4(const al::span<const float> src, float *RESTRICT dst)
|
||||
{
|
||||
const float gain{fourth.gain};
|
||||
const float b1{fourth.b1};
|
||||
const float b2{fourth.b2};
|
||||
const float b3{fourth.b3};
|
||||
const float b4{fourth.b4};
|
||||
const float a1{fourth.a1};
|
||||
const float a2{fourth.a2};
|
||||
const float a3{fourth.a3};
|
||||
const float a4{fourth.a4};
|
||||
float z1{fourth.z[0]};
|
||||
float z2{fourth.z[1]};
|
||||
float z3{fourth.z[2]};
|
||||
float z4{fourth.z[3]};
|
||||
auto proc_sample = [gain,b1,b2,b3,b4,a1,a2,a3,a4,&z1,&z2,&z3,&z4](const float in) noexcept -> float
|
||||
{
|
||||
float y{in*gain - a1*z1 - a2*z2};
|
||||
float out{y + b1*z1 + b2*z2};
|
||||
z2 += z1;
|
||||
z1 += y;
|
||||
|
||||
y = out - a3*z3 - a4*z4;
|
||||
out = y + b3*z3 + b4*z4;
|
||||
z4 += z3;
|
||||
z3 += y;
|
||||
return out;
|
||||
};
|
||||
std::transform(src.cbegin(), src.cend(), dst, proc_sample);
|
||||
fourth.z[0] = z1;
|
||||
fourth.z[1] = z2;
|
||||
fourth.z[2] = z3;
|
||||
fourth.z[3] = z4;
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
#ifndef FILTER_NFC_H
|
||||
#define FILTER_NFC_H
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include "alspan.h"
|
||||
|
||||
|
||||
struct NfcFilter1 {
|
||||
float base_gain, gain;
|
||||
float b1, a1;
|
||||
float z[1];
|
||||
};
|
||||
struct NfcFilter2 {
|
||||
float base_gain, gain;
|
||||
float b1, b2, a1, a2;
|
||||
float z[2];
|
||||
};
|
||||
struct NfcFilter3 {
|
||||
float base_gain, gain;
|
||||
float b1, b2, b3, a1, a2, a3;
|
||||
float z[3];
|
||||
};
|
||||
struct NfcFilter4 {
|
||||
float base_gain, gain;
|
||||
float b1, b2, b3, b4, a1, a2, a3, a4;
|
||||
float z[4];
|
||||
};
|
||||
|
||||
class NfcFilter {
|
||||
NfcFilter1 first;
|
||||
NfcFilter2 second;
|
||||
NfcFilter3 third;
|
||||
NfcFilter4 fourth;
|
||||
|
||||
public:
|
||||
/* NOTE:
|
||||
* w0 = speed_of_sound / (source_distance * sample_rate);
|
||||
* w1 = speed_of_sound / (control_distance * sample_rate);
|
||||
*
|
||||
* Generally speaking, the control distance should be approximately the
|
||||
* average speaker distance, or based on the reference delay if outputing
|
||||
* NFC-HOA. It must not be negative, 0, or infinite. The source distance
|
||||
* should not be too small relative to the control distance.
|
||||
*/
|
||||
|
||||
void init(const float w1) noexcept;
|
||||
void adjust(const float w0) noexcept;
|
||||
|
||||
/* Near-field control filter for first-order ambisonic channels (1-3). */
|
||||
void process1(const al::span<const float> src, float *RESTRICT dst);
|
||||
|
||||
/* Near-field control filter for second-order ambisonic channels (4-8). */
|
||||
void process2(const al::span<const float> src, float *RESTRICT dst);
|
||||
|
||||
/* Near-field control filter for third-order ambisonic channels (9-15). */
|
||||
void process3(const al::span<const float> src, float *RESTRICT dst);
|
||||
|
||||
/* Near-field control filter for fourth-order ambisonic channels (16-24). */
|
||||
void process4(const al::span<const float> src, float *RESTRICT dst);
|
||||
};
|
||||
|
||||
#endif /* FILTER_NFC_H */
|
||||
@@ -1,113 +0,0 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "splitter.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
#include "math_defs.h"
|
||||
#include "opthelpers.h"
|
||||
|
||||
|
||||
template<typename Real>
|
||||
void BandSplitterR<Real>::init(Real f0norm)
|
||||
{
|
||||
const Real w{f0norm * al::MathDefs<Real>::Tau()};
|
||||
const Real cw{std::cos(w)};
|
||||
if(cw > std::numeric_limits<float>::epsilon())
|
||||
mCoeff = (std::sin(w) - 1.0f) / cw;
|
||||
else
|
||||
mCoeff = cw * -0.5f;
|
||||
|
||||
mLpZ1 = 0.0f;
|
||||
mLpZ2 = 0.0f;
|
||||
mApZ1 = 0.0f;
|
||||
}
|
||||
|
||||
template<typename Real>
|
||||
void BandSplitterR<Real>::process(const al::span<const Real> input, Real *hpout, Real *lpout)
|
||||
{
|
||||
const Real ap_coeff{mCoeff};
|
||||
const Real lp_coeff{mCoeff*0.5f + 0.5f};
|
||||
Real lp_z1{mLpZ1};
|
||||
Real lp_z2{mLpZ2};
|
||||
Real ap_z1{mApZ1};
|
||||
auto proc_sample = [ap_coeff,lp_coeff,&lp_z1,&lp_z2,&ap_z1,&lpout](const Real in) noexcept -> Real
|
||||
{
|
||||
/* Low-pass sample processing. */
|
||||
Real d{(in - lp_z1) * lp_coeff};
|
||||
Real lp_y{lp_z1 + d};
|
||||
lp_z1 = lp_y + d;
|
||||
|
||||
d = (lp_y - lp_z2) * lp_coeff;
|
||||
lp_y = lp_z2 + d;
|
||||
lp_z2 = lp_y + d;
|
||||
|
||||
*(lpout++) = lp_y;
|
||||
|
||||
/* All-pass sample processing. */
|
||||
Real ap_y{in*ap_coeff + ap_z1};
|
||||
ap_z1 = in - ap_y*ap_coeff;
|
||||
|
||||
/* High-pass generated from removing low-passed output. */
|
||||
return ap_y - lp_y;
|
||||
};
|
||||
std::transform(input.cbegin(), input.cend(), hpout, proc_sample);
|
||||
mLpZ1 = lp_z1;
|
||||
mLpZ2 = lp_z2;
|
||||
mApZ1 = ap_z1;
|
||||
}
|
||||
|
||||
template<typename Real>
|
||||
void BandSplitterR<Real>::applyHfScale(const al::span<Real> samples, const Real hfscale)
|
||||
{
|
||||
const Real ap_coeff{mCoeff};
|
||||
const Real lp_coeff{mCoeff*0.5f + 0.5f};
|
||||
Real lp_z1{mLpZ1};
|
||||
Real lp_z2{mLpZ2};
|
||||
Real ap_z1{mApZ1};
|
||||
auto proc_sample = [hfscale,ap_coeff,lp_coeff,&lp_z1,&lp_z2,&ap_z1](const Real in) noexcept -> Real
|
||||
{
|
||||
/* Low-pass sample processing. */
|
||||
Real d{(in - lp_z1) * lp_coeff};
|
||||
Real lp_y{lp_z1 + d};
|
||||
lp_z1 = lp_y + d;
|
||||
|
||||
d = (lp_y - lp_z2) * lp_coeff;
|
||||
lp_y = lp_z2 + d;
|
||||
lp_z2 = lp_y + d;
|
||||
|
||||
/* All-pass sample processing. */
|
||||
Real ap_y{in*ap_coeff + ap_z1};
|
||||
ap_z1 = in - ap_y*ap_coeff;
|
||||
|
||||
/* High-pass generated by removing the low-passed signal, which is then
|
||||
* scaled and added back to the low-passed signal.
|
||||
*/
|
||||
return (ap_y-lp_y)*hfscale + lp_y;
|
||||
};
|
||||
std::transform(samples.begin(), samples.end(), samples.begin(), proc_sample);
|
||||
mLpZ1 = lp_z1;
|
||||
mLpZ2 = lp_z2;
|
||||
mApZ1 = ap_z1;
|
||||
}
|
||||
|
||||
template<typename Real>
|
||||
void BandSplitterR<Real>::applyAllpass(const al::span<Real> samples) const
|
||||
{
|
||||
const Real coeff{mCoeff};
|
||||
Real z1{0.0f};
|
||||
auto proc_sample = [coeff,&z1](const Real in) noexcept -> Real
|
||||
{
|
||||
const Real out{in*coeff + z1};
|
||||
z1 = in - out*coeff;
|
||||
return out;
|
||||
};
|
||||
std::transform(samples.begin(), samples.end(), samples.begin(), proc_sample);
|
||||
}
|
||||
|
||||
|
||||
template class BandSplitterR<float>;
|
||||
template class BandSplitterR<double>;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user