Compare commits
388 Commits
Author | SHA1 | Date |
---|---|---|
![]() |
ad9af1640c | |
![]() |
a1caefa014 | |
![]() |
42b91c6f9d | |
![]() |
da2c2eb2a8 | |
![]() |
2dbe524f20 | |
![]() |
eb26ab8be4 | |
![]() |
8443464b54 | |
![]() |
f64ec07b43 | |
![]() |
1cd3a3230c | |
![]() |
6c0db041e3 | |
![]() |
22cdf0aa4f | |
![]() |
10f22d137c | |
![]() |
877660b4cc | |
![]() |
fa373b9ff1 | |
![]() |
55cb51c330 | |
![]() |
db338f929a | |
![]() |
13254f25b1 | |
![]() |
1115e9651d | |
![]() |
836147b94b | |
![]() |
b47d2154b4 | |
![]() |
9e54885b97 | |
![]() |
11ffdcb1c1 | |
![]() |
4d106c40ce | |
![]() |
9d3089462e | |
![]() |
3e2286683a | |
![]() |
c1e5aa21fc | |
![]() |
549f426e65 | |
![]() |
c68b4b9585 | |
![]() |
0b82160512 | |
![]() |
ed38e4f8d0 | |
![]() |
799299df3a | |
![]() |
57a0ea6e87 | |
![]() |
de974548de | |
![]() |
b0c718003b | |
![]() |
593d7d155a | |
![]() |
190d6959fd | |
![]() |
790eb5508b | |
![]() |
c34d869b99 | |
![]() |
5743652879 | |
![]() |
4131d4eae4 | |
![]() |
9bfbeaadb5 | |
![]() |
26242673d1 | |
![]() |
57c2adf946 | |
![]() |
01c38c9440 | |
![]() |
72ee38ad14 | |
![]() |
6daa0de425 | |
![]() |
1b9f460538 | |
![]() |
1de501a64a | |
![]() |
4b37ffebee | |
![]() |
5e0a3308c0 | |
![]() |
85283b99a1 | |
![]() |
63bd6b29d7 | |
![]() |
fadeec1827 | |
![]() |
9c67eb78fc | |
![]() |
d8f0a19524 | |
![]() |
5cd5fe1c5e | |
![]() |
0c5c905af3 | |
![]() |
364f13338d | |
![]() |
c627a9fc58 | |
![]() |
c40b25b571 | |
![]() |
66e1d67b32 | |
![]() |
3663140064 | |
![]() |
31ddbbec29 | |
![]() |
7fb894d721 | |
![]() |
504ddcf82b | |
![]() |
02a7fe84cb | |
![]() |
b88bee3f4a | |
![]() |
0d7fd92c82 | |
![]() |
bc66220b36 | |
![]() |
b1f97f3399 | |
![]() |
e7aec3547d | |
![]() |
a9dc4b564c | |
![]() |
e25cfbf22c | |
![]() |
695a03d9e8 | |
![]() |
066ee806dc | |
|
2f90baf4bf | |
![]() |
2ead57412b | |
![]() |
309bd3bfef | |
![]() |
323bc8f0df | |
![]() |
ce723fd44e | |
![]() |
bc7331cf8c | |
![]() |
fc372ef8b4 | |
![]() |
55882c0380 | |
![]() |
37065adf8a | |
![]() |
514374019b | |
![]() |
3a42f8896b | |
![]() |
fadb3e9ee2 | |
![]() |
d7418c3b33 | |
![]() |
41a04760eb | |
![]() |
aed52840ca | |
![]() |
7e267f06cb | |
![]() |
e5aa4287d1 | |
![]() |
06b4fc3ccc | |
![]() |
5019c6b4f6 | |
![]() |
93e3dd7aa6 | |
![]() |
f9c7e85e72 | |
![]() |
fbe232f791 | |
![]() |
ea3d38bcae | |
![]() |
49f88ae37f | |
![]() |
c2a880fa52 | |
![]() |
700cb962a3 | |
![]() |
9560edf53d | |
![]() |
f54b6c2c9a | |
![]() |
3f7b375bfb | |
![]() |
af453816a0 | |
![]() |
daa0c3fc61 | |
![]() |
660cb1bab1 | |
![]() |
4ec2146549 | |
![]() |
7cb22cfeb0 | |
![]() |
86dfe8b518 | |
![]() |
b04be78f20 | |
![]() |
177f055910 | |
![]() |
9f84a8bff5 | |
![]() |
37cc0b4d30 | |
![]() |
8809c0d257 | |
![]() |
8bc94e9a48 | |
![]() |
14eb923529 | |
![]() |
4e24c5b829 | |
![]() |
360464ceec | |
![]() |
9d2d29d0f4 | |
![]() |
c612994cb2 | |
![]() |
e330c64f43 | |
![]() |
e796e23afd | |
![]() |
e2d228bbc0 | |
![]() |
f62a897f7d | |
![]() |
85a18f9a3e | |
![]() |
1c04d5e664 | |
![]() |
35dd0d38b3 | |
![]() |
9c4d9ebb2e | |
![]() |
e14c630034 | |
![]() |
28d5849505 | |
![]() |
81d31dbe58 | |
![]() |
41e87e3830 | |
![]() |
716170dcad | |
![]() |
533c36a745 | |
![]() |
4c4f479fb6 | |
![]() |
b3d10bdd4d | |
![]() |
2a8aba1cf2 | |
![]() |
8bd37943aa | |
![]() |
7bf1bd85b8 | |
![]() |
6fe7883ed2 | |
![]() |
b5f6e0ec20 | |
![]() |
3f212f0cfb | |
![]() |
804e7652a8 | |
![]() |
051b86cbaf | |
![]() |
faa546bc49 | |
![]() |
a5e6988af6 | |
![]() |
97707e9b8f | |
![]() |
647c754311 | |
![]() |
96acc738e3 | |
![]() |
838c0d08e8 | |
![]() |
193af27e7e | |
![]() |
b62d2237f8 | |
![]() |
64fc906f40 | |
![]() |
af64f93e99 | |
![]() |
c708e30d8c | |
![]() |
b95cb59a61 | |
![]() |
1a0ab6723a | |
![]() |
08e76bc9f3 | |
![]() |
4044646280 | |
![]() |
7cb0054d77 | |
![]() |
8034054d72 | |
![]() |
f05bf71320 | |
![]() |
ad9b0df8ed | |
![]() |
eb9d642192 | |
![]() |
34884d825a | |
![]() |
1713e0df7c | |
![]() |
273cdef6b9 | |
![]() |
68d323545b | |
![]() |
c4e975319a | |
![]() |
2c0c77dee2 | |
![]() |
9fdefbf88a | |
![]() |
32dcc5725b | |
![]() |
0518f99aaf | |
![]() |
6a5fa23807 | |
![]() |
29dfe593f1 | |
![]() |
8c4f9a7123 | |
![]() |
5238965534 | |
![]() |
8c0abf5fab | |
![]() |
40cc36d8df | |
![]() |
9462e9fb70 | |
![]() |
7adaeb4dd0 | |
![]() |
824675ae59 | |
![]() |
7a3d571492 | |
![]() |
25b90f300f | |
|
a217bbec1e | |
|
8cf84e5b90 | |
|
fc6d454432 | |
|
ff16c583a0 | |
![]() |
a92b857161 | |
![]() |
1ed85a1c84 | |
![]() |
2ecf110632 | |
![]() |
1ad6c78d83 | |
![]() |
6134cc47fe | |
![]() |
33e1086842 | |
![]() |
e6908ac945 | |
![]() |
833e298d3d | |
![]() |
ac9e404b23 | |
![]() |
36040582ce | |
![]() |
5e09d8f4ef | |
![]() |
8d851e5bda | |
![]() |
c43ebba424 | |
![]() |
c32d14a048 | |
![]() |
cbc1c85a71 | |
![]() |
f150065194 | |
![]() |
1c6db85555 | |
![]() |
54238d29e4 | |
|
02f488c7bf | |
|
c735c8f387 | |
![]() |
2251650b6e | |
|
079301025c | |
![]() |
f1381b11fd | |
![]() |
b0ec50e96e | |
![]() |
7c3500a925 | |
![]() |
413ec69ee4 | |
![]() |
ef0dd921ab | |
|
375eae4be2 | |
![]() |
1d3bc8045c | |
![]() |
8ee4a04f3b | |
![]() |
44e1fc5c49 | |
![]() |
7d8c2c53a0 | |
![]() |
3a16a53bd1 | |
![]() |
812da2e331 | |
![]() |
3d1be2ca0d | |
![]() |
a0621b2537 | |
![]() |
38beb9e930 | |
![]() |
0732b176fd | |
![]() |
7e516f8cfe | |
![]() |
36d8fa5ba5 | |
![]() |
6984ce326c | |
![]() |
4956569206 | |
![]() |
0c6e69015f | |
![]() |
adc159eb05 | |
![]() |
8f4fb4a44c | |
![]() |
9bf5bbba80 | |
![]() |
aa7eb52231 | |
![]() |
1fb636ffd2 | |
![]() |
933b06d5dd | |
![]() |
fceee35896 | |
![]() |
3958af0670 | |
![]() |
de72631ea5 | |
![]() |
a474b469fa | |
|
ebe301a9b8 | |
![]() |
aba75e2184 | |
![]() |
c3ca29db0d | |
![]() |
b0d1533b43 | |
![]() |
c4790af7c0 | |
![]() |
81c98736fa | |
![]() |
cd45941a93 | |
![]() |
a1ea668a41 | |
![]() |
e37d1d15b1 | |
![]() |
511f1a1937 | |
![]() |
92bce537d1 | |
![]() |
aeee143de2 | |
![]() |
9e66838b9b | |
![]() |
740613b95a | |
![]() |
969bf4f502 | |
![]() |
6a46389310 | |
![]() |
e41f4e8556 | |
![]() |
4450f13a8e | |
![]() |
310a6d44f9 | |
![]() |
e9f580cfbc | |
![]() |
bf1afd1923 | |
![]() |
fce21785c2 | |
|
baec7ed72d | |
![]() |
5d7a9798fe | |
![]() |
33a24d4d3e | |
![]() |
18a8baf93b | |
![]() |
01ef37c565 | |
![]() |
a4f2664ca4 | |
![]() |
d9f111d735 | |
|
b701fa712e | |
![]() |
1b0e765d38 | |
![]() |
59bf8c6cf9 | |
![]() |
d4b440d6a5 | |
![]() |
9e02492dc2 | |
![]() |
da1973bcd9 | |
![]() |
8b8fbad6d9 | |
![]() |
099588b7bd | |
![]() |
73c4360b1d | |
![]() |
da21cd7341 | |
![]() |
27478e5df2 | |
![]() |
d739e68e8e | |
![]() |
4ce180fb2a | |
![]() |
3c0ccd9d46 | |
![]() |
b24b9744c1 | |
![]() |
61778b7165 | |
![]() |
7d26150c26 | |
![]() |
35400e1320 | |
|
3d94978f82 | |
|
fe7c424843 | |
![]() |
1a4c140708 | |
![]() |
29c070a616 | |
![]() |
8b7418857d | |
![]() |
a35459719d | |
![]() |
802a86a3d8 | |
![]() |
f8d2837c4e | |
![]() |
112366c850 | |
![]() |
a1a8607a84 | |
![]() |
690153f561 | |
![]() |
b33e3ff6b3 | |
![]() |
bedfac4e33 | |
![]() |
66102a6cda | |
![]() |
b36ebfe740 | |
![]() |
2ed78a0639 | |
![]() |
ae019409c5 | |
![]() |
66262c2a71 | |
![]() |
f567750d56 | |
![]() |
2a99bb076f | |
![]() |
dd65665990 | |
![]() |
c477fbf553 | |
![]() |
03b3b43cfe | |
![]() |
9d8e278a57 | |
![]() |
ed9e352543 | |
![]() |
16fdc1de6a | |
![]() |
fc37e96099 | |
![]() |
03a2c0c16e | |
![]() |
206a3e42e7 | |
![]() |
6f6b5d6986 | |
![]() |
038b0b8067 | |
![]() |
a57827f4da | |
![]() |
39be3ab962 | |
![]() |
17df6cde4f | |
![]() |
2945de085d | |
![]() |
c2ef7d0e61 | |
![]() |
96a7255779 | |
![]() |
050aaaf852 | |
![]() |
5f52a4be81 | |
![]() |
5f2335b796 | |
![]() |
d33475953e | |
![]() |
7a56447271 | |
![]() |
b86ec3340f | |
![]() |
36c82a61e7 | |
![]() |
ab13b66755 | |
![]() |
86de03a1ab | |
![]() |
0f923e6072 | |
![]() |
cb2ce5675a | |
![]() |
1f56e5ef5a | |
![]() |
2663b5007d | |
![]() |
72572c9c7f | |
![]() |
3de7a34eb8 | |
![]() |
86d8f04966 | |
![]() |
b55955ef1c | |
![]() |
8e1175bbd4 | |
![]() |
62e2880b49 | |
![]() |
1ac3e5cd22 | |
![]() |
d2eb4af3d5 | |
![]() |
50b7390181 | |
![]() |
abf80c19ac | |
![]() |
b21a02fe1b | |
![]() |
dd35639174 | |
![]() |
43433c244e | |
![]() |
870a5afed7 | |
![]() |
b9ef5853d6 | |
![]() |
1a9c73d166 | |
![]() |
ec393d186a | |
![]() |
dfb773e9cb | |
![]() |
438b142558 | |
![]() |
26a63e85b7 | |
![]() |
ccbeaf16dc | |
![]() |
cc5de69e9f | |
![]() |
80adb85b36 | |
![]() |
f3f3d436ba | |
|
be6453e048 | |
![]() |
8af32e8d2e | |
![]() |
5b6dacd29c | |
![]() |
ff44bc4d34 | |
![]() |
62824a94bd | |
![]() |
903bc9cba5 | |
![]() |
f274a8ef73 | |
|
0317f2a649 | |
|
b484a0be44 | |
![]() |
36a9411b14 | |
|
64fefce18d | |
![]() |
3a6e41cabd | |
![]() |
632ba36f13 | |
![]() |
271b9b34d0 | |
![]() |
8d031f394b | |
![]() |
f2c6ae39c3 | |
![]() |
bb95270747 | |
![]() |
aa36e3ce10 | |
![]() |
da8a4dffdf | |
|
a4692b80a4 | |
|
de5cce8351 | |
![]() |
05da7450da | |
![]() |
2b8156e86c | |
![]() |
d59b5335e9 | |
![]() |
bc9e7b5d67 |
|
@ -0,0 +1,44 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 2809.9 600" style="enable-background:new 0 0 2809.9 600;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FF6336;}
|
||||
.st1{fill:#FFC501;}
|
||||
.st2{fill:#A4A14A;}
|
||||
</style>
|
||||
<g>
|
||||
<polygon class="st0" points="393.5,468.8 524.7,468.8 524.7,376.1 355.1,376.1 262.3,468.8 169.6,376.1 0,376.1 0,468.8
|
||||
131.2,468.8 262.3,600 "/>
|
||||
<rect y="190.6" class="st1" width="524.7" height="92.8"/>
|
||||
<rect y="5.1" class="st2" width="524.7" height="92.8"/>
|
||||
</g>
|
||||
<path d="M733.3,5.1h83.4v455.2h-83.4V5.1z"/>
|
||||
<path d="M946.7,447.3c-26.3-14.5-47.2-34.5-62.6-59.7c-15.4-25.3-23.1-53.1-23.1-83.4s7.7-58.2,23.1-83.4
|
||||
c15.4-25.3,36.2-45.2,62.6-59.7c26.3-14.5,54.9-21.8,85.7-21.8c30.8,0,59.3,7.3,85.7,21.8c26.3,14.5,47.2,34.5,62.6,59.7
|
||||
c15.4,25.3,23.1,53.1,23.1,83.4s-7.7,58.2-23.1,83.4c-15.4,25.3-36.3,45.2-62.6,59.7c-26.3,14.5-54.9,21.8-85.7,21.8
|
||||
C1001.5,469.1,973,461.8,946.7,447.3z M1076.2,380.9c13.3-7.4,23.9-17.8,31.9-31.3s12-28.7,12-45.5s-4-32-12-45.5
|
||||
s-18.6-23.9-31.9-31.3c-13.3-7.4-27.9-11.1-43.9-11.1s-30.7,3.7-43.9,11.1c-13.3,7.4-23.9,17.8-31.9,31.3s-12,28.7-12,45.5
|
||||
c0,16.9,4,32,12,45.5s18.6,23.9,31.9,31.3c13.3,7.4,27.9,11.1,43.9,11.1C1048.3,391.9,1063,388.3,1076.2,380.9z"/>
|
||||
<path d="M1247.9,5.1h83.4v271.2L1440,147.9h99.2l-122.6,144.8l131.5,167.5h-106.8l-110-144.8v144.8h-83.4L1247.9,5.1L1247.9,5.1z"/>
|
||||
<path d="M1626.9,448.8c-23.4-13.5-42.4-32.8-56.9-57.8c-14.5-25.1-21.8-54-21.8-86.9c0-29.9,7-57.5,20.9-82.8s32.9-45.3,56.9-60.1
|
||||
c24-14.7,50.3-22.1,79-22.1c20.2,0,38.7,3.4,55.3,10.1c16.6,6.7,29.4,15.8,38.3,27.2V148h83.4v312.3h-83.4v-28.4
|
||||
c-13.1,12.2-27,21.5-41.7,27.8c-14.8,6.3-33.7,9.5-56.9,9.5C1674.6,469.1,1650.2,462.3,1626.9,448.8z M1778.9,366.7
|
||||
c15.6-16.9,23.4-37.7,23.4-62.6s-7.8-45.7-23.4-62.6c-15.6-16.8-36.2-25.3-61.9-25.3c-25.7,0-46.4,8.4-62,25.3s-23.4,37.7-23.4,62.6
|
||||
s7.8,45.7,23.4,62.6s36.2,25.3,62,25.3C1742.6,391.9,1763.3,383.5,1778.9,366.7z"/>
|
||||
<path d="M1942.6,5.1h83.4v455.2h-83.4V5.1z"/>
|
||||
<path d="M2091.2,89.8C2081,79.7,2076,67.4,2076,53.1c0-14.7,5.1-27.3,15.2-37.6C2101.3,5.2,2113.5,0,2127.8,0
|
||||
c14.7,0,27.3,5.2,37.6,15.5s15.5,22.9,15.5,37.6c0,14.3-5.2,26.5-15.5,36.7c-10.3,10.1-22.9,15.2-37.6,15.2
|
||||
C2113.5,104.9,2101.3,99.9,2091.2,89.8z M2086.7,147.9h83.4v312.3h-83.4V147.9z"/>
|
||||
<path d="M2227.1,438.7l19-78.4h3.8c27.4,21.1,55.4,31.6,84.1,31.6c11.8,0,21.4-2.2,28.8-6.6c7.4-4.4,11.1-10.8,11.1-19.3
|
||||
c0-8.8-4.3-16-13-21.5c-8.6-5.5-24.8-12.2-48.4-20.2c-24-8-42.7-19.6-55.9-34.8c-13.3-15.2-19.9-33.1-19.9-53.7
|
||||
c0-29.1,10.8-52.5,32.6-70.2c21.7-17.7,49.2-26.6,82.5-26.6c16.9,0,31.8,1.6,44.9,4.7c13.1,3.2,25.5,8.3,37.3,15.5l3.2,79.6h-4.4
|
||||
c-15.2-9.7-28.7-17-40.5-21.8s-25.1-7.3-39.8-7.3c-10.5,0-19.2,2.1-25.9,6.3s-10.1,9.7-10.1,16.4c0,8.9,4.2,16.1,12.7,21.8
|
||||
c8.4,5.7,24.2,12.5,47.4,20.5c26.5,8.9,46.7,19.6,60.4,32.2s20.5,32.2,20.5,58.8c0,21.9-5.5,40.7-16.4,56.3
|
||||
c-11,15.6-25.4,27.3-43.3,35.1c-17.9,7.8-37.6,11.7-59.1,11.7C2294.9,469.1,2257.8,459,2227.1,438.7z"/>
|
||||
<path d="M2574.8,446.9c-26.1-14.7-46.7-34.9-61.6-60.4c-15-25.5-22.4-53.8-22.4-85c0-30.3,7.1-57.8,21.2-82.5
|
||||
c14.1-24.7,33.7-44.1,58.8-58.5c25.1-14.3,53.4-21.5,85-21.5c32,0,59.7,7.5,83.1,22.4c23.4,15,41.1,34.9,53.1,59.7
|
||||
c12,24.9,18,51.6,18,80.3v24.7h-239.6c5.1,23.6,15.9,41.5,32.6,53.7c16.6,12.2,38.7,18.3,66.1,18.3c41.7,0,78.2-13.7,109.4-41.1h8.9
|
||||
l-3.2,79c-19,11-39,19.2-60.1,24.7s-41.3,8.2-60.7,8.2C2630.4,469.1,2600.9,461.7,2574.8,446.9z M2726.5,269.3
|
||||
c-2.1-19-10-33.8-23.7-44.6c-13.7-10.7-30.5-16.1-50.3-16.1c-19.4,0-36.4,5.2-50.9,15.5s-24.3,25.4-29.4,45.2H2726.5z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 3.6 KiB |
|
@ -11,7 +11,7 @@ on:
|
|||
jobs:
|
||||
build:
|
||||
name: Build Universal Cog.app
|
||||
runs-on: macos-11
|
||||
runs-on: macos-12
|
||||
env:
|
||||
XCODE_DERIVEDDATA_PATH: build
|
||||
steps:
|
||||
|
@ -19,6 +19,9 @@ jobs:
|
|||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Unpack libraries
|
||||
run: >
|
||||
cd ThirdParty && tar xvf libraries.tar.xz
|
||||
- name: Run xcodebuild
|
||||
run: >
|
||||
xcodebuild
|
||||
|
|
|
@ -8,4 +8,46 @@ xcuserdata
|
|||
# User-specific xcconfig files
|
||||
Xcode-config/DEVELOPMENT_TEAM.xcconfig
|
||||
|
||||
Cog.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
|
||||
# Plist derived from template at build time
|
||||
/Info.plist
|
||||
|
||||
# This indicates the libraries are up to date
|
||||
/ThirdParty/libraries.updated
|
||||
|
||||
# Temporary file to indicate libraries are being extracted by one process
|
||||
/ThirdParty/libraries.extracting
|
||||
|
||||
# The project will unpack these before building, if necessary
|
||||
/ThirdParty/BASS/libbass.dylib
|
||||
/ThirdParty/BASS/libbass_mpc.dylib
|
||||
/ThirdParty/BASS/libbassflac.dylib
|
||||
/ThirdParty/BASS/libbassmidi.dylib
|
||||
/ThirdParty/BASS/libbassopus.dylib
|
||||
/ThirdParty/BASS/libbasswv.dylib
|
||||
/ThirdParty/avif/lib/libaom.a
|
||||
/ThirdParty/avif/lib/libavif.a
|
||||
/ThirdParty/fdk-aac/lib/libfdk-aac.2.dylib
|
||||
/ThirdParty/fdk-aac/lib/libfdk-aac.a
|
||||
/ThirdParty/fdk-aac/lib/libfdk-aac.dylib
|
||||
/ThirdParty/fdk-aac/lib/libfdk-aac.la
|
||||
/ThirdParty/fdk-aac/lib/pkgconfig/fdk-aac.pc
|
||||
/ThirdParty/ffmpeg/lib/libavcodec.59.dylib
|
||||
/ThirdParty/ffmpeg/lib/libavformat.59.dylib
|
||||
/ThirdParty/ffmpeg/lib/libavutil.57.dylib
|
||||
/ThirdParty/ffmpeg/lib/libswresample.4.dylib
|
||||
/ThirdParty/flac/lib/libFLAC.12.dylib
|
||||
/ThirdParty/libid3tag/lib/libid3tag.a
|
||||
/ThirdParty/libmad/lib/libmad.a
|
||||
/ThirdParty/libopenmpt/lib/libopenmpt.a
|
||||
/ThirdParty/libopenmpt_old/lib/libopenmpt.old.a
|
||||
/ThirdParty/libvgm/lib/libvgm-emu.a
|
||||
/ThirdParty/libvgm/lib/libvgm-player.a
|
||||
/ThirdParty/libvgm/lib/libvgm-utils.a
|
||||
/ThirdParty/mpg123/lib/libmpg123.0.dylib
|
||||
/ThirdParty/ogg/lib/libogg.0.dylib
|
||||
/ThirdParty/opus/lib/libopus.0.dylib
|
||||
/ThirdParty/opusfile/lib/libopusfile.0.dylib
|
||||
/ThirdParty/speex/libspeex.a
|
||||
/ThirdParty/vorbis/lib/libvorbisfile.3.dylib
|
||||
/ThirdParty/vorbis/lib/libvorbis.0.dylib
|
||||
/ThirdParty/soxr/lib/libsoxr.0.dylib
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
url = https://github.com/kode54/mgba.git
|
||||
[submodule "Frameworks/AdPlug/AdPlug/adplug"]
|
||||
path = Frameworks/AdPlug/AdPlug/adplug
|
||||
url = https://github.com/adplug/adplug.git
|
||||
url = https://github.com/kode54/adplug.git
|
||||
[submodule "Frameworks/libbinio/libbinio/libbinio"]
|
||||
path = Frameworks/libbinio/libbinio/libbinio
|
||||
url = https://github.com/adplug/libbinio.git
|
||||
|
@ -15,10 +15,7 @@
|
|||
url = https://github.com/Thealexbarney/LibAtrac9.git
|
||||
[submodule "Frameworks/shpakovski/MASShortcut"]
|
||||
path = Frameworks/shpakovski/MASShortcut
|
||||
url = https://github.com/shpakovski/MASShortcut.git
|
||||
url = https://github.com/kode54/MASShortcut.git
|
||||
[submodule "Frameworks/libsidplayfp/sidplayfp"]
|
||||
path = Frameworks/libsidplayfp/sidplayfp
|
||||
url = https://github.com/kode54/libsidplayfp.git
|
||||
[submodule "Audio/ThirdParty/r8brain-free-src"]
|
||||
path = Audio/ThirdParty/r8brain-free-src
|
||||
url = https://github.com/kode54/r8brain-free-src
|
||||
|
|
BIN
AboutCog.jp2
|
@ -7,6 +7,7 @@
|
|||
@class PlaylistController;
|
||||
@class PlaylistView;
|
||||
@class PlaylistLoader;
|
||||
@class PreferencesController;
|
||||
|
||||
@interface AppController : NSObject {
|
||||
IBOutlet NSObjectController *currentEntryController;
|
||||
|
@ -47,6 +48,8 @@
|
|||
|
||||
IBOutlet FileTreeViewController *fileTreeViewController;
|
||||
|
||||
IBOutlet PreferencesController *preferencesController;
|
||||
|
||||
NSOperationQueue *queue; // Since we are the app delegate, we take care of the op queue
|
||||
|
||||
NSMutableSet *expandedNodes;
|
||||
|
@ -68,6 +71,8 @@
|
|||
- (IBAction)openKofiPage:(id)sender;
|
||||
- (IBAction)openPatreonPage:(id)sender;
|
||||
|
||||
- (IBAction)privacyPolicy:(id)sender;
|
||||
|
||||
- (IBAction)feedback:(id)sender;
|
||||
|
||||
- (void)initDefaults;
|
||||
|
@ -97,6 +102,13 @@
|
|||
- (IBAction)toggleMiniMode:(id)sender;
|
||||
- (IBAction)toggleToolbarStyle:(id)sender;
|
||||
|
||||
- (BOOL)pathSuggesterEmpty;
|
||||
+ (BOOL)globalPathSuggesterEmpty;
|
||||
- (void)showPathSuggester;
|
||||
+ (void)globalShowPathSuggester;
|
||||
|
||||
- (IBAction)checkForUpdates:(id)sender;
|
||||
|
||||
@property NSWindow *mainWindow;
|
||||
@property NSWindow *miniWindow;
|
||||
|
||||
|
|
|
@ -28,12 +28,34 @@
|
|||
#import "Shortcuts.h"
|
||||
#import <MASShortcut/Shortcut.h>
|
||||
|
||||
#import <Sparkle/Sparkle.h>
|
||||
|
||||
#import "PreferencesController.h"
|
||||
|
||||
@import Firebase;
|
||||
|
||||
void *kAppControllerContext = &kAppControllerContext;
|
||||
|
||||
BOOL kAppControllerShuttingDown = NO;
|
||||
|
||||
static AppController *kAppController = nil;
|
||||
|
||||
@interface SparkleBridge : NSObject
|
||||
+ (SPUStandardUpdaterController *)sharedStandardUpdaterController;
|
||||
@end
|
||||
|
||||
@implementation SparkleBridge
|
||||
|
||||
+ (SPUStandardUpdaterController *)sharedStandardUpdaterController {
|
||||
static SPUStandardUpdaterController *sharedStandardUpdaterController_ = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
sharedStandardUpdaterController_ = [[SPUStandardUpdaterController alloc] initWithUpdaterDelegate: nil userDriverDelegate: nil];
|
||||
});
|
||||
return sharedStandardUpdaterController_;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation AppController {
|
||||
BOOL _isFullToolbarStyle;
|
||||
}
|
||||
|
@ -63,6 +85,10 @@ BOOL kAppControllerShuttingDown = NO;
|
|||
NSValueTransformer *totalTimeTransformer = [[TotalTimeTransformer alloc] init];
|
||||
[NSValueTransformer setValueTransformer:totalTimeTransformer
|
||||
forName:@"TotalTimeTransformer"];
|
||||
|
||||
NSValueTransformer *numberHertzToStringTransformer = [[NumberHertzToStringTransformer alloc] init];
|
||||
[NSValueTransformer setValueTransformer:numberHertzToStringTransformer
|
||||
forName:@"NumberHertzToStringTransformer"];
|
||||
}
|
||||
- (id)init {
|
||||
self = [super init];
|
||||
|
@ -70,6 +96,8 @@ BOOL kAppControllerShuttingDown = NO;
|
|||
[self initDefaults];
|
||||
|
||||
queue = [[NSOperationQueue alloc] init];
|
||||
|
||||
kAppController = self;
|
||||
}
|
||||
|
||||
return self;
|
||||
|
@ -88,8 +116,10 @@ BOOL kAppControllerShuttingDown = NO;
|
|||
[p beginSheetModalForWindow:mainWindow
|
||||
completionHandler:^(NSInteger result) {
|
||||
if(result == NSModalResponseOK) {
|
||||
[self->playlistLoader willInsertURLs:[p URLs] origin:URLOriginExternal];
|
||||
[self->playlistLoader didInsertURLs:[self->playlistLoader addURLs:[p URLs] sort:YES] origin:URLOriginExternal];
|
||||
NSDictionary *loadEntryData = @{@"entries": [p URLs],
|
||||
@"sort": @(YES),
|
||||
@"origin": @(URLOriginExternal)};
|
||||
[self->playlistController performSelectorInBackground:@selector(addURLsInBackground:) withObject:loadEntryData];
|
||||
} else {
|
||||
[p close];
|
||||
}
|
||||
|
@ -126,8 +156,10 @@ BOOL kAppControllerShuttingDown = NO;
|
|||
|
||||
- (void)openURLPanelDidEnd:(OpenURLPanel *)panel returnCode:(int)returnCode contextInfo:(void *)contextInfo {
|
||||
if(returnCode == NSModalResponseOK) {
|
||||
[playlistLoader willInsertURLs:@[[panel url]] origin:URLOriginExternal];
|
||||
[playlistLoader didInsertURLs:[playlistLoader addURLs:@[[panel url]] sort:NO] origin:URLOriginExternal];
|
||||
NSDictionary *loadEntriesData = @{ @"entries": @[[panel url]],
|
||||
@"sort": @(NO),
|
||||
@"origin": @(URLOriginExternal) };
|
||||
[playlistController performSelectorInBackground:@selector(addURLsInBackground:) withObject:loadEntriesData];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -144,10 +176,21 @@ BOOL kAppControllerShuttingDown = NO;
|
|||
}
|
||||
|
||||
- (void)awakeFromNib {
|
||||
#if DEBUG
|
||||
[[NSUserDefaults standardUserDefaults] registerDefaults:@{ @"NSApplicationCrashOnExceptions": @(NO) }];
|
||||
#else
|
||||
[[NSUserDefaults standardUserDefaults] registerDefaults:@{ @"NSApplicationCrashOnExceptions": @(YES) }];
|
||||
#endif
|
||||
|
||||
[FIRApp configure];
|
||||
[FIRAnalytics setAnalyticsCollectionEnabled:YES];
|
||||
|
||||
[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.crashlyticsConsented" options:(NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew) context:kAppControllerContext];
|
||||
|
||||
#ifdef DEBUG
|
||||
// Prevent updates automatically in debug builds
|
||||
[[[SparkleBridge sharedStandardUpdaterController] updater] setAutomaticallyChecksForUpdates:NO];
|
||||
#endif
|
||||
[[[SparkleBridge sharedStandardUpdaterController] updater] setUpdateCheckInterval:3600];
|
||||
|
||||
[[totalTimeField cell] setBackgroundStyle:NSBackgroundStyleRaised];
|
||||
|
||||
|
@ -187,6 +230,7 @@ BOOL kAppControllerShuttingDown = NO;
|
|||
if(!sandboxBroker) {
|
||||
ALog(@"Sandbox broker init failed.");
|
||||
}
|
||||
[SandboxBroker cleanupFolderAccess];
|
||||
|
||||
[[playlistController undoManager] enableUndoRegistration];
|
||||
|
||||
|
@ -284,8 +328,12 @@ BOOL kAppControllerShuttingDown = NO;
|
|||
if(context != kAppControllerContext) {
|
||||
return;
|
||||
}
|
||||
|
||||
if([keyPath isEqualToString:@"playlistController.currentEntry"]) {
|
||||
|
||||
if([keyPath isEqualToString:@"values.crashlyticsConsented"]) {
|
||||
BOOL enabled = [[NSUserDefaults standardUserDefaults] boolForKey:@"crashlyticsConsented"];
|
||||
[[FIRCrashlytics crashlytics] setCrashlyticsCollectionEnabled:enabled];
|
||||
[FIRAnalytics setAnalyticsCollectionEnabled:enabled];
|
||||
} else if([keyPath isEqualToString:@"playlistController.currentEntry"]) {
|
||||
PlaylistEntry *entry = playlistController.currentEntry;
|
||||
NSString *appTitle = NSLocalizedString(@"CogTitle", @"");
|
||||
if(!entry) {
|
||||
|
@ -460,8 +508,10 @@ BOOL kAppControllerShuttingDown = NO;
|
|||
|
||||
- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename {
|
||||
NSArray *urls = @[[NSURL fileURLWithPath:filename]];
|
||||
[playlistLoader willInsertURLs:urls origin:URLOriginExternal];
|
||||
[playlistLoader didInsertURLs:[playlistLoader addURLs:urls sort:NO] origin:URLOriginExternal];
|
||||
NSDictionary *loadEntriesData = @{ @"entries": urls,
|
||||
@"sort": @(NO),
|
||||
@"origin": @(URLOriginExternal) };
|
||||
[playlistController performSelectorInBackground:@selector(addURLsInBackground:) withObject:loadEntriesData];
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
@ -470,10 +520,45 @@ BOOL kAppControllerShuttingDown = NO;
|
|||
NSMutableArray *urls = [NSMutableArray array];
|
||||
|
||||
for(NSString *filename in filenames) {
|
||||
[urls addObject:[NSURL fileURLWithPath:filename]];
|
||||
NSURL *url = nil;
|
||||
if([[NSFileManager defaultManager] fileExistsAtPath:filename]) {
|
||||
url = [NSURL fileURLWithPath:filename];
|
||||
} else {
|
||||
if([filename hasPrefix:@"/http/::"] ||
|
||||
[filename hasPrefix:@"/https/::"]) {
|
||||
// Stupid Carbon bodge for AppleScript
|
||||
NSString *method = nil;
|
||||
NSString *server = nil;
|
||||
NSString *path = nil;
|
||||
|
||||
NSScanner *objScanner = [NSScanner scannerWithString:filename];
|
||||
|
||||
if(![objScanner scanString:@"/" intoString:nil] ||
|
||||
![objScanner scanUpToString:@"/" intoString:&method] ||
|
||||
![objScanner scanString:@"/::" intoString:nil] ||
|
||||
![objScanner scanUpToString:@":" intoString:&server] ||
|
||||
![objScanner scanString:@":" intoString:nil]) {
|
||||
continue;
|
||||
}
|
||||
[objScanner scanUpToCharactersFromSet:[NSCharacterSet illegalCharacterSet] intoString:&path];
|
||||
// Colons in server were converted to shashes, convert back
|
||||
NSString *convertedServer = [server stringByReplacingOccurrencesOfString:@"/" withString:@":"];
|
||||
// Slashes in path were converted to colons, convert back
|
||||
NSString *convertedPath = [path stringByReplacingOccurrencesOfString:@":" withString:@"/"];
|
||||
url = [NSURL URLWithString:[NSString stringWithFormat:@"%@://%@/%@", method, convertedServer, convertedPath]];
|
||||
}
|
||||
}
|
||||
if(url) {
|
||||
[urls addObject:url];
|
||||
}
|
||||
}
|
||||
[playlistLoader willInsertURLs:urls origin:URLOriginExternal];
|
||||
[playlistLoader didInsertURLs:[playlistLoader addURLs:urls sort:YES] origin:URLOriginExternal];
|
||||
|
||||
NSDictionary *loadEntriesData = @{ @"entries": urls,
|
||||
@"sort": @(YES),
|
||||
@"origin": @(URLOriginExternal) };
|
||||
|
||||
[playlistController performSelectorInBackground:@selector(addURLsInBackground:) withObject:loadEntriesData];
|
||||
|
||||
[theApplication replyToOpenOrPrint:NSApplicationDelegateReplySuccess];
|
||||
}
|
||||
|
||||
|
@ -493,6 +578,10 @@ BOOL kAppControllerShuttingDown = NO;
|
|||
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"https://www.patreon.com/kode54"]];
|
||||
}
|
||||
|
||||
- (IBAction)privacyPolicy:(id)sender {
|
||||
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:NSLocalizedString(@"PrivacyPolicyURL", @"Privacy policy URL from Iubenda.")]];
|
||||
}
|
||||
|
||||
- (IBAction)feedback:(id)sender {
|
||||
NSString *version = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
|
||||
|
||||
|
@ -516,7 +605,7 @@ BOOL kAppControllerShuttingDown = NO;
|
|||
NSMutableDictionary *userDefaultsValuesDict = [NSMutableDictionary dictionary];
|
||||
|
||||
// Font defaults
|
||||
float fFontSize = [NSFont systemFontSizeForControlSize:NSControlSizeSmall];
|
||||
float fFontSize = [NSFont systemFontSizeForControlSize:NSControlSizeRegular];
|
||||
NSNumber *fontSize = @(fFontSize);
|
||||
[userDefaultsValuesDict setObject:fontSize forKey:@"fontSize"];
|
||||
|
||||
|
@ -548,9 +637,15 @@ BOOL kAppControllerShuttingDown = NO;
|
|||
NSData *barColor = [colorToValueTransformer reverseTransformedValue:[NSColor colorWithSRGBRed:1.0 green:0.5 blue:0 alpha:1.0]];
|
||||
NSData *dotColor = [colorToValueTransformer reverseTransformedValue:[NSColor systemRedColor]];
|
||||
|
||||
[userDefaultsValuesDict setObject:@(YES) forKey:@"spectrumSceneKit"];
|
||||
[userDefaultsValuesDict setObject:barColor forKey:@"spectrumBarColor"];
|
||||
[userDefaultsValuesDict setObject:dotColor forKey:@"spectrumDotColor"];
|
||||
|
||||
[userDefaultsValuesDict setObject:@(150.0) forKey:@"synthDefaultSeconds"];
|
||||
[userDefaultsValuesDict setObject:@(8.0) forKey:@"synthDefaultFadeSeconds"];
|
||||
[userDefaultsValuesDict setObject:@(2) forKey:@"synthDefaultLoopCount"];
|
||||
[userDefaultsValuesDict setObject:@(44100) forKey:@"synthSampleRate"];
|
||||
|
||||
// Register and sync defaults
|
||||
[[NSUserDefaults standardUserDefaults] registerDefaults:userDefaultsValuesDict];
|
||||
[[NSUserDefaults standardUserDefaults] synchronize];
|
||||
|
@ -712,4 +807,24 @@ BOOL kAppControllerShuttingDown = NO;
|
|||
}
|
||||
}
|
||||
|
||||
- (BOOL)pathSuggesterEmpty {
|
||||
return [playlistController pathSuggesterEmpty];
|
||||
}
|
||||
|
||||
+ (BOOL)globalPathSuggesterEmpty {
|
||||
return [kAppController pathSuggesterEmpty];
|
||||
}
|
||||
|
||||
- (void)showPathSuggester {
|
||||
[preferencesController showPathSuggester:self];
|
||||
}
|
||||
|
||||
+ (void)globalShowPathSuggester {
|
||||
[kAppController showPathSuggester];
|
||||
}
|
||||
|
||||
- (IBAction)checkForUpdates:(id)sender {
|
||||
[[SparkleBridge sharedStandardUpdaterController] checkForUpdates:[[NSApplication sharedApplication] delegate]];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -110,7 +110,7 @@ static NSString *getBadgeName(NSString *baseName, BOOL colorfulIcons) {
|
|||
[dockTile setContentView:imageView];
|
||||
|
||||
progressIndicator = [[NSProgressIndicator alloc] initWithFrame:NSMakeRect(0.0, 0.0, dockTile.size.width, 10.0)];
|
||||
[progressIndicator setStyle:NSProgressIndicatorBarStyle];
|
||||
[progressIndicator setStyle:NSProgressIndicatorStyleBar];
|
||||
[progressIndicator setIndeterminate:NO];
|
||||
[progressIndicator setBezeled:YES];
|
||||
[progressIndicator setMinValue:0];
|
||||
|
|
|
@ -565,19 +565,6 @@ NSDictionary *makeRGInfo(PlaylistEntry *pe) {
|
|||
|
||||
- (void)audioPlayer:(AudioPlayer *)player removeEqualizer:(AudioUnit)eq {
|
||||
if(eq == _eq) {
|
||||
OSStatus err;
|
||||
CFPropertyListRef classData;
|
||||
UInt32 size;
|
||||
|
||||
size = sizeof(classData);
|
||||
err = AudioUnitGetProperty(eq, kAudioUnitProperty_ClassInfo, kAudioUnitScope_Global, 0, &classData, &size);
|
||||
if(err == noErr) {
|
||||
CFPreferencesSetAppValue(CFSTR("GraphEQ_Preset"), classData, kCFPreferencesCurrentApplication);
|
||||
CFRelease(classData);
|
||||
}
|
||||
|
||||
CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication);
|
||||
|
||||
[equalizerWindowController setEQ:nil];
|
||||
|
||||
_eq = nil;
|
||||
|
@ -617,12 +604,15 @@ NSDictionary *makeRGInfo(PlaylistEntry *pe) {
|
|||
|
||||
// Delay the action until this function has returned to the audio thread
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_MSEC), dispatch_get_main_queue(), ^{
|
||||
[[FIRCrashlytics crashlytics] logWithFormat:@"Updating UI with track: %@", pe.url];
|
||||
if(pe) {
|
||||
[[FIRCrashlytics crashlytics] logWithFormat:@"Updating UI with track: %@", pe.url];
|
||||
}
|
||||
|
||||
[self->playlistController setCurrentEntry:pe];
|
||||
|
||||
if(self->_eq)
|
||||
if(pe && self->_eq) {
|
||||
equalizerApplyGenre(self->_eq, [pe genre]);
|
||||
}
|
||||
|
||||
self->lastPosition = -10;
|
||||
|
||||
|
@ -631,7 +621,9 @@ NSDictionary *makeRGInfo(PlaylistEntry *pe) {
|
|||
[self removeHDCD:nil];
|
||||
});
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:CogPlaybackDidBeginNotficiation object:pe];
|
||||
if(pe) {
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:CogPlaybackDidBeginNotficiation object:pe];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)audioPlayer:(AudioPlayer *)player didChangeStatus:(NSNumber *)s userInfo:(id)userInfo {
|
||||
|
@ -685,14 +677,6 @@ NSDictionary *makeRGInfo(PlaylistEntry *pe) {
|
|||
break;
|
||||
}
|
||||
|
||||
if(status == CogStatusStopped) {
|
||||
status = CogStatusStopping;
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
|
||||
if([self playbackStatus] == CogStatusStopping)
|
||||
[self setPlaybackStatus:CogStatusStopped];
|
||||
});
|
||||
}
|
||||
|
||||
[self setPlaybackStatus:status];
|
||||
// If we don't send it here, if we've stopped, then the NPIC will be stuck at the last file we played.
|
||||
[self sendMetaData];
|
||||
|
@ -725,10 +709,11 @@ NSDictionary *makeRGInfo(PlaylistEntry *pe) {
|
|||
[pe setMetadata:info];
|
||||
[playlistView refreshTrack:pe];
|
||||
// Delay the action until this function has returned to the audio thread
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_MSEC), dispatch_get_main_queue(), ^{
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 50 * NSEC_PER_MSEC), dispatch_get_main_queue(), ^{
|
||||
self->playlistController.currentEntry = pe;
|
||||
[self sendMetaData];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:CogPlaybackDidBeginNotficiation object:pe];
|
||||
});
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:CogPlaybackDidBeginNotficiation object:pe];
|
||||
}
|
||||
|
||||
- (void)audioPlayer:(AudioPlayer *)player reportPlayCountForTrack:(id)userInfo {
|
||||
|
|
|
@ -13,5 +13,6 @@
|
|||
}
|
||||
|
||||
+ (NSArray *)urlsForContainerURL:(NSURL *)url;
|
||||
+ (NSArray *)dependencyUrlsForContainerURL:(NSURL *)url;
|
||||
|
||||
@end
|
||||
|
|
|
@ -18,4 +18,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
+ (NSArray *)dependencyUrlsForContainerURL:(NSURL *)url {
|
||||
@autoreleasepool {
|
||||
return [[PluginController sharedPluginController] dependencyUrlsForContainerURL:url];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#import <CogAudio/Semaphore.h>
|
||||
#import <CogAudio/CogSemaphore.h>
|
||||
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <AudioToolbox/AudioToolbox.h>
|
||||
|
@ -16,13 +16,7 @@
|
|||
#import <CoreAudio/CoreAudio.h>
|
||||
#import <CoreAudio/CoreAudioTypes.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
#import <atomic>
|
||||
using std::atomic_int;
|
||||
using std::atomic_bool;
|
||||
#else
|
||||
#import <stdatomic.h>
|
||||
#endif
|
||||
|
||||
@class BufferChain;
|
||||
@class OutputNode;
|
||||
|
@ -39,6 +33,8 @@ using std::atomic_bool;
|
|||
id nextStreamUserInfo;
|
||||
NSDictionary *nextStreamRGInfo;
|
||||
|
||||
id previousUserInfo; // Track currently last heard track for play counts
|
||||
|
||||
id delegate;
|
||||
|
||||
BOOL outputLaunched;
|
||||
|
@ -116,6 +112,7 @@ using std::atomic_bool;
|
|||
- (void)setShouldContinue:(BOOL)s;
|
||||
//- (BufferChain *)bufferChain;
|
||||
- (void)launchOutputThread;
|
||||
- (BOOL)selectNextBuffer;
|
||||
- (void)endOfInputPlayed;
|
||||
- (void)reportPlayCount;
|
||||
- (void)sendDelegateMethod:(SEL)selector withVoid:(void *)obj waitUntilDone:(BOOL)wait;
|
||||
|
|
|
@ -62,9 +62,11 @@
|
|||
[self waitUntilCallbacksExit];
|
||||
if(output) {
|
||||
[output setShouldContinue:NO];
|
||||
output = nil;
|
||||
[output close];
|
||||
}
|
||||
if(!output) {
|
||||
output = [[OutputNode alloc] initWithController:self previous:nil];
|
||||
}
|
||||
output = [[OutputNode alloc] initWithController:self previous:nil];
|
||||
[output setup];
|
||||
[output setVolume:volume];
|
||||
@synchronized(chainQueue) {
|
||||
|
@ -83,11 +85,15 @@
|
|||
bufferChain = [[BufferChain alloc] initWithController:self];
|
||||
[self notifyStreamChanged:userInfo];
|
||||
|
||||
while(![bufferChain open:url withOutputFormat:[output format] withOutputConfig:[output config] withUserInfo:userInfo withRGInfo:rgi]) {
|
||||
while(![bufferChain open:url withUserInfo:userInfo withRGInfo:rgi]) {
|
||||
bufferChain = nil;
|
||||
|
||||
[self requestNextStream:userInfo];
|
||||
|
||||
if([nextStream isEqualTo:url]) {
|
||||
return;
|
||||
}
|
||||
|
||||
url = nextStream;
|
||||
if(url == nil) {
|
||||
return;
|
||||
|
@ -111,6 +117,7 @@
|
|||
outputLaunched = NO;
|
||||
startedPaused = paused;
|
||||
initialBufferFilled = NO;
|
||||
previousUserInfo = userInfo;
|
||||
|
||||
[bufferChain launchThreads];
|
||||
|
||||
|
@ -133,6 +140,10 @@
|
|||
bufferChain = nil;
|
||||
}
|
||||
}
|
||||
if(output) {
|
||||
[output setShouldContinue:NO];
|
||||
[output close];
|
||||
}
|
||||
output = nil;
|
||||
}
|
||||
|
||||
|
@ -219,7 +230,7 @@
|
|||
}
|
||||
|
||||
- (void)restartPlaybackAtCurrentPosition {
|
||||
[self sendDelegateMethod:@selector(audioPlayer:restartPlaybackAtCurrentPosition:) withObject:[bufferChain userInfo] waitUntilDone:NO];
|
||||
[self sendDelegateMethod:@selector(audioPlayer:restartPlaybackAtCurrentPosition:) withObject:previousUserInfo waitUntilDone:NO];
|
||||
}
|
||||
|
||||
- (void)pushInfo:(NSDictionary *)info toTrack:(id)userInfo {
|
||||
|
@ -290,6 +301,8 @@
|
|||
|
||||
- (BOOL)endOfInputReached:(BufferChain *)sender // Sender is a BufferChain
|
||||
{
|
||||
previousUserInfo = [sender userInfo];
|
||||
|
||||
BufferChain *newChain = nil;
|
||||
|
||||
if(atomic_load_explicit(&resettingNow, memory_order_relaxed))
|
||||
|
@ -302,6 +315,8 @@
|
|||
// if there's already one at the head of chainQueue... r-r-right?
|
||||
for(BufferChain *chain in chainQueue) {
|
||||
if([chain isRunning]) {
|
||||
if(output)
|
||||
[output setShouldPlayOutBuffer:YES];
|
||||
atomic_fetch_sub(&refCount, 1);
|
||||
return YES;
|
||||
}
|
||||
|
@ -325,6 +340,8 @@
|
|||
while(duration >= 30.0 && shouldContinue) {
|
||||
[semaphore wait];
|
||||
if(atomic_load_explicit(&resettingNow, memory_order_relaxed)) {
|
||||
if(output)
|
||||
[output setShouldPlayOutBuffer:YES];
|
||||
atomic_fetch_sub(&refCount, 1);
|
||||
return YES;
|
||||
}
|
||||
|
@ -344,83 +361,148 @@
|
|||
[self requestNextStream:nextStreamUserInfo];
|
||||
|
||||
if(!nextStream) {
|
||||
if(output)
|
||||
[output setShouldPlayOutBuffer:YES];
|
||||
atomic_fetch_sub(&refCount, 1);
|
||||
return YES;
|
||||
}
|
||||
|
||||
BufferChain *lastChain;
|
||||
|
||||
@synchronized(chainQueue) {
|
||||
newChain = [[BufferChain alloc] initWithController:self];
|
||||
|
||||
endOfInputReached = YES;
|
||||
|
||||
BufferChain *lastChain = [chainQueue lastObject];
|
||||
lastChain = [chainQueue lastObject];
|
||||
if(lastChain == nil) {
|
||||
lastChain = bufferChain;
|
||||
}
|
||||
}
|
||||
|
||||
BOOL pathsEqual = NO;
|
||||
BOOL pathsEqual = NO;
|
||||
|
||||
if([nextStream isFileURL] && [[lastChain streamURL] isFileURL]) {
|
||||
NSString *unixPathNext = [nextStream path];
|
||||
NSString *unixPathPrev = [[lastChain streamURL] path];
|
||||
if([nextStream isFileURL] && [[lastChain streamURL] isFileURL]) {
|
||||
NSString *unixPathNext = [nextStream path];
|
||||
NSString *unixPathPrev = [[lastChain streamURL] path];
|
||||
|
||||
if([unixPathNext isEqualToString:unixPathPrev])
|
||||
pathsEqual = YES;
|
||||
}
|
||||
if([unixPathNext isEqualToString:unixPathPrev])
|
||||
pathsEqual = YES;
|
||||
}
|
||||
|
||||
if(pathsEqual || ([[nextStream scheme] isEqualToString:[[lastChain streamURL] scheme]] && (([nextStream host] == nil && [[lastChain streamURL] host] == nil) || [[nextStream host] isEqualToString:[[lastChain streamURL] host]]) && [[nextStream path] isEqualToString:[[lastChain streamURL] path]])) {
|
||||
if([lastChain setTrack:nextStream] && [newChain openWithInput:[lastChain inputNode] withOutputFormat:[output format] withOutputConfig:[output config] withUserInfo:nextStreamUserInfo withRGInfo:nextStreamRGInfo]) {
|
||||
[newChain setStreamURL:nextStream];
|
||||
if(pathsEqual || ([[nextStream scheme] isEqualToString:[[lastChain streamURL] scheme]] && (([nextStream host] == nil && [[lastChain streamURL] host] == nil) || [[nextStream host] isEqualToString:[[lastChain streamURL] host]]) && [[nextStream path] isEqualToString:[[lastChain streamURL] path]])) {
|
||||
if([lastChain setTrack:nextStream] && [newChain openWithInput:[lastChain inputNode] withUserInfo:nextStreamUserInfo withRGInfo:nextStreamRGInfo]) {
|
||||
[newChain setStreamURL:nextStream];
|
||||
|
||||
@synchronized(chainQueue) {
|
||||
[self addChainToQueue:newChain];
|
||||
DLog(@"TRACK SET!!! %@", newChain);
|
||||
// Keep on-playin
|
||||
newChain = nil;
|
||||
|
||||
atomic_fetch_sub(&refCount, 1);
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
lastChain = nil;
|
||||
|
||||
while(shouldContinue && ![newChain open:nextStream withOutputFormat:[output format] withOutputConfig:[output config] withUserInfo:nextStreamUserInfo withRGInfo:nextStreamRGInfo]) {
|
||||
if(nextStream == nil) {
|
||||
newChain = nil;
|
||||
atomic_fetch_sub(&refCount, 1);
|
||||
return YES;
|
||||
}
|
||||
|
||||
DLog(@"TRACK SET!!! %@", newChain);
|
||||
// Keep on-playin
|
||||
newChain = nil;
|
||||
[self requestNextStream:nextStreamUserInfo];
|
||||
|
||||
newChain = [[BufferChain alloc] initWithController:self];
|
||||
atomic_fetch_sub(&refCount, 1);
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
[self addChainToQueue:newChain];
|
||||
lastChain = nil;
|
||||
|
||||
NSURL *url = nextStream;
|
||||
|
||||
while(shouldContinue && ![newChain open:url withUserInfo:nextStreamUserInfo withRGInfo:nextStreamRGInfo]) {
|
||||
if(nextStream == nil) {
|
||||
newChain = nil;
|
||||
if(output)
|
||||
[output setShouldPlayOutBuffer:YES];
|
||||
atomic_fetch_sub(&refCount, 1);
|
||||
return YES;
|
||||
}
|
||||
|
||||
newChain = nil;
|
||||
[self requestNextStream:nextStreamUserInfo];
|
||||
|
||||
// I'm stupid and can't hold too much stuff in my head all at once, so writing it here.
|
||||
//
|
||||
// Once we get here:
|
||||
// - buffer chain for previous stream finished reading
|
||||
// - there are (probably) some bytes of the previous stream in the output buffer which haven't been played
|
||||
// (by output node) yet
|
||||
// - self.bufferChain == previous playlist entry's buffer chain
|
||||
// - self.nextStream == next playlist entry's URL
|
||||
// - self.nextStreamUserInfo == next playlist entry
|
||||
// - head of chainQueue is the buffer chain for the next entry (which has launched its threads already)
|
||||
if([nextStream isEqualTo:url]) {
|
||||
newChain = nil;
|
||||
if(output)
|
||||
[output setShouldPlayOutBuffer:YES];
|
||||
atomic_fetch_sub(&refCount, 1);
|
||||
return YES;
|
||||
}
|
||||
|
||||
url = nextStream;
|
||||
|
||||
newChain = [[BufferChain alloc] initWithController:self];
|
||||
}
|
||||
|
||||
@synchronized(chainQueue) {
|
||||
[self addChainToQueue:newChain];
|
||||
}
|
||||
|
||||
newChain = nil;
|
||||
|
||||
// I'm stupid and can't hold too much stuff in my head all at once, so writing it here.
|
||||
//
|
||||
// Once we get here:
|
||||
// - buffer chain for previous stream finished reading
|
||||
// - there are (probably) some bytes of the previous stream in the output buffer which haven't been played
|
||||
// (by output node) yet
|
||||
// - self.bufferChain == previous playlist entry's buffer chain
|
||||
// - self.nextStream == next playlist entry's URL
|
||||
// - self.nextStreamUserInfo == next playlist entry
|
||||
// - head of chainQueue is the buffer chain for the next entry (which has launched its threads already)
|
||||
|
||||
if(output)
|
||||
[output setShouldPlayOutBuffer:YES];
|
||||
|
||||
atomic_fetch_sub(&refCount, 1);
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)reportPlayCount {
|
||||
if(bufferChain) {
|
||||
[self reportPlayCountForTrack:[bufferChain userInfo]];
|
||||
[self reportPlayCountForTrack:previousUserInfo];
|
||||
}
|
||||
|
||||
- (BOOL)selectNextBuffer {
|
||||
BOOL signalStopped = NO;
|
||||
do {
|
||||
@synchronized(chainQueue) {
|
||||
endOfInputReached = NO;
|
||||
|
||||
if([chainQueue count] <= 0) {
|
||||
// End of playlist
|
||||
signalStopped = YES;
|
||||
break;
|
||||
}
|
||||
|
||||
bufferChain = nil;
|
||||
bufferChain = [chainQueue objectAtIndex:0];
|
||||
|
||||
[chainQueue removeObjectAtIndex:0];
|
||||
DLog(@"New!!! %@ %@", bufferChain, [[bufferChain inputNode] decoder]);
|
||||
|
||||
[semaphore signal];
|
||||
}
|
||||
} while(0);
|
||||
|
||||
if(signalStopped) {
|
||||
double latency = 0;
|
||||
if(output) latency = [output latency];
|
||||
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, latency * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
|
||||
[self stop];
|
||||
|
||||
self->bufferChain = nil;
|
||||
|
||||
[self notifyPlaybackStopped:nil];
|
||||
});
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
[output setEndOfStream:NO];
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)endOfInputPlayed {
|
||||
|
@ -428,32 +510,8 @@
|
|||
// - the buffer chain for the next playlist entry (started in endOfInputReached) have been working for some time
|
||||
// already, so that there is some decoded and converted data to play
|
||||
// - the buffer chain for the next entry is the first item in chainQueue
|
||||
|
||||
@synchronized(chainQueue) {
|
||||
endOfInputReached = NO;
|
||||
|
||||
if([chainQueue count] <= 0) {
|
||||
// End of playlist
|
||||
[self stop];
|
||||
|
||||
bufferChain = nil;
|
||||
|
||||
[self notifyPlaybackStopped:nil];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
bufferChain = nil;
|
||||
bufferChain = [chainQueue objectAtIndex:0];
|
||||
|
||||
[chainQueue removeObjectAtIndex:0];
|
||||
DLog(@"New!!! %@ %@", bufferChain, [[bufferChain inputNode] decoder]);
|
||||
|
||||
[semaphore signal];
|
||||
}
|
||||
|
||||
[self notifyStreamChanged:[bufferChain userInfo]];
|
||||
[output setEndOfStream:NO];
|
||||
previousUserInfo = [bufferChain userInfo];
|
||||
[self notifyStreamChanged:previousUserInfo];
|
||||
}
|
||||
|
||||
- (BOOL)chainQueueHasTracks {
|
||||
|
|
|
@ -67,6 +67,7 @@ enum {
|
|||
uint32_t channelConfig;
|
||||
BOOL formatAssigned;
|
||||
BOOL lossless;
|
||||
BOOL hdcd;
|
||||
}
|
||||
|
||||
@property AudioStreamBasicDescription format;
|
||||
|
@ -80,17 +81,23 @@ enum {
|
|||
+ (uint32_t)findChannelIndex:(uint32_t)flag;
|
||||
|
||||
- (id)init;
|
||||
- (id)initWithProperties:(NSDictionary *)properties;
|
||||
|
||||
- (void)assignSamples:(const void *)data frameCount:(size_t)count;
|
||||
- (void)assignData:(NSData *)data;
|
||||
|
||||
- (NSData *)removeSamples:(size_t)frameCount;
|
||||
|
||||
- (BOOL)isEmpty;
|
||||
|
||||
- (size_t)frameCount;
|
||||
- (void)setFrameCount:(size_t)count; // For truncation only
|
||||
|
||||
- (double)duration;
|
||||
|
||||
- (BOOL)isHDCD;
|
||||
- (void)setHDCD;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
#import "AudioChunk.h"
|
||||
|
||||
#import "CoreAudioUtils.h"
|
||||
|
||||
@implementation AudioChunk
|
||||
|
||||
- (id)init {
|
||||
|
@ -16,6 +18,19 @@
|
|||
chunkData = [[NSMutableData alloc] init];
|
||||
formatAssigned = NO;
|
||||
lossless = NO;
|
||||
hdcd = NO;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithProperties:(NSDictionary *)properties {
|
||||
self = [super init];
|
||||
|
||||
if(self) {
|
||||
chunkData = [[NSMutableData alloc] init];
|
||||
[self setFormat:propertiesToASBD(properties)];
|
||||
lossless = [[properties objectForKey:@"encoding"] isEqualToString:@"lossless"];
|
||||
}
|
||||
|
||||
return self;
|
||||
|
@ -133,13 +148,19 @@ static const uint32_t AudioChannelConfigTable[] = {
|
|||
}
|
||||
}
|
||||
|
||||
- (void)assignData:(NSData *)data {
|
||||
[chunkData appendData:data];
|
||||
}
|
||||
|
||||
- (NSData *)removeSamples:(size_t)frameCount {
|
||||
if(formatAssigned) {
|
||||
const size_t bytesPerPacket = format.mBytesPerPacket;
|
||||
const size_t byteCount = bytesPerPacket * frameCount;
|
||||
NSData *ret = [chunkData subdataWithRange:NSMakeRange(0, byteCount)];
|
||||
[chunkData replaceBytesInRange:NSMakeRange(0, byteCount) withBytes:NULL length:0];
|
||||
return ret;
|
||||
@autoreleasepool {
|
||||
const size_t bytesPerPacket = format.mBytesPerPacket;
|
||||
const size_t byteCount = bytesPerPacket * frameCount;
|
||||
NSData *ret = [chunkData subdataWithRange:NSMakeRange(0, byteCount)];
|
||||
[chunkData replaceBytesInRange:NSMakeRange(0, byteCount) withBytes:NULL length:0];
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
return [NSData data];
|
||||
}
|
||||
|
@ -156,6 +177,16 @@ static const uint32_t AudioChannelConfigTable[] = {
|
|||
return 0;
|
||||
}
|
||||
|
||||
- (void)setFrameCount:(size_t)count {
|
||||
if(formatAssigned) {
|
||||
count *= format.mBytesPerPacket;
|
||||
size_t currentLength = [chunkData length];
|
||||
if(count < currentLength) {
|
||||
[chunkData replaceBytesInRange:NSMakeRange(count, currentLength - count) withBytes:NULL length:0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (double)duration {
|
||||
if(formatAssigned) {
|
||||
const size_t bytesPerPacket = format.mBytesPerPacket;
|
||||
|
@ -165,4 +196,12 @@ static const uint32_t AudioChannelConfigTable[] = {
|
|||
return 0.0;
|
||||
}
|
||||
|
||||
- (BOOL)isHDCD {
|
||||
return hdcd;
|
||||
}
|
||||
|
||||
- (void)setHDCD {
|
||||
hdcd = YES;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -28,15 +28,13 @@
|
|||
- (id)initWithController:(id)c;
|
||||
- (void)buildChain;
|
||||
|
||||
- (BOOL)open:(NSURL *)url withOutputFormat:(AudioStreamBasicDescription)outputFormat withOutputConfig:(uint32_t)outputConfig withUserInfo:(id)userInfo withRGInfo:(NSDictionary *)rgi;
|
||||
- (BOOL)open:(NSURL *)url withUserInfo:(id)userInfo withRGInfo:(NSDictionary *)rgi;
|
||||
|
||||
// Used when changing tracks to reuse the same decoder
|
||||
- (BOOL)openWithInput:(InputNode *)i withOutputFormat:(AudioStreamBasicDescription)outputFormat withOutputConfig:(uint32_t)outputConfig withUserInfo:(id)userInfo withRGInfo:(NSDictionary *)rgi;
|
||||
- (BOOL)openWithInput:(InputNode *)i withUserInfo:(id)userInfo withRGInfo:(NSDictionary *)rgi;
|
||||
|
||||
// Used when resetting the decoder on seek
|
||||
- (BOOL)openWithDecoder:(id<CogDecoder>)decoder
|
||||
withOutputFormat:(AudioStreamBasicDescription)outputFormat
|
||||
withOutputConfig:(uint32_t)outputConfig
|
||||
withUserInfo:(id)userInfo
|
||||
withRGInfo:(NSDictionary *)rgi;
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
finalNode = converterNode;
|
||||
}
|
||||
|
||||
- (BOOL)open:(NSURL *)url withOutputFormat:(AudioStreamBasicDescription)outputFormat withOutputConfig:(uint32_t)outputConfig withUserInfo:(id)userInfo withRGInfo:(NSDictionary *)rgi {
|
||||
- (BOOL)open:(NSURL *)url withUserInfo:(id)userInfo withRGInfo:(NSDictionary *)rgi {
|
||||
[self setStreamURL:url];
|
||||
[self setUserInfo:userInfo];
|
||||
|
||||
|
@ -66,13 +66,7 @@
|
|||
if([properties valueForKey:@"channelConfig"])
|
||||
inputChannelConfig = [[properties valueForKey:@"channelConfig"] unsignedIntValue];
|
||||
|
||||
outputFormat.mChannelsPerFrame = inputFormat.mChannelsPerFrame;
|
||||
outputFormat.mBytesPerFrame = ((outputFormat.mBitsPerChannel + 7) / 8) * outputFormat.mChannelsPerFrame;
|
||||
outputFormat.mBytesPerPacket = outputFormat.mBytesPerFrame * outputFormat.mFramesPerPacket;
|
||||
|
||||
outputConfig = inputChannelConfig;
|
||||
|
||||
if(![converterNode setupWithInputFormat:inputFormat withInputConfig:inputChannelConfig outputFormat:outputFormat outputConfig:outputConfig isLossless:[[properties valueForKey:@"encoding"] isEqualToString:@"lossless"]])
|
||||
if(![converterNode setupWithInputFormat:inputFormat withInputConfig:inputChannelConfig isLossless:[[properties valueForKey:@"encoding"] isEqualToString:@"lossless"]])
|
||||
return NO;
|
||||
|
||||
[self setRGInfo:rgi];
|
||||
|
@ -82,7 +76,7 @@
|
|||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)openWithInput:(InputNode *)i withOutputFormat:(AudioStreamBasicDescription)outputFormat withOutputConfig:(uint32_t)outputConfig withUserInfo:(id)userInfo withRGInfo:(NSDictionary *)rgi {
|
||||
- (BOOL)openWithInput:(InputNode *)i withUserInfo:(id)userInfo withRGInfo:(NSDictionary *)rgi {
|
||||
DLog(@"New buffer chain!");
|
||||
[self setUserInfo:userInfo];
|
||||
[self buildChain];
|
||||
|
@ -97,14 +91,8 @@
|
|||
if([properties valueForKey:@"channelConfig"])
|
||||
inputChannelConfig = [[properties valueForKey:@"channelConfig"] unsignedIntValue];
|
||||
|
||||
outputFormat.mChannelsPerFrame = inputFormat.mChannelsPerFrame;
|
||||
outputFormat.mBytesPerFrame = ((outputFormat.mBitsPerChannel + 7) / 8) * outputFormat.mChannelsPerFrame;
|
||||
outputFormat.mBytesPerPacket = outputFormat.mBytesPerFrame * outputFormat.mFramesPerPacket;
|
||||
|
||||
outputConfig = inputChannelConfig;
|
||||
|
||||
DLog(@"Input Properties: %@", properties);
|
||||
if(![converterNode setupWithInputFormat:inputFormat withInputConfig:inputChannelConfig outputFormat:outputFormat outputConfig:outputConfig isLossless:[[properties objectForKey:@"encoding"] isEqualToString:@"lossless"]])
|
||||
if(![converterNode setupWithInputFormat:inputFormat withInputConfig:inputChannelConfig isLossless:[[properties objectForKey:@"encoding"] isEqualToString:@"lossless"]])
|
||||
return NO;
|
||||
|
||||
[self setRGInfo:rgi];
|
||||
|
@ -113,8 +101,6 @@
|
|||
}
|
||||
|
||||
- (BOOL)openWithDecoder:(id<CogDecoder>)decoder
|
||||
withOutputFormat:(AudioStreamBasicDescription)outputFormat
|
||||
withOutputConfig:(uint32_t)outputConfig
|
||||
withUserInfo:(id)userInfo
|
||||
withRGInfo:(NSDictionary *)rgi;
|
||||
{
|
||||
|
@ -134,13 +120,7 @@
|
|||
if([properties valueForKey:@"channelConfig"])
|
||||
inputChannelConfig = [[properties valueForKey:@"channelConfig"] unsignedIntValue];
|
||||
|
||||
outputFormat.mChannelsPerFrame = inputFormat.mChannelsPerFrame;
|
||||
outputFormat.mBytesPerFrame = ((outputFormat.mBitsPerChannel + 7) / 8) * outputFormat.mChannelsPerFrame;
|
||||
outputFormat.mBytesPerPacket = outputFormat.mBytesPerFrame * outputFormat.mFramesPerPacket;
|
||||
|
||||
outputConfig = inputChannelConfig;
|
||||
|
||||
if(![converterNode setupWithInputFormat:inputFormat withInputConfig:inputChannelConfig outputFormat:outputFormat outputConfig:outputConfig isLossless:[[properties objectForKey:@"encoding"] isEqualToString:@"lossless"]])
|
||||
if(![converterNode setupWithInputFormat:inputFormat withInputConfig:inputChannelConfig isLossless:[[properties objectForKey:@"encoding"] isEqualToString:@"lossless"]])
|
||||
return NO;
|
||||
|
||||
[self setRGInfo:rgi];
|
||||
|
@ -176,7 +156,8 @@
|
|||
[inputNode setShouldContinue:NO];
|
||||
[[inputNode exitAtTheEndOfTheStream] signal];
|
||||
[[inputNode semaphore] signal];
|
||||
[[inputNode exitAtTheEndOfTheStream] wait]; // wait for decoder to be closed (see InputNode's -(void)process )
|
||||
if(![inputNode threadExited])
|
||||
[[inputNode exitAtTheEndOfTheStream] wait]; // wait for decoder to be closed (see InputNode's -(void)process )
|
||||
|
||||
DLog(@"Bufferchain dealloc");
|
||||
}
|
||||
|
|
|
@ -10,10 +10,12 @@
|
|||
|
||||
#import "AudioChunk.h"
|
||||
|
||||
#import "Semaphore.h"
|
||||
#import <CogAudio/CogSemaphore.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
#define DSD_DECIMATE 1
|
||||
|
||||
@interface ChunkList : NSObject {
|
||||
NSMutableArray<AudioChunk *> *chunkList;
|
||||
double listDuration;
|
||||
|
@ -23,6 +25,29 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
BOOL inRemover;
|
||||
BOOL inPeeker;
|
||||
BOOL stopping;
|
||||
|
||||
// For format converter
|
||||
void *inputBuffer;
|
||||
size_t inputBufferSize;
|
||||
|
||||
#if DSD_DECIMATE
|
||||
void **dsd2pcm;
|
||||
size_t dsd2pcmCount;
|
||||
int dsd2pcmLatency;
|
||||
#endif
|
||||
|
||||
void *hdcd_decoder;
|
||||
|
||||
BOOL formatRead;
|
||||
|
||||
AudioStreamBasicDescription inputFormat;
|
||||
AudioStreamBasicDescription floatFormat;
|
||||
|
||||
uint32_t inputChannelConfig;
|
||||
BOOL inputLossless;
|
||||
|
||||
uint8_t *tempData;
|
||||
size_t tempDataSize;
|
||||
}
|
||||
|
||||
@property(readonly) double listDuration;
|
||||
|
@ -38,6 +63,8 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
- (void)addChunk:(AudioChunk *)chunk;
|
||||
- (AudioChunk *)removeSamples:(size_t)maxFrameCount;
|
||||
|
||||
- (AudioChunk *)removeSamplesAsFloat32:(size_t)maxFrameCount;
|
||||
|
||||
- (BOOL)peekFormat:(nonnull AudioStreamBasicDescription *)format channelConfig:(nonnull uint32_t *)config;
|
||||
|
||||
@end
|
||||
|
|
|
@ -5,8 +5,357 @@
|
|||
// Created by Christopher Snowhill on 2/5/22.
|
||||
//
|
||||
|
||||
#import <Accelerate/Accelerate.h>
|
||||
|
||||
#import "ChunkList.h"
|
||||
|
||||
#import "hdcd_decode2.h"
|
||||
|
||||
#if !DSD_DECIMATE
|
||||
#import "dsd2float.h"
|
||||
#endif
|
||||
|
||||
#ifdef _DEBUG
|
||||
#import "BadSampleCleaner.h"
|
||||
#endif
|
||||
|
||||
#if DSD_DECIMATE
|
||||
/**
|
||||
* DSD 2 PCM: Stage 1:
|
||||
* Decimate by factor 8
|
||||
* (one byte (8 samples) -> one float sample)
|
||||
* The bits are processed from least signicifant to most signicicant.
|
||||
* @author Sebastian Gesemann
|
||||
*/
|
||||
|
||||
#define dsd2pcm_FILTER_COEFFS_COUNT 64
|
||||
static const float dsd2pcm_FILTER_COEFFS[64] = {
|
||||
0.09712411121659f, 0.09613438994044f, 0.09417884216316f, 0.09130441727307f,
|
||||
0.08757947648990f, 0.08309142055179f, 0.07794369263673f, 0.07225228745463f,
|
||||
0.06614191680338f, 0.05974199351302f, 0.05318259916599f, 0.04659059631228f,
|
||||
0.04008603356890f, 0.03377897290478f, 0.02776684382775f, 0.02213240062966f,
|
||||
0.01694232798846f, 0.01224650881275f, 0.00807793792573f, 0.00445323755944f,
|
||||
0.00137370697215f, -0.00117318019994f, -0.00321193033831f, -0.00477694265140f,
|
||||
-0.00591028841335f, -0.00665946056286f, -0.00707518873201f, -0.00720940203988f,
|
||||
-0.00711340642819f, -0.00683632603227f, -0.00642384017266f, -0.00591723006715f,
|
||||
-0.00535273320457f, -0.00476118922548f, -0.00416794965654f, -0.00359301524813f,
|
||||
-0.00305135909510f, -0.00255339111833f, -0.00210551956895f, -0.00171076760278f,
|
||||
-0.00136940723130f, -0.00107957856005f, -0.00083786862365f, -0.00063983084245f,
|
||||
-0.00048043272086f, -0.00035442550015f, -0.00025663481039f, -0.00018217573430f,
|
||||
-0.00012659899635f, -0.00008597726991f, -0.00005694188820f, -0.00003668060332f,
|
||||
-0.00002290670286f, -0.00001380895679f, -0.00000799057558f, -0.00000440385083f,
|
||||
-0.00000228567089f, -0.00000109760778f, -0.00000047286430f, -0.00000017129652f,
|
||||
-0.00000004282776f, 0.00000000119422f, 0.00000000949179f, 0.00000000747450f
|
||||
};
|
||||
|
||||
struct dsd2pcm_state {
|
||||
/*
|
||||
* This is the 2nd half of an even order symmetric FIR
|
||||
* lowpass filter (to be used on a signal sampled at 44100*64 Hz)
|
||||
* Passband is 0-24 kHz (ripples +/- 0.025 dB)
|
||||
* Stopband starts at 176.4 kHz (rejection: 170 dB)
|
||||
* The overall gain is 2.0
|
||||
*/
|
||||
|
||||
/* These remain constant for the duration */
|
||||
int FILT_LOOKUP_PARTS;
|
||||
float *FILT_LOOKUP_TABLE;
|
||||
uint8_t *REVERSE_BITS;
|
||||
int FIFO_LENGTH;
|
||||
int FIFO_OFS_MASK;
|
||||
|
||||
/* These are altered */
|
||||
int *fifo;
|
||||
int fpos;
|
||||
};
|
||||
|
||||
static void dsd2pcm_free(void *);
|
||||
static void dsd2pcm_reset(void *);
|
||||
|
||||
static void *dsd2pcm_alloc(void) {
|
||||
struct dsd2pcm_state *state = (struct dsd2pcm_state *)calloc(1, sizeof(struct dsd2pcm_state));
|
||||
|
||||
float *FILT_LOOKUP_TABLE;
|
||||
double *temp;
|
||||
uint8_t *REVERSE_BITS;
|
||||
|
||||
if(!state)
|
||||
return NULL;
|
||||
|
||||
state->FILT_LOOKUP_PARTS = (dsd2pcm_FILTER_COEFFS_COUNT + 7) / 8;
|
||||
const int FILT_LOOKUP_PARTS = state->FILT_LOOKUP_PARTS;
|
||||
// The current 128 tap FIR leads to an 8 KB lookup table
|
||||
state->FILT_LOOKUP_TABLE = (float *)calloc(sizeof(float), FILT_LOOKUP_PARTS << 8);
|
||||
if(!state->FILT_LOOKUP_TABLE)
|
||||
goto fail;
|
||||
FILT_LOOKUP_TABLE = state->FILT_LOOKUP_TABLE;
|
||||
temp = (double *)calloc(sizeof(double), 0x100);
|
||||
if(!temp)
|
||||
goto fail;
|
||||
for(int part = 0, sofs = 0, dofs = 0; part < FILT_LOOKUP_PARTS;) {
|
||||
memset(temp, 0, 0x100 * sizeof(double));
|
||||
for(int bit = 0, bitmask = 0x80; bit < 8 && sofs + bit < dsd2pcm_FILTER_COEFFS_COUNT;) {
|
||||
double coeff = dsd2pcm_FILTER_COEFFS[sofs + bit];
|
||||
for(int bite = 0; bite < 0x100; bite++) {
|
||||
if((bite & bitmask) == 0) {
|
||||
temp[bite] -= coeff;
|
||||
} else {
|
||||
temp[bite] += coeff;
|
||||
}
|
||||
}
|
||||
bit++;
|
||||
bitmask >>= 1;
|
||||
}
|
||||
for(int s = 0; s < 0x100;) {
|
||||
FILT_LOOKUP_TABLE[dofs++] = (float)temp[s++];
|
||||
}
|
||||
part++;
|
||||
sofs += 8;
|
||||
}
|
||||
free(temp);
|
||||
{ // calculate FIFO stuff
|
||||
int k = 1;
|
||||
while(k < FILT_LOOKUP_PARTS * 2) k <<= 1;
|
||||
state->FIFO_LENGTH = k;
|
||||
state->FIFO_OFS_MASK = k - 1;
|
||||
}
|
||||
state->REVERSE_BITS = (uint8_t *)calloc(1, 0x100);
|
||||
if(!state->REVERSE_BITS)
|
||||
goto fail;
|
||||
REVERSE_BITS = state->REVERSE_BITS;
|
||||
for(int i = 0, j = 0; i < 0x100; i++) {
|
||||
REVERSE_BITS[i] = (uint8_t)j;
|
||||
// "reverse-increment" of j
|
||||
for(int bitmask = 0x80;;) {
|
||||
if(((j ^= bitmask) & bitmask) != 0) break;
|
||||
if(bitmask == 1) break;
|
||||
bitmask >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
state->fifo = (int *)calloc(sizeof(int), state->FIFO_LENGTH);
|
||||
if(!state->fifo)
|
||||
goto fail;
|
||||
|
||||
dsd2pcm_reset(state);
|
||||
|
||||
return (void *)state;
|
||||
|
||||
fail:
|
||||
dsd2pcm_free(state);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void *dsd2pcm_dup(void *_state) {
|
||||
struct dsd2pcm_state *state = (struct dsd2pcm_state *)_state;
|
||||
if(state) {
|
||||
struct dsd2pcm_state *newstate = (struct dsd2pcm_state *)calloc(1, sizeof(struct dsd2pcm_state));
|
||||
if(newstate) {
|
||||
newstate->FILT_LOOKUP_PARTS = state->FILT_LOOKUP_PARTS;
|
||||
newstate->FIFO_LENGTH = state->FIFO_LENGTH;
|
||||
newstate->FIFO_OFS_MASK = state->FIFO_OFS_MASK;
|
||||
newstate->fpos = state->fpos;
|
||||
|
||||
newstate->FILT_LOOKUP_TABLE = (float *)calloc(sizeof(float), state->FILT_LOOKUP_PARTS << 8);
|
||||
if(!newstate->FILT_LOOKUP_TABLE)
|
||||
goto fail;
|
||||
|
||||
memcpy(newstate->FILT_LOOKUP_TABLE, state->FILT_LOOKUP_TABLE, sizeof(float) * (state->FILT_LOOKUP_PARTS << 8));
|
||||
|
||||
newstate->REVERSE_BITS = (uint8_t *)calloc(1, 0x100);
|
||||
if(!newstate->REVERSE_BITS)
|
||||
goto fail;
|
||||
|
||||
memcpy(newstate->REVERSE_BITS, state->REVERSE_BITS, 0x100);
|
||||
|
||||
newstate->fifo = (int *)calloc(sizeof(int), state->FIFO_LENGTH);
|
||||
if(!newstate->fifo)
|
||||
goto fail;
|
||||
|
||||
memcpy(newstate->fifo, state->fifo, sizeof(int) * state->FIFO_LENGTH);
|
||||
|
||||
return (void *)newstate;
|
||||
}
|
||||
|
||||
fail:
|
||||
dsd2pcm_free(newstate);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void dsd2pcm_free(void *_state) {
|
||||
struct dsd2pcm_state *state = (struct dsd2pcm_state *)_state;
|
||||
if(state) {
|
||||
free(state->fifo);
|
||||
free(state->REVERSE_BITS);
|
||||
free(state->FILT_LOOKUP_TABLE);
|
||||
free(state);
|
||||
}
|
||||
}
|
||||
|
||||
static void dsd2pcm_reset(void *_state) {
|
||||
struct dsd2pcm_state *state = (struct dsd2pcm_state *)_state;
|
||||
const int FILT_LOOKUP_PARTS = state->FILT_LOOKUP_PARTS;
|
||||
int *fifo = state->fifo;
|
||||
for(int i = 0; i < FILT_LOOKUP_PARTS; i++) {
|
||||
fifo[i] = 0x55;
|
||||
fifo[i + FILT_LOOKUP_PARTS] = 0xAA;
|
||||
}
|
||||
state->fpos = FILT_LOOKUP_PARTS;
|
||||
}
|
||||
|
||||
static int dsd2pcm_latency(void *_state) {
|
||||
struct dsd2pcm_state *state = (struct dsd2pcm_state *)_state;
|
||||
if(state)
|
||||
return state->FIFO_LENGTH;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dsd2pcm_process(void *_state, const uint8_t *src, size_t sofs, size_t sinc, float *dest, size_t dofs, size_t dinc, size_t len) {
|
||||
struct dsd2pcm_state *state = (struct dsd2pcm_state *)_state;
|
||||
int bite1, bite2, temp;
|
||||
float sample;
|
||||
int *fifo = state->fifo;
|
||||
const uint8_t *REVERSE_BITS = state->REVERSE_BITS;
|
||||
const float *FILT_LOOKUP_TABLE = state->FILT_LOOKUP_TABLE;
|
||||
const int FILT_LOOKUP_PARTS = state->FILT_LOOKUP_PARTS;
|
||||
const int FIFO_OFS_MASK = state->FIFO_OFS_MASK;
|
||||
int fpos = state->fpos;
|
||||
while(len > 0) {
|
||||
fifo[fpos] = REVERSE_BITS[fifo[fpos]] & 0xFF;
|
||||
fifo[(fpos + FILT_LOOKUP_PARTS) & FIFO_OFS_MASK] = src[sofs] & 0xFF;
|
||||
sofs += sinc;
|
||||
temp = (fpos + 1) & FIFO_OFS_MASK;
|
||||
sample = 0;
|
||||
for(int k = 0, lofs = 0; k < FILT_LOOKUP_PARTS;) {
|
||||
bite1 = fifo[(fpos - k) & FIFO_OFS_MASK];
|
||||
bite2 = fifo[(temp + k) & FIFO_OFS_MASK];
|
||||
sample += FILT_LOOKUP_TABLE[lofs + bite1] + FILT_LOOKUP_TABLE[lofs + bite2];
|
||||
k++;
|
||||
lofs += 0x100;
|
||||
}
|
||||
fpos = temp;
|
||||
dest[dofs] = sample;
|
||||
dofs += dinc;
|
||||
len--;
|
||||
}
|
||||
state->fpos = fpos;
|
||||
}
|
||||
|
||||
static void convert_dsd_to_f32(float *output, const uint8_t *input, size_t count, size_t channels, void **dsd2pcm) {
|
||||
for(size_t channel = 0; channel < channels; ++channel) {
|
||||
dsd2pcm_process(dsd2pcm[channel], input, channel, channels, output, channel, channels, count);
|
||||
}
|
||||
}
|
||||
#else
|
||||
static void convert_dsd_to_f32(float *output, const uint8_t *input, size_t count, size_t channels) {
|
||||
const uint8_t *iptr = input;
|
||||
float *optr = output;
|
||||
for(size_t index = 0; index < count; ++index) {
|
||||
for(size_t channel = 0; channel < channels; ++channel) {
|
||||
uint8_t sample = *iptr++;
|
||||
cblas_scopy(8, &dsd2float[sample][0], 1, optr++, (int)channels);
|
||||
}
|
||||
optr += channels * 7;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static void convert_u8_to_s16(int16_t *output, const uint8_t *input, size_t count) {
|
||||
for(size_t i = 0; i < count; ++i) {
|
||||
uint16_t sample = (input[i] << 8) | input[i];
|
||||
sample ^= 0x8080;
|
||||
output[i] = (int16_t)(sample);
|
||||
}
|
||||
}
|
||||
|
||||
static void convert_s8_to_s16(int16_t *output, const uint8_t *input, size_t count) {
|
||||
for(size_t i = 0; i < count; ++i) {
|
||||
uint16_t sample = (input[i] << 8) | input[i];
|
||||
output[i] = (int16_t)(sample);
|
||||
}
|
||||
}
|
||||
|
||||
static void convert_u16_to_s16(int16_t *buffer, size_t count) {
|
||||
for(size_t i = 0; i < count; ++i) {
|
||||
buffer[i] ^= 0x8000;
|
||||
}
|
||||
}
|
||||
|
||||
static void convert_s16_to_hdcd_input(int32_t *output, const int16_t *input, size_t count) {
|
||||
for(size_t i = 0; i < count; ++i) {
|
||||
output[i] = input[i];
|
||||
}
|
||||
}
|
||||
|
||||
static void convert_s24_to_s32(int32_t *output, const uint8_t *input, size_t count) {
|
||||
for(size_t i = 0; i < count; ++i) {
|
||||
int32_t sample = (input[i * 3] << 8) | (input[i * 3 + 1] << 16) | (input[i * 3 + 2] << 24);
|
||||
output[i] = sample;
|
||||
}
|
||||
}
|
||||
|
||||
static void convert_u24_to_s32(int32_t *output, const uint8_t *input, size_t count) {
|
||||
for(size_t i = 0; i < count; ++i) {
|
||||
int32_t sample = (input[i * 3] << 8) | (input[i * 3 + 1] << 16) | (input[i * 3 + 2] << 24);
|
||||
output[i] = sample ^ 0x80000000;
|
||||
}
|
||||
}
|
||||
|
||||
static void convert_u32_to_s32(int32_t *buffer, size_t count) {
|
||||
for(size_t i = 0; i < count; ++i) {
|
||||
buffer[i] ^= 0x80000000;
|
||||
}
|
||||
}
|
||||
|
||||
static void convert_f64_to_f32(float *output, const double *input, size_t count) {
|
||||
vDSP_vdpsp(input, 1, output, 1, count);
|
||||
}
|
||||
|
||||
static void convert_be_to_le(uint8_t *buffer, size_t bitsPerSample, size_t bytes) {
|
||||
size_t i;
|
||||
bitsPerSample = (bitsPerSample + 7) / 8;
|
||||
switch(bitsPerSample) {
|
||||
case 2:
|
||||
for(i = 0; i < bytes; i += 2) {
|
||||
*(int16_t *)buffer = __builtin_bswap16(*(int16_t *)buffer);
|
||||
buffer += 2;
|
||||
}
|
||||
break;
|
||||
|
||||
case 3: {
|
||||
union {
|
||||
vDSP_int24 int24;
|
||||
uint32_t int32;
|
||||
} intval;
|
||||
intval.int32 = 0;
|
||||
for(i = 0; i < bytes; i += 3) {
|
||||
intval.int24 = *(vDSP_int24 *)buffer;
|
||||
intval.int32 = __builtin_bswap32(intval.int32 << 8);
|
||||
*(vDSP_int24 *)buffer = intval.int24;
|
||||
buffer += 3;
|
||||
}
|
||||
} break;
|
||||
|
||||
case 4:
|
||||
for(i = 0; i < bytes; i += 4) {
|
||||
*(uint32_t *)buffer = __builtin_bswap32(*(uint32_t *)buffer);
|
||||
buffer += 4;
|
||||
}
|
||||
break;
|
||||
|
||||
case 8:
|
||||
for(i = 0; i < bytes; i += 8) {
|
||||
*(uint64_t *)buffer = __builtin_bswap64(*(uint64_t *)buffer);
|
||||
buffer += 8;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@implementation ChunkList
|
||||
|
||||
@synthesize listDuration;
|
||||
|
@ -24,6 +373,17 @@
|
|||
inRemover = NO;
|
||||
inPeeker = NO;
|
||||
stopping = NO;
|
||||
|
||||
formatRead = NO;
|
||||
|
||||
inputBuffer = NULL;
|
||||
inputBufferSize = 0;
|
||||
|
||||
#if DSD_DECIMATE
|
||||
dsd2pcm = NULL;
|
||||
dsd2pcmCount = 0;
|
||||
dsd2pcmLatency = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
return self;
|
||||
|
@ -34,6 +394,23 @@
|
|||
while(inAdder || inRemover || inPeeker) {
|
||||
usleep(500);
|
||||
}
|
||||
if(hdcd_decoder) {
|
||||
free(hdcd_decoder);
|
||||
hdcd_decoder = NULL;
|
||||
}
|
||||
#if DSD_DECIMATE
|
||||
if(dsd2pcm && dsd2pcmCount) {
|
||||
for(size_t i = 0; i < dsd2pcmCount; ++i) {
|
||||
dsd2pcm_free(dsd2pcm[i]);
|
||||
dsd2pcm[i] = NULL;
|
||||
}
|
||||
free(dsd2pcm);
|
||||
dsd2pcm = NULL;
|
||||
}
|
||||
#endif
|
||||
if(tempData) {
|
||||
free(tempData);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)reset {
|
||||
|
@ -90,13 +467,282 @@
|
|||
AudioChunk *ret = [[AudioChunk alloc] init];
|
||||
[ret setFormat:[chunk format]];
|
||||
[ret setChannelConfig:[chunk channelConfig]];
|
||||
[ret assignSamples:[removedData bytes] frameCount:maxFrameCount];
|
||||
[ret assignData:removedData];
|
||||
listDuration -= [ret duration];
|
||||
inRemover = NO;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
- (AudioChunk *)removeSamplesAsFloat32:(size_t)maxFrameCount {
|
||||
if(stopping) {
|
||||
return [[AudioChunk alloc] init];
|
||||
}
|
||||
|
||||
@synchronized (chunkList) {
|
||||
inRemover = YES;
|
||||
if(![chunkList count]) {
|
||||
inRemover = NO;
|
||||
return [[AudioChunk alloc] init];
|
||||
}
|
||||
AudioChunk *chunk = [chunkList objectAtIndex:0];
|
||||
#if !DSD_DECIMATE
|
||||
AudioStreamBasicDescription asbd = [chunk format];
|
||||
if(asbd.mBitsPerChannel == 1) {
|
||||
maxFrameCount /= 8;
|
||||
}
|
||||
#endif
|
||||
if([chunk frameCount] <= maxFrameCount) {
|
||||
[chunkList removeObjectAtIndex:0];
|
||||
listDuration -= [chunk duration];
|
||||
inRemover = NO;
|
||||
return [self convertChunk:chunk];
|
||||
}
|
||||
NSData *removedData = [chunk removeSamples:maxFrameCount];
|
||||
AudioChunk *ret = [[AudioChunk alloc] init];
|
||||
[ret setFormat:[chunk format]];
|
||||
[ret setChannelConfig:[chunk channelConfig]];
|
||||
[ret assignData:removedData];
|
||||
listDuration -= [ret duration];
|
||||
inRemover = NO;
|
||||
return [self convertChunk:ret];
|
||||
}
|
||||
}
|
||||
|
||||
- (AudioChunk *)convertChunk:(AudioChunk *)inChunk {
|
||||
AudioStreamBasicDescription chunkFormat = [inChunk format];
|
||||
uint32_t chunkConfig = [inChunk channelConfig];
|
||||
BOOL chunkLossless = [inChunk lossless];
|
||||
if(!formatRead || memcmp(&chunkFormat, &inputFormat, sizeof(chunkFormat)) != 0 ||
|
||||
chunkConfig != inputChannelConfig || chunkLossless != inputLossless) {
|
||||
formatRead = YES;
|
||||
inputFormat = chunkFormat;
|
||||
inputChannelConfig = chunkConfig;
|
||||
inputLossless = chunkLossless;
|
||||
|
||||
BOOL isFloat = !!(inputFormat.mFormatFlags & kAudioFormatFlagIsFloat);
|
||||
if((!isFloat && !(inputFormat.mBitsPerChannel >= 1 && inputFormat.mBitsPerChannel <= 32)) || (isFloat && !(inputFormat.mBitsPerChannel == 32 || inputFormat.mBitsPerChannel == 64)))
|
||||
return [[AudioChunk alloc] init];
|
||||
|
||||
// These are really placeholders, as we're doing everything internally now
|
||||
if(inputLossless &&
|
||||
inputFormat.mBitsPerChannel == 16 &&
|
||||
inputFormat.mChannelsPerFrame == 2 &&
|
||||
inputFormat.mSampleRate == 44100) {
|
||||
// possibly HDCD, run through decoder
|
||||
if(hdcd_decoder) {
|
||||
free(hdcd_decoder);
|
||||
hdcd_decoder = NULL;
|
||||
}
|
||||
hdcd_decoder = calloc(1, sizeof(hdcd_state_stereo_t));
|
||||
hdcd_reset_stereo((hdcd_state_stereo_t *)hdcd_decoder, 44100);
|
||||
}
|
||||
|
||||
floatFormat = inputFormat;
|
||||
floatFormat.mFormatFlags = kAudioFormatFlagsNativeFloatPacked;
|
||||
floatFormat.mBitsPerChannel = 32;
|
||||
floatFormat.mBytesPerFrame = (32 / 8) * floatFormat.mChannelsPerFrame;
|
||||
floatFormat.mBytesPerPacket = floatFormat.mBytesPerFrame * floatFormat.mFramesPerPacket;
|
||||
|
||||
#if DSD_DECIMATE
|
||||
if(inputFormat.mBitsPerChannel == 1) {
|
||||
// Decimate this for speed
|
||||
floatFormat.mSampleRate *= 1.0 / 8.0;
|
||||
if(dsd2pcm && dsd2pcmCount) {
|
||||
for(size_t i = 0; i < dsd2pcmCount; ++i) {
|
||||
dsd2pcm_free(dsd2pcm[i]);
|
||||
dsd2pcm[i] = NULL;
|
||||
}
|
||||
free(dsd2pcm);
|
||||
dsd2pcm = NULL;
|
||||
}
|
||||
dsd2pcmCount = floatFormat.mChannelsPerFrame;
|
||||
dsd2pcm = (void **)calloc(dsd2pcmCount, sizeof(void *));
|
||||
dsd2pcm[0] = dsd2pcm_alloc();
|
||||
dsd2pcmLatency = dsd2pcm_latency(dsd2pcm[0]);
|
||||
for(size_t i = 1; i < dsd2pcmCount; ++i) {
|
||||
dsd2pcm[i] = dsd2pcm_dup(dsd2pcm[0]);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
NSUInteger samplesRead = [inChunk frameCount];
|
||||
|
||||
if(!samplesRead) {
|
||||
return [[AudioChunk alloc] init];
|
||||
}
|
||||
|
||||
BOOL isFloat = !!(inputFormat.mFormatFlags & kAudioFormatFlagIsFloat);
|
||||
BOOL isUnsigned = !isFloat && !(inputFormat.mFormatFlags & kAudioFormatFlagIsSignedInteger);
|
||||
size_t bitsPerSample = inputFormat.mBitsPerChannel;
|
||||
BOOL isBigEndian = !!(inputFormat.mFormatFlags & kAudioFormatFlagIsBigEndian);
|
||||
|
||||
NSData *inputData = [inChunk removeSamples:samplesRead];
|
||||
|
||||
#if DSD_DECIMATE
|
||||
const size_t sizeFactor = 3;
|
||||
#else
|
||||
const size_t sizeFactor = (bitsPerSample == 1) ? 9 : 3;
|
||||
#endif
|
||||
size_t newSize = samplesRead * floatFormat.mBytesPerPacket * sizeFactor + 64;
|
||||
if(!tempData || tempDataSize < newSize)
|
||||
tempData = realloc(tempData, tempDataSize = newSize); // Either two buffers plus padding, and/or double precision in case of endian flip
|
||||
|
||||
// double buffer system, with alignment
|
||||
const size_t buffer_adder_base = (samplesRead * floatFormat.mBytesPerPacket + 31) & ~31;
|
||||
|
||||
NSUInteger bytesReadFromInput = samplesRead * inputFormat.mBytesPerPacket;
|
||||
|
||||
uint8_t *inputBuffer = (uint8_t *)[inputData bytes];
|
||||
BOOL inputChanged = NO;
|
||||
|
||||
BOOL hdcdSustained = NO;
|
||||
|
||||
if(bytesReadFromInput && isBigEndian) {
|
||||
// Time for endian swap!
|
||||
memcpy(&tempData[0], [inputData bytes], bytesReadFromInput);
|
||||
convert_be_to_le((uint8_t *)(&tempData[0]), inputFormat.mBitsPerChannel, bytesReadFromInput);
|
||||
inputBuffer = &tempData[0];
|
||||
inputChanged = YES;
|
||||
}
|
||||
|
||||
if(bytesReadFromInput && isFloat && bitsPerSample == 64) {
|
||||
// Time for precision loss from weird inputs
|
||||
const size_t buffer_adder = (inputBuffer == &tempData[0]) ? buffer_adder_base * 2 : 0;
|
||||
samplesRead = bytesReadFromInput / sizeof(double);
|
||||
convert_f64_to_f32((float *)(&tempData[buffer_adder]), (const double *)inputBuffer, samplesRead);
|
||||
bytesReadFromInput = samplesRead * sizeof(float);
|
||||
inputBuffer = &tempData[buffer_adder];
|
||||
inputChanged = YES;
|
||||
bitsPerSample = 32;
|
||||
}
|
||||
|
||||
if(bytesReadFromInput && !isFloat) {
|
||||
float gain = 1.0;
|
||||
if(bitsPerSample == 1) {
|
||||
const size_t buffer_adder = (inputBuffer == &tempData[0]) ? buffer_adder_base : 0;
|
||||
samplesRead = bytesReadFromInput / inputFormat.mBytesPerPacket;
|
||||
convert_dsd_to_f32((float *)(&tempData[buffer_adder]), (const uint8_t *)inputBuffer, samplesRead, inputFormat.mChannelsPerFrame
|
||||
#if DSD_DECIMATE
|
||||
,
|
||||
dsd2pcm
|
||||
#endif
|
||||
);
|
||||
#if !DSD_DECIMATE
|
||||
samplesRead *= 8;
|
||||
#endif
|
||||
bitsPerSample = 32;
|
||||
bytesReadFromInput = samplesRead * floatFormat.mBytesPerPacket;
|
||||
isFloat = YES;
|
||||
inputBuffer = &tempData[buffer_adder];
|
||||
inputChanged = YES;
|
||||
#if DSD_DECIMATE
|
||||
float scaleFactor = 2.0f;
|
||||
vDSP_vsdiv((float *)inputBuffer, 1, &scaleFactor, (float *)inputBuffer, 1, bytesReadFromInput / sizeof(float));
|
||||
#endif
|
||||
} else if(bitsPerSample <= 8) {
|
||||
samplesRead = bytesReadFromInput;
|
||||
const size_t buffer_adder = (inputBuffer == &tempData[0]) ? buffer_adder_base : 0;
|
||||
if(!isUnsigned)
|
||||
convert_s8_to_s16((int16_t *)(&tempData[buffer_adder]), (const uint8_t *)inputBuffer, samplesRead);
|
||||
else
|
||||
convert_u8_to_s16((int16_t *)(&tempData[buffer_adder]), (const uint8_t *)inputBuffer, samplesRead);
|
||||
bitsPerSample = 16;
|
||||
bytesReadFromInput = samplesRead * 2;
|
||||
isUnsigned = NO;
|
||||
inputBuffer = &tempData[buffer_adder];
|
||||
inputChanged = YES;
|
||||
}
|
||||
if(hdcd_decoder) { // implied bits per sample is 16, produces 32 bit int scale
|
||||
samplesRead = bytesReadFromInput / 2;
|
||||
const size_t buffer_adder = (inputBuffer == &tempData[0]) ? buffer_adder_base : 0;
|
||||
if(isUnsigned)
|
||||
convert_u16_to_s16((int16_t *)inputBuffer, samplesRead);
|
||||
convert_s16_to_hdcd_input((int32_t *)(&tempData[buffer_adder]), (int16_t *)inputBuffer, samplesRead);
|
||||
hdcd_process_stereo((hdcd_state_stereo_t *)hdcd_decoder, (int32_t *)(&tempData[buffer_adder]), (int)(samplesRead / 2));
|
||||
if(((hdcd_state_stereo_t *)hdcd_decoder)->channel[0].sustain &&
|
||||
((hdcd_state_stereo_t *)hdcd_decoder)->channel[1].sustain) {
|
||||
hdcdSustained = YES;
|
||||
}
|
||||
gain = 2.0;
|
||||
bitsPerSample = 32;
|
||||
bytesReadFromInput = samplesRead * 4;
|
||||
isUnsigned = NO;
|
||||
inputBuffer = &tempData[buffer_adder];
|
||||
inputChanged = YES;
|
||||
} else if(bitsPerSample <= 16) {
|
||||
samplesRead = bytesReadFromInput / 2;
|
||||
const size_t buffer_adder = (inputBuffer == &tempData[0]) ? buffer_adder_base : 0;
|
||||
if(isUnsigned) {
|
||||
if(!inputChanged) {
|
||||
memcpy(&tempData[buffer_adder], inputBuffer, samplesRead * 2);
|
||||
inputBuffer = &tempData[buffer_adder];
|
||||
inputChanged = YES;
|
||||
}
|
||||
convert_u16_to_s16((int16_t *)inputBuffer, samplesRead);
|
||||
}
|
||||
const size_t buffer_adder2 = (inputBuffer == &tempData[0]) ? buffer_adder_base : 0;
|
||||
vDSP_vflt16((const short *)inputBuffer, 1, (float *)(&tempData[buffer_adder2]), 1, samplesRead);
|
||||
float scale = 1ULL << 15;
|
||||
vDSP_vsdiv((const float *)(&tempData[buffer_adder2]), 1, &scale, (float *)(&tempData[buffer_adder2]), 1, samplesRead);
|
||||
bitsPerSample = 32;
|
||||
bytesReadFromInput = samplesRead * sizeof(float);
|
||||
isUnsigned = NO;
|
||||
isFloat = YES;
|
||||
inputBuffer = &tempData[buffer_adder2];
|
||||
inputChanged = YES;
|
||||
} else if(bitsPerSample <= 24) {
|
||||
const size_t buffer_adder = (inputBuffer == &tempData[0]) ? buffer_adder_base : 0;
|
||||
samplesRead = bytesReadFromInput / 3;
|
||||
if(isUnsigned)
|
||||
convert_u24_to_s32((int32_t *)(&tempData[buffer_adder]), (uint8_t *)inputBuffer, samplesRead);
|
||||
else
|
||||
convert_s24_to_s32((int32_t *)(&tempData[buffer_adder]), (uint8_t *)inputBuffer, samplesRead);
|
||||
bitsPerSample = 32;
|
||||
bytesReadFromInput = samplesRead * 4;
|
||||
isUnsigned = NO;
|
||||
inputBuffer = &tempData[buffer_adder];
|
||||
inputChanged = YES;
|
||||
}
|
||||
if(!isFloat && bitsPerSample <= 32) {
|
||||
samplesRead = bytesReadFromInput / 4;
|
||||
if(isUnsigned) {
|
||||
if(!inputChanged) {
|
||||
memcpy(&tempData[0], inputBuffer, bytesReadFromInput);
|
||||
inputBuffer = &tempData[0];
|
||||
}
|
||||
convert_u32_to_s32((int32_t *)inputBuffer, samplesRead);
|
||||
}
|
||||
const size_t buffer_adder = (inputBuffer == &tempData[0]) ? buffer_adder_base : 0; // vDSP functions expect aligned to four elements
|
||||
vDSP_vflt32((const int *)inputBuffer, 1, (float *)(&tempData[buffer_adder]), 1, samplesRead);
|
||||
float scale = (1ULL << 31) / gain;
|
||||
vDSP_vsdiv((const float *)(&tempData[buffer_adder]), 1, &scale, (float *)(&tempData[buffer_adder]), 1, samplesRead);
|
||||
bitsPerSample = 32;
|
||||
bytesReadFromInput = samplesRead * sizeof(float);
|
||||
isUnsigned = NO;
|
||||
isFloat = YES;
|
||||
inputBuffer = &tempData[buffer_adder];
|
||||
}
|
||||
|
||||
#ifdef _DEBUG
|
||||
[BadSampleCleaner cleanSamples:(float *)inputBuffer
|
||||
amount:bytesReadFromInput / sizeof(float)
|
||||
location:@"post int to float conversion"];
|
||||
#endif
|
||||
}
|
||||
|
||||
AudioChunk *outChunk = [[AudioChunk alloc] init];
|
||||
[outChunk setFormat:floatFormat];
|
||||
[outChunk setChannelConfig:inputChannelConfig];
|
||||
[outChunk setLossless:inputLossless];
|
||||
if(hdcdSustained) [outChunk setHDCD];
|
||||
|
||||
[outChunk assignSamples:inputBuffer frameCount:bytesReadFromInput / floatFormat.mBytesPerPacket];
|
||||
|
||||
return outChunk;
|
||||
}
|
||||
|
||||
- (BOOL)peekFormat:(AudioStreamBasicDescription *)format channelConfig:(uint32_t *)config {
|
||||
if(stopping) return NO;
|
||||
@synchronized(chunkList) {
|
||||
|
|
|
@ -14,15 +14,9 @@
|
|||
|
||||
#import "Node.h"
|
||||
|
||||
#import "HeadphoneFilter.h"
|
||||
|
||||
#define DSD_DECIMATE 1
|
||||
|
||||
@interface ConverterNode : Node {
|
||||
NSDictionary *rgInfo;
|
||||
|
||||
void *_r8bstate;
|
||||
|
||||
void *inputBuffer;
|
||||
size_t inputBufferSize;
|
||||
size_t inpSize, inpOffset;
|
||||
|
@ -31,70 +25,32 @@
|
|||
BOOL convertEntered;
|
||||
BOOL paused;
|
||||
|
||||
BOOL skipResampler;
|
||||
|
||||
unsigned int PRIME_LEN_;
|
||||
unsigned int N_samples_to_add_;
|
||||
unsigned int N_samples_to_drop_;
|
||||
|
||||
unsigned int is_preextrapolated_;
|
||||
unsigned int is_postextrapolated_;
|
||||
|
||||
int latencyEaten;
|
||||
int latencyEatenPost;
|
||||
|
||||
double sampleRatio;
|
||||
|
||||
float volumeScale;
|
||||
|
||||
void *floatBuffer;
|
||||
size_t floatBufferSize;
|
||||
size_t floatSize, floatOffset;
|
||||
|
||||
void *extrapolateBuffer;
|
||||
size_t extrapolateBufferSize;
|
||||
|
||||
#if DSD_DECIMATE
|
||||
void **dsd2pcm;
|
||||
size_t dsd2pcmCount;
|
||||
int dsd2pcmLatency;
|
||||
#endif
|
||||
|
||||
BOOL rememberedLossless;
|
||||
|
||||
AudioStreamBasicDescription inputFormat;
|
||||
AudioStreamBasicDescription floatFormat;
|
||||
AudioStreamBasicDescription dmFloatFormat; // downmixed/upmixed float format
|
||||
AudioStreamBasicDescription outputFormat;
|
||||
|
||||
uint32_t inputChannelConfig;
|
||||
uint32_t outputChannelConfig;
|
||||
|
||||
BOOL streamFormatChanged;
|
||||
AudioStreamBasicDescription newInputFormat;
|
||||
uint32_t newInputChannelConfig;
|
||||
|
||||
AudioChunk *lastChunkIn;
|
||||
|
||||
void *hdcd_decoder;
|
||||
|
||||
HeadphoneFilter *hFilter;
|
||||
}
|
||||
|
||||
@property AudioStreamBasicDescription inputFormat;
|
||||
|
||||
- (id)initWithController:(id)c previous:(id)p;
|
||||
|
||||
- (BOOL)setupWithInputFormat:(AudioStreamBasicDescription)inputFormat withInputConfig:(uint32_t)inputConfig outputFormat:(AudioStreamBasicDescription)outputFormat outputConfig:(uint32_t)outputConfig isLossless:(BOOL)lossless;
|
||||
- (BOOL)setupWithInputFormat:(AudioStreamBasicDescription)inputFormat withInputConfig:(uint32_t)inputConfig isLossless:(BOOL)lossless;
|
||||
- (void)cleanUp;
|
||||
|
||||
- (void)process;
|
||||
- (int)convert:(void *)dest amount:(int)amount;
|
||||
- (AudioChunk *)convert;
|
||||
|
||||
- (void)setRGInfo:(NSDictionary *)rgi;
|
||||
|
||||
- (void)setOutputFormat:(AudioStreamBasicDescription)format outputConfig:(uint32_t)outputConfig;
|
||||
|
||||
- (void)inputFormatDidChange:(AudioStreamBasicDescription)format inputConfig:(uint32_t)inputConfig;
|
||||
|
||||
- (void)refreshVolumeScaling;
|
||||
|
|
|
@ -0,0 +1,354 @@
|
|||
//
|
||||
// ConverterNode.m
|
||||
// Cog
|
||||
//
|
||||
// Created by Zaphod Beeblebrox on 8/2/05.
|
||||
// Copyright 2005 __MyCompanyName__. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Accelerate/Accelerate.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "ConverterNode.h"
|
||||
|
||||
#import "BufferChain.h"
|
||||
#import "OutputNode.h"
|
||||
|
||||
#import "Logging.h"
|
||||
|
||||
#ifdef _DEBUG
|
||||
#import "BadSampleCleaner.h"
|
||||
#endif
|
||||
|
||||
void PrintStreamDesc(AudioStreamBasicDescription *inDesc) {
|
||||
if(!inDesc) {
|
||||
DLog(@"Can't print a NULL desc!\n");
|
||||
return;
|
||||
}
|
||||
DLog(@"- - - - - - - - - - - - - - - - - - - -\n");
|
||||
DLog(@" Sample Rate:%f\n", inDesc->mSampleRate);
|
||||
DLog(@" Format ID:%s\n", (char *)&inDesc->mFormatID);
|
||||
DLog(@" Format Flags:%X\n", inDesc->mFormatFlags);
|
||||
DLog(@" Bytes per Packet:%d\n", inDesc->mBytesPerPacket);
|
||||
DLog(@" Frames per Packet:%d\n", inDesc->mFramesPerPacket);
|
||||
DLog(@" Bytes per Frame:%d\n", inDesc->mBytesPerFrame);
|
||||
DLog(@" Channels per Frame:%d\n", inDesc->mChannelsPerFrame);
|
||||
DLog(@" Bits per Channel:%d\n", inDesc->mBitsPerChannel);
|
||||
DLog(@"- - - - - - - - - - - - - - - - - - - -\n");
|
||||
}
|
||||
|
||||
@implementation ConverterNode
|
||||
|
||||
static void *kConverterNodeContext = &kConverterNodeContext;
|
||||
|
||||
@synthesize inputFormat;
|
||||
|
||||
- (id)initWithController:(id)c previous:(id)p {
|
||||
self = [super initWithController:c previous:p];
|
||||
if(self) {
|
||||
rgInfo = nil;
|
||||
|
||||
inputBuffer = NULL;
|
||||
inputBufferSize = 0;
|
||||
|
||||
stopping = NO;
|
||||
convertEntered = NO;
|
||||
paused = NO;
|
||||
|
||||
[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.volumeScaling" options:0 context:kConverterNodeContext];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
void scale_by_volume(float *buffer, size_t count, float volume) {
|
||||
if(volume != 1.0) {
|
||||
size_t unaligned = (uintptr_t)buffer & 15;
|
||||
if(unaligned) {
|
||||
size_t count3 = unaligned >> 2;
|
||||
while(count3 > 0) {
|
||||
*buffer++ *= volume;
|
||||
count3--;
|
||||
count--;
|
||||
}
|
||||
}
|
||||
|
||||
vDSP_vsmul(buffer, 1, &volume, buffer, 1, count);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)process {
|
||||
// Removed endOfStream check from here, since we want to be able to flush the converter
|
||||
// when the end of stream is reached. Convert function instead processes what it can,
|
||||
// and returns 0 samples when it has nothing more to process at the end of stream.
|
||||
while([self shouldContinue] == YES) {
|
||||
AudioChunk *chunk = nil;
|
||||
while(paused) {
|
||||
usleep(500);
|
||||
}
|
||||
@autoreleasepool {
|
||||
chunk = [self convert];
|
||||
}
|
||||
if(!chunk) {
|
||||
if(paused) {
|
||||
continue;
|
||||
} else if(!streamFormatChanged) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
@autoreleasepool {
|
||||
[self writeChunk:chunk];
|
||||
chunk = nil;
|
||||
}
|
||||
}
|
||||
if(streamFormatChanged) {
|
||||
[self cleanUp];
|
||||
[self setupWithInputFormat:newInputFormat withInputConfig:newInputChannelConfig isLossless:rememberedLossless];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (AudioChunk *)convert {
|
||||
UInt32 ioNumberPackets;
|
||||
|
||||
if(stopping)
|
||||
return 0;
|
||||
|
||||
convertEntered = YES;
|
||||
|
||||
if(stopping || [self shouldContinue] == NO) {
|
||||
convertEntered = NO;
|
||||
return nil;
|
||||
}
|
||||
|
||||
while(inpOffset == inpSize) {
|
||||
// Approximately the most we want on input
|
||||
ioNumberPackets = 4096;
|
||||
|
||||
size_t newSize = ioNumberPackets * floatFormat.mBytesPerPacket;
|
||||
if(!inputBuffer || inputBufferSize < newSize)
|
||||
inputBuffer = realloc(inputBuffer, inputBufferSize = newSize);
|
||||
|
||||
ssize_t amountToWrite = ioNumberPackets * floatFormat.mBytesPerPacket;
|
||||
|
||||
ssize_t bytesReadFromInput = 0;
|
||||
|
||||
while(bytesReadFromInput < amountToWrite && !stopping && !paused && !streamFormatChanged && [self shouldContinue] == YES && [self endOfStream] == NO) {
|
||||
AudioStreamBasicDescription inf;
|
||||
uint32_t config;
|
||||
if([self peekFormat:&inf channelConfig:&config]) {
|
||||
if(config != inputChannelConfig || memcmp(&inf, &inputFormat, sizeof(inf)) != 0) {
|
||||
if(inputChannelConfig == 0 && memcmp(&inf, &inputFormat, sizeof(inf)) == 0) {
|
||||
inputChannelConfig = config;
|
||||
continue;
|
||||
} else {
|
||||
newInputFormat = inf;
|
||||
newInputChannelConfig = config;
|
||||
streamFormatChanged = YES;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AudioChunk *chunk = [self readChunkAsFloat32:((amountToWrite - bytesReadFromInput) / floatFormat.mBytesPerPacket)];
|
||||
inf = [chunk format];
|
||||
size_t frameCount = [chunk frameCount];
|
||||
config = [chunk channelConfig];
|
||||
size_t bytesRead = frameCount * inf.mBytesPerPacket;
|
||||
if(frameCount) {
|
||||
NSData *samples = [chunk removeSamples:frameCount];
|
||||
memcpy(((uint8_t *)inputBuffer) + bytesReadFromInput, [samples bytes], bytesRead);
|
||||
if([chunk isHDCD]) {
|
||||
[controller sustainHDCD];
|
||||
}
|
||||
}
|
||||
bytesReadFromInput += bytesRead;
|
||||
if(!frameCount) {
|
||||
usleep(500);
|
||||
}
|
||||
}
|
||||
|
||||
if(!bytesReadFromInput) {
|
||||
convertEntered = NO;
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Input now contains bytesReadFromInput worth of floats, in the input sample rate
|
||||
inpSize = bytesReadFromInput;
|
||||
inpOffset = 0;
|
||||
}
|
||||
|
||||
ioNumberPackets = (UInt32)(inpSize - inpOffset);
|
||||
|
||||
ioNumberPackets -= ioNumberPackets % floatFormat.mBytesPerPacket;
|
||||
|
||||
if(ioNumberPackets) {
|
||||
AudioChunk *chunk = [[AudioChunk alloc] init];
|
||||
[chunk setFormat:nodeFormat];
|
||||
if(nodeChannelConfig) {
|
||||
[chunk setChannelConfig:nodeChannelConfig];
|
||||
}
|
||||
scale_by_volume((float *)(((uint8_t *)inputBuffer) + inpOffset), ioNumberPackets / sizeof(float), volumeScale);
|
||||
[chunk assignSamples:(((uint8_t *)inputBuffer) + inpOffset) frameCount:ioNumberPackets / floatFormat.mBytesPerPacket];
|
||||
|
||||
inpOffset += ioNumberPackets;
|
||||
convertEntered = NO;
|
||||
return chunk;
|
||||
}
|
||||
|
||||
convertEntered = NO;
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath
|
||||
ofObject:(id)object
|
||||
change:(NSDictionary *)change
|
||||
context:(void *)context {
|
||||
if(context == kConverterNodeContext) {
|
||||
DLog(@"SOMETHING CHANGED!");
|
||||
if([keyPath isEqualToString:@"values.volumeScaling"]) {
|
||||
// User reset the volume scaling option
|
||||
[self refreshVolumeScaling];
|
||||
}
|
||||
} else {
|
||||
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
|
||||
}
|
||||
}
|
||||
|
||||
static float db_to_scale(float db) {
|
||||
return pow(10.0, db / 20);
|
||||
}
|
||||
|
||||
- (void)refreshVolumeScaling {
|
||||
if(rgInfo == nil) {
|
||||
volumeScale = 1.0;
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *scaling = [[NSUserDefaults standardUserDefaults] stringForKey:@"volumeScaling"];
|
||||
BOOL useAlbum = [scaling hasPrefix:@"albumGain"];
|
||||
BOOL useTrack = useAlbum || [scaling hasPrefix:@"trackGain"];
|
||||
BOOL useVolume = useAlbum || useTrack || [scaling isEqualToString:@"volumeScale"];
|
||||
BOOL usePeak = [scaling hasSuffix:@"WithPeak"];
|
||||
float scale = 1.0;
|
||||
float peak = 0.0;
|
||||
if(useVolume) {
|
||||
id pVolumeScale = [rgInfo objectForKey:@"volume"];
|
||||
if(pVolumeScale != nil)
|
||||
scale = [pVolumeScale floatValue];
|
||||
}
|
||||
if(useTrack) {
|
||||
id trackGain = [rgInfo objectForKey:@"replayGainTrackGain"];
|
||||
id trackPeak = [rgInfo objectForKey:@"replayGainTrackPeak"];
|
||||
if(trackGain != nil)
|
||||
scale = db_to_scale([trackGain floatValue]);
|
||||
if(trackPeak != nil)
|
||||
peak = [trackPeak floatValue];
|
||||
}
|
||||
if(useAlbum) {
|
||||
id albumGain = [rgInfo objectForKey:@"replayGainAlbumGain"];
|
||||
id albumPeak = [rgInfo objectForKey:@"replayGainAlbumPeak"];
|
||||
if(albumGain != nil)
|
||||
scale = db_to_scale([albumGain floatValue]);
|
||||
if(albumPeak != nil)
|
||||
peak = [albumPeak floatValue];
|
||||
}
|
||||
if(usePeak) {
|
||||
if(scale * peak > 1.0)
|
||||
scale = 1.0 / peak;
|
||||
}
|
||||
volumeScale = scale;
|
||||
}
|
||||
|
||||
- (BOOL)setupWithInputFormat:(AudioStreamBasicDescription)inf withInputConfig:(uint32_t)inputConfig isLossless:(BOOL)lossless {
|
||||
// Make the converter
|
||||
inputFormat = inf;
|
||||
|
||||
inputChannelConfig = inputConfig;
|
||||
|
||||
rememberedLossless = lossless;
|
||||
|
||||
// These are the only sample formats we support translating
|
||||
BOOL isFloat = !!(inputFormat.mFormatFlags & kAudioFormatFlagIsFloat);
|
||||
if((!isFloat && !(inputFormat.mBitsPerChannel >= 1 && inputFormat.mBitsPerChannel <= 32)) || (isFloat && !(inputFormat.mBitsPerChannel == 32 || inputFormat.mBitsPerChannel == 64)))
|
||||
return NO;
|
||||
|
||||
floatFormat = inputFormat;
|
||||
floatFormat.mFormatFlags = kAudioFormatFlagsNativeFloatPacked;
|
||||
floatFormat.mBitsPerChannel = 32;
|
||||
floatFormat.mBytesPerFrame = (32 / 8) * floatFormat.mChannelsPerFrame;
|
||||
floatFormat.mBytesPerPacket = floatFormat.mBytesPerFrame * floatFormat.mFramesPerPacket;
|
||||
|
||||
#if DSD_DECIMATE
|
||||
if(inputFormat.mBitsPerChannel == 1) {
|
||||
// Decimate this for speed
|
||||
floatFormat.mSampleRate *= 1.0 / 8.0;
|
||||
}
|
||||
#endif
|
||||
|
||||
inpOffset = 0;
|
||||
inpSize = 0;
|
||||
|
||||
// This is a post resampler, post-down/upmix format
|
||||
|
||||
nodeFormat = floatFormat;
|
||||
nodeChannelConfig = inputChannelConfig;
|
||||
|
||||
PrintStreamDesc(&inf);
|
||||
PrintStreamDesc(&nodeFormat);
|
||||
|
||||
[self refreshVolumeScaling];
|
||||
|
||||
// Move this here so process call isn't running the resampler until it's allocated
|
||||
stopping = NO;
|
||||
convertEntered = NO;
|
||||
streamFormatChanged = NO;
|
||||
paused = NO;
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
DLog(@"Decoder dealloc");
|
||||
|
||||
[[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.volumeScaling" context:kConverterNodeContext];
|
||||
|
||||
paused = NO;
|
||||
[self cleanUp];
|
||||
}
|
||||
|
||||
- (void)inputFormatDidChange:(AudioStreamBasicDescription)format inputConfig:(uint32_t)inputConfig {
|
||||
DLog(@"FORMAT CHANGED");
|
||||
paused = YES;
|
||||
while(convertEntered) {
|
||||
usleep(500);
|
||||
}
|
||||
[self cleanUp];
|
||||
[self setupWithInputFormat:format withInputConfig:inputConfig isLossless:rememberedLossless];
|
||||
}
|
||||
|
||||
- (void)setRGInfo:(NSDictionary *)rgi {
|
||||
DLog(@"Setting ReplayGain info");
|
||||
rgInfo = rgi;
|
||||
[self refreshVolumeScaling];
|
||||
}
|
||||
|
||||
- (void)cleanUp {
|
||||
stopping = YES;
|
||||
while(convertEntered) {
|
||||
usleep(500);
|
||||
}
|
||||
if(inputBuffer) {
|
||||
free(inputBuffer);
|
||||
inputBuffer = NULL;
|
||||
inputBufferSize = 0;
|
||||
}
|
||||
inpOffset = 0;
|
||||
inpSize = 0;
|
||||
}
|
||||
|
||||
- (double)secondsBuffered {
|
||||
return [buffer listDuration];
|
||||
}
|
||||
|
||||
@end
|
|
@ -9,11 +9,7 @@
|
|||
#import <CoreAudio/CoreAudio.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "HeadphoneFilter.h"
|
||||
|
||||
@interface DownmixProcessor : NSObject {
|
||||
HeadphoneFilter *hFilter;
|
||||
|
||||
AudioStreamBasicDescription inputFormat;
|
||||
AudioStreamBasicDescription outputFormat;
|
||||
|
||||
|
|
|
@ -149,10 +149,12 @@ static void downmix_to_stereo(const float *inBuffer, int channels, uint32_t conf
|
|||
|
||||
static void downmix_to_mono(const float *inBuffer, int channels, uint32_t config, float *outBuffer, size_t count) {
|
||||
float tempBuffer[count * 2];
|
||||
downmix_to_stereo(inBuffer, channels, config, tempBuffer, count);
|
||||
inBuffer = tempBuffer;
|
||||
channels = 2;
|
||||
config = AudioConfigStereo;
|
||||
if(channels > 2 || config != AudioConfigStereo) {
|
||||
downmix_to_stereo(inBuffer, channels, config, tempBuffer, count);
|
||||
inBuffer = tempBuffer;
|
||||
channels = 2;
|
||||
config = AudioConfigStereo;
|
||||
}
|
||||
cblas_scopy((int)count, inBuffer, 2, outBuffer, 1);
|
||||
vDSP_vadd(outBuffer, 1, inBuffer + 1, 2, outBuffer, 1, count);
|
||||
}
|
||||
|
@ -275,92 +277,12 @@ static void *kDownmixProcessorContext = &kDownmixProcessorContext;
|
|||
|
||||
inConfig = iConfig;
|
||||
outConfig = oConfig;
|
||||
|
||||
[self setupVirt];
|
||||
|
||||
[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.headphoneVirtualization" options:0 context:kDownmixProcessorContext];
|
||||
[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.hrirPath" options:0 context:kDownmixProcessorContext];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.headphoneVirtualization" context:kDownmixProcessorContext];
|
||||
[[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.hrirPath" context:kDownmixProcessorContext];
|
||||
}
|
||||
|
||||
- (void)setupVirt {
|
||||
@synchronized(hFilter) {
|
||||
hFilter = nil;
|
||||
}
|
||||
|
||||
BOOL hVirt = [[[NSUserDefaultsController sharedUserDefaultsController] defaults] boolForKey:@"headphoneVirtualization"];
|
||||
|
||||
if(hVirt &&
|
||||
outputFormat.mChannelsPerFrame >= 2 &&
|
||||
(outConfig & AudioConfigStereo) == AudioConfigStereo &&
|
||||
inputFormat.mChannelsPerFrame >= 1 &&
|
||||
(inConfig & (AudioConfig7Point1 | AudioChannelBackCenter)) != 0) {
|
||||
NSString *userPreset = [[[NSUserDefaultsController sharedUserDefaultsController] defaults] stringForKey:@"hrirPath"];
|
||||
|
||||
NSURL *presetUrl = nil;
|
||||
|
||||
if(userPreset && ![userPreset isEqualToString:@""]) {
|
||||
presetUrl = [NSURL fileURLWithPath:userPreset];
|
||||
if(![HeadphoneFilter validateImpulseFile:presetUrl])
|
||||
presetUrl = nil;
|
||||
}
|
||||
|
||||
if(!presetUrl) {
|
||||
presetUrl = [[NSBundle mainBundle] URLForResource:@"gsx" withExtension:@"wv"];
|
||||
if(![HeadphoneFilter validateImpulseFile:presetUrl])
|
||||
presetUrl = nil;
|
||||
}
|
||||
|
||||
if(presetUrl) {
|
||||
@synchronized(hFilter) {
|
||||
hFilter = [[HeadphoneFilter alloc] initWithImpulseFile:presetUrl forSampleRate:outputFormat.mSampleRate withInputChannels:inputFormat.mChannelsPerFrame withConfig:inConfig];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath
|
||||
ofObject:(id)object
|
||||
change:(NSDictionary *)change
|
||||
context:(void *)context {
|
||||
if(context == kDownmixProcessorContext) {
|
||||
DLog(@"SOMETHING CHANGED!");
|
||||
if([keyPath isEqualToString:@"values.headphoneVirtualization"] ||
|
||||
[keyPath isEqualToString:@"values.hrirPath"]) {
|
||||
// Reset the converter, without rebuffering
|
||||
[self setupVirt];
|
||||
}
|
||||
} else {
|
||||
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)process:(const void *)inBuffer frameCount:(size_t)frames output:(void *)outBuffer {
|
||||
@synchronized(hFilter) {
|
||||
if(hFilter) {
|
||||
uint32_t outChannels = outputFormat.mChannelsPerFrame;
|
||||
if(outChannels > 2) {
|
||||
float tempBuffer[frames * 2];
|
||||
[hFilter process:(const float *)inBuffer sampleCount:frames toBuffer:&tempBuffer[0]];
|
||||
cblas_scopy((int)frames, tempBuffer, 2, (float *)outBuffer, outChannels);
|
||||
cblas_scopy((int)frames, tempBuffer + 1, 2, ((float *)outBuffer) + 1, outChannels);
|
||||
for(size_t i = 2; i < outChannels; ++i) {
|
||||
vDSP_vclr(((float *)outBuffer) + i, outChannels, (int)frames);
|
||||
}
|
||||
} else {
|
||||
[hFilter process:(const float *)inBuffer sampleCount:frames toBuffer:(float *)outBuffer];
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(inputFormat.mChannelsPerFrame > 2 && outConfig == AudioConfigStereo) {
|
||||
downmix_to_stereo((const float *)inBuffer, inputFormat.mChannelsPerFrame, inConfig, (float *)outBuffer, frames);
|
||||
} else if(inputFormat.mChannelsPerFrame > 1 && outConfig == AudioConfigMono) {
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
//
|
||||
// HeadphoneFilter.h
|
||||
// CogAudio Framework
|
||||
//
|
||||
// Created by Christopher Snowhill on 1/24/22.
|
||||
//
|
||||
|
||||
#ifndef HeadphoneFilter_h
|
||||
#define HeadphoneFilter_h
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#import "pffft.h"
|
||||
|
||||
@interface HeadphoneFilter : NSObject {
|
||||
PFFFT_Setup *fftSetup;
|
||||
|
||||
size_t fftSize;
|
||||
size_t bufferSize;
|
||||
size_t paddedBufferSize;
|
||||
size_t channelCount;
|
||||
|
||||
float *workBuffer;
|
||||
|
||||
float **impulse_responses;
|
||||
|
||||
float **prevInputs;
|
||||
|
||||
float *left_result;
|
||||
float *right_result;
|
||||
|
||||
float *paddedSignal;
|
||||
}
|
||||
|
||||
+ (BOOL)validateImpulseFile:(NSURL *)url;
|
||||
|
||||
- (id)initWithImpulseFile:(NSURL *)url forSampleRate:(double)sampleRate withInputChannels:(size_t)channels withConfig:(uint32_t)config;
|
||||
|
||||
- (void)process:(const float *)inBuffer sampleCount:(size_t)count toBuffer:(float *)outBuffer;
|
||||
|
||||
- (void)reset;
|
||||
|
||||
@end
|
||||
|
||||
#endif /* HeadphoneFilter_h */
|
|
@ -1,447 +0,0 @@
|
|||
//
|
||||
// HeadphoneFilter.m
|
||||
// CogAudio Framework
|
||||
//
|
||||
// Created by Christopher Snowhill on 1/24/22.
|
||||
//
|
||||
|
||||
#import "AudioChunk.h"
|
||||
#import "AudioDecoder.h"
|
||||
#import "AudioSource.h"
|
||||
#import "HeadphoneFilter.h"
|
||||
|
||||
#import <stdlib.h>
|
||||
|
||||
#import "r8bstate.h"
|
||||
|
||||
#import "lpc.h"
|
||||
#import "util.h"
|
||||
|
||||
#import "pffft_double.h"
|
||||
|
||||
@implementation HeadphoneFilter
|
||||
|
||||
enum {
|
||||
speaker_is_back_center = -1,
|
||||
speaker_not_present = -2,
|
||||
};
|
||||
|
||||
static const uint32_t max_speaker_index = 10;
|
||||
|
||||
static const int8_t speakers_to_hesuvi_7[11][2] = {
|
||||
// front left
|
||||
{ 0, 1 },
|
||||
// front right
|
||||
{ 1, 0 },
|
||||
// front center
|
||||
{ 6, 6 },
|
||||
// lfe
|
||||
{ 6, 6 },
|
||||
// back left
|
||||
{ 4, 5 },
|
||||
// back right
|
||||
{ 5, 4 },
|
||||
// front center left
|
||||
{ speaker_not_present, speaker_not_present },
|
||||
// front center right
|
||||
{ speaker_not_present, speaker_not_present },
|
||||
// back center
|
||||
{ speaker_is_back_center, speaker_is_back_center },
|
||||
// side left
|
||||
{ 2, 3 },
|
||||
// side right
|
||||
{ 3, 2 }
|
||||
};
|
||||
|
||||
static const int8_t speakers_to_hesuvi_14[11][2] = {
|
||||
// front left
|
||||
{ 0, 1 },
|
||||
// front right
|
||||
{ 8, 7 },
|
||||
// front center
|
||||
{ 6, 13 },
|
||||
// lfe
|
||||
{ 6, 13 },
|
||||
// back left
|
||||
{ 4, 5 },
|
||||
// back right
|
||||
{ 12, 11 },
|
||||
// front center left
|
||||
{ speaker_not_present, speaker_not_present },
|
||||
// front center right
|
||||
{ speaker_not_present, speaker_not_present },
|
||||
// back center
|
||||
{ speaker_is_back_center, speaker_is_back_center },
|
||||
// side left
|
||||
{ 2, 3 },
|
||||
// side right
|
||||
{ 10, 9 }
|
||||
};
|
||||
|
||||
+ (BOOL)validateImpulseFile:(NSURL *)url {
|
||||
id<CogSource> source = [AudioSource audioSourceForURL:url];
|
||||
if(!source)
|
||||
return NO;
|
||||
|
||||
if(![source open:url])
|
||||
return NO;
|
||||
|
||||
id<CogDecoder> decoder = [AudioDecoder audioDecoderForSource:source];
|
||||
|
||||
if(decoder == nil) {
|
||||
[source close];
|
||||
source = nil;
|
||||
return NO;
|
||||
}
|
||||
|
||||
if(![decoder open:source]) {
|
||||
decoder = nil;
|
||||
[source close];
|
||||
source = nil;
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSDictionary *properties = [decoder properties];
|
||||
|
||||
[decoder close];
|
||||
decoder = nil;
|
||||
[source close];
|
||||
source = nil;
|
||||
|
||||
int impulseChannels = [[properties objectForKey:@"channels"] intValue];
|
||||
|
||||
if([[properties objectForKey:@"floatingPoint"] boolValue] != YES ||
|
||||
[[properties objectForKey:@"bitsPerSample"] intValue] != 32 ||
|
||||
!([[properties objectForKey:@"endian"] isEqualToString:@"host"] ||
|
||||
[[properties objectForKey:@"endian"] isEqualToString:@"little"]) ||
|
||||
(impulseChannels != 14 && impulseChannels != 7))
|
||||
return NO;
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (id)initWithImpulseFile:(NSURL *)url forSampleRate:(double)sampleRate withInputChannels:(size_t)channels withConfig:(uint32_t)config {
|
||||
self = [super init];
|
||||
|
||||
if(self) {
|
||||
id<CogSource> source = [AudioSource audioSourceForURL:url];
|
||||
if(!source)
|
||||
return nil;
|
||||
|
||||
if(![source open:url])
|
||||
return nil;
|
||||
|
||||
id<CogDecoder> decoder = [AudioDecoder audioDecoderForSource:source];
|
||||
|
||||
if(decoder == nil) {
|
||||
[source close];
|
||||
source = nil;
|
||||
return nil;
|
||||
}
|
||||
|
||||
if(![decoder open:source]) {
|
||||
decoder = nil;
|
||||
[source close];
|
||||
source = nil;
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSDictionary *properties = [decoder properties];
|
||||
|
||||
double sampleRateOfSource = [[properties objectForKey:@"sampleRate"] doubleValue];
|
||||
|
||||
int sampleCount = [[properties objectForKey:@"totalFrames"] intValue];
|
||||
int impulseChannels = [[properties objectForKey:@"channels"] intValue];
|
||||
|
||||
if([[properties objectForKey:@"floatingPoint"] boolValue] != YES ||
|
||||
[[properties objectForKey:@"bitsPerSample"] intValue] != 32 ||
|
||||
!([[properties objectForKey:@"endian"] isEqualToString:@"host"] ||
|
||||
[[properties objectForKey:@"endian"] isEqualToString:@"little"]) ||
|
||||
(impulseChannels != 14 && impulseChannels != 7)) {
|
||||
[decoder close];
|
||||
decoder = nil;
|
||||
[source close];
|
||||
source = nil;
|
||||
return nil;
|
||||
}
|
||||
|
||||
float *impulseBuffer = (float *)pffft_aligned_malloc(sampleCount * sizeof(float) * impulseChannels);
|
||||
if(!impulseBuffer) {
|
||||
[decoder close];
|
||||
decoder = nil;
|
||||
[source close];
|
||||
source = nil;
|
||||
return nil;
|
||||
}
|
||||
|
||||
if([decoder readAudio:impulseBuffer frames:sampleCount] != sampleCount) {
|
||||
pffft_aligned_free(impulseBuffer);
|
||||
[decoder close];
|
||||
decoder = nil;
|
||||
[source close];
|
||||
source = nil;
|
||||
return nil;
|
||||
}
|
||||
|
||||
[decoder close];
|
||||
decoder = nil;
|
||||
[source close];
|
||||
source = nil;
|
||||
|
||||
if(sampleRateOfSource != sampleRate) {
|
||||
double sampleRatio = sampleRate / sampleRateOfSource;
|
||||
int resampledCount = (int)ceil((double)sampleCount * sampleRatio);
|
||||
|
||||
r8bstate *_r8bstate = new r8bstate(impulseChannels, 1024, sampleRateOfSource, sampleRate);
|
||||
|
||||
unsigned long PRIME_LEN_ = MAX(sampleRateOfSource / 20, 1024u);
|
||||
PRIME_LEN_ = MIN(PRIME_LEN_, 16384u);
|
||||
PRIME_LEN_ = MAX(PRIME_LEN_, 2 * LPC_ORDER + 1);
|
||||
|
||||
unsigned int N_samples_to_add_ = sampleRateOfSource;
|
||||
unsigned int N_samples_to_drop_ = sampleRate;
|
||||
|
||||
samples_len(&N_samples_to_add_, &N_samples_to_drop_, 20, 8192u);
|
||||
|
||||
int resamplerLatencyIn = (int)N_samples_to_add_;
|
||||
int resamplerLatencyOut = (int)N_samples_to_drop_;
|
||||
|
||||
float *tempImpulse = (float *)pffft_aligned_malloc((sampleCount + resamplerLatencyIn * 2 + 1024) * sizeof(float) * impulseChannels);
|
||||
if(!tempImpulse) {
|
||||
pffft_aligned_free(impulseBuffer);
|
||||
return nil;
|
||||
}
|
||||
|
||||
resampledCount += resamplerLatencyOut * 2 + 1024;
|
||||
|
||||
float *resampledImpulse = (float *)pffft_aligned_malloc(resampledCount * sizeof(float) * impulseChannels);
|
||||
if(!resampledImpulse) {
|
||||
pffft_aligned_free(impulseBuffer);
|
||||
pffft_aligned_free(tempImpulse);
|
||||
return nil;
|
||||
}
|
||||
|
||||
size_t prime = MIN(sampleCount, PRIME_LEN_);
|
||||
|
||||
void *extrapolate_buffer = NULL;
|
||||
size_t extrapolate_buffer_size = 0;
|
||||
|
||||
memcpy(tempImpulse + resamplerLatencyIn * impulseChannels, impulseBuffer, sampleCount * sizeof(float) * impulseChannels);
|
||||
lpc_extrapolate_bkwd(tempImpulse + N_samples_to_add_ * impulseChannels, sampleCount, prime, impulseChannels, LPC_ORDER, N_samples_to_add_, &extrapolate_buffer, &extrapolate_buffer_size);
|
||||
lpc_extrapolate_fwd(tempImpulse + N_samples_to_add_ * impulseChannels, sampleCount, prime, impulseChannels, LPC_ORDER, N_samples_to_add_, &extrapolate_buffer, &extrapolate_buffer_size);
|
||||
free(extrapolate_buffer);
|
||||
|
||||
size_t inputDone = 0;
|
||||
size_t outputDone = 0;
|
||||
|
||||
outputDone = _r8bstate->resample(tempImpulse, sampleCount + N_samples_to_add_ * 2, &inputDone, resampledImpulse, resampledCount);
|
||||
|
||||
if(outputDone < resampledCount) {
|
||||
outputDone += _r8bstate->flush(resampledImpulse + outputDone * impulseChannels, resampledCount - outputDone);
|
||||
}
|
||||
|
||||
delete _r8bstate;
|
||||
|
||||
outputDone -= N_samples_to_drop_ * 2;
|
||||
|
||||
memmove(resampledImpulse, resampledImpulse + N_samples_to_drop_ * impulseChannels, outputDone * sizeof(float) * impulseChannels);
|
||||
|
||||
pffft_aligned_free(tempImpulse);
|
||||
pffft_aligned_free(impulseBuffer);
|
||||
impulseBuffer = resampledImpulse;
|
||||
sampleCount = (int)outputDone;
|
||||
|
||||
// Normalize resampled impulse by sample ratio
|
||||
float fSampleRatio = (float)sampleRatio;
|
||||
vDSP_vsdiv(impulseBuffer, 1, &fSampleRatio, impulseBuffer, 1, sampleCount * impulseChannels);
|
||||
}
|
||||
|
||||
channelCount = channels;
|
||||
|
||||
bufferSize = 512;
|
||||
fftSize = sampleCount + bufferSize;
|
||||
|
||||
fftSize = (size_t)pffftd_next_power_of_two((int)fftSize);
|
||||
|
||||
float *deinterleavedImpulseBuffer = (float *)pffft_aligned_malloc(fftSize * sizeof(float) * impulseChannels);
|
||||
if(!deinterleavedImpulseBuffer) {
|
||||
pffft_aligned_free(impulseBuffer);
|
||||
return nil;
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < impulseChannels; ++i) {
|
||||
cblas_scopy(sampleCount, impulseBuffer + i, impulseChannels, deinterleavedImpulseBuffer + i * fftSize, 1);
|
||||
vDSP_vclr(deinterleavedImpulseBuffer + i * fftSize + sampleCount, 1, fftSize - sampleCount);
|
||||
}
|
||||
|
||||
pffft_aligned_free(impulseBuffer);
|
||||
|
||||
paddedBufferSize = fftSize;
|
||||
|
||||
fftSetup = pffft_new_setup((int)fftSize, PFFFT_REAL);
|
||||
if(!fftSetup) {
|
||||
pffft_aligned_free(deinterleavedImpulseBuffer);
|
||||
return nil;
|
||||
}
|
||||
|
||||
workBuffer = (float *)pffft_aligned_malloc(sizeof(float) * fftSize);
|
||||
if(!workBuffer) {
|
||||
pffft_aligned_free(deinterleavedImpulseBuffer);
|
||||
return nil;
|
||||
}
|
||||
|
||||
paddedSignal = (float *)pffft_aligned_malloc(sizeof(float) * paddedBufferSize);
|
||||
if(!paddedSignal) {
|
||||
pffft_aligned_free(deinterleavedImpulseBuffer);
|
||||
return nil;
|
||||
}
|
||||
|
||||
impulse_responses = (float **)calloc(sizeof(float *), channels * 2);
|
||||
if(!impulse_responses) {
|
||||
pffft_aligned_free(deinterleavedImpulseBuffer);
|
||||
return nil;
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < channels; ++i) {
|
||||
impulse_responses[i * 2 + 0] = (float *)pffft_aligned_malloc(sizeof(float) * fftSize * 2);
|
||||
impulse_responses[i * 2 + 1] = (float *)pffft_aligned_malloc(sizeof(float) * fftSize * 2);
|
||||
|
||||
if(!impulse_responses[i * 2 + 0] || !impulse_responses[i * 2 + 1]) {
|
||||
pffft_aligned_free(deinterleavedImpulseBuffer);
|
||||
return nil;
|
||||
}
|
||||
|
||||
uint32_t channelFlag = [AudioChunk extractChannelFlag:(uint32_t)i fromConfig:config];
|
||||
uint32_t channelIndex = [AudioChunk findChannelIndex:channelFlag];
|
||||
|
||||
int leftInChannel = speaker_not_present;
|
||||
int rightInChannel = speaker_not_present;
|
||||
|
||||
if(impulseChannels == 7) {
|
||||
if(channelIndex <= max_speaker_index) {
|
||||
leftInChannel = speakers_to_hesuvi_7[channelIndex][0];
|
||||
rightInChannel = speakers_to_hesuvi_7[channelIndex][1];
|
||||
}
|
||||
} else {
|
||||
if(channelIndex <= max_speaker_index) {
|
||||
leftInChannel = speakers_to_hesuvi_14[channelIndex][0];
|
||||
rightInChannel = speakers_to_hesuvi_14[channelIndex][1];
|
||||
}
|
||||
}
|
||||
|
||||
if(leftInChannel == speaker_is_back_center || rightInChannel == speaker_is_back_center) {
|
||||
if(impulseChannels == 7) {
|
||||
cblas_scopy((int)fftSize, deinterleavedImpulseBuffer + 4 * fftSize, 1, impulse_responses[i * 2 + 0], 1);
|
||||
vDSP_vadd(impulse_responses[i * 2 + 0], 1, deinterleavedImpulseBuffer + 5 * fftSize, 1, impulse_responses[i * 2 + 0], 1, fftSize);
|
||||
cblas_scopy((int)fftSize, impulse_responses[i * 2 + 0], 1, impulse_responses[i * 2 + 1], 1);
|
||||
} else {
|
||||
cblas_scopy((int)fftSize, deinterleavedImpulseBuffer + 4 * fftSize, 1, impulse_responses[i * 2 + 0], 1);
|
||||
vDSP_vadd(impulse_responses[i * 2 + 0], 1, deinterleavedImpulseBuffer + 12 * fftSize, 1, impulse_responses[i * 2 + 0], 1, fftSize);
|
||||
|
||||
cblas_scopy((int)fftSize, deinterleavedImpulseBuffer + 5 * fftSize, 1, impulse_responses[i * 2 + 1], 1);
|
||||
vDSP_vadd(impulse_responses[i * 2 + 1], 1, deinterleavedImpulseBuffer + 11 * fftSize, 1, impulse_responses[i * 2 + 1], 1, fftSize);
|
||||
}
|
||||
} else if(leftInChannel == speaker_not_present || rightInChannel == speaker_not_present) {
|
||||
vDSP_vclr(impulse_responses[i * 2 + 0], 1, fftSize);
|
||||
vDSP_vclr(impulse_responses[i * 2 + 1], 1, fftSize);
|
||||
} else {
|
||||
cblas_scopy((int)fftSize, deinterleavedImpulseBuffer + leftInChannel * fftSize, 1, impulse_responses[i * 2 + 0], 1);
|
||||
cblas_scopy((int)fftSize, deinterleavedImpulseBuffer + rightInChannel * fftSize, 1, impulse_responses[i * 2 + 1], 1);
|
||||
}
|
||||
|
||||
pffft_transform(fftSetup, impulse_responses[i * 2 + 0], impulse_responses[i * 2 + 0], workBuffer, PFFFT_FORWARD);
|
||||
pffft_transform(fftSetup, impulse_responses[i * 2 + 1], impulse_responses[i * 2 + 1], workBuffer, PFFFT_FORWARD);
|
||||
}
|
||||
|
||||
pffft_aligned_free(deinterleavedImpulseBuffer);
|
||||
|
||||
left_result = (float *)pffft_aligned_malloc(sizeof(float) * fftSize);
|
||||
right_result = (float *)pffft_aligned_malloc(sizeof(float) * fftSize);
|
||||
if(!left_result || !right_result)
|
||||
return nil;
|
||||
|
||||
prevInputs = (float **)calloc(sizeof(float *), channels);
|
||||
if(!prevInputs) {
|
||||
return nil;
|
||||
}
|
||||
for(size_t i = 0; i < channels; ++i) {
|
||||
prevInputs[i] = (float *)pffft_aligned_malloc(sizeof(float) * fftSize);
|
||||
if(!prevInputs[i]) {
|
||||
return nil;
|
||||
}
|
||||
vDSP_vclr(prevInputs[i], 1, fftSize);
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
if(fftSetup) pffft_destroy_setup(fftSetup);
|
||||
|
||||
pffft_aligned_free(workBuffer);
|
||||
|
||||
pffft_aligned_free(paddedSignal);
|
||||
|
||||
if(impulse_responses) {
|
||||
for(size_t i = 0; i < channelCount * 2; ++i) {
|
||||
pffft_aligned_free(impulse_responses[i]);
|
||||
}
|
||||
free(impulse_responses);
|
||||
}
|
||||
|
||||
if(prevInputs) {
|
||||
for(size_t i = 0; i < channelCount; ++i) {
|
||||
pffft_aligned_free(prevInputs[i]);
|
||||
}
|
||||
free(prevInputs);
|
||||
}
|
||||
|
||||
pffft_aligned_free(left_result);
|
||||
pffft_aligned_free(right_result);
|
||||
}
|
||||
|
||||
- (void)process:(const float *)inBuffer sampleCount:(size_t)count toBuffer:(float *)outBuffer {
|
||||
const float scale = 1.0 / ((float)fftSize);
|
||||
|
||||
while(count > 0) {
|
||||
const size_t countToDo = (count > bufferSize) ? bufferSize : count;
|
||||
const size_t outOffset = fftSize - countToDo;
|
||||
|
||||
vDSP_vclr(left_result, 1, fftSize);
|
||||
vDSP_vclr(right_result, 1, fftSize);
|
||||
|
||||
for(size_t i = 0; i < channelCount; ++i) {
|
||||
cblas_scopy((int)outOffset, prevInputs[i] + countToDo, 1, paddedSignal, 1);
|
||||
cblas_scopy((int)countToDo, inBuffer + i, (int)channelCount, paddedSignal + outOffset, 1);
|
||||
cblas_scopy((int)fftSize, paddedSignal, 1, prevInputs[i], 1);
|
||||
|
||||
pffft_transform(fftSetup, paddedSignal, paddedSignal, workBuffer, PFFFT_FORWARD);
|
||||
|
||||
pffft_zconvolve_accumulate(fftSetup, paddedSignal, impulse_responses[i * 2 + 0], left_result, 1.0);
|
||||
pffft_zconvolve_accumulate(fftSetup, paddedSignal, impulse_responses[i * 2 + 1], right_result, 1.0);
|
||||
}
|
||||
|
||||
pffft_transform(fftSetup, left_result, left_result, workBuffer, PFFFT_BACKWARD);
|
||||
pffft_transform(fftSetup, right_result, right_result, workBuffer, PFFFT_BACKWARD);
|
||||
|
||||
vDSP_vsmul(left_result + outOffset, 1, &scale, left_result + outOffset, 1, countToDo);
|
||||
vDSP_vsmul(right_result + outOffset, 1, &scale, right_result + outOffset, 1, countToDo);
|
||||
|
||||
cblas_scopy((int)countToDo, left_result + outOffset, 1, outBuffer + 0, 2);
|
||||
cblas_scopy((int)countToDo, right_result + outOffset, 1, outBuffer + 1, 2);
|
||||
|
||||
inBuffer += countToDo * channelCount;
|
||||
outBuffer += countToDo * 2;
|
||||
|
||||
count -= countToDo;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)reset {
|
||||
for(size_t i = 0; i < channelCount; ++i) {
|
||||
vDSP_vclr(prevInputs[i], 1, fftSize);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
|
@ -34,6 +34,7 @@
|
|||
Semaphore *exitAtTheEndOfTheStream;
|
||||
}
|
||||
@property(readonly) Semaphore *exitAtTheEndOfTheStream;
|
||||
@property(readonly) BOOL threadExited;
|
||||
|
||||
- (BOOL)openWithSource:(id<CogSource>)source;
|
||||
- (BOOL)openWithDecoder:(id<CogDecoder>)d;
|
||||
|
|
|
@ -21,12 +21,14 @@
|
|||
|
||||
static void *kInputNodeContext = &kInputNodeContext;
|
||||
|
||||
@synthesize threadExited;
|
||||
@synthesize exitAtTheEndOfTheStream;
|
||||
|
||||
- (id)initWithController:(id)c previous:(id)p {
|
||||
self = [super initWithController:c previous:p];
|
||||
if(self) {
|
||||
exitAtTheEndOfTheStream = [[Semaphore alloc] init];
|
||||
threadExited = NO;
|
||||
}
|
||||
|
||||
return self;
|
||||
|
@ -142,10 +144,6 @@ static void *kInputNodeContext = &kInputNodeContext;
|
|||
}
|
||||
|
||||
- (void)process {
|
||||
int amountInBuffer = 0;
|
||||
int bytesInBuffer = 0;
|
||||
void *inputBuffer = malloc(CHUNK_SIZE * 8 * 18); // Maximum 18 channels, dunno what we'll receive
|
||||
|
||||
BOOL shouldClose = YES;
|
||||
BOOL seekError = NO;
|
||||
|
||||
|
@ -165,7 +163,6 @@ static void *kInputNodeContext = &kInputNodeContext;
|
|||
ConverterNode *converter = [bufferChain converter];
|
||||
DLog(@"SEEKING! Resetting Buffer");
|
||||
|
||||
amountInBuffer = 0;
|
||||
// This resets the converter's buffer
|
||||
[self resetBuffer];
|
||||
[converter resetBuffer];
|
||||
|
@ -174,7 +171,9 @@ static void *kInputNodeContext = &kInputNodeContext;
|
|||
DLog(@"Reset buffer!");
|
||||
|
||||
DLog(@"SEEKING!");
|
||||
seekError = [decoder seek:seekFrame] < 0;
|
||||
@autoreleasepool {
|
||||
seekError = [decoder seek:seekFrame] < 0;
|
||||
}
|
||||
|
||||
shouldSeek = NO;
|
||||
DLog(@"Seeked! Resetting Buffer");
|
||||
|
@ -185,43 +184,39 @@ static void *kInputNodeContext = &kInputNodeContext;
|
|||
}
|
||||
}
|
||||
|
||||
if(amountInBuffer < CHUNK_SIZE) {
|
||||
int framesToRead = CHUNK_SIZE - amountInBuffer;
|
||||
int framesRead;
|
||||
AudioChunk *chunk;
|
||||
@autoreleasepool {
|
||||
chunk = [decoder readAudio];
|
||||
}
|
||||
|
||||
if(chunk && [chunk frameCount]) {
|
||||
@autoreleasepool {
|
||||
framesRead = [decoder readAudio:((char *)inputBuffer) + bytesInBuffer frames:framesToRead];
|
||||
[self writeChunk:chunk];
|
||||
chunk = nil;
|
||||
}
|
||||
} else {
|
||||
DLog(@"End of stream? %@", [self properties]);
|
||||
|
||||
endOfStream = YES;
|
||||
shouldClose = [controller endOfInputReached]; // Lets us know if we should keep going or not (occassionally, for track changes within a file)
|
||||
DLog(@"closing? is %i", shouldClose);
|
||||
|
||||
// Move this here, so that the above endOfInputReached has a chance to queue another track before starting output
|
||||
// Technically, the output should still play out its buffer first before checking if it should stop
|
||||
if(initialBufferFilled == NO) {
|
||||
[controller initialBufferFilled:self];
|
||||
}
|
||||
|
||||
if(framesRead > 0 && !seekError) {
|
||||
amountInBuffer += framesRead;
|
||||
bytesInBuffer += framesRead * bytesPerFrame;
|
||||
[self writeData:inputBuffer amount:bytesInBuffer];
|
||||
amountInBuffer = 0;
|
||||
bytesInBuffer = 0;
|
||||
// wait before exiting, as we might still get seeking request
|
||||
DLog("InputNode: Before wait")
|
||||
[exitAtTheEndOfTheStream waitIndefinitely];
|
||||
DLog("InputNode: After wait, should seek = %d", shouldSeek);
|
||||
if(shouldSeek) {
|
||||
endOfStream = NO;
|
||||
shouldClose = NO;
|
||||
continue;
|
||||
} else {
|
||||
DLog(@"End of stream? %@", [self properties]);
|
||||
|
||||
endOfStream = YES;
|
||||
shouldClose = [controller endOfInputReached]; // Lets us know if we should keep going or not (occassionally, for track changes within a file)
|
||||
DLog(@"closing? is %i", shouldClose);
|
||||
|
||||
// Move this here, so that the above endOfInputReached has a chance to queue another track before starting output
|
||||
// Technically, the output should still play out its buffer first before checking if it should stop
|
||||
if(initialBufferFilled == NO) {
|
||||
[controller initialBufferFilled:self];
|
||||
}
|
||||
|
||||
// wait before exiting, as we might still get seeking request
|
||||
DLog("InputNode: Before wait")
|
||||
[exitAtTheEndOfTheStream waitIndefinitely];
|
||||
DLog("InputNode: After wait, should seek = %d", shouldSeek) if(shouldSeek) {
|
||||
endOfStream = NO;
|
||||
shouldClose = NO;
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -229,9 +224,8 @@ static void *kInputNodeContext = &kInputNodeContext;
|
|||
if(shouldClose)
|
||||
[decoder close];
|
||||
|
||||
free(inputBuffer);
|
||||
|
||||
[exitAtTheEndOfTheStream signal];
|
||||
threadExited = YES;
|
||||
|
||||
DLog("Input node thread stopping");
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
//
|
||||
|
||||
#import "ChunkList.h"
|
||||
#import "Semaphore.h"
|
||||
#import <CogAudio/CogSemaphore.h>
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#import <os/workgroup.h>
|
||||
|
@ -19,7 +19,7 @@
|
|||
ChunkList *buffer;
|
||||
Semaphore *semaphore;
|
||||
|
||||
NSRecursiveLock *accessLock;
|
||||
NSLock *accessLock;
|
||||
|
||||
id __weak previousNode;
|
||||
id __weak controller;
|
||||
|
@ -37,7 +37,9 @@
|
|||
- (id _Nullable)initWithController:(id _Nonnull)c previous:(id _Nullable)p;
|
||||
|
||||
- (void)writeData:(const void *_Nonnull)ptr amount:(size_t)a;
|
||||
- (void)writeChunk:(AudioChunk *_Nonnull)chunk;
|
||||
- (AudioChunk *_Nonnull)readChunk:(size_t)maxFrames;
|
||||
- (AudioChunk *_Nonnull)readChunkAsFloat32:(size_t)maxFrames;
|
||||
|
||||
- (BOOL)peekFormat:(AudioStreamBasicDescription *_Nonnull)format channelConfig:(uint32_t *_Nonnull)config;
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
#import "BufferChain.h"
|
||||
#import "Logging.h"
|
||||
|
||||
#import "OutputCoreAudio.h"
|
||||
#import "OutputAVFoundation.h"
|
||||
|
||||
#import <pthread.h>
|
||||
|
||||
|
@ -25,7 +25,7 @@
|
|||
buffer = [[ChunkList alloc] initWithMaximumDuration:3.0];
|
||||
semaphore = [[Semaphore alloc] init];
|
||||
|
||||
accessLock = [[NSRecursiveLock alloc] init];
|
||||
accessLock = [[NSLock alloc] init];
|
||||
|
||||
initialBufferFilled = NO;
|
||||
|
||||
|
@ -91,6 +91,35 @@
|
|||
[accessLock unlock];
|
||||
}
|
||||
|
||||
- (void)writeChunk:(AudioChunk *)chunk {
|
||||
[accessLock lock];
|
||||
|
||||
const double chunkDuration = [chunk duration];
|
||||
double durationLeft = [buffer maxDuration] - [buffer listDuration];
|
||||
|
||||
while(shouldContinue == YES && chunkDuration > durationLeft) {
|
||||
if(durationLeft < chunkDuration) {
|
||||
if(initialBufferFilled == NO) {
|
||||
initialBufferFilled = YES;
|
||||
if([controller respondsToSelector:@selector(initialBufferFilled:)])
|
||||
[controller performSelector:@selector(initialBufferFilled:) withObject:self];
|
||||
}
|
||||
}
|
||||
|
||||
if(durationLeft < chunkDuration || shouldReset) {
|
||||
[accessLock unlock];
|
||||
[semaphore wait];
|
||||
[accessLock lock];
|
||||
}
|
||||
|
||||
durationLeft = [buffer maxDuration] - [buffer listDuration];
|
||||
}
|
||||
|
||||
[buffer addChunk:chunk];
|
||||
|
||||
[accessLock unlock];
|
||||
}
|
||||
|
||||
// Should be overwriten by subclass.
|
||||
- (void)process {
|
||||
}
|
||||
|
@ -146,6 +175,41 @@
|
|||
return ret;
|
||||
}
|
||||
|
||||
- (AudioChunk *)readChunkAsFloat32:(size_t)maxFrames {
|
||||
[accessLock lock];
|
||||
|
||||
if([[previousNode buffer] isEmpty] && [previousNode endOfStream] == YES) {
|
||||
endOfStream = YES;
|
||||
[accessLock unlock];
|
||||
return [[AudioChunk alloc] init];
|
||||
}
|
||||
|
||||
if([previousNode shouldReset] == YES) {
|
||||
@autoreleasepool {
|
||||
[buffer reset];
|
||||
}
|
||||
|
||||
shouldReset = YES;
|
||||
[previousNode setShouldReset:NO];
|
||||
|
||||
[[previousNode semaphore] signal];
|
||||
}
|
||||
|
||||
AudioChunk *ret;
|
||||
|
||||
@autoreleasepool {
|
||||
ret = [[previousNode buffer] removeSamplesAsFloat32:maxFrames];
|
||||
}
|
||||
|
||||
[accessLock unlock];
|
||||
|
||||
if([ret frameCount]) {
|
||||
[[previousNode semaphore] signal];
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
- (void)launchThread {
|
||||
[NSThread detachNewThreadSelector:@selector(threadEntry:) toTarget:self withObject:nil];
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
#import <CoreAudio/AudioHardware.h>
|
||||
|
||||
#import "Node.h"
|
||||
#import "OutputCoreAudio.h"
|
||||
#import "OutputAVFoundation.h"
|
||||
|
||||
@interface OutputNode : Node {
|
||||
AudioStreamBasicDescription format;
|
||||
|
@ -21,7 +21,7 @@
|
|||
|
||||
double amountPlayed;
|
||||
double amountPlayedInterval;
|
||||
OutputCoreAudio *output;
|
||||
OutputAVFoundation *output;
|
||||
|
||||
BOOL paused;
|
||||
BOOL started;
|
||||
|
@ -39,6 +39,7 @@
|
|||
- (void)resetAmountPlayed;
|
||||
- (void)resetAmountPlayedInterval;
|
||||
|
||||
- (BOOL)selectNextBuffer;
|
||||
- (void)endOfInputPlayed;
|
||||
|
||||
- (BOOL)chainQueueHasTracks;
|
||||
|
@ -50,6 +51,8 @@
|
|||
- (void)close;
|
||||
- (void)seek:(double)time;
|
||||
|
||||
- (double)latency;
|
||||
|
||||
- (AudioChunk *)readChunk:(size_t)amount;
|
||||
|
||||
- (void)setFormat:(AudioStreamBasicDescription *)f channelConfig:(uint32_t)channelConfig;
|
||||
|
@ -60,6 +63,8 @@
|
|||
|
||||
- (void)setShouldContinue:(BOOL)s;
|
||||
|
||||
- (void)setShouldPlayOutBuffer:(BOOL)s;
|
||||
|
||||
- (void)pause;
|
||||
- (void)resume;
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
#import "OutputNode.h"
|
||||
#import "AudioPlayer.h"
|
||||
#import "BufferChain.h"
|
||||
#import "OutputCoreAudio.h"
|
||||
#import "OutputAVFoundation.h"
|
||||
|
||||
#import "Logging.h"
|
||||
|
||||
|
@ -23,7 +23,7 @@
|
|||
started = NO;
|
||||
intervalReported = NO;
|
||||
|
||||
output = [[OutputCoreAudio alloc] initWithController:self];
|
||||
output = [[OutputAVFoundation alloc] initWithController:self];
|
||||
|
||||
[output setup];
|
||||
}
|
||||
|
@ -68,6 +68,10 @@
|
|||
intervalReported = NO;
|
||||
}
|
||||
|
||||
- (BOOL)selectNextBuffer {
|
||||
return [controller selectNextBuffer];
|
||||
}
|
||||
|
||||
- (void)endOfInputPlayed {
|
||||
if(!intervalReported) {
|
||||
intervalReported = YES;
|
||||
|
@ -99,6 +103,14 @@
|
|||
}
|
||||
}
|
||||
|
||||
- (BOOL)peekFormat:(nonnull AudioStreamBasicDescription *)format channelConfig:(nonnull uint32_t *)config {
|
||||
@autoreleasepool {
|
||||
[self setPreviousNode:[[controller bufferChain] finalNode]];
|
||||
|
||||
return [super peekFormat:format channelConfig:config];
|
||||
}
|
||||
}
|
||||
|
||||
- (double)amountPlayed {
|
||||
return amountPlayed;
|
||||
}
|
||||
|
@ -137,8 +149,6 @@
|
|||
format.mBytesPerPacket = format.mBytesPerFrame * format.mFramesPerPacket;
|
||||
channelConfig = config;
|
||||
|
||||
[converter setOutputFormat:format
|
||||
outputConfig:channelConfig];
|
||||
[converter inputFormatDidChange:[bufferChain inputFormat] inputConfig:[bufferChain inputConfig]];
|
||||
}
|
||||
}
|
||||
|
@ -160,6 +170,10 @@
|
|||
// [output stop];
|
||||
}
|
||||
|
||||
- (void)setShouldPlayOutBuffer:(BOOL)s {
|
||||
[output setShouldPlayOutBuffer:s];
|
||||
}
|
||||
|
||||
- (BOOL)isPaused {
|
||||
return paused;
|
||||
}
|
||||
|
@ -184,4 +198,8 @@
|
|||
[controller restartPlaybackAtCurrentPosition];
|
||||
}
|
||||
|
||||
- (double)latency {
|
||||
return [output latency];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
#import "ThirdParty/deadbeef/fft.h"
|
|
@ -25,11 +25,11 @@
|
|||
17D21CA80B8BE4BA00D1EBDE /* Node.m in Sources */ = {isa = PBXBuildFile; fileRef = 17D21C7D0B8BE4BA00D1EBDE /* Node.m */; };
|
||||
17D21CA90B8BE4BA00D1EBDE /* OutputNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 17D21C7E0B8BE4BA00D1EBDE /* OutputNode.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
17D21CAA0B8BE4BA00D1EBDE /* OutputNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 17D21C7F0B8BE4BA00D1EBDE /* OutputNode.m */; };
|
||||
17D21CC50B8BE4BA00D1EBDE /* OutputCoreAudio.h in Headers */ = {isa = PBXBuildFile; fileRef = 17D21C9C0B8BE4BA00D1EBDE /* OutputCoreAudio.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
17D21CC60B8BE4BA00D1EBDE /* OutputCoreAudio.m in Sources */ = {isa = PBXBuildFile; fileRef = 17D21C9D0B8BE4BA00D1EBDE /* OutputCoreAudio.m */; };
|
||||
17D21CC50B8BE4BA00D1EBDE /* OutputAVFoundation.h in Headers */ = {isa = PBXBuildFile; fileRef = 17D21C9C0B8BE4BA00D1EBDE /* OutputAVFoundation.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
17D21CC60B8BE4BA00D1EBDE /* OutputAVFoundation.m in Sources */ = {isa = PBXBuildFile; fileRef = 17D21C9D0B8BE4BA00D1EBDE /* OutputAVFoundation.m */; };
|
||||
17D21CC70B8BE4BA00D1EBDE /* Status.h in Headers */ = {isa = PBXBuildFile; fileRef = 17D21C9E0B8BE4BA00D1EBDE /* Status.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
17D21CF30B8BE5EF00D1EBDE /* Semaphore.h in Headers */ = {isa = PBXBuildFile; fileRef = 17D21CF10B8BE5EF00D1EBDE /* Semaphore.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
17D21CF40B8BE5EF00D1EBDE /* Semaphore.m in Sources */ = {isa = PBXBuildFile; fileRef = 17D21CF20B8BE5EF00D1EBDE /* Semaphore.m */; };
|
||||
17D21CF30B8BE5EF00D1EBDE /* CogSemaphore.h in Headers */ = {isa = PBXBuildFile; fileRef = 17D21CF10B8BE5EF00D1EBDE /* CogSemaphore.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
17D21CF40B8BE5EF00D1EBDE /* CogSemaphore.m in Sources */ = {isa = PBXBuildFile; fileRef = 17D21CF20B8BE5EF00D1EBDE /* CogSemaphore.m */; };
|
||||
17D21DAD0B8BE76800D1EBDE /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 17D21DA90B8BE76800D1EBDE /* AudioToolbox.framework */; };
|
||||
17D21DAE0B8BE76800D1EBDE /* AudioUnit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 17D21DAA0B8BE76800D1EBDE /* AudioUnit.framework */; };
|
||||
17D21DAF0B8BE76800D1EBDE /* CoreAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 17D21DAB0B8BE76800D1EBDE /* CoreAudio.framework */; };
|
||||
|
@ -41,6 +41,9 @@
|
|||
17F94DD50B8D0F7000A34E87 /* PluginController.h in Headers */ = {isa = PBXBuildFile; fileRef = 17F94DD30B8D0F7000A34E87 /* PluginController.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
17F94DD60B8D0F7000A34E87 /* PluginController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 17F94DD40B8D0F7000A34E87 /* PluginController.mm */; };
|
||||
17F94DDD0B8D101100A34E87 /* Plugin.h in Headers */ = {isa = PBXBuildFile; fileRef = 17F94DDC0B8D101100A34E87 /* Plugin.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
831A50142865A7FD0049CFE4 /* rsstate.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 831A50132865A7FD0049CFE4 /* rsstate.hpp */; };
|
||||
831A50162865A8800049CFE4 /* rsstate.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 831A50152865A8800049CFE4 /* rsstate.cpp */; };
|
||||
831A50182865A8B30049CFE4 /* rsstate.h in Headers */ = {isa = PBXBuildFile; fileRef = 831A50172865A8B30049CFE4 /* rsstate.h */; };
|
||||
8328995327CB511000D7F028 /* RedundantPlaylistDataStore.m in Sources */ = {isa = PBXBuildFile; fileRef = 8328995127CB510F00D7F028 /* RedundantPlaylistDataStore.m */; };
|
||||
8328995427CB511000D7F028 /* RedundantPlaylistDataStore.h in Headers */ = {isa = PBXBuildFile; fileRef = 8328995227CB511000D7F028 /* RedundantPlaylistDataStore.h */; };
|
||||
8328995727CB51B700D7F028 /* SHA256Digest.h in Headers */ = {isa = PBXBuildFile; fileRef = 8328995527CB51B700D7F028 /* SHA256Digest.h */; };
|
||||
|
@ -48,82 +51,54 @@
|
|||
8328995A27CB51C900D7F028 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8328995927CB51C900D7F028 /* Security.framework */; };
|
||||
8347C7412796C58800FA8A7D /* NSFileHandle+CreateFile.h in Headers */ = {isa = PBXBuildFile; fileRef = 8347C73F2796C58800FA8A7D /* NSFileHandle+CreateFile.h */; };
|
||||
8347C7422796C58800FA8A7D /* NSFileHandle+CreateFile.m in Sources */ = {isa = PBXBuildFile; fileRef = 8347C7402796C58800FA8A7D /* NSFileHandle+CreateFile.m */; };
|
||||
834A41A9287A90AB00EB9D9B /* freesurround_decoder.h in Headers */ = {isa = PBXBuildFile; fileRef = 834A41A5287A90AB00EB9D9B /* freesurround_decoder.h */; };
|
||||
834A41AA287A90AB00EB9D9B /* freesurround_decoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 834A41A6287A90AB00EB9D9B /* freesurround_decoder.cpp */; };
|
||||
834A41AB287A90AB00EB9D9B /* channelmaps.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 834A41A7287A90AB00EB9D9B /* channelmaps.cpp */; };
|
||||
834A41AC287A90AB00EB9D9B /* channelmaps.h in Headers */ = {isa = PBXBuildFile; fileRef = 834A41A8287A90AB00EB9D9B /* channelmaps.h */; };
|
||||
834A41AF287ABD6F00EB9D9B /* FSurroundFilter.h in Headers */ = {isa = PBXBuildFile; fileRef = 834A41AD287ABD6F00EB9D9B /* FSurroundFilter.h */; };
|
||||
834A41B0287ABD6F00EB9D9B /* FSurroundFilter.mm in Sources */ = {isa = PBXBuildFile; fileRef = 834A41AE287ABD6F00EB9D9B /* FSurroundFilter.mm */; };
|
||||
834FD4EB27AF8F380063BC83 /* AudioChunk.h in Headers */ = {isa = PBXBuildFile; fileRef = 834FD4EA27AF8F380063BC83 /* AudioChunk.h */; };
|
||||
834FD4ED27AF91220063BC83 /* AudioChunk.m in Sources */ = {isa = PBXBuildFile; fileRef = 834FD4EC27AF91220063BC83 /* AudioChunk.m */; };
|
||||
834FD4F027AF93680063BC83 /* ChunkList.h in Headers */ = {isa = PBXBuildFile; fileRef = 834FD4EE27AF93680063BC83 /* ChunkList.h */; };
|
||||
834FD4F127AF93680063BC83 /* ChunkList.m in Sources */ = {isa = PBXBuildFile; fileRef = 834FD4EF27AF93680063BC83 /* ChunkList.m */; };
|
||||
834FD4F427AFA2150063BC83 /* Downmix.h in Headers */ = {isa = PBXBuildFile; fileRef = 834FD4F227AFA2150063BC83 /* Downmix.h */; };
|
||||
834FD4F527AFA2150063BC83 /* Downmix.m in Sources */ = {isa = PBXBuildFile; fileRef = 834FD4F327AFA2150063BC83 /* Downmix.m */; };
|
||||
835C88A82797D4D400E28EAE /* LICENSE.LGPL in Resources */ = {isa = PBXBuildFile; fileRef = 835C88A42797D4D400E28EAE /* LICENSE.LGPL */; };
|
||||
835C88A92797D4D400E28EAE /* License.txt in Resources */ = {isa = PBXBuildFile; fileRef = 835C88A52797D4D400E28EAE /* License.txt */; };
|
||||
835C88AA2797D4D400E28EAE /* lpc.c in Sources */ = {isa = PBXBuildFile; fileRef = 835C88A62797D4D400E28EAE /* lpc.c */; };
|
||||
835C88AB2797D4D400E28EAE /* lpc.h in Headers */ = {isa = PBXBuildFile; fileRef = 835C88A72797D4D400E28EAE /* lpc.h */; };
|
||||
835C88AD2797DA5800E28EAE /* util.h in Headers */ = {isa = PBXBuildFile; fileRef = 835C88AC2797DA5800E28EAE /* util.h */; };
|
||||
83504165286447DA006B32CC /* Downmix.h in Headers */ = {isa = PBXBuildFile; fileRef = 83504163286447DA006B32CC /* Downmix.h */; };
|
||||
83504166286447DA006B32CC /* Downmix.m in Sources */ = {isa = PBXBuildFile; fileRef = 83504164286447DA006B32CC /* Downmix.m */; };
|
||||
8350416D28646149006B32CC /* CoreMedia.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8350416C28646149006B32CC /* CoreMedia.framework */; };
|
||||
835C88B1279811A500E28EAE /* hdcd_decode2.h in Headers */ = {isa = PBXBuildFile; fileRef = 835C88AF279811A500E28EAE /* hdcd_decode2.h */; };
|
||||
835C88B2279811A500E28EAE /* hdcd_decode2.c in Sources */ = {isa = PBXBuildFile; fileRef = 835C88B0279811A500E28EAE /* hdcd_decode2.c */; };
|
||||
835EDD7B279FE23A001EDCCE /* HeadphoneFilter.mm in Sources */ = {isa = PBXBuildFile; fileRef = 835EDD7A279FE23A001EDCCE /* HeadphoneFilter.mm */; };
|
||||
835EDD7D279FE307001EDCCE /* HeadphoneFilter.h in Headers */ = {isa = PBXBuildFile; fileRef = 835EDD7C279FE307001EDCCE /* HeadphoneFilter.h */; };
|
||||
835FAC5E27BCA14D00BA8562 /* BadSampleCleaner.h in Headers */ = {isa = PBXBuildFile; fileRef = 835FAC5C27BCA14D00BA8562 /* BadSampleCleaner.h */; };
|
||||
835FAC5F27BCA14D00BA8562 /* BadSampleCleaner.m in Sources */ = {isa = PBXBuildFile; fileRef = 835FAC5D27BCA14D00BA8562 /* BadSampleCleaner.m */; };
|
||||
8363BABE284E428F00E5C9DD /* pffft.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8363BABD284E428F00E5C9DD /* pffft.cpp */; };
|
||||
836DF618298F6F5F00CD0580 /* libsoxr.0.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 836DF615298F6E8900CD0580 /* libsoxr.0.dylib */; };
|
||||
83725A9027AA16C90003F694 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 83725A7B27AA0D8A0003F694 /* Accelerate.framework */; };
|
||||
83725A9127AA16D50003F694 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 83725A7C27AA0D8E0003F694 /* AVFoundation.framework */; };
|
||||
8377C64C27B8C51500E8BC0F /* fft_accelerate.c in Sources */ = {isa = PBXBuildFile; fileRef = 8377C64B27B8C51500E8BC0F /* fft_accelerate.c */; };
|
||||
8377C64E27B8C54400E8BC0F /* fft.h in Headers */ = {isa = PBXBuildFile; fileRef = 8377C64D27B8C54400E8BC0F /* fft.h */; };
|
||||
8377C65227B8CAD100E8BC0F /* VisualizationController.h in Headers */ = {isa = PBXBuildFile; fileRef = 8377C65027B8CAD100E8BC0F /* VisualizationController.h */; };
|
||||
8377C65327B8CAD100E8BC0F /* VisualizationController.m in Sources */ = {isa = PBXBuildFile; fileRef = 8377C65127B8CAD100E8BC0F /* VisualizationController.m */; };
|
||||
8384912718080FF100E7332D /* Logging.h in Headers */ = {isa = PBXBuildFile; fileRef = 8384912618080FF100E7332D /* Logging.h */; };
|
||||
839065F32853338700636FBB /* dsd2float.h in Headers */ = {isa = PBXBuildFile; fileRef = 839065F22853338700636FBB /* dsd2float.h */; };
|
||||
839366671815923C006DD712 /* CogPluginMulti.h in Headers */ = {isa = PBXBuildFile; fileRef = 839366651815923C006DD712 /* CogPluginMulti.h */; };
|
||||
839366681815923C006DD712 /* CogPluginMulti.m in Sources */ = {isa = PBXBuildFile; fileRef = 839366661815923C006DD712 /* CogPluginMulti.m */; };
|
||||
8399CF2C27B5D1D5008751F1 /* NSDictionary+Merge.h in Headers */ = {isa = PBXBuildFile; fileRef = 8399CF2A27B5D1D4008751F1 /* NSDictionary+Merge.h */; };
|
||||
8399CF2D27B5D1D5008751F1 /* NSDictionary+Merge.m in Sources */ = {isa = PBXBuildFile; fileRef = 8399CF2B27B5D1D4008751F1 /* NSDictionary+Merge.m */; };
|
||||
83B69B752845DF6500D2435A /* pffft_double.h in Headers */ = {isa = PBXBuildFile; fileRef = 83B69B6B2845DF6500D2435A /* pffft_double.h */; };
|
||||
83B69B762845DF6500D2435A /* pf_neon_double_from_avx.h in Headers */ = {isa = PBXBuildFile; fileRef = 83B69B6D2845DF6500D2435A /* pf_neon_double_from_avx.h */; };
|
||||
83B69B772845DF6500D2435A /* pf_double.h in Headers */ = {isa = PBXBuildFile; fileRef = 83B69B6E2845DF6500D2435A /* pf_double.h */; };
|
||||
83B69B782845DF6500D2435A /* pf_neon_double.h in Headers */ = {isa = PBXBuildFile; fileRef = 83B69B6F2845DF6500D2435A /* pf_neon_double.h */; };
|
||||
83B69B792845DF6500D2435A /* pf_sse2_double.h in Headers */ = {isa = PBXBuildFile; fileRef = 83B69B702845DF6500D2435A /* pf_sse2_double.h */; };
|
||||
83B69B7A2845DF6500D2435A /* pf_avx_double.h in Headers */ = {isa = PBXBuildFile; fileRef = 83B69B712845DF6500D2435A /* pf_avx_double.h */; };
|
||||
83B69B7B2845DF6500D2435A /* pf_scalar_double.h in Headers */ = {isa = PBXBuildFile; fileRef = 83B69B722845DF6500D2435A /* pf_scalar_double.h */; };
|
||||
83B69B7C2845DF6500D2435A /* pffft_priv_impl.h in Headers */ = {isa = PBXBuildFile; fileRef = 83B69B732845DF6500D2435A /* pffft_priv_impl.h */; };
|
||||
83D40B572852CB3B003BB85C /* pffft_double.c in Sources */ = {isa = PBXBuildFile; fileRef = 83B69B742845DF6500D2435A /* pffft_double.c */; };
|
||||
83F18B1E27D1E8EF00385946 /* CDSPHBDownsampler.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F18ADF27D1E8EF00385946 /* CDSPHBDownsampler.h */; };
|
||||
83F18B3327D1E8EF00385946 /* CDSPSincFilterGen.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F18AF827D1E8EF00385946 /* CDSPSincFilterGen.h */; };
|
||||
83F18B3427D1E8EF00385946 /* r8butil.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F18AF927D1E8EF00385946 /* r8butil.h */; };
|
||||
83F18B3627D1E8EF00385946 /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = 83F18AFB27D1E8EF00385946 /* LICENSE */; };
|
||||
83F18B3727D1E8EF00385946 /* r8bbase.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F18AFC27D1E8EF00385946 /* r8bbase.h */; };
|
||||
83F18B3827D1E8EF00385946 /* CDSPFIRFilter.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F18AFD27D1E8EF00385946 /* CDSPFIRFilter.h */; };
|
||||
83F18B4227D1E8EF00385946 /* CDSPProcessor.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F18B0827D1E8EF00385946 /* CDSPProcessor.h */; };
|
||||
83F18B4327D1E8EF00385946 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 83F18B0927D1E8EF00385946 /* README.md */; };
|
||||
83F18B4427D1E8EF00385946 /* fft4g.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F18B0A27D1E8EF00385946 /* fft4g.h */; };
|
||||
83F18B4527D1E8EF00385946 /* CDSPRealFFT.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F18B0B27D1E8EF00385946 /* CDSPRealFFT.h */; };
|
||||
83F18B4627D1E8EF00385946 /* CDSPFracInterpolator.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F18B0C27D1E8EF00385946 /* CDSPFracInterpolator.h */; };
|
||||
83F18B4E27D1E8F000385946 /* CDSPBlockConvolver.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F18B1727D1E8EF00385946 /* CDSPBlockConvolver.h */; };
|
||||
83F18B5027D1E8F000385946 /* r8bconf.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F18B1927D1E8EF00385946 /* r8bconf.h */; };
|
||||
83F18B5127D1E8F000385946 /* r8bbase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83F18B1A27D1E8EF00385946 /* r8bbase.cpp */; };
|
||||
83F18B5227D1E8F000385946 /* pffft.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F18B1B27D1E8EF00385946 /* pffft.h */; };
|
||||
83F18B5327D1E8F000385946 /* CDSPResampler.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F18B1C27D1E8EF00385946 /* CDSPResampler.h */; };
|
||||
83F18B5427D1E8F000385946 /* CDSPHBUpsampler.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F18B1D27D1E8EF00385946 /* CDSPHBUpsampler.h */; };
|
||||
83F18B5627D1F5E900385946 /* r8bstate.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F18B5527D1F5E900385946 /* r8bstate.h */; };
|
||||
839B83FA286D91ED00F529EE /* VisualizationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 839B83F9286D91ED00F529EE /* VisualizationController.swift */; };
|
||||
839E56E52879450300DFB5F4 /* HrtfData.h in Headers */ = {isa = PBXBuildFile; fileRef = 839E56E12879450300DFB5F4 /* HrtfData.h */; };
|
||||
839E56E62879450300DFB5F4 /* Endianness.h in Headers */ = {isa = PBXBuildFile; fileRef = 839E56E22879450300DFB5F4 /* Endianness.h */; };
|
||||
839E56E72879450300DFB5F4 /* HrtfData.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 839E56E32879450300DFB5F4 /* HrtfData.cpp */; };
|
||||
839E56E82879450300DFB5F4 /* IHrtfData.h in Headers */ = {isa = PBXBuildFile; fileRef = 839E56E42879450300DFB5F4 /* IHrtfData.h */; };
|
||||
839E56EA28794F6300DFB5F4 /* HrtfTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 839E56E928794F6300DFB5F4 /* HrtfTypes.h */; };
|
||||
839E56ED2879515D00DFB5F4 /* HeadphoneFilter.h in Headers */ = {isa = PBXBuildFile; fileRef = 839E56EB2879515D00DFB5F4 /* HeadphoneFilter.h */; };
|
||||
839E56EE2879515D00DFB5F4 /* HeadphoneFilter.mm in Sources */ = {isa = PBXBuildFile; fileRef = 839E56EC2879515D00DFB5F4 /* HeadphoneFilter.mm */; };
|
||||
839E56F7287974A100DFB5F4 /* SandboxBroker.h in Headers */ = {isa = PBXBuildFile; fileRef = 839E56F6287974A100DFB5F4 /* SandboxBroker.h */; };
|
||||
83B74281289E027F005AAC28 /* CogAudio-Bridging-Header.h in Headers */ = {isa = PBXBuildFile; fileRef = 83B74280289E027F005AAC28 /* CogAudio-Bridging-Header.h */; };
|
||||
8DC2EF570486A6940098B216 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7B1FEA5585E11CA2CBB /* Cocoa.framework */; };
|
||||
8E8D3D2F0CBAEE6E00135C1B /* AudioContainer.h in Headers */ = {isa = PBXBuildFile; fileRef = 8E8D3D2D0CBAEE6E00135C1B /* AudioContainer.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
8E8D3D300CBAEE6E00135C1B /* AudioContainer.m in Sources */ = {isa = PBXBuildFile; fileRef = 8E8D3D2E0CBAEE6E00135C1B /* AudioContainer.m */; };
|
||||
8EC1225F0B993BD500C5B3AD /* ConverterNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 8EC1225D0B993BD500C5B3AD /* ConverterNode.h */; };
|
||||
8EC122600B993BD500C5B3AD /* ConverterNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8EC1225E0B993BD500C5B3AD /* ConverterNode.mm */; };
|
||||
8EC122600B993BD500C5B3AD /* ConverterNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 8EC1225E0B993BD500C5B3AD /* ConverterNode.m */; };
|
||||
B0575F2D0D687A0800411D77 /* Helper.h in Headers */ = {isa = PBXBuildFile; fileRef = B0575F2C0D687A0800411D77 /* Helper.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
B0575F300D687A4000411D77 /* Helper.m in Sources */ = {isa = PBXBuildFile; fileRef = B0575F2F0D687A4000411D77 /* Helper.m */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
17D21D2B0B8BE6A200D1EBDE /* CopyFiles */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
83725A8D27AA0DDB0003F694 /* CopyFiles */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
@ -157,11 +132,11 @@
|
|||
17D21C7D0B8BE4BA00D1EBDE /* Node.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = Node.m; sourceTree = "<group>"; };
|
||||
17D21C7E0B8BE4BA00D1EBDE /* OutputNode.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = OutputNode.h; sourceTree = "<group>"; };
|
||||
17D21C7F0B8BE4BA00D1EBDE /* OutputNode.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = OutputNode.m; sourceTree = "<group>"; };
|
||||
17D21C9C0B8BE4BA00D1EBDE /* OutputCoreAudio.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = OutputCoreAudio.h; sourceTree = "<group>"; };
|
||||
17D21C9D0B8BE4BA00D1EBDE /* OutputCoreAudio.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = OutputCoreAudio.m; sourceTree = "<group>"; };
|
||||
17D21C9C0B8BE4BA00D1EBDE /* OutputAVFoundation.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = OutputAVFoundation.h; sourceTree = "<group>"; };
|
||||
17D21C9D0B8BE4BA00D1EBDE /* OutputAVFoundation.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = OutputAVFoundation.m; sourceTree = "<group>"; };
|
||||
17D21C9E0B8BE4BA00D1EBDE /* Status.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Status.h; sourceTree = "<group>"; };
|
||||
17D21CF10B8BE5EF00D1EBDE /* Semaphore.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Semaphore.h; sourceTree = "<group>"; };
|
||||
17D21CF20B8BE5EF00D1EBDE /* Semaphore.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = Semaphore.m; sourceTree = "<group>"; };
|
||||
17D21CF10B8BE5EF00D1EBDE /* CogSemaphore.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = CogSemaphore.h; sourceTree = "<group>"; };
|
||||
17D21CF20B8BE5EF00D1EBDE /* CogSemaphore.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = CogSemaphore.m; sourceTree = "<group>"; };
|
||||
17D21DA90B8BE76800D1EBDE /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = /System/Library/Frameworks/AudioToolbox.framework; sourceTree = "<absolute>"; };
|
||||
17D21DAA0B8BE76800D1EBDE /* AudioUnit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioUnit.framework; path = /System/Library/Frameworks/AudioUnit.framework; sourceTree = "<absolute>"; };
|
||||
17D21DAB0B8BE76800D1EBDE /* CoreAudio.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreAudio.framework; path = /System/Library/Frameworks/CoreAudio.framework; sourceTree = "<absolute>"; };
|
||||
|
@ -174,6 +149,9 @@
|
|||
17F94DD40B8D0F7000A34E87 /* PluginController.mm */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.objcpp; path = PluginController.mm; sourceTree = "<group>"; };
|
||||
17F94DDC0B8D101100A34E87 /* Plugin.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Plugin.h; sourceTree = "<group>"; };
|
||||
32DBCF5E0370ADEE00C91783 /* CogAudio_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CogAudio_Prefix.pch; sourceTree = "<group>"; };
|
||||
831A50132865A7FD0049CFE4 /* rsstate.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = rsstate.hpp; sourceTree = "<group>"; };
|
||||
831A50152865A8800049CFE4 /* rsstate.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = rsstate.cpp; sourceTree = "<group>"; };
|
||||
831A50172865A8B30049CFE4 /* rsstate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = rsstate.h; sourceTree = "<group>"; };
|
||||
8328995127CB510F00D7F028 /* RedundantPlaylistDataStore.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RedundantPlaylistDataStore.m; path = ../../Utils/RedundantPlaylistDataStore.m; sourceTree = "<group>"; };
|
||||
8328995227CB511000D7F028 /* RedundantPlaylistDataStore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RedundantPlaylistDataStore.h; path = ../../Utils/RedundantPlaylistDataStore.h; sourceTree = "<group>"; };
|
||||
8328995527CB51B700D7F028 /* SHA256Digest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SHA256Digest.h; path = ../../Utils/SHA256Digest.h; sourceTree = "<group>"; };
|
||||
|
@ -181,70 +159,50 @@
|
|||
8328995927CB51C900D7F028 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
|
||||
8347C73F2796C58800FA8A7D /* NSFileHandle+CreateFile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSFileHandle+CreateFile.h"; path = "../../Utils/NSFileHandle+CreateFile.h"; sourceTree = "<group>"; };
|
||||
8347C7402796C58800FA8A7D /* NSFileHandle+CreateFile.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSFileHandle+CreateFile.m"; path = "../../Utils/NSFileHandle+CreateFile.m"; sourceTree = "<group>"; };
|
||||
834A41A5287A90AB00EB9D9B /* freesurround_decoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = freesurround_decoder.h; sourceTree = "<group>"; };
|
||||
834A41A6287A90AB00EB9D9B /* freesurround_decoder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = freesurround_decoder.cpp; sourceTree = "<group>"; };
|
||||
834A41A7287A90AB00EB9D9B /* channelmaps.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = channelmaps.cpp; sourceTree = "<group>"; };
|
||||
834A41A8287A90AB00EB9D9B /* channelmaps.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = channelmaps.h; sourceTree = "<group>"; };
|
||||
834A41AD287ABD6F00EB9D9B /* FSurroundFilter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FSurroundFilter.h; sourceTree = "<group>"; };
|
||||
834A41AE287ABD6F00EB9D9B /* FSurroundFilter.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSurroundFilter.mm; sourceTree = "<group>"; };
|
||||
834FD4EA27AF8F380063BC83 /* AudioChunk.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AudioChunk.h; sourceTree = "<group>"; };
|
||||
834FD4EC27AF91220063BC83 /* AudioChunk.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AudioChunk.m; sourceTree = "<group>"; };
|
||||
834FD4EE27AF93680063BC83 /* ChunkList.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ChunkList.h; sourceTree = "<group>"; };
|
||||
834FD4EF27AF93680063BC83 /* ChunkList.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ChunkList.m; sourceTree = "<group>"; };
|
||||
834FD4F227AFA2150063BC83 /* Downmix.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Downmix.h; sourceTree = "<group>"; };
|
||||
834FD4F327AFA2150063BC83 /* Downmix.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Downmix.m; sourceTree = "<group>"; };
|
||||
835C88A42797D4D400E28EAE /* LICENSE.LGPL */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE.LGPL; sourceTree = "<group>"; };
|
||||
835C88A52797D4D400E28EAE /* License.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = License.txt; sourceTree = "<group>"; };
|
||||
835C88A62797D4D400E28EAE /* lpc.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = lpc.c; sourceTree = "<group>"; };
|
||||
835C88A72797D4D400E28EAE /* lpc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = lpc.h; sourceTree = "<group>"; };
|
||||
835C88AC2797DA5800E28EAE /* util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = util.h; path = ThirdParty/lvqcl/util.h; sourceTree = SOURCE_ROOT; };
|
||||
83504163286447DA006B32CC /* Downmix.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Downmix.h; sourceTree = "<group>"; };
|
||||
83504164286447DA006B32CC /* Downmix.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Downmix.m; sourceTree = "<group>"; };
|
||||
8350416C28646149006B32CC /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = SDKROOT; };
|
||||
835C88AF279811A500E28EAE /* hdcd_decode2.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = hdcd_decode2.h; sourceTree = "<group>"; };
|
||||
835C88B0279811A500E28EAE /* hdcd_decode2.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = hdcd_decode2.c; sourceTree = "<group>"; };
|
||||
835EDD7A279FE23A001EDCCE /* HeadphoneFilter.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = HeadphoneFilter.mm; sourceTree = "<group>"; };
|
||||
835EDD7C279FE307001EDCCE /* HeadphoneFilter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HeadphoneFilter.h; sourceTree = "<group>"; };
|
||||
835FAC5C27BCA14D00BA8562 /* BadSampleCleaner.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = BadSampleCleaner.h; path = Utils/BadSampleCleaner.h; sourceTree = SOURCE_ROOT; };
|
||||
835FAC5D27BCA14D00BA8562 /* BadSampleCleaner.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = BadSampleCleaner.m; path = Utils/BadSampleCleaner.m; sourceTree = SOURCE_ROOT; };
|
||||
8363BABD284E428F00E5C9DD /* pffft.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = pffft.cpp; sourceTree = "<group>"; };
|
||||
836DF615298F6E8900CD0580 /* libsoxr.0.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libsoxr.0.dylib; path = ../ThirdParty/soxr/lib/libsoxr.0.dylib; sourceTree = "<group>"; };
|
||||
83725A7B27AA0D8A0003F694 /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = System/Library/Frameworks/Accelerate.framework; sourceTree = SDKROOT; };
|
||||
83725A7C27AA0D8E0003F694 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; };
|
||||
8377C64B27B8C51500E8BC0F /* fft_accelerate.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = fft_accelerate.c; sourceTree = "<group>"; };
|
||||
8377C64D27B8C54400E8BC0F /* fft.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fft.h; sourceTree = "<group>"; };
|
||||
8377C65027B8CAD100E8BC0F /* VisualizationController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VisualizationController.h; sourceTree = "<group>"; };
|
||||
8377C65127B8CAD100E8BC0F /* VisualizationController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VisualizationController.m; sourceTree = "<group>"; };
|
||||
8384912618080FF100E7332D /* Logging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Logging.h; path = ../../Utils/Logging.h; sourceTree = "<group>"; };
|
||||
839065F22853338700636FBB /* dsd2float.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = dsd2float.h; sourceTree = "<group>"; };
|
||||
839366651815923C006DD712 /* CogPluginMulti.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CogPluginMulti.h; sourceTree = "<group>"; };
|
||||
839366661815923C006DD712 /* CogPluginMulti.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CogPluginMulti.m; sourceTree = "<group>"; };
|
||||
8399CF2A27B5D1D4008751F1 /* NSDictionary+Merge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSDictionary+Merge.h"; path = "../../Utils/NSDictionary+Merge.h"; sourceTree = "<group>"; };
|
||||
8399CF2B27B5D1D4008751F1 /* NSDictionary+Merge.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSDictionary+Merge.m"; path = "../../Utils/NSDictionary+Merge.m"; sourceTree = "<group>"; };
|
||||
83B69B6B2845DF6500D2435A /* pffft_double.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pffft_double.h; sourceTree = "<group>"; };
|
||||
83B69B6D2845DF6500D2435A /* pf_neon_double_from_avx.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pf_neon_double_from_avx.h; sourceTree = "<group>"; };
|
||||
83B69B6E2845DF6500D2435A /* pf_double.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pf_double.h; sourceTree = "<group>"; };
|
||||
83B69B6F2845DF6500D2435A /* pf_neon_double.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pf_neon_double.h; sourceTree = "<group>"; };
|
||||
83B69B702845DF6500D2435A /* pf_sse2_double.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pf_sse2_double.h; sourceTree = "<group>"; };
|
||||
83B69B712845DF6500D2435A /* pf_avx_double.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pf_avx_double.h; sourceTree = "<group>"; };
|
||||
83B69B722845DF6500D2435A /* pf_scalar_double.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pf_scalar_double.h; sourceTree = "<group>"; };
|
||||
83B69B732845DF6500D2435A /* pffft_priv_impl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pffft_priv_impl.h; sourceTree = "<group>"; };
|
||||
83B69B742845DF6500D2435A /* pffft_double.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = pffft_double.c; sourceTree = "<group>"; };
|
||||
83F18ADF27D1E8EF00385946 /* CDSPHBDownsampler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDSPHBDownsampler.h; sourceTree = "<group>"; };
|
||||
83F18AF827D1E8EF00385946 /* CDSPSincFilterGen.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDSPSincFilterGen.h; sourceTree = "<group>"; };
|
||||
83F18AF927D1E8EF00385946 /* r8butil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = r8butil.h; sourceTree = "<group>"; };
|
||||
83F18AFB27D1E8EF00385946 /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; };
|
||||
83F18AFC27D1E8EF00385946 /* r8bbase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = r8bbase.h; sourceTree = "<group>"; };
|
||||
83F18AFD27D1E8EF00385946 /* CDSPFIRFilter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDSPFIRFilter.h; sourceTree = "<group>"; };
|
||||
83F18B0827D1E8EF00385946 /* CDSPProcessor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDSPProcessor.h; sourceTree = "<group>"; };
|
||||
83F18B0927D1E8EF00385946 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
|
||||
83F18B0A27D1E8EF00385946 /* fft4g.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fft4g.h; sourceTree = "<group>"; };
|
||||
83F18B0B27D1E8EF00385946 /* CDSPRealFFT.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDSPRealFFT.h; sourceTree = "<group>"; };
|
||||
83F18B0C27D1E8EF00385946 /* CDSPFracInterpolator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDSPFracInterpolator.h; sourceTree = "<group>"; };
|
||||
83F18B1727D1E8EF00385946 /* CDSPBlockConvolver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDSPBlockConvolver.h; sourceTree = "<group>"; };
|
||||
83F18B1827D1E8EF00385946 /* CDSPHBUpsampler.inc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.pascal; path = CDSPHBUpsampler.inc; sourceTree = "<group>"; };
|
||||
83F18B1927D1E8EF00385946 /* r8bconf.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = r8bconf.h; sourceTree = "<group>"; };
|
||||
83F18B1A27D1E8EF00385946 /* r8bbase.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = r8bbase.cpp; sourceTree = "<group>"; };
|
||||
83F18B1B27D1E8EF00385946 /* pffft.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pffft.h; sourceTree = "<group>"; };
|
||||
83F18B1C27D1E8EF00385946 /* CDSPResampler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDSPResampler.h; sourceTree = "<group>"; };
|
||||
83F18B1D27D1E8EF00385946 /* CDSPHBUpsampler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDSPHBUpsampler.h; sourceTree = "<group>"; };
|
||||
83F18B5527D1F5E900385946 /* r8bstate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = r8bstate.h; path = ThirdParty/r8bstate.h; sourceTree = SOURCE_ROOT; };
|
||||
839B83F9286D91ED00F529EE /* VisualizationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisualizationController.swift; sourceTree = "<group>"; };
|
||||
839E56E12879450300DFB5F4 /* HrtfData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HrtfData.h; sourceTree = "<group>"; };
|
||||
839E56E22879450300DFB5F4 /* Endianness.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Endianness.h; sourceTree = "<group>"; };
|
||||
839E56E32879450300DFB5F4 /* HrtfData.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = HrtfData.cpp; sourceTree = "<group>"; };
|
||||
839E56E42879450300DFB5F4 /* IHrtfData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IHrtfData.h; sourceTree = "<group>"; };
|
||||
839E56E928794F6300DFB5F4 /* HrtfTypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HrtfTypes.h; sourceTree = "<group>"; };
|
||||
839E56EB2879515D00DFB5F4 /* HeadphoneFilter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HeadphoneFilter.h; sourceTree = "<group>"; };
|
||||
839E56EC2879515D00DFB5F4 /* HeadphoneFilter.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = HeadphoneFilter.mm; sourceTree = "<group>"; };
|
||||
839E56F6287974A100DFB5F4 /* SandboxBroker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SandboxBroker.h; path = ../Utils/SandboxBroker.h; sourceTree = "<group>"; };
|
||||
83B74280289E027F005AAC28 /* CogAudio-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CogAudio-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
8DC2EF5A0486A6940098B216 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
|
||||
8DC2EF5B0486A6940098B216 /* CogAudio.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CogAudio.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
8E8D3D2D0CBAEE6E00135C1B /* AudioContainer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AudioContainer.h; sourceTree = "<group>"; };
|
||||
8E8D3D2E0CBAEE6E00135C1B /* AudioContainer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AudioContainer.m; sourceTree = "<group>"; };
|
||||
8EC1225D0B993BD500C5B3AD /* ConverterNode.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = ConverterNode.h; sourceTree = "<group>"; };
|
||||
8EC1225E0B993BD500C5B3AD /* ConverterNode.mm */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.objcpp; path = ConverterNode.mm; sourceTree = "<group>"; };
|
||||
8EC1225E0B993BD500C5B3AD /* ConverterNode.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = ConverterNode.m; sourceTree = "<group>"; };
|
||||
B0575F2C0D687A0800411D77 /* Helper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Helper.h; sourceTree = "<group>"; };
|
||||
B0575F2F0D687A4000411D77 /* Helper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Helper.m; sourceTree = "<group>"; };
|
||||
D2F7E79907B2D74100F64583 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = /System/Library/Frameworks/CoreData.framework; sourceTree = "<absolute>"; };
|
||||
|
@ -258,8 +216,10 @@
|
|||
8328995A27CB51C900D7F028 /* Security.framework in Frameworks */,
|
||||
83725A9127AA16D50003F694 /* AVFoundation.framework in Frameworks */,
|
||||
8DC2EF570486A6940098B216 /* Cocoa.framework in Frameworks */,
|
||||
8350416D28646149006B32CC /* CoreMedia.framework in Frameworks */,
|
||||
83725A9027AA16C90003F694 /* Accelerate.framework in Frameworks */,
|
||||
17D21DAD0B8BE76800D1EBDE /* AudioToolbox.framework in Frameworks */,
|
||||
836DF618298F6F5F00CD0580 /* libsoxr.0.dylib in Frameworks */,
|
||||
17D21DAE0B8BE76800D1EBDE /* AudioUnit.framework in Frameworks */,
|
||||
17D21DAF0B8BE76800D1EBDE /* CoreAudio.framework in Frameworks */,
|
||||
17D21DB00B8BE76800D1EBDE /* CoreAudioKit.framework in Frameworks */,
|
||||
|
@ -310,6 +270,7 @@
|
|||
08FB77AEFE84172EC02AAC07 /* Classes */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
83B74280289E027F005AAC28 /* CogAudio-Bridging-Header.h */,
|
||||
8377C64F27B8CAAB00E8BC0F /* Visualization */,
|
||||
17F94DDC0B8D101100A34E87 /* Plugin.h */,
|
||||
17D21EBB0B8BF44000D1EBDE /* AudioPlayer.h */,
|
||||
|
@ -332,6 +293,7 @@
|
|||
17F94DD40B8D0F7000A34E87 /* PluginController.mm */,
|
||||
17D21C750B8BE4BA00D1EBDE /* Chain */,
|
||||
17D21C9B0B8BE4BA00D1EBDE /* Output */,
|
||||
839E56F6287974A100DFB5F4 /* SandboxBroker.h */,
|
||||
17D21C9E0B8BE4BA00D1EBDE /* Status.h */,
|
||||
B0575F2C0D687A0800411D77 /* Helper.h */,
|
||||
B0575F2F0D687A4000411D77 /* Helper.m */,
|
||||
|
@ -352,6 +314,7 @@
|
|||
1058C7B2FEA5585E11CA2CBB /* Other Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
836DF615298F6E8900CD0580 /* libsoxr.0.dylib */,
|
||||
83725A7B27AA0D8A0003F694 /* Accelerate.framework */,
|
||||
17D21DAA0B8BE76800D1EBDE /* AudioUnit.framework */,
|
||||
17D21DA90B8BE76800D1EBDE /* AudioToolbox.framework */,
|
||||
|
@ -370,22 +333,20 @@
|
|||
children = (
|
||||
834FD4EA27AF8F380063BC83 /* AudioChunk.h */,
|
||||
834FD4EC27AF91220063BC83 /* AudioChunk.m */,
|
||||
834FD4EE27AF93680063BC83 /* ChunkList.h */,
|
||||
834FD4EF27AF93680063BC83 /* ChunkList.m */,
|
||||
834FD4F227AFA2150063BC83 /* Downmix.h */,
|
||||
834FD4F327AFA2150063BC83 /* Downmix.m */,
|
||||
17D21C760B8BE4BA00D1EBDE /* BufferChain.h */,
|
||||
17D21C770B8BE4BA00D1EBDE /* BufferChain.m */,
|
||||
834FD4EE27AF93680063BC83 /* ChunkList.h */,
|
||||
834FD4EF27AF93680063BC83 /* ChunkList.m */,
|
||||
8EC1225D0B993BD500C5B3AD /* ConverterNode.h */,
|
||||
8EC1225E0B993BD500C5B3AD /* ConverterNode.mm */,
|
||||
8EC1225E0B993BD500C5B3AD /* ConverterNode.m */,
|
||||
83504163286447DA006B32CC /* Downmix.h */,
|
||||
83504164286447DA006B32CC /* Downmix.m */,
|
||||
17D21C7A0B8BE4BA00D1EBDE /* InputNode.h */,
|
||||
17D21C7B0B8BE4BA00D1EBDE /* InputNode.m */,
|
||||
17D21C7C0B8BE4BA00D1EBDE /* Node.h */,
|
||||
17D21C7D0B8BE4BA00D1EBDE /* Node.m */,
|
||||
17D21C7E0B8BE4BA00D1EBDE /* OutputNode.h */,
|
||||
17D21C7F0B8BE4BA00D1EBDE /* OutputNode.m */,
|
||||
835EDD7C279FE307001EDCCE /* HeadphoneFilter.h */,
|
||||
835EDD7A279FE23A001EDCCE /* HeadphoneFilter.mm */,
|
||||
);
|
||||
path = Chain;
|
||||
sourceTree = "<group>";
|
||||
|
@ -393,8 +354,12 @@
|
|||
17D21C9B0B8BE4BA00D1EBDE /* Output */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
17D21C9C0B8BE4BA00D1EBDE /* OutputCoreAudio.h */,
|
||||
17D21C9D0B8BE4BA00D1EBDE /* OutputCoreAudio.m */,
|
||||
834A41AD287ABD6F00EB9D9B /* FSurroundFilter.h */,
|
||||
834A41AE287ABD6F00EB9D9B /* FSurroundFilter.mm */,
|
||||
839E56EB2879515D00DFB5F4 /* HeadphoneFilter.h */,
|
||||
839E56EC2879515D00DFB5F4 /* HeadphoneFilter.mm */,
|
||||
17D21C9C0B8BE4BA00D1EBDE /* OutputAVFoundation.h */,
|
||||
17D21C9D0B8BE4BA00D1EBDE /* OutputAVFoundation.m */,
|
||||
);
|
||||
path = Output;
|
||||
sourceTree = "<group>";
|
||||
|
@ -402,10 +367,13 @@
|
|||
17D21CD80B8BE5B400D1EBDE /* ThirdParty */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
83F18ADE27D1E8EF00385946 /* r8brain-free-src */,
|
||||
834A41A4287A90AB00EB9D9B /* fsurround */,
|
||||
839E56E02879450300DFB5F4 /* hrtf */,
|
||||
831A50152865A8800049CFE4 /* rsstate.cpp */,
|
||||
831A50172865A8B30049CFE4 /* rsstate.h */,
|
||||
831A50132865A7FD0049CFE4 /* rsstate.hpp */,
|
||||
8377C64A27B8C51500E8BC0F /* deadbeef */,
|
||||
835C88AE279811A500E28EAE /* hdcd */,
|
||||
835C88A22797D4D400E28EAE /* lvqcl */,
|
||||
17D21DC40B8BE79700D1EBDE /* CoreAudioUtils */,
|
||||
);
|
||||
path = ThirdParty;
|
||||
|
@ -426,8 +394,8 @@
|
|||
8347C73F2796C58800FA8A7D /* NSFileHandle+CreateFile.h */,
|
||||
8347C7402796C58800FA8A7D /* NSFileHandle+CreateFile.m */,
|
||||
8384912618080FF100E7332D /* Logging.h */,
|
||||
17D21CF10B8BE5EF00D1EBDE /* Semaphore.h */,
|
||||
17D21CF20B8BE5EF00D1EBDE /* Semaphore.m */,
|
||||
17D21CF10B8BE5EF00D1EBDE /* CogSemaphore.h */,
|
||||
17D21CF20B8BE5EF00D1EBDE /* CogSemaphore.m */,
|
||||
);
|
||||
path = Utils;
|
||||
sourceTree = "<group>";
|
||||
|
@ -449,24 +417,15 @@
|
|||
name = "Other Sources";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
835C88A22797D4D400E28EAE /* lvqcl */ = {
|
||||
834A41A4287A90AB00EB9D9B /* fsurround */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
835C88A32797D4D400E28EAE /* License */,
|
||||
835C88A62797D4D400E28EAE /* lpc.c */,
|
||||
835C88A72797D4D400E28EAE /* lpc.h */,
|
||||
835C88AC2797DA5800E28EAE /* util.h */,
|
||||
834A41A5287A90AB00EB9D9B /* freesurround_decoder.h */,
|
||||
834A41A6287A90AB00EB9D9B /* freesurround_decoder.cpp */,
|
||||
834A41A7287A90AB00EB9D9B /* channelmaps.cpp */,
|
||||
834A41A8287A90AB00EB9D9B /* channelmaps.h */,
|
||||
);
|
||||
path = lvqcl;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
835C88A32797D4D400E28EAE /* License */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
835C88A42797D4D400E28EAE /* LICENSE.LGPL */,
|
||||
835C88A52797D4D400E28EAE /* License.txt */,
|
||||
);
|
||||
path = License;
|
||||
path = fsurround;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
835C88AE279811A500E28EAE /* hdcd */ = {
|
||||
|
@ -481,6 +440,7 @@
|
|||
83725A8F27AA16C90003F694 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
8350416C28646149006B32CC /* CoreMedia.framework */,
|
||||
8328995927CB51C900D7F028 /* Security.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
|
@ -498,62 +458,21 @@
|
|||
8377C64F27B8CAAB00E8BC0F /* Visualization */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
8377C65027B8CAD100E8BC0F /* VisualizationController.h */,
|
||||
8377C65127B8CAD100E8BC0F /* VisualizationController.m */,
|
||||
839B83F9286D91ED00F529EE /* VisualizationController.swift */,
|
||||
);
|
||||
path = Visualization;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
83B69B6A2845DF6500D2435A /* pffft_double */ = {
|
||||
839E56E02879450300DFB5F4 /* hrtf */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
83B69B6B2845DF6500D2435A /* pffft_double.h */,
|
||||
83B69B6C2845DF6500D2435A /* simd */,
|
||||
83B69B732845DF6500D2435A /* pffft_priv_impl.h */,
|
||||
83B69B742845DF6500D2435A /* pffft_double.c */,
|
||||
839E56E22879450300DFB5F4 /* Endianness.h */,
|
||||
839E56E32879450300DFB5F4 /* HrtfData.cpp */,
|
||||
839E56E12879450300DFB5F4 /* HrtfData.h */,
|
||||
839E56E928794F6300DFB5F4 /* HrtfTypes.h */,
|
||||
839E56E42879450300DFB5F4 /* IHrtfData.h */,
|
||||
);
|
||||
path = pffft_double;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
83B69B6C2845DF6500D2435A /* simd */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
83B69B6D2845DF6500D2435A /* pf_neon_double_from_avx.h */,
|
||||
83B69B6E2845DF6500D2435A /* pf_double.h */,
|
||||
83B69B6F2845DF6500D2435A /* pf_neon_double.h */,
|
||||
83B69B702845DF6500D2435A /* pf_sse2_double.h */,
|
||||
83B69B712845DF6500D2435A /* pf_avx_double.h */,
|
||||
83B69B722845DF6500D2435A /* pf_scalar_double.h */,
|
||||
);
|
||||
path = simd;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
83F18ADE27D1E8EF00385946 /* r8brain-free-src */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
8363BABD284E428F00E5C9DD /* pffft.cpp */,
|
||||
83B69B6A2845DF6500D2435A /* pffft_double */,
|
||||
83F18ADF27D1E8EF00385946 /* CDSPHBDownsampler.h */,
|
||||
83F18AF827D1E8EF00385946 /* CDSPSincFilterGen.h */,
|
||||
83F18AF927D1E8EF00385946 /* r8butil.h */,
|
||||
83F18AFB27D1E8EF00385946 /* LICENSE */,
|
||||
83F18AFC27D1E8EF00385946 /* r8bbase.h */,
|
||||
83F18AFD27D1E8EF00385946 /* CDSPFIRFilter.h */,
|
||||
83F18B0827D1E8EF00385946 /* CDSPProcessor.h */,
|
||||
83F18B0927D1E8EF00385946 /* README.md */,
|
||||
83F18B0A27D1E8EF00385946 /* fft4g.h */,
|
||||
83F18B0B27D1E8EF00385946 /* CDSPRealFFT.h */,
|
||||
83F18B0C27D1E8EF00385946 /* CDSPFracInterpolator.h */,
|
||||
83F18B1727D1E8EF00385946 /* CDSPBlockConvolver.h */,
|
||||
83F18B1827D1E8EF00385946 /* CDSPHBUpsampler.inc */,
|
||||
83F18B1927D1E8EF00385946 /* r8bconf.h */,
|
||||
83F18B1A27D1E8EF00385946 /* r8bbase.cpp */,
|
||||
83F18B1B27D1E8EF00385946 /* pffft.h */,
|
||||
83F18B1C27D1E8EF00385946 /* CDSPResampler.h */,
|
||||
83F18B1D27D1E8EF00385946 /* CDSPHBUpsampler.h */,
|
||||
83F18B5527D1F5E900385946 /* r8bstate.h */,
|
||||
);
|
||||
path = "r8brain-free-src";
|
||||
path = hrtf;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
@ -563,65 +482,50 @@
|
|||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
83B69B7C2845DF6500D2435A /* pffft_priv_impl.h in Headers */,
|
||||
839E56E82879450300DFB5F4 /* IHrtfData.h in Headers */,
|
||||
17D21CA10B8BE4BA00D1EBDE /* BufferChain.h in Headers */,
|
||||
831A50142865A7FD0049CFE4 /* rsstate.hpp in Headers */,
|
||||
834A41AC287A90AB00EB9D9B /* channelmaps.h in Headers */,
|
||||
17D21CA50B8BE4BA00D1EBDE /* InputNode.h in Headers */,
|
||||
834A41A9287A90AB00EB9D9B /* freesurround_decoder.h in Headers */,
|
||||
17D21CA70B8BE4BA00D1EBDE /* Node.h in Headers */,
|
||||
83B69B7B2845DF6500D2435A /* pf_scalar_double.h in Headers */,
|
||||
83F18B3427D1E8EF00385946 /* r8butil.h in Headers */,
|
||||
8399CF2C27B5D1D5008751F1 /* NSDictionary+Merge.h in Headers */,
|
||||
17D21CA90B8BE4BA00D1EBDE /* OutputNode.h in Headers */,
|
||||
83B69B792845DF6500D2435A /* pf_sse2_double.h in Headers */,
|
||||
83F18B4527D1E8EF00385946 /* CDSPRealFFT.h in Headers */,
|
||||
83F18B3827D1E8EF00385946 /* CDSPFIRFilter.h in Headers */,
|
||||
8328995427CB511000D7F028 /* RedundantPlaylistDataStore.h in Headers */,
|
||||
17D21CC50B8BE4BA00D1EBDE /* OutputCoreAudio.h in Headers */,
|
||||
834FD4F427AFA2150063BC83 /* Downmix.h in Headers */,
|
||||
17D21CC50B8BE4BA00D1EBDE /* OutputAVFoundation.h in Headers */,
|
||||
83504165286447DA006B32CC /* Downmix.h in Headers */,
|
||||
839E56E52879450300DFB5F4 /* HrtfData.h in Headers */,
|
||||
83B74281289E027F005AAC28 /* CogAudio-Bridging-Header.h in Headers */,
|
||||
17D21CC70B8BE4BA00D1EBDE /* Status.h in Headers */,
|
||||
835C88AB2797D4D400E28EAE /* lpc.h in Headers */,
|
||||
17D21CF30B8BE5EF00D1EBDE /* Semaphore.h in Headers */,
|
||||
17D21CF30B8BE5EF00D1EBDE /* CogSemaphore.h in Headers */,
|
||||
839E56E62879450300DFB5F4 /* Endianness.h in Headers */,
|
||||
17D21DC70B8BE79700D1EBDE /* CoreAudioUtils.h in Headers */,
|
||||
83F18B3327D1E8EF00385946 /* CDSPSincFilterGen.h in Headers */,
|
||||
839E56ED2879515D00DFB5F4 /* HeadphoneFilter.h in Headers */,
|
||||
17D21EBD0B8BF44000D1EBDE /* AudioPlayer.h in Headers */,
|
||||
83F18B4427D1E8EF00385946 /* fft4g.h in Headers */,
|
||||
83F18B1E27D1E8EF00385946 /* CDSPHBDownsampler.h in Headers */,
|
||||
83B69B762845DF6500D2435A /* pf_neon_double_from_avx.h in Headers */,
|
||||
83F18B5627D1F5E900385946 /* r8bstate.h in Headers */,
|
||||
8377C65227B8CAD100E8BC0F /* VisualizationController.h in Headers */,
|
||||
83B69B772845DF6500D2435A /* pf_double.h in Headers */,
|
||||
831A50182865A8B30049CFE4 /* rsstate.h in Headers */,
|
||||
834FD4F027AF93680063BC83 /* ChunkList.h in Headers */,
|
||||
17F94DD50B8D0F7000A34E87 /* PluginController.h in Headers */,
|
||||
83B69B782845DF6500D2435A /* pf_neon_double.h in Headers */,
|
||||
17F94DDD0B8D101100A34E87 /* Plugin.h in Headers */,
|
||||
83F18B5227D1E8F000385946 /* pffft.h in Headers */,
|
||||
8328995727CB51B700D7F028 /* SHA256Digest.h in Headers */,
|
||||
83B69B752845DF6500D2435A /* pffft_double.h in Headers */,
|
||||
83F18B4627D1E8EF00385946 /* CDSPFracInterpolator.h in Headers */,
|
||||
834FD4EB27AF8F380063BC83 /* AudioChunk.h in Headers */,
|
||||
83F18B4E27D1E8F000385946 /* CDSPBlockConvolver.h in Headers */,
|
||||
83F18B4227D1E8EF00385946 /* CDSPProcessor.h in Headers */,
|
||||
83B69B7A2845DF6500D2435A /* pf_avx_double.h in Headers */,
|
||||
834A41AF287ABD6F00EB9D9B /* FSurroundFilter.h in Headers */,
|
||||
17A2D3C50B8D1D37000778C4 /* AudioDecoder.h in Headers */,
|
||||
8347C7412796C58800FA8A7D /* NSFileHandle+CreateFile.h in Headers */,
|
||||
17C940230B900909008627D6 /* AudioMetadataReader.h in Headers */,
|
||||
839E56F7287974A100DFB5F4 /* SandboxBroker.h in Headers */,
|
||||
839065F32853338700636FBB /* dsd2float.h in Headers */,
|
||||
17B619300B909BC300BC003F /* AudioPropertiesReader.h in Headers */,
|
||||
83F18B5427D1E8F000385946 /* CDSPHBUpsampler.h in Headers */,
|
||||
835EDD7D279FE307001EDCCE /* HeadphoneFilter.h in Headers */,
|
||||
839366671815923C006DD712 /* CogPluginMulti.h in Headers */,
|
||||
83F18B5327D1E8F000385946 /* CDSPResampler.h in Headers */,
|
||||
17ADB13C0B97926D00257CA2 /* AudioSource.h in Headers */,
|
||||
835C88B1279811A500E28EAE /* hdcd_decode2.h in Headers */,
|
||||
8EC1225F0B993BD500C5B3AD /* ConverterNode.h in Headers */,
|
||||
8384912718080FF100E7332D /* Logging.h in Headers */,
|
||||
8377C64E27B8C54400E8BC0F /* fft.h in Headers */,
|
||||
83F18B5027D1E8F000385946 /* r8bconf.h in Headers */,
|
||||
835FAC5E27BCA14D00BA8562 /* BadSampleCleaner.h in Headers */,
|
||||
8E8D3D2F0CBAEE6E00135C1B /* AudioContainer.h in Headers */,
|
||||
83F18B3727D1E8EF00385946 /* r8bbase.h in Headers */,
|
||||
B0575F2D0D687A0800411D77 /* Helper.h in Headers */,
|
||||
835C88AD2797DA5800E28EAE /* util.h in Headers */,
|
||||
07DB5F3E0ED353A900C2E3EF /* AudioMetadataWriter.h in Headers */,
|
||||
839E56EA28794F6300DFB5F4 /* HrtfTypes.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -632,7 +536,6 @@
|
|||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 1DEB91AD08733DA50010E9CD /* Build configuration list for PBXNativeTarget "CogAudio Framework" */;
|
||||
buildPhases = (
|
||||
17D21D2B0B8BE6A200D1EBDE /* CopyFiles */,
|
||||
8DC2EF500486A6940098B216 /* Headers */,
|
||||
8DC2EF540486A6940098B216 /* Sources */,
|
||||
8DC2EF560486A6940098B216 /* Frameworks */,
|
||||
|
@ -658,8 +561,8 @@
|
|||
LastUpgradeCheck = 1400;
|
||||
TargetAttributes = {
|
||||
8DC2EF4F0486A6940098B216 = {
|
||||
DevelopmentTeam = "";
|
||||
ProvisioningStyle = Automatic;
|
||||
LastSwiftMigration = 1330;
|
||||
ProvisioningStyle = Manual;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
@ -686,10 +589,6 @@
|
|||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
83F18B3627D1E8EF00385946 /* LICENSE in Resources */,
|
||||
835C88A92797D4D400E28EAE /* License.txt in Resources */,
|
||||
835C88A82797D4D400E28EAE /* LICENSE.LGPL in Resources */,
|
||||
83F18B4327D1E8EF00385946 /* README.md in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -700,39 +599,40 @@
|
|||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
83D40B572852CB3B003BB85C /* pffft_double.c in Sources */,
|
||||
835EDD7B279FE23A001EDCCE /* HeadphoneFilter.mm in Sources */,
|
||||
17D21CA20B8BE4BA00D1EBDE /* BufferChain.m in Sources */,
|
||||
17D21CA60B8BE4BA00D1EBDE /* InputNode.m in Sources */,
|
||||
839E56EE2879515D00DFB5F4 /* HeadphoneFilter.mm in Sources */,
|
||||
83504166286447DA006B32CC /* Downmix.m in Sources */,
|
||||
8399CF2D27B5D1D5008751F1 /* NSDictionary+Merge.m in Sources */,
|
||||
834A41AB287A90AB00EB9D9B /* channelmaps.cpp in Sources */,
|
||||
831A50162865A8800049CFE4 /* rsstate.cpp in Sources */,
|
||||
17D21CA80B8BE4BA00D1EBDE /* Node.m in Sources */,
|
||||
17D21CAA0B8BE4BA00D1EBDE /* OutputNode.m in Sources */,
|
||||
8377C65327B8CAD100E8BC0F /* VisualizationController.m in Sources */,
|
||||
834FD4F527AFA2150063BC83 /* Downmix.m in Sources */,
|
||||
17D21CC60B8BE4BA00D1EBDE /* OutputCoreAudio.m in Sources */,
|
||||
17D21CC60B8BE4BA00D1EBDE /* OutputAVFoundation.m in Sources */,
|
||||
835C88B2279811A500E28EAE /* hdcd_decode2.c in Sources */,
|
||||
835FAC5F27BCA14D00BA8562 /* BadSampleCleaner.m in Sources */,
|
||||
834FD4ED27AF91220063BC83 /* AudioChunk.m in Sources */,
|
||||
83F18B5127D1E8F000385946 /* r8bbase.cpp in Sources */,
|
||||
17D21CF40B8BE5EF00D1EBDE /* Semaphore.m in Sources */,
|
||||
17D21CF40B8BE5EF00D1EBDE /* CogSemaphore.m in Sources */,
|
||||
839B83FA286D91ED00F529EE /* VisualizationController.swift in Sources */,
|
||||
8347C7422796C58800FA8A7D /* NSFileHandle+CreateFile.m in Sources */,
|
||||
17D21DC80B8BE79700D1EBDE /* CoreAudioUtils.m in Sources */,
|
||||
8328995327CB511000D7F028 /* RedundantPlaylistDataStore.m in Sources */,
|
||||
8377C64C27B8C51500E8BC0F /* fft_accelerate.c in Sources */,
|
||||
8363BABE284E428F00E5C9DD /* pffft.cpp in Sources */,
|
||||
839366681815923C006DD712 /* CogPluginMulti.m in Sources */,
|
||||
835C88AA2797D4D400E28EAE /* lpc.c in Sources */,
|
||||
17D21EBE0B8BF44000D1EBDE /* AudioPlayer.m in Sources */,
|
||||
17F94DD60B8D0F7000A34E87 /* PluginController.mm in Sources */,
|
||||
839E56E72879450300DFB5F4 /* HrtfData.cpp in Sources */,
|
||||
17A2D3C60B8D1D37000778C4 /* AudioDecoder.m in Sources */,
|
||||
8328995827CB51B700D7F028 /* SHA256Digest.m in Sources */,
|
||||
17C940240B900909008627D6 /* AudioMetadataReader.m in Sources */,
|
||||
17B619310B909BC300BC003F /* AudioPropertiesReader.m in Sources */,
|
||||
17ADB13D0B97926D00257CA2 /* AudioSource.m in Sources */,
|
||||
834FD4F127AF93680063BC83 /* ChunkList.m in Sources */,
|
||||
8EC122600B993BD500C5B3AD /* ConverterNode.mm in Sources */,
|
||||
834A41B0287ABD6F00EB9D9B /* FSurroundFilter.mm in Sources */,
|
||||
8EC122600B993BD500C5B3AD /* ConverterNode.m in Sources */,
|
||||
8E8D3D300CBAEE6E00135C1B /* AudioContainer.m in Sources */,
|
||||
B0575F300D687A4000411D77 /* Helper.m in Sources */,
|
||||
834A41AA287A90AB00EB9D9B /* freesurround_decoder.cpp in Sources */,
|
||||
07DB5F3F0ED353A900C2E3EF /* AudioMetadataWriter.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
@ -743,69 +643,64 @@
|
|||
1DEB91AE08733DA50010E9CD /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "c++17";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
DEFINES_MODULE = YES;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
FRAMEWORK_VERSION = A;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_ENABLE_OBJC_EXCEPTIONS = YES;
|
||||
GCC_MODEL_TUNING = G5;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREFIX_HEADER = CogAudio_Prefix.pch;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"R8B_EXTFFT=1",
|
||||
"R8B_PFFFT_DOUBLE=1",
|
||||
);
|
||||
GCC_PREPROCESSOR_DEFINITIONS = "DEBUG=1";
|
||||
HEADER_SEARCH_PATHS = ../ThirdParty/soxr/include;
|
||||
INFOPLIST_FILE = Info.plist;
|
||||
INSTALL_PATH = "@executable_path/../Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = "@loader_path/Frameworks";
|
||||
LIBRARY_SEARCH_PATHS = ../ThirdParty/soxr/lib;
|
||||
OTHER_LDFLAGS = "";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.cogx.cogaudio;
|
||||
PRODUCT_NAME = CogAudio;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SDKROOT = macosx;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "CogAudio-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
WARNING_LDFLAGS = "";
|
||||
WRAPPER_EXTENSION = framework;
|
||||
ZERO_LINK = YES;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
1DEB91AF08733DA50010E9CD /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "c++17";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
DEFINES_MODULE = YES;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
FRAMEWORK_VERSION = A;
|
||||
GCC_ENABLE_OBJC_EXCEPTIONS = YES;
|
||||
GCC_MODEL_TUNING = G5;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREFIX_HEADER = CogAudio_Prefix.pch;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"R8B_EXTFFT=1",
|
||||
"R8B_PFFFT_DOUBLE=1",
|
||||
);
|
||||
GCC_PREPROCESSOR_DEFINITIONS = "";
|
||||
HEADER_SEARCH_PATHS = ../ThirdParty/soxr/include;
|
||||
INFOPLIST_FILE = Info.plist;
|
||||
INSTALL_PATH = "@executable_path/../Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = "@loader_path/Frameworks";
|
||||
LIBRARY_SEARCH_PATHS = ../ThirdParty/soxr/lib;
|
||||
OTHER_LDFLAGS = "";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.cogx.cogaudio;
|
||||
PRODUCT_NAME = CogAudio;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SDKROOT = macosx;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "CogAudio-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
WARNING_LDFLAGS = "";
|
||||
WRAPPER_EXTENSION = framework;
|
||||
};
|
||||
|
@ -816,6 +711,7 @@
|
|||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
|
@ -850,8 +746,10 @@
|
|||
GCC_WARN_UNINITIALIZED_AUTOS = YES;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.12;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.13;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
OTHER_CFLAGS = "-Wframe-larger-than=4000";
|
||||
OTHER_CPLUSPLUSFLAGS = "-Wframe-larger-than=16000";
|
||||
SDKROOT = macosx;
|
||||
SYMROOT = ../build;
|
||||
};
|
||||
|
@ -862,6 +760,7 @@
|
|||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
|
@ -892,7 +791,9 @@
|
|||
GCC_WARN_UNINITIALIZED_AUTOS = YES;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.12;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.13;
|
||||
OTHER_CFLAGS = "-Wframe-larger-than=4000";
|
||||
OTHER_CPLUSPLUSFLAGS = "-Wframe-larger-than=16000";
|
||||
SDKROOT = macosx;
|
||||
SYMROOT = ../build;
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1400"
|
||||
LastUpgradeVersion = "1500"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
}
|
||||
|
||||
+ (NSArray *)urlsForContainerURL:(NSURL *)url containers:(NSArray *)containers;
|
||||
+ (NSArray *)dependencyUrlsForContainerURL:(NSURL *)url containers:(NSArray *)containers;
|
||||
|
||||
@end
|
||||
|
||||
|
|
|
@ -79,9 +79,9 @@ static void *kCogDecoderMultiContext = &kCogDecoderMultiContext;
|
|||
return @{};
|
||||
}
|
||||
|
||||
- (int)readAudio:(void *)buffer frames:(UInt32)frames {
|
||||
if(theDecoder != nil) return [theDecoder readAudio:buffer frames:frames];
|
||||
return 0;
|
||||
- (AudioChunk *)readAudio {
|
||||
if(theDecoder != nil) return [theDecoder readAudio];
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (BOOL)open:(id<CogSource>)source {
|
||||
|
@ -171,6 +171,19 @@ static void *kCogDecoderMultiContext = &kCogDecoderMultiContext;
|
|||
return nil;
|
||||
}
|
||||
|
||||
+ (NSArray *)dependencyUrlsForContainerURL:(NSURL *)url containers:(NSArray *)containers {
|
||||
NSArray *sortedContainers = sortClassesByPriority(containers);
|
||||
for(NSString *classString in sortedContainers) {
|
||||
Class container = NSClassFromString(classString);
|
||||
if([container respondsToSelector:@selector(dependencyUrlsForContainerURL:)]) {
|
||||
NSArray *urls = [container dependencyUrlsForContainerURL:url];
|
||||
if([urls count])
|
||||
return urls;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CogMetadataReaderMulti
|
||||
|
|
|
@ -7,5 +7,5 @@
|
|||
*
|
||||
*/
|
||||
|
||||
double logarithmicToLinear(double logarithmic, double MAX_VOLUME);
|
||||
double linearToLogarithmic(double linear, double MAX_VOLUME);
|
||||
double logarithmicToLinear(const double logarithmic, double MAX_VOLUME);
|
||||
double linearToLogarithmic(const double linear, double MAX_VOLUME);
|
||||
|
|
|
@ -13,13 +13,13 @@
|
|||
// These functions are helpers for the process of converting volume from a linear to logarithmic scale.
|
||||
// Numbers that goes in to audioPlayer should be logarithmic. Numbers that are displayed to the user should be linear.
|
||||
// Here's why: http://www.dr-lex.34sp.com/info-stuff/volumecontrols.html
|
||||
// We are using the approximation of X^4.
|
||||
// We are using the approximation of X^2 when volume is limited to 100% and X^4 when volume is limited to 800%.
|
||||
// Input/Output values are in percents.
|
||||
double logarithmicToLinear(double logarithmic, double MAX_VOLUME) {
|
||||
return (MAX_VOLUME == 100.0) ? logarithmic : pow((logarithmic / MAX_VOLUME), 0.25) * 100.0;
|
||||
double logarithmicToLinear(const double logarithmic, double MAX_VOLUME) {
|
||||
return (MAX_VOLUME == 100.0) ? pow((logarithmic / MAX_VOLUME), 0.5) * 100.0 : pow((logarithmic / MAX_VOLUME), 0.25) * 100.0;
|
||||
}
|
||||
|
||||
double linearToLogarithmic(double linear, double MAX_VOLUME) {
|
||||
return (MAX_VOLUME == 100.0) ? linear : (linear / 100.0) * (linear / 100.0) * (linear / 100.0) * (linear / 100.0) * MAX_VOLUME;
|
||||
double linearToLogarithmic(const double linear, double MAX_VOLUME) {
|
||||
return (MAX_VOLUME == 100.0) ? (linear / 100.0) * (linear / 100.0) * MAX_VOLUME : (linear / 100.0) * (linear / 100.0) * (linear / 100.0) * (linear / 100.0) * MAX_VOLUME;
|
||||
}
|
||||
// End helper volume function thingies. ONWARDS TO GLORY!
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
//
|
||||
// FSurroundFilter.h
|
||||
// CogAudio
|
||||
//
|
||||
// Created by Christopher Snowhill on 7/9/22.
|
||||
//
|
||||
|
||||
#ifndef FSurroundFilter_h
|
||||
#define FSurroundFilter_h
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#import <stdint.h>
|
||||
|
||||
#define FSurroundChunkSize 4096
|
||||
|
||||
@interface FSurroundFilter : NSObject {
|
||||
void *decoder;
|
||||
void *params;
|
||||
double srate;
|
||||
uint32_t channelCount;
|
||||
uint32_t channelConfig;
|
||||
float tempBuffer[4096 * 2];
|
||||
}
|
||||
|
||||
- (id)initWithSampleRate:(double)srate;
|
||||
|
||||
- (uint32_t)channelCount;
|
||||
- (uint32_t)channelConfig;
|
||||
- (double)srate;
|
||||
|
||||
- (void)process:(const float *)samplesIn output:(float *)samplesOut count:(uint32_t)count;
|
||||
|
||||
@end
|
||||
|
||||
#endif /* FSurround_h */
|
|
@ -0,0 +1,156 @@
|
|||
//
|
||||
// FSurroundFilter.m
|
||||
// CogAudio Framework
|
||||
//
|
||||
// Created by Christopher Snowhill on 7/9/22.
|
||||
//
|
||||
|
||||
#import "FSurroundFilter.h"
|
||||
|
||||
#import "freesurround_decoder.h"
|
||||
|
||||
#import "AudioChunk.h"
|
||||
|
||||
#import <Accelerate/Accelerate.h>
|
||||
|
||||
#import <map>
|
||||
#import <vector>
|
||||
|
||||
struct freesurround_params {
|
||||
// the user-configurable parameters
|
||||
float center_image, shift, depth, circular_wrap, focus, front_sep, rear_sep, bass_lo, bass_hi;
|
||||
bool use_lfe;
|
||||
channel_setup channels_fs; // FreeSurround channel setup
|
||||
std::vector<unsigned> chanmap; // FreeSurround -> WFX channel index translation (derived data for faster lookup)
|
||||
|
||||
// construct with defaults
|
||||
freesurround_params()
|
||||
: center_image(0.7), shift(0), depth(1), circular_wrap(90), focus(0), front_sep(1), rear_sep(1),
|
||||
bass_lo(40), bass_hi(90), use_lfe(false) {
|
||||
set_channels_fs(cs_5point1);
|
||||
}
|
||||
|
||||
// compute the WFX version of the channel setup code
|
||||
unsigned channel_count() {
|
||||
return (unsigned)chanmap.size();
|
||||
}
|
||||
unsigned channels_wfx() {
|
||||
unsigned res = 0;
|
||||
for(unsigned i = 0; i < chanmap.size(); res |= chanmap[i++]) {};
|
||||
return res;
|
||||
}
|
||||
|
||||
// assign a channel setup & recompute derived data
|
||||
void set_channels_fs(channel_setup setup) {
|
||||
channels_fs = setup;
|
||||
chanmap.clear();
|
||||
// Note: Because WFX does not define a few of the more exotic channels (side front left&right, side rear left&right, back center left&right),
|
||||
// the side front/back channel pairs (both left and right sides, resp.) are mapped here onto foobar's top front/back channel pairs and the
|
||||
// back (off-)center left/right channels are mapped onto foobar's top front center and top back center, respectively.
|
||||
// Therefore, these speakers should be connected to those outputs instead.
|
||||
std::map<channel_id, uint32_t> fs2wfx;
|
||||
fs2wfx[ci_front_left] = AudioChannelFrontLeft;
|
||||
fs2wfx[ci_front_center_left] = AudioChannelFrontCenterLeft;
|
||||
fs2wfx[ci_front_center] = AudioChannelFrontCenter;
|
||||
fs2wfx[ci_front_center_right] = AudioChannelFrontCenterRight;
|
||||
fs2wfx[ci_front_right] = AudioChannelFrontRight;
|
||||
fs2wfx[ci_side_front_left] = AudioChannelFrontLeft;
|
||||
fs2wfx[ci_side_front_right] = AudioChannelTopFrontRight;
|
||||
fs2wfx[ci_side_center_left] = AudioChannelSideLeft;
|
||||
fs2wfx[ci_side_center_right] = AudioChannelSideRight;
|
||||
fs2wfx[ci_side_back_left] = AudioChannelTopBackLeft;
|
||||
fs2wfx[ci_side_back_right] = AudioChannelTopBackRight;
|
||||
fs2wfx[ci_back_left] = AudioChannelBackLeft;
|
||||
fs2wfx[ci_back_center_left] = AudioChannelTopFrontCenter;
|
||||
fs2wfx[ci_back_center] = AudioChannelBackCenter;
|
||||
fs2wfx[ci_back_center_right] = AudioChannelTopBackCenter;
|
||||
fs2wfx[ci_back_right] = AudioChannelBackRight;
|
||||
fs2wfx[ci_lfe] = AudioChannelLFE;
|
||||
for(unsigned i = 0; i < freesurround_decoder::num_channels(channels_fs); i++)
|
||||
chanmap.push_back(fs2wfx[freesurround_decoder::channel_at(channels_fs, i)]);
|
||||
}
|
||||
};
|
||||
|
||||
@implementation FSurroundFilter
|
||||
|
||||
- (id)initWithSampleRate:(double)srate {
|
||||
self = [super init];
|
||||
if(!self) return nil;
|
||||
|
||||
self->srate = srate;
|
||||
|
||||
freesurround_params *_params = new freesurround_params;
|
||||
params = (void *)_params;
|
||||
|
||||
freesurround_decoder *_decoder = new freesurround_decoder(cs_5point1, 4096);
|
||||
decoder = (void *)_decoder;
|
||||
|
||||
_decoder->circular_wrap(_params->circular_wrap);
|
||||
_decoder->shift(_params->shift);
|
||||
_decoder->depth(_params->depth);
|
||||
_decoder->focus(_params->focus);
|
||||
_decoder->center_image(_params->center_image);
|
||||
_decoder->front_separation(_params->front_sep);
|
||||
_decoder->rear_separation(_params->rear_sep);
|
||||
_decoder->bass_redirection(_params->use_lfe);
|
||||
_decoder->low_cutoff(_params->bass_lo / (srate / 2.0));
|
||||
_decoder->high_cutoff(_params->bass_hi / (srate / 2.0));
|
||||
|
||||
channelCount = _params->channel_count();
|
||||
channelConfig = _params->channels_wfx();
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
if(decoder) {
|
||||
freesurround_decoder *_decoder = (freesurround_decoder *)decoder;
|
||||
delete _decoder;
|
||||
}
|
||||
if(params) {
|
||||
freesurround_params *_params = (freesurround_params *)params;
|
||||
delete _params;
|
||||
}
|
||||
}
|
||||
|
||||
- (uint32_t)channelCount {
|
||||
return channelCount;
|
||||
}
|
||||
|
||||
- (uint32_t)channelConfig {
|
||||
return channelConfig;
|
||||
}
|
||||
|
||||
- (double)srate {
|
||||
return srate;
|
||||
}
|
||||
|
||||
- (void)process:(const float *)samplesIn output:(float *)samplesOut count:(uint32_t)count {
|
||||
freesurround_params *_params = (freesurround_params *)params;
|
||||
freesurround_decoder *_decoder = (freesurround_decoder *)decoder;
|
||||
|
||||
uint32_t zeroCount = 0;
|
||||
|
||||
if(count > 4096) {
|
||||
zeroCount = count - 4096;
|
||||
count = 4096;
|
||||
}
|
||||
|
||||
if(count < 4096) {
|
||||
cblas_scopy(count * 2, samplesIn, 1, &tempBuffer[0], 1);
|
||||
vDSP_vclr(&tempBuffer[count * 2], 1, (4096 - count) * 2);
|
||||
samplesIn = &tempBuffer[0];
|
||||
}
|
||||
|
||||
float *src = _decoder->decode(samplesIn);
|
||||
|
||||
for(unsigned c = 0, num = channelCount; c < num; c++) {
|
||||
unsigned idx = [AudioChunk channelIndexFromConfig:channelConfig forFlag:_params->chanmap[c]];
|
||||
cblas_scopy(count, src + c, num, samplesOut + idx, num);
|
||||
if(zeroCount) {
|
||||
vDSP_vclr(samplesOut + idx + count, num, zeroCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
|
@ -15,11 +15,11 @@
|
|||
vDSP_DFT_Setup dftSetupF;
|
||||
vDSP_DFT_Setup dftSetupB;
|
||||
|
||||
size_t fftSize;
|
||||
size_t fftSizeOver2;
|
||||
size_t bufferSize;
|
||||
size_t paddedBufferSize;
|
||||
size_t channelCount;
|
||||
int fftSize;
|
||||
int fftSizeOver2;
|
||||
int bufferSize;
|
||||
int paddedBufferSize;
|
||||
int channelCount;
|
||||
|
||||
DSPSplitComplex signal_fft;
|
||||
DSPSplitComplex input_filtered_signal_per_channel[2];
|
||||
|
@ -36,9 +36,9 @@
|
|||
|
||||
+ (BOOL)validateImpulseFile:(NSURL *)url;
|
||||
|
||||
- (id)initWithImpulseFile:(NSURL *)url forSampleRate:(double)sampleRate withInputChannels:(size_t)channels withConfig:(uint32_t)config;
|
||||
- (id)initWithImpulseFile:(NSURL *)url forSampleRate:(double)sampleRate withInputChannels:(int)channels withConfig:(uint32_t)config;
|
||||
|
||||
- (void)process:(const float *)inBuffer sampleCount:(size_t)count toBuffer:(float *)outBuffer;
|
||||
- (void)process:(const float *)inBuffer sampleCount:(int)count toBuffer:(float *)outBuffer;
|
||||
|
||||
- (void)reset;
|
||||
|
|
@ -12,16 +12,49 @@
|
|||
|
||||
#import <stdlib.h>
|
||||
|
||||
#import "r8bstate.h"
|
||||
#import <fstream>
|
||||
|
||||
#import "lpc.h"
|
||||
#import "util.h"
|
||||
#import "rsstate.h"
|
||||
|
||||
#import "HrtfData.h"
|
||||
|
||||
#import "Logging.h"
|
||||
|
||||
typedef struct speakerPosition {
|
||||
float elevation;
|
||||
float azimuth;
|
||||
float distance;
|
||||
} speakerPosition;
|
||||
|
||||
#define DEGREES(x) ((x)*M_PI / 180.0)
|
||||
|
||||
static const speakerPosition speakerPositions[18] = {
|
||||
{ .elevation = DEGREES(0.0), .azimuth = DEGREES(-30.0), .distance = 1.0 },
|
||||
{ .elevation = DEGREES(0.0), .azimuth = DEGREES(+30.0), .distance = 1.0 },
|
||||
{ .elevation = DEGREES(0.0), .azimuth = DEGREES(0.0), .distance = 1.0 },
|
||||
{ .elevation = DEGREES(0.0), .azimuth = DEGREES(0.0), .distance = 1.0 },
|
||||
{ .elevation = DEGREES(0.0), .azimuth = DEGREES(-135.0), .distance = 1.0 },
|
||||
{ .elevation = DEGREES(0.0), .azimuth = DEGREES(+135.0), .distance = 1.0 },
|
||||
{ .elevation = DEGREES(0.0), .azimuth = DEGREES(-15.0), .distance = 1.0 },
|
||||
{ .elevation = DEGREES(0.0), .azimuth = DEGREES(+15.0), .distance = 1.0 },
|
||||
{ .elevation = DEGREES(0.0), .azimuth = DEGREES(-180.0), .distance = 1.0 },
|
||||
{ .elevation = DEGREES(0.0), .azimuth = DEGREES(-90.0), .distance = 1.0 },
|
||||
{ .elevation = DEGREES(0.0), .azimuth = DEGREES(+90.0), .distance = 1.0 },
|
||||
{ .elevation = DEGREES(+90.0), .azimuth = DEGREES(0.0), .distance = 1.0 },
|
||||
{ .elevation = DEGREES(+45.0), .azimuth = DEGREES(-30.0), .distance = 1.0 },
|
||||
{ .elevation = DEGREES(+45.0), .azimuth = DEGREES(0.0), .distance = 1.0 },
|
||||
{ .elevation = DEGREES(+45.0), .azimuth = DEGREES(+30.0), .distance = 1.0 },
|
||||
{ .elevation = DEGREES(+45.0), .azimuth = DEGREES(-135.0), .distance = 1.0 },
|
||||
{ .elevation = DEGREES(+45.0), .azimuth = DEGREES(0.0), .distance = 1.0 },
|
||||
{ .elevation = DEGREES(+45.0), .azimuth = DEGREES(+135.0), .distance = 1.0 }
|
||||
};
|
||||
|
||||
@interface impulseCacheObject : NSObject {
|
||||
}
|
||||
@property NSURL *URL;
|
||||
@property int sampleCount;
|
||||
@property int channelCount;
|
||||
@property uint32_t channelConfig;
|
||||
@property double sampleRate;
|
||||
@property double targetSampleRate;
|
||||
@property NSData *data;
|
||||
|
@ -31,6 +64,7 @@
|
|||
@synthesize URL;
|
||||
@synthesize sampleCount;
|
||||
@synthesize channelCount;
|
||||
@synthesize channelConfig;
|
||||
@synthesize sampleRate;
|
||||
@synthesize targetSampleRate;
|
||||
@synthesize data;
|
||||
|
@ -40,7 +74,7 @@
|
|||
}
|
||||
@property NSMutableArray<impulseCacheObject *> *cacheObjects;
|
||||
+ (impulseCache *)sharedController;
|
||||
- (const float *)getImpulse:(NSURL *)url sampleCount:(int *)sampleCount channelCount:(int *)channelCount sampleRate:(double)sampleRate;
|
||||
- (const float *)getImpulse:(NSURL *)url sampleCount:(int *)sampleCount channelCount:(int)channelCount channelConfig:(uint32_t)channelConfig sampleRate:(double)sampleRate;
|
||||
@end
|
||||
|
||||
// Apparently _mm_malloc is Intel-only on newer macOS targets, so use supported posix_memalign
|
||||
|
@ -73,7 +107,7 @@ static impulseCache *_sharedController = nil;
|
|||
return self;
|
||||
}
|
||||
|
||||
- (impulseCacheObject *)addImpulse:(NSURL *)url sampleCount:(int)sampleCount channelCount:(int)channelCount originalSampleRate:(double)originalSampleRate targetSampleRate:(double)targetSampleRate impulseBuffer:(const float *)impulseBuffer {
|
||||
- (impulseCacheObject *)addImpulse:(NSURL *)url sampleCount:(int)sampleCount channelCount:(int)channelCount channelConfig:(uint32_t)channelConfig originalSampleRate:(double)originalSampleRate targetSampleRate:(double)targetSampleRate impulseBuffer:(const float *)impulseBuffer {
|
||||
impulseCacheObject *obj = [[impulseCacheObject alloc] init];
|
||||
|
||||
obj.URL = url;
|
||||
|
@ -81,7 +115,7 @@ static impulseCache *_sharedController = nil;
|
|||
obj.channelCount = channelCount;
|
||||
obj.sampleRate = originalSampleRate;
|
||||
obj.targetSampleRate = targetSampleRate;
|
||||
obj.data = [NSData dataWithBytes:impulseBuffer length:(sampleCount * channelCount * sizeof(float))];
|
||||
obj.data = [NSData dataWithBytes:impulseBuffer length:(sampleCount * channelCount * sizeof(float) * 2)];
|
||||
|
||||
@synchronized(self.cacheObjects) {
|
||||
[self.cacheObjects addObject:obj];
|
||||
|
@ -90,29 +124,30 @@ static impulseCache *_sharedController = nil;
|
|||
return obj;
|
||||
}
|
||||
|
||||
- (const float *)getImpulse:(NSURL *)url sampleCount:(int *)retSampleCount channelCount:(int *)retImpulseChannels sampleRate:(double)sampleRate {
|
||||
- (const float *)getImpulse:(NSURL *)url sampleCount:(int *)retSampleCount channelCount:(int)channelCount channelConfig:(uint32_t)channelConfig sampleRate:(double)sampleRate {
|
||||
BOOL impulseFound = NO;
|
||||
const float *impulseData = NULL;
|
||||
double sampleRateOfSource = 0;
|
||||
int sampleCount = 0;
|
||||
int impulseChannels = 0;
|
||||
impulseCacheObject *cacheObject = nil;
|
||||
|
||||
@synchronized(self.cacheObjects) {
|
||||
for(impulseCacheObject *obj in self.cacheObjects) {
|
||||
if([obj.URL isEqualTo:url] &&
|
||||
obj.targetSampleRate == sampleRate) {
|
||||
obj.targetSampleRate == sampleRate &&
|
||||
obj.channelCount == channelCount &&
|
||||
obj.channelConfig == channelConfig) {
|
||||
*retSampleCount = obj.sampleCount;
|
||||
*retImpulseChannels = obj.channelCount;
|
||||
return (const float *)[obj.data bytes];
|
||||
}
|
||||
}
|
||||
for(impulseCacheObject *obj in self.cacheObjects) {
|
||||
if([obj.URL isEqualTo:url] &&
|
||||
obj.sampleRate == obj.targetSampleRate) {
|
||||
obj.sampleRate == obj.targetSampleRate &&
|
||||
obj.channelCount == channelCount &&
|
||||
obj.channelConfig == channelConfig) {
|
||||
impulseData = (const float *)[obj.data bytes];
|
||||
sampleCount = obj.sampleCount;
|
||||
impulseChannels = obj.channelCount;
|
||||
sampleRateOfSource = obj.sampleRate;
|
||||
impulseFound = YES;
|
||||
break;
|
||||
|
@ -121,145 +156,81 @@ static impulseCache *_sharedController = nil;
|
|||
}
|
||||
|
||||
if(!impulseFound) {
|
||||
id<CogSource> source = [AudioSource audioSourceForURL:url];
|
||||
if(!source)
|
||||
return NULL;
|
||||
NSString *filePath = [url path];
|
||||
|
||||
if(![source open:url])
|
||||
return NULL;
|
||||
try {
|
||||
std::ifstream file([filePath UTF8String], std::fstream::binary);
|
||||
|
||||
id<CogDecoder> decoder = [AudioDecoder audioDecoderForSource:source];
|
||||
if(!file.is_open()) {
|
||||
throw std::logic_error("Cannot open file.");
|
||||
}
|
||||
|
||||
if(decoder == nil) {
|
||||
[source close];
|
||||
source = nil;
|
||||
return NULL;
|
||||
HrtfData data(file);
|
||||
|
||||
file.close();
|
||||
|
||||
sampleRateOfSource = data.get_sample_rate();
|
||||
|
||||
uint32_t sampleCountExact = data.get_response_length();
|
||||
sampleCount = sampleCountExact + ((data.get_longest_delay() + 2) >> 2);
|
||||
|
||||
std::vector<float> hrtfData(sampleCount * channelCount * 2, 0.0);
|
||||
|
||||
for(uint32_t i = 0; i < channelCount; ++i) {
|
||||
uint32_t channelFlag = [AudioChunk extractChannelFlag:i fromConfig:channelConfig];
|
||||
uint32_t channelNumber = [AudioChunk findChannelIndex:channelFlag];
|
||||
|
||||
if(channelNumber < 18) {
|
||||
const speakerPosition &speaker = speakerPositions[channelNumber];
|
||||
DirectionData hrtfLeft;
|
||||
DirectionData hrtfRight;
|
||||
|
||||
data.get_direction_data(speaker.elevation, speaker.azimuth, speaker.distance, hrtfLeft, hrtfRight);
|
||||
|
||||
cblas_scopy(sampleCountExact, &hrtfLeft.impulse_response[0], 1, &hrtfData[((hrtfLeft.delay + 2) >> 2) * channelCount * 2 + i * 2], channelCount * 2);
|
||||
cblas_scopy(sampleCountExact, &hrtfRight.impulse_response[0], 1, &hrtfData[((hrtfLeft.delay + 2) >> 2) * channelCount * 2 + i * 2 + 1], channelCount * 2);
|
||||
}
|
||||
}
|
||||
|
||||
cacheObject = [self addImpulse:url sampleCount:sampleCount channelCount:channelCount channelConfig:channelConfig originalSampleRate:sampleRateOfSource targetSampleRate:sampleRateOfSource impulseBuffer:&hrtfData[0]];
|
||||
|
||||
impulseData = (const float *)[cacheObject.data bytes];
|
||||
} catch(std::exception &e) {
|
||||
ALog(@"Exception caught: %s", e.what());
|
||||
return nil;
|
||||
}
|
||||
|
||||
if(![decoder open:source]) {
|
||||
decoder = nil;
|
||||
[source close];
|
||||
source = nil;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
NSDictionary *properties = [decoder properties];
|
||||
|
||||
sampleRateOfSource = [[properties objectForKey:@"sampleRate"] floatValue];
|
||||
|
||||
sampleCount = [[properties objectForKey:@"totalFrames"] intValue];
|
||||
impulseChannels = [[properties objectForKey:@"channels"] intValue];
|
||||
|
||||
if([[properties objectForKey:@"floatingPoint"] boolValue] != YES ||
|
||||
[[properties objectForKey:@"bitsPerSample"] intValue] != 32 ||
|
||||
!([[properties objectForKey:@"endian"] isEqualToString:@"host"] ||
|
||||
[[properties objectForKey:@"endian"] isEqualToString:@"little"]) ||
|
||||
(impulseChannels != 14 && impulseChannels != 7)) {
|
||||
[decoder close];
|
||||
decoder = nil;
|
||||
[source close];
|
||||
source = nil;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
float *impulseBuffer = (float *)_memalign_malloc(sampleCount * sizeof(float) * impulseChannels, 16);
|
||||
if(!impulseBuffer) {
|
||||
[decoder close];
|
||||
decoder = nil;
|
||||
[source close];
|
||||
source = nil;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if([decoder readAudio:impulseBuffer frames:sampleCount] != sampleCount) {
|
||||
free(impulseBuffer);
|
||||
[decoder close];
|
||||
decoder = nil;
|
||||
[source close];
|
||||
source = nil;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
[decoder close];
|
||||
decoder = nil;
|
||||
[source close];
|
||||
source = nil;
|
||||
|
||||
cacheObject = [self addImpulse:url sampleCount:sampleCount channelCount:impulseChannels originalSampleRate:sampleRateOfSource targetSampleRate:sampleRateOfSource impulseBuffer:impulseBuffer];
|
||||
|
||||
free(impulseBuffer);
|
||||
|
||||
impulseData = (const float *)[cacheObject.data bytes];
|
||||
}
|
||||
|
||||
if(sampleRateOfSource != sampleRate) {
|
||||
double sampleRatio = sampleRate / sampleRateOfSource;
|
||||
int resampledCount = (int)ceil((double)sampleCount * sampleRatio);
|
||||
|
||||
r8bstate *_r8bstate = new r8bstate(impulseChannels, 1024, sampleRateOfSource, sampleRate);
|
||||
void *rsstate = rsstate_new(channelCount * 2, sampleRateOfSource, sampleRate);
|
||||
|
||||
unsigned long PRIME_LEN_ = MAX(sampleRateOfSource / 20, 1024u);
|
||||
PRIME_LEN_ = MIN(PRIME_LEN_, 16384u);
|
||||
PRIME_LEN_ = MAX(PRIME_LEN_, 2 * LPC_ORDER + 1);
|
||||
|
||||
unsigned int N_samples_to_add_ = sampleRateOfSource;
|
||||
unsigned int N_samples_to_drop_ = sampleRate;
|
||||
|
||||
samples_len(&N_samples_to_add_, &N_samples_to_drop_, 20, 8192u);
|
||||
|
||||
int resamplerLatencyIn = (int)N_samples_to_add_;
|
||||
int resamplerLatencyOut = (int)N_samples_to_drop_;
|
||||
|
||||
float *tempImpulse = (float *)_memalign_malloc((sampleCount + resamplerLatencyIn * 2 + 1024) * sizeof(float) * impulseChannels, 16);
|
||||
if(!tempImpulse) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
resampledCount += resamplerLatencyOut * 2 + 1024;
|
||||
|
||||
float *resampledImpulse = (float *)_memalign_malloc(resampledCount * sizeof(float) * impulseChannels, 16);
|
||||
float *resampledImpulse = (float *)_memalign_malloc(resampledCount * sizeof(float) * channelCount * 2, 16);
|
||||
if(!resampledImpulse) {
|
||||
free(tempImpulse);
|
||||
rsstate_delete(rsstate);
|
||||
return nil;
|
||||
}
|
||||
|
||||
size_t prime = MIN(sampleCount, PRIME_LEN_);
|
||||
|
||||
void *extrapolate_buffer = NULL;
|
||||
size_t extrapolate_buffer_size = 0;
|
||||
|
||||
memcpy(tempImpulse + resamplerLatencyIn * impulseChannels, impulseData, sampleCount * sizeof(float) * impulseChannels);
|
||||
lpc_extrapolate_bkwd(tempImpulse + N_samples_to_add_ * impulseChannels, sampleCount, prime, impulseChannels, LPC_ORDER, N_samples_to_add_, &extrapolate_buffer, &extrapolate_buffer_size);
|
||||
lpc_extrapolate_fwd(tempImpulse + N_samples_to_add_ * impulseChannels, sampleCount, prime, impulseChannels, LPC_ORDER, N_samples_to_add_, &extrapolate_buffer, &extrapolate_buffer_size);
|
||||
free(extrapolate_buffer);
|
||||
|
||||
size_t inputDone = 0;
|
||||
size_t outputDone = 0;
|
||||
|
||||
outputDone = _r8bstate->resample(tempImpulse, sampleCount + N_samples_to_add_ * 2, &inputDone, resampledImpulse, resampledCount);
|
||||
outputDone = rsstate_resample(rsstate, impulseData, sampleCount, &inputDone, resampledImpulse, resampledCount);
|
||||
|
||||
free(tempImpulse);
|
||||
|
||||
if(outputDone < resampledCount) {
|
||||
outputDone += _r8bstate->flush(resampledImpulse + outputDone * impulseChannels, resampledCount - outputDone);
|
||||
while(outputDone < resampledCount) {
|
||||
outputDone += rsstate_flush(rsstate, resampledImpulse + outputDone * channelCount * 2, resampledCount - outputDone);
|
||||
}
|
||||
|
||||
delete _r8bstate;
|
||||
|
||||
outputDone -= N_samples_to_drop_ * 2;
|
||||
|
||||
// Do this instead of the memmove
|
||||
float *resampledImpulseData = resampledImpulse + N_samples_to_drop_ * impulseChannels;
|
||||
|
||||
/*memmove(resampledImpulse, resampledImpulse + N_samples_to_drop_ * impulseChannels, outputDone * sizeof(float) * impulseChannels);*/
|
||||
rsstate_delete(rsstate);
|
||||
|
||||
sampleCount = (int)outputDone;
|
||||
|
||||
// Normalize resampled impulse by sample ratio
|
||||
float fSampleRatio = (float)sampleRatio;
|
||||
vDSP_vsdiv(resampledImpulseData, 1, &fSampleRatio, resampledImpulseData, 1, sampleCount * impulseChannels);
|
||||
vDSP_vsdiv(resampledImpulse, 1, &fSampleRatio, resampledImpulse, 1, sampleCount * channelCount * 2);
|
||||
|
||||
cacheObject = [self addImpulse:url sampleCount:sampleCount channelCount:impulseChannels originalSampleRate:sampleRateOfSource targetSampleRate:sampleRate impulseBuffer:resampledImpulseData];
|
||||
cacheObject = [self addImpulse:url sampleCount:sampleCount channelCount:channelCount channelConfig:channelConfig originalSampleRate:sampleRateOfSource targetSampleRate:sampleRate impulseBuffer:resampledImpulse];
|
||||
|
||||
free(resampledImpulse);
|
||||
|
||||
|
@ -267,7 +238,6 @@ static impulseCache *_sharedController = nil;
|
|||
}
|
||||
|
||||
*retSampleCount = sampleCount;
|
||||
*retImpulseChannels = impulseChannels;
|
||||
return impulseData;
|
||||
}
|
||||
|
||||
|
@ -275,112 +245,33 @@ static impulseCache *_sharedController = nil;
|
|||
|
||||
@implementation HeadphoneFilter
|
||||
|
||||
enum {
|
||||
speaker_is_back_center = -1,
|
||||
speaker_not_present = -2,
|
||||
};
|
||||
|
||||
static const uint32_t max_speaker_index = 10;
|
||||
|
||||
static const int8_t speakers_to_hesuvi_7[11][2] = {
|
||||
// front left
|
||||
{ 0, 1 },
|
||||
// front right
|
||||
{ 1, 0 },
|
||||
// front center
|
||||
{ 6, 6 },
|
||||
// lfe
|
||||
{ 6, 6 },
|
||||
// back left
|
||||
{ 4, 5 },
|
||||
// back right
|
||||
{ 5, 4 },
|
||||
// front center left
|
||||
{ speaker_not_present, speaker_not_present },
|
||||
// front center right
|
||||
{ speaker_not_present, speaker_not_present },
|
||||
// back center
|
||||
{ speaker_is_back_center, speaker_is_back_center },
|
||||
// side left
|
||||
{ 2, 3 },
|
||||
// side right
|
||||
{ 3, 2 }
|
||||
};
|
||||
|
||||
static const int8_t speakers_to_hesuvi_14[11][2] = {
|
||||
// front left
|
||||
{ 0, 1 },
|
||||
// front right
|
||||
{ 8, 7 },
|
||||
// front center
|
||||
{ 6, 13 },
|
||||
// lfe
|
||||
{ 6, 13 },
|
||||
// back left
|
||||
{ 4, 5 },
|
||||
// back right
|
||||
{ 12, 11 },
|
||||
// front center left
|
||||
{ speaker_not_present, speaker_not_present },
|
||||
// front center right
|
||||
{ speaker_not_present, speaker_not_present },
|
||||
// back center
|
||||
{ speaker_is_back_center, speaker_is_back_center },
|
||||
// side left
|
||||
{ 2, 3 },
|
||||
// side right
|
||||
{ 10, 9 }
|
||||
};
|
||||
|
||||
+ (BOOL)validateImpulseFile:(NSURL *)url {
|
||||
id<CogSource> source = [AudioSource audioSourceForURL:url];
|
||||
if(!source)
|
||||
return NO;
|
||||
NSString *filePath = [url path];
|
||||
|
||||
if(![source open:url])
|
||||
return NO;
|
||||
try {
|
||||
std::ifstream file([filePath UTF8String], std::fstream::binary);
|
||||
|
||||
id<CogDecoder> decoder = [AudioDecoder audioDecoderForSource:source];
|
||||
if(!file.is_open()) {
|
||||
throw std::logic_error("Cannot open file.");
|
||||
}
|
||||
|
||||
if(decoder == nil) {
|
||||
[source close];
|
||||
source = nil;
|
||||
HrtfData data(file);
|
||||
|
||||
file.close();
|
||||
|
||||
return YES;
|
||||
} catch(std::exception &e) {
|
||||
ALog(@"Exception thrown: %s", e.what());
|
||||
return NO;
|
||||
}
|
||||
|
||||
if(![decoder open:source]) {
|
||||
decoder = nil;
|
||||
[source close];
|
||||
source = nil;
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSDictionary *properties = [decoder properties];
|
||||
|
||||
[decoder close];
|
||||
decoder = nil;
|
||||
[source close];
|
||||
source = nil;
|
||||
|
||||
int impulseChannels = [[properties objectForKey:@"channels"] intValue];
|
||||
|
||||
if([[properties objectForKey:@"floatingPoint"] boolValue] != YES ||
|
||||
[[properties objectForKey:@"bitsPerSample"] intValue] != 32 ||
|
||||
!([[properties objectForKey:@"endian"] isEqualToString:@"host"] ||
|
||||
[[properties objectForKey:@"endian"] isEqualToString:@"little"]) ||
|
||||
(impulseChannels != 14 && impulseChannels != 7))
|
||||
return NO;
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (id)initWithImpulseFile:(NSURL *)url forSampleRate:(double)sampleRate withInputChannels:(size_t)channels withConfig:(uint32_t)config {
|
||||
- (id)initWithImpulseFile:(NSURL *)url forSampleRate:(double)sampleRate withInputChannels:(int)channels withConfig:(uint32_t)config {
|
||||
self = [super init];
|
||||
|
||||
if(self) {
|
||||
int sampleCount = 0;
|
||||
int impulseChannels = 0;
|
||||
const float *impulseBuffer = [[impulseCache sharedController] getImpulse:url sampleCount:&sampleCount channelCount:&impulseChannels sampleRate:sampleRate];
|
||||
const float *impulseBuffer = [[impulseCache sharedController] getImpulse:url sampleCount:&sampleCount channelCount:channels channelConfig:config sampleRate:sampleRate];
|
||||
if(!impulseBuffer) {
|
||||
return nil;
|
||||
}
|
||||
|
@ -397,19 +288,21 @@ static const int8_t speakers_to_hesuvi_14[11][2] = {
|
|||
}
|
||||
fftSize = 2 << pow;
|
||||
|
||||
float *deinterleavedImpulseBuffer = (float *)_memalign_malloc(fftSize * sizeof(float) * impulseChannels, 16);
|
||||
float *deinterleavedImpulseBuffer = (float *)_memalign_malloc(fftSize * sizeof(float) * channelCount * 2, 16);
|
||||
if(!deinterleavedImpulseBuffer) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < impulseChannels; ++i) {
|
||||
cblas_scopy(sampleCount, impulseBuffer + i, impulseChannels, deinterleavedImpulseBuffer + i * fftSize, 1);
|
||||
vDSP_vclr(deinterleavedImpulseBuffer + i * fftSize + sampleCount, 1, fftSize - sampleCount);
|
||||
for(int i = 0; i < channelCount; ++i) {
|
||||
cblas_scopy(sampleCount, impulseBuffer + i * 2, (int)channelCount * 2, deinterleavedImpulseBuffer + i * fftSize * 2, 1);
|
||||
vDSP_vclr(deinterleavedImpulseBuffer + i * fftSize * 2 + sampleCount, 1, fftSize - sampleCount);
|
||||
cblas_scopy(sampleCount, impulseBuffer + i * 2 + 1, (int)channelCount * 2, deinterleavedImpulseBuffer + i * fftSize * 2 + fftSize, 1);
|
||||
vDSP_vclr(deinterleavedImpulseBuffer + i * fftSize * 2 + fftSize + sampleCount, 1, fftSize - sampleCount);
|
||||
}
|
||||
|
||||
paddedBufferSize = fftSize;
|
||||
fftSizeOver2 = (fftSize + 1) / 2;
|
||||
const size_t fftSizeOver2Plus1 = fftSizeOver2 + 1; // DFT float overwrites plus one, double doesn't
|
||||
const int fftSizeOver2Plus1 = fftSizeOver2 + 1; // DFT float overwrites plus one, double doesn't
|
||||
|
||||
dftSetupF = vDSP_DFT_zrop_CreateSetup(nil, fftSize, vDSP_DFT_FORWARD);
|
||||
dftSetupB = vDSP_DFT_zrop_CreateSetup(nil, fftSize, vDSP_DFT_INVERSE);
|
||||
|
@ -469,7 +362,7 @@ static const int8_t speakers_to_hesuvi_14[11][2] = {
|
|||
return nil;
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < channels; ++i) {
|
||||
for(int i = 0; i < channels; ++i) {
|
||||
impulse_responses[i * 2 + 0].realp = (float *)_memalign_malloc(sizeof(float) * fftSizeOver2Plus1, 16);
|
||||
impulse_responses[i * 2 + 0].imagp = (float *)_memalign_malloc(sizeof(float) * fftSizeOver2Plus1, 16);
|
||||
impulse_responses[i * 2 + 1].realp = (float *)_memalign_malloc(sizeof(float) * fftSizeOver2Plus1, 16);
|
||||
|
@ -481,63 +374,8 @@ static const int8_t speakers_to_hesuvi_14[11][2] = {
|
|||
return nil;
|
||||
}
|
||||
|
||||
uint32_t channelFlag = [AudioChunk extractChannelFlag:(uint32_t)i fromConfig:config];
|
||||
uint32_t channelIndex = [AudioChunk findChannelIndex:channelFlag];
|
||||
|
||||
int leftInChannel = speaker_not_present;
|
||||
int rightInChannel = speaker_not_present;
|
||||
|
||||
if(impulseChannels == 7) {
|
||||
if(channelIndex <= max_speaker_index) {
|
||||
leftInChannel = speakers_to_hesuvi_7[channelIndex][0];
|
||||
rightInChannel = speakers_to_hesuvi_7[channelIndex][1];
|
||||
}
|
||||
} else {
|
||||
if(channelIndex <= max_speaker_index) {
|
||||
leftInChannel = speakers_to_hesuvi_14[channelIndex][0];
|
||||
rightInChannel = speakers_to_hesuvi_14[channelIndex][1];
|
||||
}
|
||||
}
|
||||
|
||||
if(leftInChannel == speaker_is_back_center || rightInChannel == speaker_is_back_center) {
|
||||
float *temp;
|
||||
if(impulseChannels == 7) {
|
||||
temp = (float *)malloc(sizeof(float) * fftSize);
|
||||
if(!temp) {
|
||||
free(deinterleavedImpulseBuffer);
|
||||
return nil;
|
||||
}
|
||||
|
||||
cblas_scopy((int)fftSize, deinterleavedImpulseBuffer + 4 * fftSize, 1, temp, 1);
|
||||
vDSP_vadd(temp, 1, deinterleavedImpulseBuffer + 5 * fftSize, 1, temp, 1, fftSize);
|
||||
|
||||
vDSP_ctoz((DSPComplex *)temp, 2, &impulse_responses[i * 2 + 0], 1, fftSizeOver2);
|
||||
vDSP_ctoz((DSPComplex *)temp, 2, &impulse_responses[i * 2 + 1], 1, fftSizeOver2);
|
||||
} else {
|
||||
temp = (float *)malloc(sizeof(float) * fftSize * 2);
|
||||
if(!temp) {
|
||||
free(deinterleavedImpulseBuffer);
|
||||
return nil;
|
||||
}
|
||||
|
||||
cblas_scopy((int)fftSize, deinterleavedImpulseBuffer + 4 * fftSize, 1, temp, 1);
|
||||
vDSP_vadd(temp, 1, deinterleavedImpulseBuffer + 12 * fftSize, 1, temp, 1, fftSize);
|
||||
|
||||
cblas_scopy((int)fftSize, deinterleavedImpulseBuffer + 5 * fftSize, 1, temp + fftSize, 1);
|
||||
vDSP_vadd(temp + fftSize, 1, deinterleavedImpulseBuffer + 11 * fftSize, 1, temp + fftSize, 1, fftSize);
|
||||
|
||||
vDSP_ctoz((DSPComplex *)temp, 2, &impulse_responses[i * 2 + 0], 1, fftSizeOver2);
|
||||
vDSP_ctoz((DSPComplex *)(temp + fftSize), 2, &impulse_responses[i * 2 + 1], 1, fftSizeOver2);
|
||||
}
|
||||
|
||||
free(temp);
|
||||
} else if(leftInChannel == speaker_not_present || rightInChannel == speaker_not_present) {
|
||||
vDSP_ctoz((DSPComplex *)(deinterleavedImpulseBuffer + impulseChannels * fftSize), 2, &impulse_responses[i * 2 + 0], 1, fftSizeOver2);
|
||||
vDSP_ctoz((DSPComplex *)(deinterleavedImpulseBuffer + impulseChannels * fftSize), 2, &impulse_responses[i * 2 + 1], 1, fftSizeOver2);
|
||||
} else {
|
||||
vDSP_ctoz((DSPComplex *)(deinterleavedImpulseBuffer + leftInChannel * fftSize), 2, &impulse_responses[i * 2 + 0], 1, fftSizeOver2);
|
||||
vDSP_ctoz((DSPComplex *)(deinterleavedImpulseBuffer + rightInChannel * fftSize), 2, &impulse_responses[i * 2 + 1], 1, fftSizeOver2);
|
||||
}
|
||||
vDSP_ctoz((DSPComplex *)(deinterleavedImpulseBuffer + i * fftSize * 2), 2, &impulse_responses[i * 2 + 0], 1, fftSizeOver2);
|
||||
vDSP_ctoz((DSPComplex *)(deinterleavedImpulseBuffer + i * fftSize * 2 + fftSize), 2, &impulse_responses[i * 2 + 1], 1, fftSizeOver2);
|
||||
|
||||
vDSP_DFT_Execute(dftSetupF, impulse_responses[i * 2 + 0].realp, impulse_responses[i * 2 + 0].imagp, impulse_responses[i * 2 + 0].realp, impulse_responses[i * 2 + 0].imagp);
|
||||
vDSP_DFT_Execute(dftSetupF, impulse_responses[i * 2 + 1].realp, impulse_responses[i * 2 + 1].imagp, impulse_responses[i * 2 + 1].realp, impulse_responses[i * 2 + 1].imagp);
|
||||
|
@ -553,7 +391,7 @@ static const int8_t speakers_to_hesuvi_14[11][2] = {
|
|||
prevInputs = (float **)calloc(channels, sizeof(float *));
|
||||
if(!prevInputs)
|
||||
return nil;
|
||||
for(size_t i = 0; i < channels; ++i) {
|
||||
for(int i = 0; i < channels; ++i) {
|
||||
prevInputs[i] = (float *)_memalign_malloc(sizeof(float) * fftSize, 16);
|
||||
if(!prevInputs[i])
|
||||
return nil;
|
||||
|
@ -584,7 +422,7 @@ static const int8_t speakers_to_hesuvi_14[11][2] = {
|
|||
free(input_filtered_signal_totals[1].imagp);
|
||||
|
||||
if(impulse_responses) {
|
||||
for(size_t i = 0; i < channelCount * 2; ++i) {
|
||||
for(int i = 0; i < channelCount * 2; ++i) {
|
||||
free(impulse_responses[i].realp);
|
||||
free(impulse_responses[i].imagp);
|
||||
}
|
||||
|
@ -595,26 +433,26 @@ static const int8_t speakers_to_hesuvi_14[11][2] = {
|
|||
free(right_result);
|
||||
|
||||
if(prevInputs) {
|
||||
for(size_t i = 0; i < channelCount; ++i) {
|
||||
for(int i = 0; i < channelCount; ++i) {
|
||||
free(prevInputs[i]);
|
||||
}
|
||||
free(prevInputs);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)process:(const float *)inBuffer sampleCount:(size_t)count toBuffer:(float *)outBuffer {
|
||||
- (void)process:(const float *)inBuffer sampleCount:(int)count toBuffer:(float *)outBuffer {
|
||||
const float scale = 1.0 / (4.0 * (float)fftSize);
|
||||
|
||||
while(count > 0) {
|
||||
const size_t countToDo = (count > bufferSize) ? bufferSize : count;
|
||||
const size_t prevToDo = fftSize - countToDo;
|
||||
const int countToDo = (count > bufferSize) ? bufferSize : count;
|
||||
const int prevToDo = fftSize - countToDo;
|
||||
|
||||
vDSP_vclr(input_filtered_signal_totals[0].realp, 1, fftSizeOver2);
|
||||
vDSP_vclr(input_filtered_signal_totals[0].imagp, 1, fftSizeOver2);
|
||||
vDSP_vclr(input_filtered_signal_totals[1].realp, 1, fftSizeOver2);
|
||||
vDSP_vclr(input_filtered_signal_totals[1].imagp, 1, fftSizeOver2);
|
||||
|
||||
for(size_t i = 0; i < channelCount; ++i) {
|
||||
for(int i = 0; i < channelCount; ++i) {
|
||||
cblas_scopy((int)prevToDo, prevInputs[i] + countToDo, 1, paddedSignal, 1);
|
||||
cblas_scopy((int)countToDo, inBuffer + i, (int)channelCount, paddedSignal + prevToDo, 1);
|
||||
cblas_scopy((int)fftSize, paddedSignal, 1, prevInputs[i], 1);
|
||||
|
@ -670,7 +508,7 @@ static const int8_t speakers_to_hesuvi_14[11][2] = {
|
|||
}
|
||||
|
||||
- (void)reset {
|
||||
for(size_t i = 0; i < channelCount; ++i) {
|
||||
for(int i = 0; i < channelCount; ++i) {
|
||||
vDSP_vclr(prevInputs[i], 1, fftSize);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,158 @@
|
|||
//
|
||||
// OutputAVFoundation.h
|
||||
// Cog
|
||||
//
|
||||
// Created by Christopher Snowhill on 6/23/22.
|
||||
// Copyright 2022 Christopher Snowhill. All rights reserved.
|
||||
//
|
||||
|
||||
#import <AssertMacros.h>
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <AudioToolbox/AudioToolbox.h>
|
||||
#import <AudioUnit/AudioUnit.h>
|
||||
#import <CoreAudio/AudioHardware.h>
|
||||
#import <CoreAudio/CoreAudioTypes.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
#import <atomic>
|
||||
using std::atomic_long;
|
||||
#else
|
||||
#import <stdatomic.h>
|
||||
#endif
|
||||
|
||||
#import "Downmix.h"
|
||||
|
||||
#import <CogAudio/CogAudio-Swift.h>
|
||||
|
||||
#import "HeadphoneFilter.h"
|
||||
|
||||
//#define OUTPUT_LOG
|
||||
#ifdef OUTPUT_LOG
|
||||
#import <stdio.h>
|
||||
#endif
|
||||
|
||||
@class OutputNode;
|
||||
|
||||
@class FSurroundFilter;
|
||||
|
||||
@interface OutputAVFoundation : NSObject {
|
||||
OutputNode *outputController;
|
||||
|
||||
BOOL rsDone;
|
||||
void *rsstate, *rsold;
|
||||
|
||||
void *rsvis;
|
||||
double lastVisRate;
|
||||
|
||||
BOOL stopInvoked;
|
||||
BOOL stopCompleted;
|
||||
BOOL running;
|
||||
BOOL stopping;
|
||||
BOOL stopped;
|
||||
BOOL started;
|
||||
BOOL paused;
|
||||
BOOL restarted;
|
||||
BOOL commandStop;
|
||||
|
||||
BOOL eqEnabled;
|
||||
BOOL eqInitialized;
|
||||
|
||||
BOOL streamFormatStarted;
|
||||
BOOL streamFormatChanged;
|
||||
|
||||
double secondsHdcdSustained;
|
||||
|
||||
BOOL defaultdevicelistenerapplied;
|
||||
BOOL currentdevicelistenerapplied;
|
||||
BOOL devicealivelistenerapplied;
|
||||
BOOL observersapplied;
|
||||
BOOL outputdevicechanged;
|
||||
|
||||
float volume;
|
||||
float eqPreamp;
|
||||
|
||||
AudioDeviceID outputDeviceID;
|
||||
AudioStreamBasicDescription realStreamFormat; // stream format pre-hrtf
|
||||
AudioStreamBasicDescription streamFormat; // stream format last seen in render callback
|
||||
AudioStreamBasicDescription realNewFormat; // in case of resampler flush
|
||||
AudioStreamBasicDescription newFormat; // in case of resampler flush
|
||||
|
||||
AudioStreamBasicDescription visFormat; // Mono format for vis
|
||||
|
||||
uint32_t realStreamChannelConfig;
|
||||
uint32_t streamChannelConfig;
|
||||
uint32_t realNewChannelConfig;
|
||||
uint32_t newChannelConfig;
|
||||
|
||||
AVSampleBufferAudioRenderer *audioRenderer;
|
||||
AVSampleBufferRenderSynchronizer *renderSynchronizer;
|
||||
|
||||
CMAudioFormatDescriptionRef audioFormatDescription;
|
||||
|
||||
id currentPtsObserver;
|
||||
NSLock *currentPtsLock;
|
||||
CMTime currentPts, lastPts;
|
||||
double secondsLatency;
|
||||
|
||||
CMTime outputPts, trackPts, lastCheckpointPts;
|
||||
AudioTimeStamp timeStamp;
|
||||
|
||||
size_t _bufferSize;
|
||||
|
||||
AudioUnit _eq;
|
||||
|
||||
DownmixProcessor *downmixerForVis;
|
||||
|
||||
VisualizationController *visController;
|
||||
|
||||
BOOL enableHrtf;
|
||||
HeadphoneFilter *hrtf;
|
||||
|
||||
BOOL enableFSurround;
|
||||
BOOL FSurroundDelayRemoved;
|
||||
int inputBufferLastTime;
|
||||
FSurroundFilter *fsurround;
|
||||
|
||||
BOOL resetStreamFormat;
|
||||
|
||||
BOOL shouldPlayOutBuffer;
|
||||
|
||||
float *samplePtr;
|
||||
float tempBuffer[512 * 32];
|
||||
float rsTempBuffer[4096 * 32];
|
||||
float inputBuffer[4096 * 32]; // 4096 samples times maximum supported channel count
|
||||
float fsurroundBuffer[8192 * 6];
|
||||
float hrtfBuffer[4096 * 2];
|
||||
float eqBuffer[4096 * 32];
|
||||
|
||||
float visAudio[4096];
|
||||
float visTemp[8192];
|
||||
|
||||
#ifdef OUTPUT_LOG
|
||||
FILE *_logFile;
|
||||
#endif
|
||||
}
|
||||
|
||||
- (id)initWithController:(OutputNode *)c;
|
||||
|
||||
- (BOOL)setup;
|
||||
- (OSStatus)setOutputDeviceByID:(AudioDeviceID)deviceID;
|
||||
- (BOOL)setOutputDeviceWithDeviceDict:(NSDictionary *)deviceDict;
|
||||
- (void)start;
|
||||
- (void)pause;
|
||||
- (void)resume;
|
||||
- (void)stop;
|
||||
|
||||
- (double)latency;
|
||||
|
||||
- (void)setVolume:(double)v;
|
||||
|
||||
- (void)setEqualizerEnabled:(BOOL)enabled;
|
||||
|
||||
- (void)setShouldPlayOutBuffer:(BOOL)enabled;
|
||||
|
||||
- (void)sustainHDCD;
|
||||
|
||||
@end
|
|
@ -1,115 +0,0 @@
|
|||
//
|
||||
// OutputCoreAudio.h
|
||||
// Cog
|
||||
//
|
||||
// Created by Vincent Spader on 8/2/05.
|
||||
// Copyright 2005 Vincent Spader. All rights reserved.
|
||||
//
|
||||
|
||||
#import <AssertMacros.h>
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <AudioToolbox/AudioToolbox.h>
|
||||
#import <AudioUnit/AudioUnit.h>
|
||||
#import <CoreAudio/AudioHardware.h>
|
||||
#import <CoreAudio/CoreAudioTypes.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
#import <atomic>
|
||||
using std::atomic_long;
|
||||
#else
|
||||
#import <stdatomic.h>
|
||||
#endif
|
||||
|
||||
#import "Downmix.h"
|
||||
|
||||
#import "VisualizationController.h"
|
||||
|
||||
#import "Semaphore.h"
|
||||
|
||||
//#define OUTPUT_LOG
|
||||
#ifdef OUTPUT_LOG
|
||||
#import <stdio.h>
|
||||
#endif
|
||||
|
||||
@class OutputNode;
|
||||
|
||||
@interface OutputCoreAudio : NSObject {
|
||||
OutputNode *outputController;
|
||||
|
||||
Semaphore *writeSemaphore;
|
||||
Semaphore *readSemaphore;
|
||||
|
||||
BOOL stopInvoked;
|
||||
BOOL running;
|
||||
BOOL stopping;
|
||||
BOOL stopped;
|
||||
BOOL started;
|
||||
BOOL paused;
|
||||
BOOL stopNext;
|
||||
BOOL restarted;
|
||||
|
||||
BOOL eqEnabled;
|
||||
BOOL eqInitialized;
|
||||
|
||||
BOOL streamFormatStarted;
|
||||
|
||||
atomic_long bytesRendered;
|
||||
atomic_long bytesHdcdSustained;
|
||||
|
||||
BOOL defaultdevicelistenerapplied;
|
||||
BOOL currentdevicelistenerapplied;
|
||||
BOOL devicealivelistenerapplied;
|
||||
BOOL observersapplied;
|
||||
BOOL outputdevicechanged;
|
||||
|
||||
float volume;
|
||||
float eqPreamp;
|
||||
|
||||
AVAudioFormat *_deviceFormat;
|
||||
|
||||
AudioDeviceID outputDeviceID;
|
||||
AudioStreamBasicDescription deviceFormat; // info about the default device
|
||||
AudioStreamBasicDescription streamFormat; // stream format last seen in render callback
|
||||
|
||||
AudioStreamBasicDescription visFormat; // Mono format for vis
|
||||
|
||||
uint32_t deviceChannelConfig;
|
||||
uint32_t streamChannelConfig;
|
||||
|
||||
AUAudioUnit *_au;
|
||||
size_t _bufferSize;
|
||||
|
||||
AudioUnit _eq;
|
||||
|
||||
DownmixProcessor *downmixer;
|
||||
DownmixProcessor *downmixerForVis;
|
||||
|
||||
VisualizationController *visController;
|
||||
|
||||
os_workgroup_t wg;
|
||||
os_workgroup_join_token_s wgToken;
|
||||
|
||||
#ifdef OUTPUT_LOG
|
||||
FILE *_logFile;
|
||||
#endif
|
||||
}
|
||||
|
||||
- (id)initWithController:(OutputNode *)c;
|
||||
|
||||
- (BOOL)setup;
|
||||
- (OSStatus)setOutputDeviceByID:(AudioDeviceID)deviceID;
|
||||
- (BOOL)setOutputDeviceWithDeviceDict:(NSDictionary *)deviceDict;
|
||||
- (void)start;
|
||||
- (void)pause;
|
||||
- (void)resume;
|
||||
- (void)stop;
|
||||
|
||||
- (void)setVolume:(double)v;
|
||||
|
||||
- (void)setEqualizerEnabled:(BOOL)enabled;
|
||||
|
||||
- (void)sustainHDCD;
|
||||
|
||||
@end
|
|
@ -1,986 +0,0 @@
|
|||
//
|
||||
// OutputCoreAudio.m
|
||||
// Cog
|
||||
//
|
||||
// Created by Vincent Spader on 8/2/05.
|
||||
// Copyright 2005 Vincent Spader. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OutputCoreAudio.h"
|
||||
#import "OutputNode.h"
|
||||
|
||||
#ifdef _DEBUG
|
||||
#import "BadSampleCleaner.h"
|
||||
#endif
|
||||
|
||||
#import "Logging.h"
|
||||
|
||||
#import <Accelerate/Accelerate.h>
|
||||
|
||||
extern void scale_by_volume(float *buffer, size_t count, float volume);
|
||||
|
||||
static NSString *CogPlaybackDidBeginNotficiation = @"CogPlaybackDidBeginNotficiation";
|
||||
|
||||
@implementation OutputCoreAudio
|
||||
|
||||
static void *kOutputCoreAudioContext = &kOutputCoreAudioContext;
|
||||
|
||||
static void fillBuffers(AudioBufferList *ioData, float *inbuffer, size_t count, size_t offset) {
|
||||
const size_t channels = ioData->mNumberBuffers;
|
||||
for(int i = 0; i < channels; ++i) {
|
||||
const size_t maxCount = (ioData->mBuffers[i].mDataByteSize / sizeof(float)) - offset;
|
||||
float *output = ((float *)ioData->mBuffers[i].mData) + offset;
|
||||
const float *input = inbuffer + i;
|
||||
cblas_scopy((int)((count > maxCount) ? maxCount : count), input, (int)channels, output, 1);
|
||||
ioData->mBuffers[i].mNumberChannels = 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void clearBuffers(AudioBufferList *ioData, size_t count, size_t offset) {
|
||||
for(int i = 0; i < ioData->mNumberBuffers; ++i) {
|
||||
memset(ioData->mBuffers[i].mData + offset * sizeof(float), 0, count * sizeof(float));
|
||||
ioData->mBuffers[i].mNumberChannels = 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void scaleBuffersByVolume(AudioBufferList *ioData, float volume) {
|
||||
if(volume != 1.0) {
|
||||
for(int i = 0; i < ioData->mNumberBuffers; ++i) {
|
||||
scale_by_volume((float *)ioData->mBuffers[i].mData, ioData->mBuffers[i].mDataByteSize / sizeof(float), volume);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static OSStatus renderCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) {
|
||||
@autoreleasepool {
|
||||
if(!inRefCon) return 0;
|
||||
|
||||
OutputCoreAudio *_self = (__bridge OutputCoreAudio *)inRefCon;
|
||||
|
||||
const int channels = _self->deviceFormat.mChannelsPerFrame;
|
||||
const int bytesPerPacket = channels * sizeof(float);
|
||||
|
||||
size_t amountToRead, amountRead = 0;
|
||||
|
||||
amountToRead = inNumberFrames * bytesPerPacket;
|
||||
|
||||
int visTabulated = 0;
|
||||
float visAudio[inNumberFrames]; // Chunk size
|
||||
|
||||
if(_self->stopping == YES || [_self->outputController shouldContinue] == NO) {
|
||||
// Chain is dead, fill out the serial number pointer forever with silence
|
||||
clearBuffers(ioData, amountToRead / bytesPerPacket, 0);
|
||||
atomic_fetch_add(&_self->bytesRendered, amountToRead);
|
||||
_self->stopping = YES;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if([[_self->outputController buffer] isEmpty] && ![_self->outputController chainQueueHasTracks]) {
|
||||
// Hit end of last track, pad with silence until queue event stops us
|
||||
clearBuffers(ioData, amountToRead / bytesPerPacket, 0);
|
||||
atomic_fetch_add(&_self->bytesRendered, amountToRead);
|
||||
return 0;
|
||||
}
|
||||
|
||||
AudioChunk *chunk = [[_self->outputController buffer] removeSamples:(amountToRead / bytesPerPacket)];
|
||||
|
||||
size_t frameCount = [chunk frameCount];
|
||||
AudioStreamBasicDescription format = [chunk format];
|
||||
uint32_t config = [chunk channelConfig];
|
||||
|
||||
if(frameCount) {
|
||||
if(!_self->streamFormatStarted || config != _self->streamChannelConfig || memcmp(&_self->streamFormat, &format, sizeof(format)) != 0) {
|
||||
_self->streamFormat = format;
|
||||
_self->streamChannelConfig = config;
|
||||
_self->streamFormatStarted = YES;
|
||||
_self->downmixer = [[DownmixProcessor alloc] initWithInputFormat:format inputConfig:config andOutputFormat:_self->deviceFormat outputConfig:_self->deviceChannelConfig];
|
||||
_self->downmixerForVis = [[DownmixProcessor alloc] initWithInputFormat:format inputConfig:config andOutputFormat:_self->visFormat outputConfig:AudioConfigMono];
|
||||
}
|
||||
|
||||
double chunkDuration = [chunk duration];
|
||||
|
||||
NSData *samples = [chunk removeSamples:frameCount];
|
||||
#ifdef _DEBUG
|
||||
[BadSampleCleaner cleanSamples:(float *)[samples bytes]
|
||||
amount:frameCount * format.mChannelsPerFrame
|
||||
location:@"pre downmix"];
|
||||
#endif
|
||||
|
||||
float downmixedData[frameCount * channels];
|
||||
[_self->downmixer process:[samples bytes] frameCount:frameCount output:downmixedData];
|
||||
#ifdef _DEBUG
|
||||
[BadSampleCleaner cleanSamples:downmixedData
|
||||
amount:frameCount * channels
|
||||
location:@"post downmix"];
|
||||
#endif
|
||||
|
||||
[_self->downmixerForVis process:[samples bytes] frameCount:frameCount output:visAudio];
|
||||
visTabulated += frameCount;
|
||||
|
||||
fillBuffers(ioData, downmixedData, frameCount, 0);
|
||||
amountRead = frameCount * bytesPerPacket;
|
||||
[_self->outputController incrementAmountPlayed:chunkDuration];
|
||||
atomic_fetch_add(&_self->bytesRendered, amountRead);
|
||||
[_self->writeSemaphore signal];
|
||||
}
|
||||
|
||||
// Try repeatedly! Buffer wraps can cause a slight data shortage, as can
|
||||
// unexpected track changes.
|
||||
while((amountRead < amountToRead) && [_self->outputController shouldContinue] == YES) {
|
||||
chunk = [[_self->outputController buffer] removeSamples:((amountToRead - amountRead) / bytesPerPacket)];
|
||||
frameCount = [chunk frameCount];
|
||||
format = [chunk format];
|
||||
config = [chunk channelConfig];
|
||||
if(frameCount) {
|
||||
if(!_self->streamFormatStarted || config != _self->streamChannelConfig || memcmp(&_self->streamFormat, &format, sizeof(format)) != 0) {
|
||||
_self->streamFormat = format;
|
||||
_self->streamFormatStarted = YES;
|
||||
_self->downmixer = [[DownmixProcessor alloc] initWithInputFormat:format inputConfig:config andOutputFormat:_self->deviceFormat outputConfig:_self->deviceChannelConfig];
|
||||
_self->downmixerForVis = [[DownmixProcessor alloc] initWithInputFormat:format inputConfig:config andOutputFormat:_self->visFormat outputConfig:AudioConfigMono];
|
||||
}
|
||||
atomic_fetch_add(&_self->bytesRendered, frameCount * bytesPerPacket);
|
||||
double chunkDuration = [chunk duration];
|
||||
NSData *samples = [chunk removeSamples:frameCount];
|
||||
#ifdef _DEBUG
|
||||
[BadSampleCleaner cleanSamples:(float *)[samples bytes]
|
||||
amount:frameCount * format.mChannelsPerFrame
|
||||
location:@"pre downmix"];
|
||||
#endif
|
||||
|
||||
float downmixedData[frameCount * channels];
|
||||
[_self->downmixer process:[samples bytes] frameCount:frameCount output:downmixedData];
|
||||
fillBuffers(ioData, downmixedData, frameCount, amountRead / bytesPerPacket);
|
||||
#ifdef _DEBUG
|
||||
[BadSampleCleaner cleanSamples:downmixedData
|
||||
amount:frameCount * channels
|
||||
location:@"post downmix"];
|
||||
#endif
|
||||
|
||||
[_self->downmixerForVis process:[samples bytes] frameCount:frameCount output:visAudio + visTabulated];
|
||||
visTabulated += frameCount;
|
||||
|
||||
[_self->outputController incrementAmountPlayed:chunkDuration];
|
||||
|
||||
amountRead += frameCount * bytesPerPacket;
|
||||
[_self->writeSemaphore signal];
|
||||
} else {
|
||||
[_self->readSemaphore timedWait:500];
|
||||
}
|
||||
}
|
||||
|
||||
float volumeScale = 1.0;
|
||||
long sustained = atomic_load_explicit(&_self->bytesHdcdSustained, memory_order_relaxed);
|
||||
if(sustained) {
|
||||
if(sustained < amountRead) {
|
||||
atomic_store(&_self->bytesHdcdSustained, 0);
|
||||
} else {
|
||||
atomic_fetch_sub(&_self->bytesHdcdSustained, amountRead);
|
||||
}
|
||||
volumeScale = 0.5;
|
||||
}
|
||||
|
||||
if(_self->eqEnabled) {
|
||||
volumeScale *= _self->eqPreamp;
|
||||
}
|
||||
|
||||
scaleBuffersByVolume(ioData, _self->volume * volumeScale);
|
||||
|
||||
if(amountRead < amountToRead) {
|
||||
// Either underrun, or no data at all. Caller output tends to just
|
||||
// buffer loop if it doesn't get anything, so always produce a full
|
||||
// buffer, and silence anything we couldn't supply.
|
||||
clearBuffers(ioData, (amountToRead - amountRead) / bytesPerPacket, amountRead / bytesPerPacket);
|
||||
}
|
||||
|
||||
[_self->visController postSampleRate:_self->deviceFormat.mSampleRate];
|
||||
[_self->visController postVisPCM:visAudio amount:visTabulated];
|
||||
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
- (id)initWithController:(OutputNode *)c {
|
||||
self = [super init];
|
||||
if(self) {
|
||||
outputController = c;
|
||||
volume = 1.0;
|
||||
outputDeviceID = -1;
|
||||
|
||||
atomic_init(&bytesRendered, 0);
|
||||
atomic_init(&bytesHdcdSustained, 0);
|
||||
|
||||
writeSemaphore = [[Semaphore alloc] init];
|
||||
readSemaphore = [[Semaphore alloc] init];
|
||||
|
||||
#ifdef OUTPUT_LOG
|
||||
_logFile = fopen("/tmp/CogAudioLog.raw", "wb");
|
||||
#endif
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
static OSStatus
|
||||
default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses, void *inUserData) {
|
||||
OutputCoreAudio *this = (__bridge OutputCoreAudio *)inUserData;
|
||||
return [this setOutputDeviceByID:-1];
|
||||
}
|
||||
|
||||
static OSStatus
|
||||
current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses, void *inUserData) {
|
||||
OutputCoreAudio *this = (__bridge OutputCoreAudio *)inUserData;
|
||||
for(UInt32 i = 0; i < inNumberAddresses; ++i) {
|
||||
switch(inAddresses[i].mSelector) {
|
||||
case kAudioDevicePropertyDeviceIsAlive:
|
||||
return [this setOutputDeviceByID:-1];
|
||||
|
||||
case kAudioDevicePropertyNominalSampleRate:
|
||||
case kAudioDevicePropertyStreamFormat:
|
||||
this->outputdevicechanged = YES;
|
||||
return noErr;
|
||||
}
|
||||
}
|
||||
return noErr;
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
|
||||
if(context != kOutputCoreAudioContext) {
|
||||
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
|
||||
return;
|
||||
}
|
||||
|
||||
if([keyPath isEqualToString:@"values.outputDevice"]) {
|
||||
NSDictionary *device = [[[NSUserDefaultsController sharedUserDefaultsController] defaults] objectForKey:@"outputDevice"];
|
||||
|
||||
[self setOutputDeviceWithDeviceDict:device];
|
||||
} else if([keyPath isEqualToString:@"values.GraphicEQenable"]) {
|
||||
BOOL enabled = [[[[NSUserDefaultsController sharedUserDefaultsController] defaults] objectForKey:@"GraphicEQenable"] boolValue];
|
||||
|
||||
[self setEqualizerEnabled:enabled];
|
||||
} else if([keyPath isEqualToString:@"values.eqPreamp"]) {
|
||||
float preamp = [[[NSUserDefaultsController sharedUserDefaultsController] defaults] floatForKey:@"eqPreamp"];
|
||||
eqPreamp = pow(10.0, preamp / 20.0);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)signalEndOfStream {
|
||||
[outputController resetAmountPlayed];
|
||||
[outputController endOfInputPlayed];
|
||||
}
|
||||
|
||||
- (void)threadEntry:(id)arg {
|
||||
running = YES;
|
||||
started = NO;
|
||||
stopNext = NO;
|
||||
atomic_store(&bytesRendered, 0);
|
||||
NSMutableArray *delayedEvents = [[NSMutableArray alloc] init];
|
||||
BOOL delayedEventsPopped = YES;
|
||||
|
||||
while(!stopping) {
|
||||
if(outputdevicechanged) {
|
||||
[self resetIfOutputChanged];
|
||||
outputdevicechanged = NO;
|
||||
}
|
||||
if(restarted) break;
|
||||
if([outputController shouldReset]) {
|
||||
@autoreleasepool {
|
||||
[[outputController buffer] reset];
|
||||
}
|
||||
[outputController setShouldReset:NO];
|
||||
[delayedEvents removeAllObjects];
|
||||
delayedEventsPopped = YES;
|
||||
}
|
||||
|
||||
while([delayedEvents count]) {
|
||||
size_t localBytesRendered = atomic_load_explicit(&bytesRendered, memory_order_relaxed);
|
||||
double secondsRendered = (double)localBytesRendered / (double)(deviceFormat.mBytesPerPacket * deviceFormat.mSampleRate);
|
||||
if(secondsRendered >= [[delayedEvents objectAtIndex:0] doubleValue]) {
|
||||
if([outputController chainQueueHasTracks])
|
||||
delayedEventsPopped = YES;
|
||||
[self signalEndOfStream];
|
||||
[delayedEvents removeObjectAtIndex:0];
|
||||
} else
|
||||
break;
|
||||
}
|
||||
|
||||
if(stopping)
|
||||
break;
|
||||
|
||||
size_t frameCount = 0;
|
||||
|
||||
if(![[outputController buffer] isFull]) {
|
||||
@autoreleasepool {
|
||||
AudioChunk *chunk = [outputController readChunk:512];
|
||||
frameCount = [chunk frameCount];
|
||||
if(frameCount) {
|
||||
[[outputController buffer] addChunk:chunk];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(frameCount) {
|
||||
[readSemaphore signal];
|
||||
continue;
|
||||
} else if([outputController shouldContinue] == NO)
|
||||
break;
|
||||
else if([[outputController buffer] isFull]) {
|
||||
if(!started) {
|
||||
started = YES;
|
||||
if(!paused) {
|
||||
NSError *err;
|
||||
[_au startHardwareAndReturnError:&err];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// End of input possibly reached
|
||||
if(delayedEventsPopped && [outputController endOfStream] == YES) {
|
||||
double secondsBuffered = [[outputController buffer] listDuration];
|
||||
size_t _bytesRendered = atomic_load_explicit(&bytesRendered, memory_order_relaxed);
|
||||
secondsBuffered += (double)_bytesRendered / (double)(deviceFormat.mBytesPerPacket * deviceFormat.mSampleRate);
|
||||
if([outputController chainQueueHasTracks]) {
|
||||
if(secondsBuffered <= 0.005)
|
||||
secondsBuffered = 0.0;
|
||||
else
|
||||
secondsBuffered -= 0.005;
|
||||
} else {
|
||||
stopNext = YES;
|
||||
break;
|
||||
}
|
||||
[delayedEvents addObject:@(secondsBuffered)];
|
||||
delayedEventsPopped = NO;
|
||||
if(!started) {
|
||||
started = YES;
|
||||
if(!paused) {
|
||||
NSError *err;
|
||||
[_au startHardwareAndReturnError:&err];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
[readSemaphore signal];
|
||||
[writeSemaphore timedWait:5000];
|
||||
}
|
||||
|
||||
stopped = YES;
|
||||
[self stop];
|
||||
}
|
||||
|
||||
- (OSStatus)setOutputDeviceByID:(AudioDeviceID)deviceID {
|
||||
OSStatus err;
|
||||
BOOL defaultDevice = NO;
|
||||
AudioObjectPropertyAddress theAddress = {
|
||||
.mSelector = kAudioHardwarePropertyDefaultOutputDevice,
|
||||
.mScope = kAudioObjectPropertyScopeGlobal,
|
||||
.mElement = kAudioObjectPropertyElementMaster
|
||||
};
|
||||
|
||||
if(deviceID == -1) {
|
||||
defaultDevice = YES;
|
||||
UInt32 size = sizeof(AudioDeviceID);
|
||||
err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &theAddress, 0, NULL, &size, &deviceID);
|
||||
|
||||
if(err != noErr) {
|
||||
DLog(@"THERE'S NO DEFAULT OUTPUT DEVICE");
|
||||
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
if(_au) {
|
||||
if(defaultdevicelistenerapplied && !defaultDevice) {
|
||||
/* Already set above
|
||||
* theAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice; */
|
||||
AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &theAddress, default_device_changed, (__bridge void *_Nullable)(self));
|
||||
defaultdevicelistenerapplied = NO;
|
||||
}
|
||||
|
||||
outputdevicechanged = NO;
|
||||
|
||||
if(outputDeviceID != deviceID) {
|
||||
if(currentdevicelistenerapplied) {
|
||||
if(devicealivelistenerapplied) {
|
||||
theAddress.mSelector = kAudioDevicePropertyDeviceIsAlive;
|
||||
AudioObjectRemovePropertyListener(outputDeviceID, &theAddress, current_device_listener, (__bridge void *_Nullable)(self));
|
||||
devicealivelistenerapplied = NO;
|
||||
}
|
||||
theAddress.mSelector = kAudioDevicePropertyStreamFormat;
|
||||
AudioObjectRemovePropertyListener(outputDeviceID, &theAddress, current_device_listener, (__bridge void *_Nullable)(self));
|
||||
theAddress.mSelector = kAudioDevicePropertyNominalSampleRate;
|
||||
AudioObjectRemovePropertyListener(outputDeviceID, &theAddress, current_device_listener, (__bridge void *_Nullable)(self));
|
||||
currentdevicelistenerapplied = NO;
|
||||
}
|
||||
|
||||
DLog(@"Device: %i\n", deviceID);
|
||||
outputDeviceID = deviceID;
|
||||
|
||||
NSError *nserr;
|
||||
[_au setDeviceID:outputDeviceID error:&nserr];
|
||||
if(nserr != nil) {
|
||||
return (OSErr)[nserr code];
|
||||
}
|
||||
|
||||
outputdevicechanged = YES;
|
||||
}
|
||||
|
||||
if(!currentdevicelistenerapplied) {
|
||||
if(!devicealivelistenerapplied && !defaultDevice) {
|
||||
theAddress.mSelector = kAudioDevicePropertyDeviceIsAlive;
|
||||
AudioObjectAddPropertyListener(outputDeviceID, &theAddress, current_device_listener, (__bridge void *_Nullable)(self));
|
||||
devicealivelistenerapplied = YES;
|
||||
}
|
||||
theAddress.mSelector = kAudioDevicePropertyStreamFormat;
|
||||
AudioObjectAddPropertyListener(outputDeviceID, &theAddress, current_device_listener, (__bridge void *_Nullable)(self));
|
||||
theAddress.mSelector = kAudioDevicePropertyNominalSampleRate;
|
||||
AudioObjectAddPropertyListener(outputDeviceID, &theAddress, current_device_listener, (__bridge void *_Nullable)(self));
|
||||
currentdevicelistenerapplied = YES;
|
||||
}
|
||||
|
||||
if(!defaultdevicelistenerapplied && defaultDevice) {
|
||||
theAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
|
||||
AudioObjectAddPropertyListener(kAudioObjectSystemObject, &theAddress, default_device_changed, (__bridge void *_Nullable)(self));
|
||||
defaultdevicelistenerapplied = YES;
|
||||
}
|
||||
} else {
|
||||
err = noErr;
|
||||
}
|
||||
|
||||
if(err != noErr) {
|
||||
DLog(@"No output device with ID %d could be found.", deviceID);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
- (BOOL)setOutputDeviceWithDeviceDict:(NSDictionary *)deviceDict {
|
||||
NSNumber *deviceIDNum = [deviceDict objectForKey:@"deviceID"];
|
||||
AudioDeviceID outputDeviceID = [deviceIDNum unsignedIntValue] ?: -1;
|
||||
|
||||
__block OSStatus err = [self setOutputDeviceByID:outputDeviceID];
|
||||
|
||||
if(err != noErr) {
|
||||
// Try matching by name.
|
||||
NSString *userDeviceName = deviceDict[@"name"];
|
||||
|
||||
[self enumerateAudioOutputsUsingBlock:
|
||||
^(NSString *deviceName, AudioDeviceID deviceID, AudioDeviceID systemDefaultID, BOOL *stop) {
|
||||
if([deviceName isEqualToString:userDeviceName]) {
|
||||
err = [self setOutputDeviceByID:deviceID];
|
||||
|
||||
#if 0
|
||||
// Disable. Would cause loop by triggering `-observeValueForKeyPath:ofObject:change:context:` above.
|
||||
// Update `outputDevice`, in case the ID has changed.
|
||||
NSDictionary *deviceInfo = @{
|
||||
@"name": deviceName,
|
||||
@"deviceID": @(deviceID),
|
||||
};
|
||||
[[NSUserDefaults standardUserDefaults] setObject:deviceInfo forKey:@"outputDevice"];
|
||||
#endif
|
||||
|
||||
DLog(@"Found output device: \"%@\" (%d).", deviceName, deviceID);
|
||||
|
||||
*stop = YES;
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
if(err != noErr) {
|
||||
ALog(@"No output device could be found, your random error code is %d. Have a nice day!", err);
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
// The following is largely a copy pasta of -awakeFromNib from "OutputsArrayController.m".
|
||||
// TODO: Share the code. (How to do this across xcodeproj?)
|
||||
- (void)enumerateAudioOutputsUsingBlock:(void(NS_NOESCAPE ^ _Nonnull)(NSString *deviceName, AudioDeviceID deviceID, AudioDeviceID systemDefaultID, BOOL *stop))block {
|
||||
UInt32 propsize;
|
||||
AudioObjectPropertyAddress theAddress = {
|
||||
.mSelector = kAudioHardwarePropertyDevices,
|
||||
.mScope = kAudioObjectPropertyScopeGlobal,
|
||||
.mElement = kAudioObjectPropertyElementMaster
|
||||
};
|
||||
|
||||
__Verify_noErr(AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &theAddress, 0, NULL, &propsize));
|
||||
UInt32 nDevices = propsize / (UInt32)sizeof(AudioDeviceID);
|
||||
AudioDeviceID *devids = malloc(propsize);
|
||||
__Verify_noErr(AudioObjectGetPropertyData(kAudioObjectSystemObject, &theAddress, 0, NULL, &propsize, devids));
|
||||
|
||||
theAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
|
||||
AudioDeviceID systemDefault;
|
||||
propsize = sizeof(systemDefault);
|
||||
__Verify_noErr(AudioObjectGetPropertyData(kAudioObjectSystemObject, &theAddress, 0, NULL, &propsize, &systemDefault));
|
||||
|
||||
theAddress.mScope = kAudioDevicePropertyScopeOutput;
|
||||
|
||||
for(UInt32 i = 0; i < nDevices; ++i) {
|
||||
UInt32 isAlive = 0;
|
||||
propsize = sizeof(isAlive);
|
||||
theAddress.mSelector = kAudioDevicePropertyDeviceIsAlive;
|
||||
__Verify_noErr(AudioObjectGetPropertyData(devids[i], &theAddress, 0, NULL, &propsize, &isAlive));
|
||||
if(!isAlive) continue;
|
||||
|
||||
CFStringRef name = NULL;
|
||||
propsize = sizeof(name);
|
||||
theAddress.mSelector = kAudioDevicePropertyDeviceNameCFString;
|
||||
__Verify_noErr(AudioObjectGetPropertyData(devids[i], &theAddress, 0, NULL, &propsize, &name));
|
||||
|
||||
propsize = 0;
|
||||
theAddress.mSelector = kAudioDevicePropertyStreamConfiguration;
|
||||
__Verify_noErr(AudioObjectGetPropertyDataSize(devids[i], &theAddress, 0, NULL, &propsize));
|
||||
|
||||
if(propsize < sizeof(UInt32)) {
|
||||
if(name) CFRelease(name);
|
||||
continue;
|
||||
}
|
||||
|
||||
AudioBufferList *bufferList = (AudioBufferList *)malloc(propsize);
|
||||
__Verify_noErr(AudioObjectGetPropertyData(devids[i], &theAddress, 0, NULL, &propsize, bufferList));
|
||||
UInt32 bufferCount = bufferList->mNumberBuffers;
|
||||
free(bufferList);
|
||||
|
||||
if(!bufferCount) {
|
||||
if(name) CFRelease(name);
|
||||
continue;
|
||||
}
|
||||
|
||||
BOOL stop = NO;
|
||||
NSString *deviceName = name ? [NSString stringWithString:(__bridge NSString *)name] : [NSString stringWithFormat:@"Unknown device %u", (unsigned int)devids[i]];
|
||||
|
||||
block(deviceName,
|
||||
devids[i],
|
||||
systemDefault,
|
||||
&stop);
|
||||
|
||||
CFRelease(name);
|
||||
|
||||
if(stop) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
free(devids);
|
||||
}
|
||||
|
||||
- (void)resetIfOutputChanged {
|
||||
AVAudioFormat *format = _au.outputBusses[0].format;
|
||||
|
||||
if(!restarted && (!_deviceFormat || ![_deviceFormat isEqual:format])) {
|
||||
[outputController restartPlaybackAtCurrentPosition];
|
||||
restarted = YES;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)updateDeviceFormat {
|
||||
AVAudioFormat *format = _au.outputBusses[0].format;
|
||||
|
||||
if(!_deviceFormat || ![_deviceFormat isEqual:format]) {
|
||||
NSError *err;
|
||||
AVAudioFormat *renderFormat;
|
||||
|
||||
[outputController incrementAmountPlayed:[[outputController buffer] listDuration]];
|
||||
@autoreleasepool {
|
||||
[[outputController buffer] reset];
|
||||
}
|
||||
|
||||
_deviceFormat = format;
|
||||
deviceFormat = *(format.streamDescription);
|
||||
|
||||
/// Seems some 3rd party devices return incorrect stuff...or I just don't like noninterleaved data.
|
||||
deviceFormat.mFormatFlags &= ~kLinearPCMFormatFlagIsNonInterleaved;
|
||||
// deviceFormat.mFormatFlags &= ~kLinearPCMFormatFlagIsFloat;
|
||||
// deviceFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
|
||||
// We don't want more than 8 channels
|
||||
if(deviceFormat.mChannelsPerFrame > 8) {
|
||||
deviceFormat.mChannelsPerFrame = 8;
|
||||
}
|
||||
deviceFormat.mBytesPerFrame = deviceFormat.mChannelsPerFrame * (deviceFormat.mBitsPerChannel / 8);
|
||||
deviceFormat.mBytesPerPacket = deviceFormat.mBytesPerFrame * deviceFormat.mFramesPerPacket;
|
||||
|
||||
visFormat = deviceFormat;
|
||||
visFormat.mChannelsPerFrame = 1;
|
||||
visFormat.mBytesPerFrame = visFormat.mChannelsPerFrame * (visFormat.mBitsPerChannel / 8);
|
||||
visFormat.mBytesPerPacket = visFormat.mBytesPerFrame * visFormat.mFramesPerPacket;
|
||||
|
||||
/* Set the channel layout for the audio queue */
|
||||
AudioChannelLayoutTag tag = 0;
|
||||
switch(deviceFormat.mChannelsPerFrame) {
|
||||
case 1:
|
||||
tag = kAudioChannelLayoutTag_Mono;
|
||||
deviceChannelConfig = AudioConfigMono;
|
||||
break;
|
||||
case 2:
|
||||
tag = kAudioChannelLayoutTag_Stereo;
|
||||
deviceChannelConfig = AudioConfigStereo;
|
||||
break;
|
||||
case 3:
|
||||
tag = kAudioChannelLayoutTag_DVD_4;
|
||||
deviceChannelConfig = AudioConfig3Point0;
|
||||
break;
|
||||
case 4:
|
||||
tag = kAudioChannelLayoutTag_Quadraphonic;
|
||||
deviceChannelConfig = AudioConfig4Point0;
|
||||
break;
|
||||
case 5:
|
||||
tag = kAudioChannelLayoutTag_MPEG_5_0_A;
|
||||
deviceChannelConfig = AudioConfig5Point0;
|
||||
break;
|
||||
case 6:
|
||||
tag = kAudioChannelLayoutTag_MPEG_5_1_A;
|
||||
deviceChannelConfig = AudioConfig5Point1;
|
||||
break;
|
||||
case 7:
|
||||
tag = kAudioChannelLayoutTag_MPEG_6_1_A;
|
||||
deviceChannelConfig = AudioConfig6Point1;
|
||||
break;
|
||||
case 8:
|
||||
tag = kAudioChannelLayoutTag_MPEG_7_1_A;
|
||||
deviceChannelConfig = AudioConfig7Point1;
|
||||
break;
|
||||
}
|
||||
|
||||
renderFormat = [[AVAudioFormat alloc] initWithStreamDescription:&deviceFormat channelLayout:[[AVAudioChannelLayout alloc] initWithLayoutTag:tag]];
|
||||
[_au.inputBusses[0] setFormat:renderFormat error:&err];
|
||||
if(err != nil)
|
||||
return NO;
|
||||
|
||||
[outputController setFormat:&deviceFormat channelConfig:deviceChannelConfig];
|
||||
|
||||
AudioStreamBasicDescription asbd = deviceFormat;
|
||||
|
||||
asbd.mFormatFlags &= ~kAudioFormatFlagIsPacked;
|
||||
|
||||
AudioUnitSetProperty(_eq, kAudioUnitProperty_StreamFormat,
|
||||
kAudioUnitScope_Input, 0, &asbd, sizeof(asbd));
|
||||
|
||||
AudioUnitSetProperty(_eq, kAudioUnitProperty_StreamFormat,
|
||||
kAudioUnitScope_Output, 0, &asbd, sizeof(asbd));
|
||||
AudioUnitReset(_eq, kAudioUnitScope_Input, 0);
|
||||
AudioUnitReset(_eq, kAudioUnitScope_Output, 0);
|
||||
|
||||
AudioUnitReset(_eq, kAudioUnitScope_Global, 0);
|
||||
|
||||
eqEnabled = [[[[NSUserDefaultsController sharedUserDefaultsController] defaults] objectForKey:@"GraphicEQenable"] boolValue];
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)audioOutputBlock {
|
||||
__block AudioUnit eq = _eq;
|
||||
__block AudioStreamBasicDescription *format = &deviceFormat;
|
||||
__block BOOL *eqEnabled = &self->eqEnabled;
|
||||
__block void *refCon = (__bridge void *)self;
|
||||
|
||||
#ifdef OUTPUT_LOG
|
||||
__block FILE *logFile = _logFile;
|
||||
#endif
|
||||
|
||||
_au.outputProvider = ^AUAudioUnitStatus(AudioUnitRenderActionFlags *_Nonnull actionFlags, const AudioTimeStamp *_Nonnull timestamp, AUAudioFrameCount frameCount, NSInteger inputBusNumber, AudioBufferList *_Nonnull inputData) {
|
||||
// This expects multiple buffers, so:
|
||||
if(!frameCount) return 0;
|
||||
|
||||
int i;
|
||||
const int channels = format->mChannelsPerFrame;
|
||||
if(!channels) return 0;
|
||||
|
||||
const int channelsminusone = channels - 1;
|
||||
float buffers[frameCount * channels];
|
||||
uint8_t bufferlistbuffer[sizeof(AudioBufferList) + sizeof(AudioBuffer) * channelsminusone];
|
||||
AudioBufferList *ioData = (AudioBufferList *)(&bufferlistbuffer[0]);
|
||||
|
||||
ioData->mNumberBuffers = channels;
|
||||
|
||||
memset(buffers, 0, sizeof(buffers));
|
||||
|
||||
for(i = 0; i < channels; ++i) {
|
||||
ioData->mBuffers[i].mNumberChannels = 1;
|
||||
ioData->mBuffers[i].mData = buffers + frameCount * i;
|
||||
ioData->mBuffers[i].mDataByteSize = frameCount * sizeof(float);
|
||||
}
|
||||
|
||||
OSStatus ret;
|
||||
|
||||
if(*eqEnabled)
|
||||
ret = AudioUnitRender(eq, actionFlags, timestamp, (UInt32)inputBusNumber, frameCount, ioData);
|
||||
else
|
||||
ret = renderCallback(refCon, actionFlags, timestamp, (UInt32)inputBusNumber, frameCount, ioData);
|
||||
|
||||
if(ret)
|
||||
return ret;
|
||||
|
||||
for(i = 0; i < channels; ++i) {
|
||||
float *outBuffer = ((float *)inputData->mBuffers[0].mData) + i;
|
||||
const float *inBuffer = ((float *)ioData->mBuffers[i].mData);
|
||||
const int frameCount = ioData->mBuffers[i].mDataByteSize / sizeof(float);
|
||||
cblas_scopy(frameCount, inBuffer, 1, outBuffer, channels);
|
||||
}
|
||||
|
||||
#ifdef OUTPUT_LOG
|
||||
if(logFile) {
|
||||
fwrite(inputData->mBuffers[0].mData, 1, inputData->mBuffers[0].mDataByteSize, logFile);
|
||||
}
|
||||
|
||||
// memset(inputData->mBuffers[0].mData, 0, inputData->mBuffers[0].mDataByteSize);
|
||||
#endif
|
||||
|
||||
inputData->mBuffers[0].mNumberChannels = channels;
|
||||
|
||||
#ifdef _DEBUG
|
||||
[BadSampleCleaner cleanSamples:(float *)inputData->mBuffers[0].mData
|
||||
amount:inputData->mBuffers[0].mDataByteSize / sizeof(float)
|
||||
location:@"final output"];
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
};
|
||||
}
|
||||
|
||||
- (BOOL)setup {
|
||||
if(_au)
|
||||
[self stop];
|
||||
|
||||
@synchronized(self) {
|
||||
stopInvoked = NO;
|
||||
|
||||
running = NO;
|
||||
stopping = NO;
|
||||
stopped = NO;
|
||||
paused = NO;
|
||||
stopNext = NO;
|
||||
outputDeviceID = -1;
|
||||
restarted = NO;
|
||||
|
||||
downmixer = nil;
|
||||
downmixerForVis = nil;
|
||||
|
||||
AudioComponentDescription desc;
|
||||
NSError *err;
|
||||
|
||||
desc.componentType = kAudioUnitType_Output;
|
||||
desc.componentSubType = kAudioUnitSubType_HALOutput;
|
||||
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
|
||||
desc.componentFlags = 0;
|
||||
desc.componentFlagsMask = 0;
|
||||
|
||||
_au = [[AUAudioUnit alloc] initWithComponentDescription:desc error:&err];
|
||||
if(err != nil)
|
||||
return NO;
|
||||
|
||||
// Setup the output device before mucking with settings
|
||||
NSDictionary *device = [[[NSUserDefaultsController sharedUserDefaultsController] defaults] objectForKey:@"outputDevice"];
|
||||
if(device) {
|
||||
BOOL ok = [self setOutputDeviceWithDeviceDict:device];
|
||||
if(!ok) {
|
||||
// Ruh roh.
|
||||
[self setOutputDeviceWithDeviceDict:nil];
|
||||
|
||||
[[[NSUserDefaultsController sharedUserDefaultsController] defaults] removeObjectForKey:@"outputDevice"];
|
||||
}
|
||||
} else {
|
||||
[self setOutputDeviceWithDeviceDict:nil];
|
||||
}
|
||||
|
||||
_deviceFormat = nil;
|
||||
|
||||
AudioComponent comp = NULL;
|
||||
|
||||
desc.componentType = kAudioUnitType_Effect;
|
||||
desc.componentSubType = kAudioUnitSubType_GraphicEQ;
|
||||
|
||||
comp = AudioComponentFindNext(comp, &desc);
|
||||
if(!comp)
|
||||
return NO;
|
||||
|
||||
OSStatus _err = AudioComponentInstanceNew(comp, &_eq);
|
||||
if(err)
|
||||
return NO;
|
||||
|
||||
[self updateDeviceFormat];
|
||||
|
||||
[self audioOutputBlock];
|
||||
|
||||
[_au setMaximumFramesToRender:512];
|
||||
|
||||
UInt32 value;
|
||||
UInt32 size = sizeof(value);
|
||||
|
||||
value = CHUNK_SIZE;
|
||||
AudioUnitSetProperty(_eq, kAudioUnitProperty_MaximumFramesPerSlice,
|
||||
kAudioUnitScope_Global, 0, &value, size);
|
||||
|
||||
value = 127;
|
||||
AudioUnitSetProperty(_eq, kAudioUnitProperty_RenderQuality,
|
||||
kAudioUnitScope_Global, 0, &value, size);
|
||||
|
||||
AURenderCallbackStruct callbackStruct;
|
||||
callbackStruct.inputProcRefCon = (__bridge void *)self;
|
||||
callbackStruct.inputProc = renderCallback;
|
||||
AudioUnitSetProperty(_eq, kAudioUnitProperty_SetRenderCallback,
|
||||
kAudioUnitScope_Input, 0, &callbackStruct, sizeof(callbackStruct));
|
||||
|
||||
AudioUnitReset(_eq, kAudioUnitScope_Input, 0);
|
||||
AudioUnitReset(_eq, kAudioUnitScope_Output, 0);
|
||||
|
||||
AudioUnitReset(_eq, kAudioUnitScope_Global, 0);
|
||||
|
||||
_err = AudioUnitInitialize(_eq);
|
||||
if(_err)
|
||||
return NO;
|
||||
|
||||
eqInitialized = YES;
|
||||
|
||||
[self setEqualizerEnabled:[[[[NSUserDefaultsController sharedUserDefaultsController] defaults] objectForKey:@"GraphicEQenable"] boolValue]];
|
||||
|
||||
[outputController beginEqualizer:_eq];
|
||||
|
||||
[_au allocateRenderResourcesAndReturnError:&err];
|
||||
|
||||
visController = [VisualizationController sharedController];
|
||||
|
||||
[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.outputDevice" options:0 context:kOutputCoreAudioContext];
|
||||
[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.GraphicEQenable" options:0 context:kOutputCoreAudioContext];
|
||||
[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.eqPreamp" options:(NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew) context:kOutputCoreAudioContext];
|
||||
observersapplied = YES;
|
||||
|
||||
return (err == nil);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setVolume:(double)v {
|
||||
volume = v * 0.01f;
|
||||
}
|
||||
|
||||
- (void)setEqualizerEnabled:(BOOL)enabled {
|
||||
if(enabled && !eqEnabled) {
|
||||
if(_eq) {
|
||||
AudioUnitReset(_eq, kAudioUnitScope_Input, 0);
|
||||
AudioUnitReset(_eq, kAudioUnitScope_Output, 0);
|
||||
AudioUnitReset(_eq, kAudioUnitScope_Global, 0);
|
||||
}
|
||||
}
|
||||
|
||||
eqEnabled = enabled;
|
||||
}
|
||||
|
||||
- (void)start {
|
||||
[self threadEntry:nil];
|
||||
}
|
||||
|
||||
- (void)stop {
|
||||
@synchronized(self) {
|
||||
if(stopInvoked) return;
|
||||
stopInvoked = YES;
|
||||
if(observersapplied) {
|
||||
[[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.outputDevice" context:kOutputCoreAudioContext];
|
||||
[[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.GraphicEQenable" context:kOutputCoreAudioContext];
|
||||
[[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.eqPreamp" context:kOutputCoreAudioContext];
|
||||
observersapplied = NO;
|
||||
}
|
||||
if(stopNext && !paused) {
|
||||
if(!started) {
|
||||
// This happens if playback is started on a very short file, and the queue is empty or at the end of the playlist
|
||||
started = YES;
|
||||
NSError *err;
|
||||
[_au startHardwareAndReturnError:&err];
|
||||
}
|
||||
while(![[outputController buffer] isEmpty]) {
|
||||
[writeSemaphore signal];
|
||||
[readSemaphore signal];
|
||||
usleep(500);
|
||||
}
|
||||
}
|
||||
if(stopNext) {
|
||||
stopNext = NO;
|
||||
[self signalEndOfStream];
|
||||
}
|
||||
stopping = YES;
|
||||
paused = NO;
|
||||
[writeSemaphore signal];
|
||||
[readSemaphore signal];
|
||||
if(defaultdevicelistenerapplied || currentdevicelistenerapplied || devicealivelistenerapplied) {
|
||||
AudioObjectPropertyAddress theAddress = {
|
||||
.mScope = kAudioObjectPropertyScopeGlobal,
|
||||
.mElement = kAudioObjectPropertyElementMaster
|
||||
};
|
||||
if(defaultdevicelistenerapplied) {
|
||||
theAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
|
||||
AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &theAddress, default_device_changed, (__bridge void *_Nullable)(self));
|
||||
defaultdevicelistenerapplied = NO;
|
||||
}
|
||||
if(devicealivelistenerapplied) {
|
||||
theAddress.mSelector = kAudioDevicePropertyDeviceIsAlive;
|
||||
AudioObjectRemovePropertyListener(outputDeviceID, &theAddress, current_device_listener, (__bridge void *_Nullable)(self));
|
||||
devicealivelistenerapplied = NO;
|
||||
}
|
||||
if(currentdevicelistenerapplied) {
|
||||
theAddress.mSelector = kAudioDevicePropertyStreamFormat;
|
||||
AudioObjectRemovePropertyListener(outputDeviceID, &theAddress, current_device_listener, (__bridge void *_Nullable)(self));
|
||||
theAddress.mSelector = kAudioDevicePropertyNominalSampleRate;
|
||||
AudioObjectRemovePropertyListener(outputDeviceID, &theAddress, current_device_listener, (__bridge void *_Nullable)(self));
|
||||
currentdevicelistenerapplied = NO;
|
||||
}
|
||||
}
|
||||
if(_au) {
|
||||
if(started)
|
||||
[_au stopHardware];
|
||||
_au = nil;
|
||||
}
|
||||
if(running) {
|
||||
while(!stopped) {
|
||||
stopping = YES;
|
||||
[readSemaphore signal];
|
||||
[writeSemaphore timedWait:5000];
|
||||
}
|
||||
}
|
||||
if(_eq) {
|
||||
[outputController endEqualizer:_eq];
|
||||
if(eqInitialized) {
|
||||
AudioUnitUninitialize(_eq);
|
||||
eqInitialized = NO;
|
||||
}
|
||||
AudioComponentInstanceDispose(_eq);
|
||||
_eq = NULL;
|
||||
}
|
||||
if(downmixer) {
|
||||
downmixer = nil;
|
||||
}
|
||||
if(downmixerForVis) {
|
||||
downmixerForVis = nil;
|
||||
}
|
||||
#ifdef OUTPUT_LOG
|
||||
if(_logFile) {
|
||||
fclose(_logFile);
|
||||
_logFile = NULL;
|
||||
}
|
||||
#endif
|
||||
outputController = nil;
|
||||
visController = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[self stop];
|
||||
}
|
||||
|
||||
- (void)pause {
|
||||
paused = YES;
|
||||
if(started)
|
||||
[_au stopHardware];
|
||||
}
|
||||
|
||||
- (void)resume {
|
||||
NSError *err;
|
||||
[_au startHardwareAndReturnError:&err];
|
||||
paused = NO;
|
||||
started = YES;
|
||||
}
|
||||
|
||||
- (void)sustainHDCD {
|
||||
atomic_store(&bytesHdcdSustained, deviceFormat.mSampleRate * 10 * sizeof(float) * 2);
|
||||
}
|
||||
|
||||
@end
|
|
@ -1,5 +1,7 @@
|
|||
// Plugins! HOORAY!
|
||||
|
||||
#import "AudioChunk.h"
|
||||
|
||||
@protocol CogSource <NSObject>
|
||||
+ (NSArray *)schemes; // http, file, etc
|
||||
|
||||
|
@ -25,6 +27,9 @@
|
|||
+ (float)priority;
|
||||
|
||||
+ (NSArray *)urlsForContainerURL:(NSURL *)url;
|
||||
|
||||
@optional
|
||||
+ (NSArray *)dependencyUrlsForContainerURL:(NSURL *)url;
|
||||
@end
|
||||
|
||||
@protocol CogDecoder <NSObject>
|
||||
|
@ -39,7 +44,7 @@
|
|||
- (NSDictionary *)properties;
|
||||
- (NSDictionary *)metadata; // Only to be implemented for dynamic metadata, send events on change
|
||||
|
||||
- (int)readAudio:(void *)buffer frames:(UInt32)frames;
|
||||
- (AudioChunk *)readAudio;
|
||||
|
||||
- (BOOL)open:(id<CogSource>)source;
|
||||
- (long)seek:(long)frame;
|
||||
|
@ -93,6 +98,7 @@
|
|||
|
||||
- (id<CogSource>)audioSourceForURL:(NSURL *)url;
|
||||
- (NSArray *)urlsForContainerURL:(NSURL *)url;
|
||||
- (NSArray *)dependencyUrlsForContainerURL:(NSURL *)url;
|
||||
- (NSDictionary *)metadataForURL:(NSURL *)url skipCue:(BOOL)skip;
|
||||
- (NSDictionary *)propertiesForURL:(NSURL *)url skipCue:(BOOL)skip;
|
||||
- (id<CogDecoder>)audioDecoderForSource:(id<CogSource>)source skipCue:(BOOL)skip;
|
||||
|
|
|
@ -109,11 +109,16 @@ static NSDictionary *cache_access_metadata(NSURL *url) {
|
|||
static void cache_run() {
|
||||
std::chrono::milliseconds dura(250);
|
||||
|
||||
Cache_Running = true;
|
||||
|
||||
while(Cache_Running) {
|
||||
std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
|
||||
|
||||
@autoreleasepool {
|
||||
std::lock_guard<std::mutex> lock(Cache_Lock);
|
||||
|
||||
size_t cacheListOriginalSize = Cache_List.size();
|
||||
|
||||
for(auto it = Cache_List.begin(); it != Cache_List.end();) {
|
||||
auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(now - it->second.time_accessed);
|
||||
if(elapsed.count() >= 10) {
|
||||
|
@ -123,8 +128,9 @@ static void cache_run() {
|
|||
++it;
|
||||
}
|
||||
|
||||
if(Cache_List.size() == 0)
|
||||
if(cacheListOriginalSize && Cache_List.size() == 0) {
|
||||
[Cache_Data_Store reset];
|
||||
}
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(dura);
|
||||
|
@ -190,6 +196,9 @@ static PluginController *sharedPluginController = nil;
|
|||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(bundleDidLoad:) name:NSBundleDidLoadNotification object:nil];
|
||||
|
||||
[self loadPlugins];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSBundleDidLoadNotification object:nil];
|
||||
|
||||
[self printPluginInfo];
|
||||
}
|
||||
}
|
||||
|
@ -348,6 +357,16 @@ static PluginController *sharedPluginController = nil;
|
|||
}
|
||||
}
|
||||
|
||||
static NSString *xmlEscapeString(NSString * string) {
|
||||
CFStringRef textXML = CFXMLCreateStringByEscapingEntities(kCFAllocatorDefault, (CFStringRef)string, nil);
|
||||
if(textXML) {
|
||||
NSString *textString = (__bridge NSString *)textXML;
|
||||
CFRelease(textXML);
|
||||
return textString;
|
||||
}
|
||||
return @"";
|
||||
}
|
||||
|
||||
- (void)printPluginInfo {
|
||||
ALog(@"Sources: %@", self.sources);
|
||||
ALog(@"Containers: %@", self.containers);
|
||||
|
@ -365,6 +384,8 @@ static PluginController *sharedPluginController = nil;
|
|||
<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n\
|
||||
<plist version=\"1.0\">\n\
|
||||
<dict>\n\
|
||||
\t<key>FirebaseCrashlyticsCollectionEnabled</key>\n\
|
||||
\t<false/>\n\
|
||||
\t<key>SUEnableInstallerLauncherService</key>\n\
|
||||
\t<true/>\n\
|
||||
\t<key>CFBundleDevelopmentRegion</key>\n\
|
||||
|
@ -396,14 +417,16 @@ static PluginController *sharedPluginController = nil;
|
|||
NSString * plistFooter = @"\t</array>\n\
|
||||
\t<key>CFBundleExecutable</key>\n\
|
||||
\t<string>Cog</string>\n\
|
||||
\t<key>CFBundleHelpBookFolder</key>\n\
|
||||
\t<string>Cog.help</string>\n\
|
||||
\t<key>CFBundleHelpBookName</key>\n\
|
||||
\t<string>org.cogx.cog.help</string>\n\
|
||||
\t<key>CFBundleIdentifier</key>\n\
|
||||
\t<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>\n\
|
||||
\t<key>CFBundleInfoDictionaryVersion</key>\n\
|
||||
\t<string>6.0</string>\n\
|
||||
\t<key>CFBundleName</key>\n\
|
||||
\t<string>$(PRODUCT_NAME)</string>\n\
|
||||
\t<key>CFBundleDisplayName</key>\n\
|
||||
\t<string>$(PRODUCT_NAME)</string>\n\
|
||||
\t<key>CFBundlePackageType</key>\n\
|
||||
\t<string>APPL</string>\n\
|
||||
\t<key>CFBundleShortVersionString</key>\n\
|
||||
|
@ -504,7 +527,7 @@ static PluginController *sharedPluginController = nil;
|
|||
\t\t\t<integer>1</integer>\n\
|
||||
\t\t\t<key>CFBundleTypeName</key>\n\
|
||||
\t\t\t<string>"];
|
||||
[stringList addObject:[type objectAtIndex:0]];
|
||||
[stringList addObject:xmlEscapeString([type objectAtIndex:0])];
|
||||
[stringList addObject:@"</string>\n\
|
||||
\t\t\t<key>CFBundleTypeRole</key>\n\
|
||||
\t\t\t<string>Viewer</string>\n\
|
||||
|
@ -555,6 +578,29 @@ static PluginController *sharedPluginController = nil;
|
|||
return [container urlsForContainerURL:url];
|
||||
}
|
||||
|
||||
- (NSArray *)dependencyUrlsForContainerURL:(NSURL *)url {
|
||||
NSString *ext = [url pathExtension];
|
||||
NSArray *containerSet = [containers objectForKey:[ext lowercaseString]];
|
||||
NSString *classString;
|
||||
if(containerSet) {
|
||||
if([containerSet count] > 1) {
|
||||
return [CogContainerMulti dependencyUrlsForContainerURL:url containers:containerSet];
|
||||
} else {
|
||||
classString = [containerSet objectAtIndex:0];
|
||||
}
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
|
||||
Class container = NSClassFromString(classString);
|
||||
|
||||
if([container respondsToSelector:@selector(dependencyUrlsForContainerURL:)]) {
|
||||
return [container dependencyUrlsForContainerURL:url];
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
// Note: Source is assumed to already be opened.
|
||||
- (id<CogDecoder>)audioDecoderForSource:(id<CogSource>)source skipCue:(BOOL)skip {
|
||||
NSString *ext = [[source url] pathExtension];
|
||||
|
@ -589,11 +635,28 @@ static PluginController *sharedPluginController = nil;
|
|||
}
|
||||
}
|
||||
|
||||
if(skip && [classString isEqualToString:@"CueSheetDecoder"]) {
|
||||
classString = @"SilenceDecoder";
|
||||
}
|
||||
|
||||
Class decoder = NSClassFromString(classString);
|
||||
|
||||
return [[decoder alloc] init];
|
||||
}
|
||||
|
||||
+ (BOOL)isCoverFile:(NSString *)fileName {
|
||||
for(NSString *coverFileName in [PluginController coverNames]) {
|
||||
if([[[[fileName lastPathComponent] stringByDeletingPathExtension] lowercaseString] hasSuffix:coverFileName]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
+ (NSArray *)coverNames {
|
||||
return @[@"cover", @"folder", @"album", @"front"];
|
||||
}
|
||||
|
||||
- (NSDictionary *)metadataForURL:(NSURL *)url skipCue:(BOOL)skip {
|
||||
NSString *urlScheme = [url scheme];
|
||||
if([urlScheme isEqualToString:@"http"] ||
|
||||
|
@ -603,36 +666,76 @@ static PluginController *sharedPluginController = nil;
|
|||
NSDictionary *cacheData = cache_access_metadata(url);
|
||||
if(cacheData) return cacheData;
|
||||
|
||||
NSString *ext = [url pathExtension];
|
||||
NSArray *readers = [metadataReaders objectForKey:[ext lowercaseString]];
|
||||
NSString *classString;
|
||||
if(readers) {
|
||||
if([readers count] > 1) {
|
||||
if(skip) {
|
||||
NSMutableArray *_readers = [readers mutableCopy];
|
||||
for(int i = 0; i < [_readers count];) {
|
||||
if([[_readers objectAtIndex:i] isEqualToString:@"CueSheetMetadataReader"])
|
||||
[_readers removeObjectAtIndex:i];
|
||||
else
|
||||
++i;
|
||||
do {
|
||||
NSString *ext = [url pathExtension];
|
||||
NSArray *readers = [metadataReaders objectForKey:[ext lowercaseString]];
|
||||
NSString *classString;
|
||||
if(readers) {
|
||||
if([readers count] > 1) {
|
||||
if(skip) {
|
||||
NSMutableArray *_readers = [readers mutableCopy];
|
||||
for(int i = 0; i < [_readers count];) {
|
||||
if([[_readers objectAtIndex:i] isEqualToString:@"CueSheetMetadataReader"])
|
||||
[_readers removeObjectAtIndex:i];
|
||||
else
|
||||
++i;
|
||||
}
|
||||
cacheData = [CogMetadataReaderMulti metadataForURL:url readers:_readers];
|
||||
break;
|
||||
}
|
||||
cacheData = [CogMetadataReaderMulti metadataForURL:url readers:_readers];
|
||||
cache_insert_metadata(url, cacheData);
|
||||
return cacheData;
|
||||
cacheData = [CogMetadataReaderMulti metadataForURL:url readers:readers];
|
||||
break;
|
||||
} else {
|
||||
classString = [readers objectAtIndex:0];
|
||||
}
|
||||
cacheData = [CogMetadataReaderMulti metadataForURL:url readers:readers];
|
||||
cache_insert_metadata(url, cacheData);
|
||||
return cacheData;
|
||||
} else {
|
||||
classString = [readers objectAtIndex:0];
|
||||
cacheData = nil;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
return nil;
|
||||
|
||||
if(skip && [classString isEqualToString:@"CueSheetMetadataReader"]) {
|
||||
cacheData = nil;
|
||||
break;
|
||||
}
|
||||
|
||||
Class metadataReader = NSClassFromString(classString);
|
||||
|
||||
cacheData = [metadataReader metadataForURL:url];
|
||||
} while(0);
|
||||
|
||||
if(cacheData == nil) {
|
||||
cacheData = [NSDictionary dictionary];
|
||||
}
|
||||
|
||||
Class metadataReader = NSClassFromString(classString);
|
||||
if(cacheData) {
|
||||
NSData *image = [cacheData objectForKey:@"albumArt"];
|
||||
|
||||
if(nil == image) {
|
||||
// Try to load image from external file
|
||||
|
||||
NSString *path = [[url path] stringByDeletingLastPathComponent];
|
||||
|
||||
// Gather list of candidate image files
|
||||
|
||||
NSArray *fileNames = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:nil];
|
||||
NSArray *types = @[@"jpg", @"jpeg", @"png", @"gif", @"webp", @"avif", @"heic"];
|
||||
NSArray *imageFileNames = [fileNames pathsMatchingExtensions:types];
|
||||
|
||||
for(NSString *fileName in imageFileNames) {
|
||||
if([PluginController isCoverFile:fileName]) {
|
||||
image = [NSData dataWithContentsOfFile:[path stringByAppendingPathComponent:fileName]];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(image) {
|
||||
NSMutableDictionary *data = [cacheData mutableCopy];
|
||||
[data setValue:image forKey:@"albumArt"];
|
||||
cacheData = data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cacheData = [metadataReader metadataForURL:url];
|
||||
cache_insert_metadata(url, cacheData);
|
||||
return cacheData;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
Copyright (C) 2010 Christian Kothe
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef CHANNELMAPS_H
|
||||
#define CHANNELMAPS_H
|
||||
#include "freesurround_decoder.h"
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
const int grid_res = 21; // resolution of the lookup grid
|
||||
|
||||
// channel allocation maps (per setup)
|
||||
typedef std::vector<std::vector<float*> > alloc_lut;
|
||||
extern std::map<unsigned, alloc_lut> chn_alloc;
|
||||
// channel metadata maps (per setup)
|
||||
extern std::map<unsigned, std::vector<float> > chn_angle;
|
||||
extern std::map<unsigned, std::vector<float> > chn_xsf;
|
||||
extern std::map<unsigned, std::vector<float> > chn_ysf;
|
||||
extern std::map<unsigned, std::vector<channel_id> > chn_id;
|
||||
|
||||
#endif
|
|
@ -0,0 +1,413 @@
|
|||
/*
|
||||
Copyright (C) 2007-2010 Christian Kothe
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "freesurround_decoder.h"
|
||||
#include "channelmaps.h"
|
||||
#include <Accelerate/Accelerate.h>
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
#pragma warning(disable : 4244)
|
||||
|
||||
#define pi _pi
|
||||
const float _pi = 3.141592654f;
|
||||
const float epsilon = 0.000001f;
|
||||
using namespace std;
|
||||
|
||||
#undef min
|
||||
#undef max
|
||||
|
||||
static void *_memalign_malloc(size_t size, size_t align) {
|
||||
void *ret = NULL;
|
||||
if(posix_memalign(&ret, align, size) != 0) {
|
||||
return NULL;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void _dsp_complexalloc(DSPDoubleSplitComplex *cpx, int count) {
|
||||
cpx->realp = (double *)_memalign_malloc(count * sizeof(double), 16);
|
||||
cpx->imagp = (double *)_memalign_malloc(count * sizeof(double), 16);
|
||||
}
|
||||
|
||||
static void _dsp_complexfree(DSPDoubleSplitComplex *cpx) {
|
||||
free(cpx->realp);
|
||||
free(cpx->imagp);
|
||||
}
|
||||
|
||||
// FreeSurround implementation
|
||||
class decoder_impl {
|
||||
public:
|
||||
// instantiate the decoder with a given channel setup and processing block size (in samples)
|
||||
decoder_impl(channel_setup setup, unsigned N)
|
||||
: N(N),
|
||||
wnd(N), inbuf(3 * N), setup(setup), C((unsigned)chn_alloc[setup].size()),
|
||||
buffer_empty(true), lt(N), rt(N), dst(N), dstf(N),
|
||||
dftsetupF(vDSP_DFT_zrop_CreateSetupD(0, N, vDSP_DFT_FORWARD)),
|
||||
dftsetupB(vDSP_DFT_zrop_CreateSetupD(0, N, vDSP_DFT_INVERSE)) {
|
||||
_dsp_complexalloc(&lf, N/2 + 1);
|
||||
_dsp_complexalloc(&rf, N/2 + 1);
|
||||
|
||||
// allocate per-channel buffers
|
||||
outbuf.resize((N + N / 2) * C);
|
||||
signal.resize(C);
|
||||
for(unsigned k = 0; k < C; k++)
|
||||
_dsp_complexalloc(&signal[k], N/2 + 1);
|
||||
|
||||
// init the window function
|
||||
for(unsigned k = 0; k < N; k++)
|
||||
wnd[k] = sqrt(0.5 * (1 - cos(2 * pi * k / N)) / N);
|
||||
|
||||
// set default parameters
|
||||
set_circular_wrap(90);
|
||||
set_shift(0);
|
||||
set_depth(1);
|
||||
set_focus(0);
|
||||
set_center_image(1);
|
||||
set_front_separation(1);
|
||||
set_rear_separation(1);
|
||||
set_low_cutoff(40.0 / 22050);
|
||||
set_high_cutoff(90.0 / 22050);
|
||||
set_bass_redirection(false);
|
||||
|
||||
flush();
|
||||
}
|
||||
|
||||
~decoder_impl() {
|
||||
_dsp_complexfree(&lf);
|
||||
_dsp_complexfree(&rf);
|
||||
|
||||
for(unsigned k = 0; k < C; k++)
|
||||
_dsp_complexfree(&signal[k]);
|
||||
|
||||
vDSP_DFT_DestroySetupD(dftsetupF);
|
||||
vDSP_DFT_DestroySetupD(dftsetupB);
|
||||
}
|
||||
|
||||
// decode a stereo chunk, produces a multichannel chunk of the same size (lagged)
|
||||
float *decode(const float *input) {
|
||||
// append incoming data to the end of the input buffer
|
||||
memcpy(&inbuf[N], &input[0], 8 * N);
|
||||
// process first and second half, overlapped
|
||||
buffered_decode(&inbuf[0]);
|
||||
buffered_decode(&inbuf[N]);
|
||||
// shift last half of the input to the beginning (for overlapping with a future block)
|
||||
memcpy(&inbuf[0], &inbuf[2 * N], 4 * N);
|
||||
buffer_empty = false;
|
||||
return &outbuf[0];
|
||||
}
|
||||
|
||||
// flush the internal buffers
|
||||
void flush() {
|
||||
memset(&outbuf[0], 0, outbuf.size() * 4);
|
||||
memset(&inbuf[0], 0, inbuf.size() * 4);
|
||||
buffer_empty = true;
|
||||
}
|
||||
|
||||
// number of samples currently held in the buffer
|
||||
unsigned buffered() {
|
||||
return buffer_empty ? 0 : N / 2;
|
||||
}
|
||||
|
||||
// set soundfield & rendering parameters
|
||||
void set_circular_wrap(float v) {
|
||||
circular_wrap = v;
|
||||
}
|
||||
void set_shift(float v) {
|
||||
shift = v;
|
||||
}
|
||||
void set_depth(float v) {
|
||||
depth = v;
|
||||
}
|
||||
void set_focus(float v) {
|
||||
focus = v;
|
||||
}
|
||||
void set_center_image(float v) {
|
||||
center_image = v;
|
||||
}
|
||||
void set_front_separation(float v) {
|
||||
front_separation = v;
|
||||
}
|
||||
void set_rear_separation(float v) {
|
||||
rear_separation = v;
|
||||
}
|
||||
void set_low_cutoff(float v) {
|
||||
lo_cut = v * (N / 2);
|
||||
}
|
||||
void set_high_cutoff(float v) {
|
||||
hi_cut = v * (N / 2);
|
||||
}
|
||||
void set_bass_redirection(bool v) {
|
||||
use_lfe = v;
|
||||
}
|
||||
|
||||
private:
|
||||
// helper functions
|
||||
static inline float sqr(double x) {
|
||||
return x * x;
|
||||
}
|
||||
static inline double amplitude(const DSPDoubleSplitComplex &cpx, size_t index) {
|
||||
return sqrt(sqr(cpx.realp[index]) + sqr(cpx.imagp[index]));
|
||||
}
|
||||
static inline double phase(const DSPDoubleSplitComplex &cpx, size_t index) {
|
||||
return atan2(cpx.imagp[index], cpx.realp[index]);
|
||||
}
|
||||
static inline void polar(double a, double p, DSPDoubleSplitComplex &cpx, size_t index) {
|
||||
cpx.realp[index] = a * cos(p);
|
||||
cpx.imagp[index] = a * sin(p);
|
||||
}
|
||||
static inline float min(double a, double b) {
|
||||
return a < b ? a : b;
|
||||
}
|
||||
static inline float max(double a, double b) {
|
||||
return a > b ? a : b;
|
||||
}
|
||||
static inline float clamp(double x) {
|
||||
return max(-1, min(1, x));
|
||||
}
|
||||
static inline float sign(double x) {
|
||||
return x < 0 ? -1 : (x > 0 ? 1 : 0);
|
||||
}
|
||||
// get the distance of the soundfield edge, along a given angle
|
||||
static inline double edgedistance(double a) {
|
||||
return min(sqrt(1 + sqr(tan(a))), sqrt(1 + sqr(1 / tan(a))));
|
||||
}
|
||||
// get the index (and fractional offset!) in a piecewise-linear channel allocation grid
|
||||
int map_to_grid(double &x) {
|
||||
double gp = ((x + 1) * 0.5) * (grid_res - 1), i = min(grid_res - 2, floor(gp));
|
||||
x = gp - i;
|
||||
return i;
|
||||
}
|
||||
|
||||
// decode a block of data and overlap-add it into outbuf
|
||||
void buffered_decode(const float *input) {
|
||||
// demultiplex and apply window function
|
||||
vDSP_vspdp(input, 2, <[0], 1, N);
|
||||
vDSP_vspdp(input + 1, 2, &rt[0], 1, N);
|
||||
vDSP_vmulD(<[0], 1, &wnd[0], 1, <[0], 1, N);
|
||||
vDSP_vmulD(&rt[0], 1, &wnd[0], 1, &rt[0], 1, N);
|
||||
|
||||
// map into spectral domain
|
||||
vDSP_ctozD((DSPDoubleComplex *)(<[0]), 2, &lf, 1, N / 2);
|
||||
vDSP_ctozD((DSPDoubleComplex *)(&rt[0]), 2, &rf, 1, N / 2);
|
||||
|
||||
vDSP_DFT_ExecuteD(dftsetupF, lf.realp, lf.imagp, lf.realp, lf.imagp);
|
||||
vDSP_DFT_ExecuteD(dftsetupF, rf.realp, rf.imagp, rf.realp, rf.imagp);
|
||||
|
||||
for(unsigned c = 0; c < C; c++) {
|
||||
signal[c].realp[0] = 0;
|
||||
signal[c].imagp[0] = 0;
|
||||
signal[c].realp[N/2] = 0;
|
||||
signal[c].imagp[N/2] = 0;
|
||||
}
|
||||
|
||||
bzero(signal[C - 1].realp, sizeof(double) * (N / 2 + 1));
|
||||
bzero(signal[C - 1].imagp, sizeof(double) * (N / 2 + 1));
|
||||
|
||||
// compute multichannel output signal in the spectral domain
|
||||
for(unsigned f = 1; f < N / 2; f++) {
|
||||
// get Lt/Rt amplitudes & phases
|
||||
double ampL = amplitude(lf, f), ampR = amplitude(rf, f);
|
||||
double phaseL = phase(lf, f), phaseR = phase(rf, f);
|
||||
// calculate the amplitude & phase differences
|
||||
double ampDiff = clamp((ampL + ampR < epsilon) ? 0 : (ampR - ampL) / (ampR + ampL));
|
||||
double phaseDiff = abs(phaseL - phaseR);
|
||||
if(phaseDiff > pi) phaseDiff = 2 * pi - phaseDiff;
|
||||
|
||||
// decode into x/y soundfield position
|
||||
double x, y;
|
||||
transform_decode(ampDiff, phaseDiff, x, y);
|
||||
// add wrap control
|
||||
transform_circular_wrap(x, y, circular_wrap);
|
||||
// add shift control
|
||||
y = clamp(y - shift);
|
||||
// add depth control
|
||||
y = clamp(1 - (1 - y) * depth);
|
||||
// add focus control
|
||||
transform_focus(x, y, focus);
|
||||
// add crossfeed control
|
||||
x = clamp(x * (front_separation * (1 + y) / 2 + rear_separation * (1 - y) / 2));
|
||||
|
||||
// get total signal amplitude
|
||||
double amp_total = sqrt(ampL * ampL + ampR * ampR);
|
||||
// and total L/C/R signal phases
|
||||
double phase_of[] = { phaseL, atan2(lf.imagp[f] + rf.imagp[f], lf.realp[f] + rf.realp[f]), phaseR };
|
||||
// compute 2d channel map indexes p/q and update x/y to fractional offsets in the map grid
|
||||
int p = map_to_grid(x), q = map_to_grid(y);
|
||||
// map position to channel volumes
|
||||
for(unsigned c = 0; c < C - 1; c++) {
|
||||
// look up channel map at respective position (with bilinear interpolation) and build the signal
|
||||
const vector<float *> &a = chn_alloc[setup][c];
|
||||
polar(amp_total * ((1 - x) * (1 - y) * a[q][p] + x * (1 - y) * a[q][p + 1] + (1 - x) * y * a[q + 1][p] + x * y * a[q + 1][p + 1]),
|
||||
phase_of[1 + (int)sign(chn_xsf[setup][c])], signal[c], f);
|
||||
}
|
||||
|
||||
// optionally redirect bass
|
||||
if(use_lfe && f < hi_cut) {
|
||||
// level of LFE channel according to normalized frequency
|
||||
double lfe_level = f < lo_cut ? 1 : 0.5 * (1 + cos(pi * (f - lo_cut) / (hi_cut - lo_cut)));
|
||||
// assign LFE channel
|
||||
polar(amp_total, phase_of[1], signal[C - 1], f);
|
||||
signal[C - 1].realp[f] *= lfe_level;
|
||||
signal[C - 1].imagp[f] *= lfe_level;
|
||||
// subtract the signal from the other channels
|
||||
for(unsigned c = 0; c < C - 1; c++) {
|
||||
signal[c].realp[f] *= (1 - lfe_level);
|
||||
signal[c].imagp[f] *= (1 - lfe_level);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// shift the last 2/3 to the first 2/3 of the output buffer
|
||||
memmove(&outbuf[0], &outbuf[C * N / 2], N * C * 4);
|
||||
// and clear the rest
|
||||
memset(&outbuf[C * N], 0, C * 4 * N / 2);
|
||||
// backtransform each channel and overlap-add
|
||||
for(unsigned c = 0; c < C; c++) {
|
||||
// back-transform into time domain
|
||||
vDSP_DFT_ExecuteD(dftsetupB, signal[c].realp, signal[c].imagp, signal[c].realp, signal[c].imagp);
|
||||
vDSP_ztocD(&signal[c], 1, (DSPDoubleComplex *)(&dst[0]), 2, N / 2);
|
||||
// add the result to the last 2/3 of the output buffer, windowed (and remultiplex)
|
||||
vDSP_vmulD(&dst[0], 1, &wnd[0], 1, &dst[0], 1, N);
|
||||
vDSP_vdpsp(&dst[0], 1, &dstf[0], 1, N);
|
||||
vDSP_vadd(&outbuf[C * N / 2 + c], C, &dstf[0], 1, &outbuf[C * N / 2 + c], C, N);
|
||||
}
|
||||
}
|
||||
|
||||
// transform amp/phase difference space into x/y soundfield space
|
||||
void transform_decode(double a, double p, double &x, double &y) {
|
||||
x = clamp(1.0047 * a + 0.46804 * a * p * p * p - 0.2042 * a * p * p * p * p + 0.0080586 * a * p * p * p * p * p * p * p - 0.0001526 * a * p * p * p * p * p * p * p * p * p * p - 0.073512 * a * a * a * p - 0.2499 * a * a * a * p * p * p * p + 0.016932 * a * a * a * p * p * p * p * p * p * p - 0.00027707 * a * a * a * p * p * p * p * p * p * p * p * p * p + 0.048105 * a * a * a * a * a * p * p * p * p * p * p * p - 0.0065947 * a * a * a * a * a * p * p * p * p * p * p * p * p * p * p + 0.0016006 * a * a * a * a * a * p * p * p * p * p * p * p * p * p * p * p - 0.0071132 * a * a * a * a * a * a * a * p * p * p * p * p * p * p * p * p + 0.0022336 * a * a * a * a * a * a * a * p * p * p * p * p * p * p * p * p * p * p - 0.0004804 * a * a * a * a * a * a * a * p * p * p * p * p * p * p * p * p * p * p * p);
|
||||
y = clamp(0.98592 - 0.62237 * p + 0.077875 * p * p - 0.0026929 * p * p * p * p * p + 0.4971 * a * a * p - 0.00032124 * a * a * p * p * p * p * p * p + 9.2491e-006 * a * a * a * a * p * p * p * p * p * p * p * p * p * p + 0.051549 * a * a * a * a * a * a * a * a + 1.0727e-014 * a * a * a * a * a * a * a * a * a * a);
|
||||
}
|
||||
|
||||
// apply a circular_wrap transformation to some position
|
||||
void transform_circular_wrap(double &x, double &y, double refangle) {
|
||||
if(refangle == 90)
|
||||
return;
|
||||
refangle = refangle * pi / 180;
|
||||
double baseangle = 90 * pi / 180;
|
||||
// translate into edge-normalized polar coordinates
|
||||
double ang = atan2(x, y), len = sqrt(x * x + y * y);
|
||||
len = len / edgedistance(ang);
|
||||
// apply circular_wrap transform
|
||||
if(abs(ang) < baseangle / 2)
|
||||
// angle falls within the front region (to be enlarged)
|
||||
ang *= refangle / baseangle;
|
||||
else
|
||||
// angle falls within the rear region (to be shrunken)
|
||||
ang = pi - (-(((refangle - 2 * pi) * (pi - abs(ang)) * sign(ang)) / (2 * pi - baseangle)));
|
||||
// translate back into soundfield position
|
||||
len = len * edgedistance(ang);
|
||||
x = clamp(sin(ang) * len);
|
||||
y = clamp(cos(ang) * len);
|
||||
}
|
||||
|
||||
// apply a focus transformation to some position
|
||||
void transform_focus(double &x, double &y, double focus) {
|
||||
if(focus == 0)
|
||||
return;
|
||||
// translate into edge-normalized polar coordinates
|
||||
double ang = atan2(x, y), len = clamp(sqrt(x * x + y * y) / edgedistance(ang));
|
||||
// apply focus
|
||||
len = focus > 0 ? 1 - pow(1 - len, 1 + focus * 20) : pow(len, 1 - focus * 20);
|
||||
// back-transform into euclidian soundfield position
|
||||
len = len * edgedistance(ang);
|
||||
x = clamp(sin(ang) * len);
|
||||
y = clamp(cos(ang) * len);
|
||||
}
|
||||
|
||||
// constants
|
||||
unsigned N, C; // number of samples per input/output block, number of output channels
|
||||
channel_setup setup; // the channel setup
|
||||
|
||||
// parameters
|
||||
float circular_wrap; // angle of the front soundstage around the listener (90<39>=default)
|
||||
float shift; // forward/backward offset of the soundstage
|
||||
float depth; // backward extension of the soundstage
|
||||
float focus; // localization of the sound events
|
||||
float center_image; // presence of the center speaker
|
||||
float front_separation; // front stereo separation
|
||||
float rear_separation; // rear stereo separation
|
||||
float lo_cut, hi_cut; // LFE cutoff frequencies
|
||||
bool use_lfe; // whether to use the LFE channel
|
||||
|
||||
// FFT data structures
|
||||
vector<double> lt, rt, dst; // left total, right total (source arrays), time-domain destination buffer array
|
||||
vector<float> dstf; // float conversion destination array
|
||||
DSPDoubleSplitComplex lf, rf; // left total / right total in frequency domain
|
||||
vDSP_DFT_SetupD dftsetupF, dftsetupB; // FFT objects
|
||||
|
||||
// buffers
|
||||
bool buffer_empty; // whether the buffer is currently empty or dirty
|
||||
vector<float> inbuf; // stereo input buffer (multiplexed)
|
||||
vector<float> outbuf; // multichannel output buffer (multiplexed)
|
||||
vector<double> wnd; // the window function, precomputed
|
||||
vector<DSPDoubleSplitComplex> signal; // the signal to be constructed in every channel, in the frequency domain
|
||||
};
|
||||
|
||||
// implementation of the shell class
|
||||
freesurround_decoder::freesurround_decoder(channel_setup setup, unsigned blocksize)
|
||||
: impl(new decoder_impl(setup, blocksize)) {
|
||||
}
|
||||
freesurround_decoder::~freesurround_decoder() {
|
||||
delete impl;
|
||||
}
|
||||
float *freesurround_decoder::decode(const float *input) {
|
||||
return impl->decode(input);
|
||||
}
|
||||
void freesurround_decoder::flush() {
|
||||
impl->flush();
|
||||
}
|
||||
void freesurround_decoder::circular_wrap(float v) {
|
||||
impl->set_circular_wrap(v);
|
||||
}
|
||||
void freesurround_decoder::shift(float v) {
|
||||
impl->set_shift(v);
|
||||
}
|
||||
void freesurround_decoder::depth(float v) {
|
||||
impl->set_depth(v);
|
||||
}
|
||||
void freesurround_decoder::focus(float v) {
|
||||
impl->set_focus(v);
|
||||
}
|
||||
void freesurround_decoder::center_image(float v) {
|
||||
impl->set_center_image(v);
|
||||
}
|
||||
void freesurround_decoder::front_separation(float v) {
|
||||
impl->set_front_separation(v);
|
||||
}
|
||||
void freesurround_decoder::rear_separation(float v) {
|
||||
impl->set_rear_separation(v);
|
||||
}
|
||||
void freesurround_decoder::low_cutoff(float v) {
|
||||
impl->set_low_cutoff(v);
|
||||
}
|
||||
void freesurround_decoder::high_cutoff(float v) {
|
||||
impl->set_high_cutoff(v);
|
||||
}
|
||||
void freesurround_decoder::bass_redirection(bool v) {
|
||||
impl->set_bass_redirection(v);
|
||||
}
|
||||
unsigned freesurround_decoder::buffered() {
|
||||
return impl->buffered();
|
||||
}
|
||||
unsigned freesurround_decoder::num_channels(channel_setup s) {
|
||||
return (unsigned)chn_id[s].size();
|
||||
}
|
||||
channel_id freesurround_decoder::channel_at(channel_setup s, unsigned i) {
|
||||
return i < chn_id[s].size() ? chn_id[s][i] : ci_none;
|
||||
}
|
|
@ -0,0 +1,210 @@
|
|||
/*
|
||||
Copyright (C) 2007-2010 Christian Kothe
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef FREESURROUND_DECODER_H
|
||||
#define FREESURROUND_DECODER_H
|
||||
|
||||
/**
|
||||
* Identifiers for the supported output channels (from front to back, left to right).
|
||||
* The ordering here also determines the ordering of interleaved samples in the output signal.
|
||||
*/
|
||||
typedef enum channel_id {
|
||||
ci_none = 0,
|
||||
ci_front_left = 1 << 1,
|
||||
ci_front_center_left = 1 << 2,
|
||||
ci_front_center = 1 << 3,
|
||||
ci_front_center_right = 1 << 4,
|
||||
ci_front_right = 1 << 5,
|
||||
ci_side_front_left = 1 << 6,
|
||||
ci_side_front_right = 1 << 7,
|
||||
ci_side_center_left = 1 << 8,
|
||||
ci_side_center_right = 1 << 9,
|
||||
ci_side_back_left = 1 << 10,
|
||||
ci_side_back_right = 1 << 11,
|
||||
ci_back_left = 1 << 12,
|
||||
ci_back_center_left = 1 << 13,
|
||||
ci_back_center = 1 << 14,
|
||||
ci_back_center_right = 1 << 15,
|
||||
ci_back_right = 1 << 16,
|
||||
ci_lfe = 1 << 31
|
||||
} channel_id;
|
||||
|
||||
/**
|
||||
* The supported output channel setups.
|
||||
* A channel setup is defined by the set of channels that are present. Here is a graphic
|
||||
* of the cs_5point1 setup: http://en.wikipedia.org/wiki/File:5_1_channels_(surround_sound)_label.svg
|
||||
*/
|
||||
typedef enum channel_setup {
|
||||
cs_stereo = ci_front_left | ci_front_right | ci_lfe,
|
||||
cs_3stereo = ci_front_left | ci_front_center | ci_front_right | ci_lfe,
|
||||
cs_5stereo = ci_front_left | ci_front_center_left | ci_front_center | ci_front_center_right | ci_front_right | ci_lfe,
|
||||
cs_4point1 = ci_front_left | ci_front_right | ci_back_left | ci_back_right | ci_lfe,
|
||||
cs_5point1 = ci_front_left | ci_front_center | ci_front_right | ci_back_left | ci_back_right | ci_lfe,
|
||||
cs_6point1 = ci_front_left | ci_front_center | ci_front_right | ci_side_center_left | ci_side_center_right | ci_back_center | ci_lfe,
|
||||
cs_7point1 = ci_front_left | ci_front_center | ci_front_right | ci_side_center_left | ci_side_center_right | ci_back_left | ci_back_right | ci_lfe,
|
||||
cs_7point1_panorama = ci_front_left | ci_front_center_left | ci_front_center | ci_front_center_right | ci_front_right |
|
||||
ci_side_center_left | ci_side_center_right | ci_lfe,
|
||||
cs_7point1_tricenter = ci_front_left | ci_front_center_left | ci_front_center | ci_front_center_right | ci_front_right |
|
||||
ci_back_left | ci_back_right | ci_lfe,
|
||||
cs_8point1 = ci_front_left | ci_front_center | ci_front_right | ci_side_center_left | ci_side_center_right |
|
||||
ci_back_left | ci_back_center | ci_back_right | ci_lfe,
|
||||
cs_9point1_densepanorama = ci_front_left | ci_front_center_left | ci_front_center | ci_front_center_right | ci_front_right |
|
||||
ci_side_front_left | ci_side_front_right | ci_side_center_left | ci_side_center_right | ci_lfe,
|
||||
cs_9point1_wrap = ci_front_left | ci_front_center_left | ci_front_center | ci_front_center_right | ci_front_right |
|
||||
ci_side_center_left | ci_side_center_right | ci_back_left | ci_back_right | ci_lfe,
|
||||
cs_11point1_densewrap = ci_front_left | ci_front_center_left | ci_front_center | ci_front_center_right | ci_front_right |
|
||||
ci_side_front_left | ci_side_front_right | ci_side_center_left | ci_side_center_right |
|
||||
ci_side_back_left | ci_side_back_right | ci_lfe,
|
||||
cs_13point1_totalwrap = ci_front_left | ci_front_center_left | ci_front_center | ci_front_center_right | ci_front_right |
|
||||
ci_side_front_left | ci_side_front_right | ci_side_center_left | ci_side_center_right |
|
||||
ci_side_back_left | ci_side_back_right | ci_back_left | ci_back_right | ci_lfe,
|
||||
cs_16point1 = ci_front_left | ci_front_center_left | ci_front_center | ci_front_center_right | ci_front_right |
|
||||
ci_side_front_left | ci_side_front_right | ci_side_center_left | ci_side_center_right | ci_side_back_left |
|
||||
ci_side_back_right | ci_back_left | ci_back_center_left | ci_back_center | ci_back_center_right | ci_back_right | ci_lfe,
|
||||
cs_legacy = 0 // same channels as cs_5point1 but different upmixing transform; does not support the focus control
|
||||
} channel_setup;
|
||||
|
||||
/**
|
||||
* The FreeSurround decoder.
|
||||
*/
|
||||
class freesurround_decoder {
|
||||
public:
|
||||
/**
|
||||
* Create an instance of the decoder.
|
||||
* @param setup The output channel setup -- determines the number of output channels
|
||||
* and their place in the sound field.
|
||||
* @param blocksize Granularity at which data is processed by the decode() function.
|
||||
* Must be a power of two and should correspond to ca. 10ms worth of single-channel
|
||||
* samples (default is 4096 for 44.1Khz data). Do not make it shorter or longer
|
||||
* than 5ms to 20ms since the granularity at which locations are decoded
|
||||
* changes with this.
|
||||
*/
|
||||
freesurround_decoder(channel_setup setup = cs_5point1, unsigned blocksize = 4096);
|
||||
~freesurround_decoder();
|
||||
|
||||
/**
|
||||
* Decode a chunk of stereo sound. The output is delayed by half of the blocksize.
|
||||
* This function is the only one needed for straightforward decoding.
|
||||
* @param input Contains exactly blocksize (multiplexed) stereo samples, i.e. 2*blocksize numbers.
|
||||
* @return A pointer to an internal buffer of exactly blocksize (multiplexed) multichannel samples.
|
||||
* The actual number of values depends on the number of output channels in the chosen
|
||||
* channel setup.
|
||||
*/
|
||||
float *decode(const float *input);
|
||||
|
||||
/**
|
||||
* Flush the internal buffer.
|
||||
*/
|
||||
void flush();
|
||||
|
||||
// --- soundfield transformations
|
||||
// These functions allow to set up geometric transformations of the sound field after it has been decoded.
|
||||
// The sound field is best pictured as a 2-dimensional square with the listener in its
|
||||
// center which can be shifted or stretched in various ways before it is sent to the
|
||||
// speakers. The order in which these transformations are applied is as listed below.
|
||||
|
||||
/**
|
||||
* Allows to wrap the soundfield around the listener in a circular manner.
|
||||
* Determines the angle of the frontal sound stage relative to the listener, in degrees.
|
||||
* A setting of 90<EFBFBD> corresponds to standard surround decoding, 180<EFBFBD> stretches the front stage from
|
||||
* ear to ear, 270<EFBFBD> wraps it around most of the head. The side and rear content of the sound
|
||||
* field is compressed accordingly behind the listerer. (default: 90, range: [0<EFBFBD>..360<EFBFBD>])
|
||||
*/
|
||||
void circular_wrap(float v);
|
||||
|
||||
/**
|
||||
* Allows to shift the soundfield forward or backward.
|
||||
* Value range: [-1.0..+1.0]. 0 is no offset, positive values move the sound
|
||||
* forward, negative values move it backwards. (default: 0)
|
||||
*/
|
||||
void shift(float v);
|
||||
|
||||
/**
|
||||
* Allows to scale the soundfield backwards.
|
||||
* Value range: [0.0..+5.0] -- 0 is all compressed to the front, 1 is no change, 5 is scaled 5x backwards (default: 1)
|
||||
*/
|
||||
void depth(float v);
|
||||
|
||||
/**
|
||||
* Allows to control the localization (i.e., focality) of sources.
|
||||
* Value range: [-1.0..+1.0] -- 0 means unchanged, positive means more localized, negative means more ambient (default: 0)
|
||||
*/
|
||||
void focus(float v);
|
||||
|
||||
// --- rendering parameters
|
||||
// These parameters control how the sound field is mapped onto speakers.
|
||||
|
||||
/**
|
||||
* Set the presence of the front center channel(s).
|
||||
* Value range: [0.0..1.0] -- fully present at 1.0, fully replaced by left/right at 0.0 (default: 1).
|
||||
* The default of 1.0 results in spec-conformant decoding ("movie mode") while a value of 0.7 is
|
||||
* better suited for music reproduction (which is usually mixed without a center channel).
|
||||
*/
|
||||
void center_image(float v);
|
||||
|
||||
/**
|
||||
* Set the front stereo separation.
|
||||
* Value range: [0.0..inf] -- 1.0 is default, 0.0 is mono.
|
||||
*/
|
||||
void front_separation(float v);
|
||||
|
||||
/**
|
||||
* Set the rear stereo separation.
|
||||
* Value range: [0.0..inf] -- 1.0 is default, 0.0 is mono.
|
||||
*/
|
||||
void rear_separation(float v);
|
||||
|
||||
// --- bass redirection (to LFE)
|
||||
|
||||
/**
|
||||
* Enable/disable LFE channel (default: false = disabled)
|
||||
*/
|
||||
void bass_redirection(bool v);
|
||||
|
||||
/**
|
||||
* Set the lower end of the transition band, in Hz/Nyquist (default: 40/22050).
|
||||
*/
|
||||
void low_cutoff(float v);
|
||||
|
||||
/**
|
||||
* Set the upper end of the transition band, in Hz/Nyquist (default: 90/22050).
|
||||
*/
|
||||
void high_cutoff(float v);
|
||||
|
||||
// --- info
|
||||
|
||||
/**
|
||||
* Number of samples currently held in the buffer.
|
||||
*/
|
||||
unsigned buffered();
|
||||
|
||||
/**
|
||||
* Number of channels in the given setup.
|
||||
*/
|
||||
static unsigned num_channels(channel_setup s);
|
||||
|
||||
/**
|
||||
* Channel id of the i'th channel in the given setup.
|
||||
*/
|
||||
static channel_id channel_at(channel_setup s, unsigned i);
|
||||
|
||||
private:
|
||||
class decoder_impl *impl; // private implementation
|
||||
};
|
||||
|
||||
#endif
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (C) 2010-2022, Christopher Snowhill,
|
||||
Copyright (C) 2010-2023, Christopher Snowhill,
|
||||
All rights reserved.
|
||||
Optimizations by Gumboot
|
||||
Additional work by Burt P.
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
#pragma once
|
||||
|
||||
// The functions provide little endianness to native endianness conversion and back again
|
||||
#if(defined(_MSC_VER) && defined(_WIN32)) || defined(__APPLE__)
|
||||
template <typename T>
|
||||
inline void from_little_endian_inplace(T& x) {
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline T from_little_endian(T x) {
|
||||
return x;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline void to_little_endian_inplace(T& x) {
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline T to_little_endian(T x) {
|
||||
return x;
|
||||
}
|
||||
|
||||
#else
|
||||
#error "Specify endianness conversion for your platform"
|
||||
#endif
|
|
@ -0,0 +1,641 @@
|
|||
|
||||
#include "HrtfData.h"
|
||||
#include "Endianness.h"
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
|
||||
typedef struct {
|
||||
uint8_t bytes[3];
|
||||
} sample_int24_t;
|
||||
|
||||
const double pi = 3.1415926535897932385;
|
||||
|
||||
template <typename T>
|
||||
void read_stream(std::istream& stream, T& value) {
|
||||
stream.read(reinterpret_cast<std::istream::char_type*>(&value), sizeof(value));
|
||||
from_little_endian_inplace(value);
|
||||
}
|
||||
|
||||
HrtfData::HrtfData(std::istream& stream) {
|
||||
const char required_magic00[] = { 'M', 'i', 'n', 'P', 'H', 'R', '0', '0' };
|
||||
const char required_magic01[] = { 'M', 'i', 'n', 'P', 'H', 'R', '0', '1' };
|
||||
const char required_magic02[] = { 'M', 'i', 'n', 'P', 'H', 'R', '0', '2' };
|
||||
const char required_magic03[] = { 'M', 'i', 'n', 'P', 'H', 'R', '0', '3' };
|
||||
char actual_magic[sizeof(required_magic03) / sizeof(required_magic03[0])];
|
||||
|
||||
stream.read(actual_magic, sizeof(actual_magic));
|
||||
if(std::equal(std::begin(required_magic03), std::end(required_magic03), std::begin(actual_magic), std::end(actual_magic))) {
|
||||
LoadHrtf03(stream);
|
||||
} else if(std::equal(std::begin(required_magic02), std::end(required_magic02), std::begin(actual_magic), std::end(actual_magic))) {
|
||||
LoadHrtf02(stream);
|
||||
} else if(std::equal(std::begin(required_magic01), std::end(required_magic01), std::begin(actual_magic), std::end(actual_magic))) {
|
||||
LoadHrtf01(stream);
|
||||
} else if(std::equal(std::begin(required_magic00), std::end(required_magic00), std::begin(actual_magic), std::end(actual_magic))) {
|
||||
LoadHrtf00(stream);
|
||||
} else {
|
||||
throw std::logic_error("Bad file format.");
|
||||
}
|
||||
}
|
||||
|
||||
void HrtfData::LoadHrtf03(std::istream& stream) {
|
||||
// const uint8_t ChanType_LeftOnly{0};
|
||||
const uint8_t ChanType_LeftRight{ 1 };
|
||||
|
||||
uint32_t sample_rate;
|
||||
uint8_t channel_type;
|
||||
uint8_t impulse_response_length;
|
||||
uint8_t distances_count;
|
||||
|
||||
read_stream(stream, sample_rate);
|
||||
read_stream(stream, channel_type);
|
||||
read_stream(stream, impulse_response_length);
|
||||
read_stream(stream, distances_count);
|
||||
|
||||
if(!stream || stream.eof()) {
|
||||
throw std::logic_error("Failed reading file.");
|
||||
}
|
||||
|
||||
if(channel_type > ChanType_LeftRight) {
|
||||
throw std::logic_error("Invalid channel format.");
|
||||
}
|
||||
|
||||
int channel_count = channel_type == ChanType_LeftRight ? 2 : 1;
|
||||
|
||||
std::vector<DistanceData> distances(distances_count);
|
||||
|
||||
for(uint8_t i = 0; i < distances_count; i++) {
|
||||
uint16_t distance;
|
||||
read_stream(stream, distance);
|
||||
distances[i].distance = float(distance) / 1000.0f;
|
||||
|
||||
uint8_t elevations_count;
|
||||
read_stream(stream, elevations_count);
|
||||
distances[i].elevations.resize(elevations_count);
|
||||
|
||||
if(!stream || stream.eof()) {
|
||||
throw std::logic_error("Failed reading file.");
|
||||
}
|
||||
|
||||
for(uint8_t j = 0; j < elevations_count; j++) {
|
||||
uint8_t azimuth_count;
|
||||
read_stream(stream, azimuth_count);
|
||||
distances[i].elevations[j].azimuths.resize(azimuth_count);
|
||||
}
|
||||
|
||||
if(!stream || stream.eof()) {
|
||||
throw std::logic_error("Failed reading file.");
|
||||
}
|
||||
}
|
||||
|
||||
const float normalization_factor = 1.0f / 8388608.0f;
|
||||
|
||||
for(auto& distance : distances) {
|
||||
for(auto& elevation : distance.elevations) {
|
||||
for(auto& azimuth : elevation.azimuths) {
|
||||
azimuth.impulse_response.resize(impulse_response_length * channel_count);
|
||||
for(auto& sample : azimuth.impulse_response) {
|
||||
union {
|
||||
sample_int24_t sample;
|
||||
int32_t sample_int;
|
||||
} sample_union;
|
||||
sample_union.sample_int = 0;
|
||||
read_stream(stream, sample_union.sample);
|
||||
sample_union.sample_int <<= 8;
|
||||
sample_union.sample_int >>= 8;
|
||||
sample = sample_union.sample_int * normalization_factor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!stream || stream.eof()) {
|
||||
throw std::logic_error("Failed reading file.");
|
||||
}
|
||||
|
||||
uint8_t longest_delay = 0;
|
||||
for(auto& distance : distances) {
|
||||
for(auto& elevation : distance.elevations) {
|
||||
for(auto& azimuth : elevation.azimuths) {
|
||||
uint8_t delay;
|
||||
read_stream(stream, delay);
|
||||
azimuth.delay = delay;
|
||||
longest_delay = std::max(longest_delay, delay);
|
||||
if(channel_type == ChanType_LeftRight) {
|
||||
read_stream(stream, delay);
|
||||
azimuth.delay_right = delay;
|
||||
longest_delay = std::max(longest_delay, delay);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!stream || stream.eof()) {
|
||||
throw std::logic_error("Failed reading file.");
|
||||
}
|
||||
|
||||
std::sort(distances.begin(), distances.end(),
|
||||
[](const DistanceData& lhs, const DistanceData& rhs) noexcept { return lhs.distance > rhs.distance; });
|
||||
|
||||
m_distances = std::move(distances);
|
||||
m_channel_count = channel_count;
|
||||
m_response_length = impulse_response_length;
|
||||
m_sample_rate = sample_rate;
|
||||
m_longest_delay = longest_delay;
|
||||
}
|
||||
|
||||
void HrtfData::LoadHrtf02(std::istream& stream) {
|
||||
// const uint8_t SampleType_S16{0};
|
||||
const uint8_t SampleType_S24{ 1 };
|
||||
// const uint8_t ChanType_LeftOnly{0};
|
||||
const uint8_t ChanType_LeftRight{ 1 };
|
||||
|
||||
uint32_t sample_rate;
|
||||
uint8_t sample_type;
|
||||
uint8_t channel_type;
|
||||
uint8_t impulse_response_length;
|
||||
uint8_t distances_count;
|
||||
|
||||
read_stream(stream, sample_rate);
|
||||
read_stream(stream, sample_type);
|
||||
read_stream(stream, channel_type);
|
||||
read_stream(stream, impulse_response_length);
|
||||
read_stream(stream, distances_count);
|
||||
|
||||
if(!stream || stream.eof()) {
|
||||
throw std::logic_error("Failed reading file.");
|
||||
}
|
||||
|
||||
if(sample_type > SampleType_S24) {
|
||||
throw std::logic_error("Invalid sample type.");
|
||||
}
|
||||
|
||||
if(channel_type > ChanType_LeftRight) {
|
||||
throw std::logic_error("Invalid channel format.");
|
||||
}
|
||||
|
||||
int channel_count = channel_type == ChanType_LeftRight ? 2 : 1;
|
||||
|
||||
std::vector<DistanceData> distances(distances_count);
|
||||
|
||||
for(uint8_t i = 0; i < distances_count; i++) {
|
||||
uint16_t distance;
|
||||
read_stream(stream, distance);
|
||||
distances[i].distance = float(distance) / 1000.0f;
|
||||
|
||||
uint8_t elevations_count;
|
||||
read_stream(stream, elevations_count);
|
||||
distances[i].elevations.resize(elevations_count);
|
||||
|
||||
if(!stream || stream.eof()) {
|
||||
throw std::logic_error("Failed reading file.");
|
||||
}
|
||||
|
||||
for(uint8_t j = 0; j < elevations_count; j++) {
|
||||
uint8_t azimuth_count;
|
||||
read_stream(stream, azimuth_count);
|
||||
distances[i].elevations[j].azimuths.resize(azimuth_count);
|
||||
}
|
||||
|
||||
if(!stream || stream.eof()) {
|
||||
throw std::logic_error("Failed reading file.");
|
||||
}
|
||||
}
|
||||
|
||||
const float normalization_factor = (sample_type == SampleType_S24) ? 1.0f / 8388608.0f : 1.0f / 32768.0f;
|
||||
|
||||
for(auto& distance : distances) {
|
||||
for(auto& elevation : distance.elevations) {
|
||||
for(auto& azimuth : elevation.azimuths) {
|
||||
azimuth.impulse_response.resize(impulse_response_length * channel_count);
|
||||
if(sample_type == SampleType_S24) {
|
||||
for(auto& sample : azimuth.impulse_response) {
|
||||
union {
|
||||
sample_int24_t sample;
|
||||
int32_t sample_int;
|
||||
} sample_union;
|
||||
sample_union.sample_int = 0;
|
||||
read_stream(stream, sample_union.sample);
|
||||
sample_union.sample_int <<= 8;
|
||||
sample_union.sample_int >>= 8;
|
||||
sample = sample_union.sample_int * normalization_factor;
|
||||
}
|
||||
} else {
|
||||
for(auto& sample : azimuth.impulse_response) {
|
||||
int16_t sample_from_file;
|
||||
read_stream(stream, sample_from_file);
|
||||
sample = sample_from_file * normalization_factor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!stream || stream.eof()) {
|
||||
throw std::logic_error("Failed reading file.");
|
||||
}
|
||||
|
||||
uint8_t longest_delay = 0;
|
||||
for(auto& distance : distances) {
|
||||
for(auto& elevation : distance.elevations) {
|
||||
for(auto& azimuth : elevation.azimuths) {
|
||||
uint8_t delay;
|
||||
read_stream(stream, delay);
|
||||
azimuth.delay = delay;
|
||||
longest_delay = std::max(longest_delay, delay);
|
||||
if(channel_type == ChanType_LeftRight) {
|
||||
read_stream(stream, delay);
|
||||
azimuth.delay_right = delay;
|
||||
longest_delay = std::max(longest_delay, delay);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!stream || stream.eof()) {
|
||||
throw std::logic_error("Failed reading file.");
|
||||
}
|
||||
|
||||
std::sort(distances.begin(), distances.end(),
|
||||
[](const DistanceData& lhs, const DistanceData& rhs) noexcept { return lhs.distance > rhs.distance; });
|
||||
|
||||
m_distances = std::move(distances);
|
||||
m_channel_count = channel_count;
|
||||
m_response_length = impulse_response_length;
|
||||
m_sample_rate = sample_rate;
|
||||
m_longest_delay = longest_delay;
|
||||
}
|
||||
|
||||
void HrtfData::LoadHrtf01(std::istream& stream) {
|
||||
uint32_t sample_rate;
|
||||
uint8_t impulse_response_length;
|
||||
|
||||
read_stream(stream, sample_rate);
|
||||
read_stream(stream, impulse_response_length);
|
||||
|
||||
if(!stream || stream.eof()) {
|
||||
throw std::logic_error("Failed reading file.");
|
||||
}
|
||||
|
||||
std::vector<DistanceData> distances(1);
|
||||
|
||||
distances[0].distance = 1.0;
|
||||
|
||||
uint8_t elevations_count;
|
||||
read_stream(stream, elevations_count);
|
||||
distances[0].elevations.resize(elevations_count);
|
||||
|
||||
if(!stream || stream.eof()) {
|
||||
throw std::logic_error("Failed reading file.");
|
||||
}
|
||||
|
||||
for(uint8_t i = 0; i < elevations_count; i++) {
|
||||
uint8_t azimuth_count;
|
||||
read_stream(stream, azimuth_count);
|
||||
distances[0].elevations[i].azimuths.resize(azimuth_count);
|
||||
}
|
||||
|
||||
if(!stream || stream.eof()) {
|
||||
throw std::logic_error("Failed reading file.");
|
||||
}
|
||||
|
||||
const float normalization_factor = 1.0f / 32768.0f;
|
||||
|
||||
for(auto& elevation : distances[0].elevations) {
|
||||
for(auto& azimuth : elevation.azimuths) {
|
||||
azimuth.impulse_response.resize(impulse_response_length);
|
||||
for(auto& sample : azimuth.impulse_response) {
|
||||
int16_t sample_from_file;
|
||||
read_stream(stream, sample_from_file);
|
||||
sample = sample_from_file * normalization_factor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!stream || stream.eof()) {
|
||||
throw std::logic_error("Failed reading file.");
|
||||
}
|
||||
|
||||
uint8_t longest_delay = 0;
|
||||
for(auto& elevation : distances[0].elevations) {
|
||||
for(auto& azimuth : elevation.azimuths) {
|
||||
uint8_t delay;
|
||||
read_stream(stream, delay);
|
||||
delay <<= 2;
|
||||
azimuth.delay = delay;
|
||||
longest_delay = std::max(longest_delay, delay);
|
||||
}
|
||||
}
|
||||
|
||||
if(!stream || stream.eof()) {
|
||||
throw std::logic_error("Failed reading file.");
|
||||
}
|
||||
|
||||
m_distances = std::move(distances);
|
||||
m_channel_count = 1;
|
||||
m_response_length = impulse_response_length;
|
||||
m_sample_rate = sample_rate;
|
||||
m_longest_delay = longest_delay;
|
||||
}
|
||||
|
||||
void HrtfData::LoadHrtf00(std::istream& stream) {
|
||||
uint32_t sample_rate;
|
||||
uint16_t impulse_response_count;
|
||||
uint16_t impulse_response_length;
|
||||
|
||||
read_stream(stream, sample_rate);
|
||||
read_stream(stream, impulse_response_count);
|
||||
read_stream(stream, impulse_response_length);
|
||||
|
||||
if(!stream || stream.eof()) {
|
||||
throw std::logic_error("Failed reading file.");
|
||||
}
|
||||
|
||||
std::vector<DistanceData> distances(1);
|
||||
|
||||
distances[0].distance = 1.0;
|
||||
|
||||
uint8_t elevations_count;
|
||||
read_stream(stream, elevations_count);
|
||||
distances[0].elevations.resize(elevations_count);
|
||||
|
||||
if(!stream || stream.eof()) {
|
||||
throw std::logic_error("Failed reading file.");
|
||||
}
|
||||
|
||||
std::vector<uint16_t> irOffsets(elevations_count);
|
||||
|
||||
for(uint8_t i = 0; i < elevations_count; i++) {
|
||||
read_stream(stream, irOffsets[i]);
|
||||
}
|
||||
|
||||
if(!stream || stream.eof()) {
|
||||
throw std::logic_error("Failed reading file.");
|
||||
}
|
||||
|
||||
for(size_t i = 1; i < elevations_count; i++) {
|
||||
if(irOffsets[i] <= irOffsets[i - 1]) {
|
||||
throw std::logic_error("Invalid elevation offset.");
|
||||
}
|
||||
}
|
||||
if(impulse_response_count <= irOffsets[elevations_count - 1]) {
|
||||
throw std::logic_error("Invalid elevation offset.");
|
||||
}
|
||||
|
||||
for(size_t i = 1; i < elevations_count; i++) {
|
||||
distances[0].elevations[i - 1].azimuths.resize(irOffsets[i] - irOffsets[i - 1]);
|
||||
}
|
||||
distances[0].elevations[elevations_count - 1].azimuths.resize(impulse_response_count - irOffsets[elevations_count - 1]);
|
||||
|
||||
const float normalization_factor = 1.0f / 32768.0f;
|
||||
|
||||
for(auto& elevation : distances[0].elevations) {
|
||||
for(auto& azimuth : elevation.azimuths) {
|
||||
azimuth.impulse_response.resize(impulse_response_length);
|
||||
for(auto& sample : azimuth.impulse_response) {
|
||||
int16_t sample_from_file;
|
||||
read_stream(stream, sample_from_file);
|
||||
sample = sample_from_file * normalization_factor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!stream || stream.eof()) {
|
||||
throw std::logic_error("Failed reading file.");
|
||||
}
|
||||
|
||||
uint8_t longest_delay = 0;
|
||||
for(auto& elevation : distances[0].elevations) {
|
||||
for(auto& azimuth : elevation.azimuths) {
|
||||
uint8_t delay;
|
||||
read_stream(stream, delay);
|
||||
delay <<= 2;
|
||||
azimuth.delay = delay;
|
||||
longest_delay = std::max(longest_delay, delay);
|
||||
}
|
||||
}
|
||||
|
||||
if(!stream || stream.eof()) {
|
||||
throw std::logic_error("Failed reading file.");
|
||||
}
|
||||
|
||||
m_distances = std::move(distances);
|
||||
m_channel_count = 1;
|
||||
m_response_length = impulse_response_length;
|
||||
m_sample_rate = sample_rate;
|
||||
m_longest_delay = longest_delay;
|
||||
}
|
||||
|
||||
void HrtfData::get_direction_data(angle_t elevation, angle_t azimuth, distance_t distance, uint32_t channel, DirectionData& ref_data) const {
|
||||
assert(elevation >= -angle_t(pi * 0.5));
|
||||
assert(elevation <= angle_t(pi * 0.5));
|
||||
assert(azimuth >= -angle_t(2.0 * pi));
|
||||
assert(azimuth <= angle_t(2.0 * pi));
|
||||
|
||||
const float azimuth_mod = std::fmod(azimuth + angle_t(pi * 2.0), angle_t(pi * 2.0));
|
||||
|
||||
size_t distance_index0 = 0;
|
||||
while(distance_index0 < m_distances.size() - 1 &&
|
||||
m_distances[distance_index0].distance > distance) {
|
||||
distance_index0++;
|
||||
}
|
||||
const size_t distance_index1 = std::min(distance_index0 + 1, m_distances.size() - 1);
|
||||
const distance_t distance0 = m_distances[distance_index0].distance;
|
||||
const distance_t distance1 = m_distances[distance_index1].distance;
|
||||
const distance_t distance_delta = distance0 - distance1;
|
||||
const float distance_fractional_part = distance_delta ? (distance - distance1) / distance_delta : 0;
|
||||
|
||||
const auto& elevations0 = m_distances[distance_index0].elevations;
|
||||
const auto& elevations1 = m_distances[distance_index1].elevations;
|
||||
|
||||
const angle_t elevation_scaled0 = (elevation + angle_t(pi * 0.5)) * (elevations0.size() - 1) / angle_t(pi);
|
||||
const angle_t elevation_scaled1 = (elevation + angle_t(pi * 0.5)) * (elevations1.size() - 1) / angle_t(pi);
|
||||
const size_t elevation_index00 = static_cast<size_t>(elevation_scaled0);
|
||||
const size_t elevation_index10 = static_cast<size_t>(elevation_scaled1);
|
||||
const size_t elevation_index01 = std::min(elevation_index00 + 1, elevations0.size() - 1);
|
||||
const size_t elevation_index11 = std::min(elevation_index10 + 1, elevations1.size() - 1);
|
||||
|
||||
const float elevation_fractional_part0 = std::fmod(elevation_scaled0, 1.0);
|
||||
const float elevation_fractional_part1 = std::fmod(elevation_scaled1, 1.0);
|
||||
|
||||
const angle_t azimuth_scaled00 = azimuth_mod * elevations0[elevation_index00].azimuths.size() / angle_t(2 * pi);
|
||||
const size_t azimuth_index000 = static_cast<size_t>(azimuth_scaled00) % elevations0[elevation_index00].azimuths.size();
|
||||
const size_t azimuth_index001 = static_cast<size_t>(azimuth_scaled00 + 1) % elevations0[elevation_index00].azimuths.size();
|
||||
const float azimuth_fractional_part00 = std::fmod(azimuth_scaled00, 1.0);
|
||||
|
||||
const angle_t azimuth_scaled10 = azimuth_mod * elevations1[elevation_index10].azimuths.size() / angle_t(2 * pi);
|
||||
const size_t azimuth_index100 = static_cast<size_t>(azimuth_scaled10) % elevations1[elevation_index10].azimuths.size();
|
||||
const size_t azimuth_index101 = static_cast<size_t>(azimuth_scaled10 + 1) % elevations1[elevation_index10].azimuths.size();
|
||||
const float azimuth_fractional_part10 = std::fmod(azimuth_scaled10, 1.0);
|
||||
|
||||
const angle_t azimuth_scaled01 = azimuth_mod * elevations0[elevation_index01].azimuths.size() / angle_t(2 * pi);
|
||||
const size_t azimuth_index010 = static_cast<size_t>(azimuth_scaled01) % elevations0[elevation_index01].azimuths.size();
|
||||
const size_t azimuth_index011 = static_cast<size_t>(azimuth_scaled01 + 1) % elevations0[elevation_index01].azimuths.size();
|
||||
const float azimuth_fractional_part01 = std::fmod(azimuth_scaled01, 1.0);
|
||||
|
||||
const angle_t azimuth_scaled11 = azimuth_mod * elevations1[elevation_index11].azimuths.size() / angle_t(2 * pi);
|
||||
const size_t azimuth_index110 = static_cast<size_t>(azimuth_scaled11) % elevations1[elevation_index11].azimuths.size();
|
||||
const size_t azimuth_index111 = static_cast<size_t>(azimuth_scaled11 + 1) % elevations1[elevation_index11].azimuths.size();
|
||||
const float azimuth_fractional_part11 = std::fmod(azimuth_scaled11, 1.0);
|
||||
|
||||
const float blend_factor_000 = (1.0f - elevation_fractional_part0) * (1.0f - azimuth_fractional_part00) * distance_fractional_part;
|
||||
const float blend_factor_001 = (1.0f - elevation_fractional_part0) * azimuth_fractional_part00 * distance_fractional_part;
|
||||
const float blend_factor_010 = elevation_fractional_part0 * (1.0f - azimuth_fractional_part01) * distance_fractional_part;
|
||||
const float blend_factor_011 = elevation_fractional_part0 * azimuth_fractional_part01 * distance_fractional_part;
|
||||
|
||||
const float blend_factor_100 = (1.0f - elevation_fractional_part1) * (1.0f - azimuth_fractional_part10) * (1.0f - distance_fractional_part);
|
||||
const float blend_factor_101 = (1.0f - elevation_fractional_part1) * azimuth_fractional_part10 * (1.0f - distance_fractional_part);
|
||||
const float blend_factor_110 = elevation_fractional_part1 * (1.0f - azimuth_fractional_part11) * (1.0f - distance_fractional_part);
|
||||
const float blend_factor_111 = elevation_fractional_part1 * azimuth_fractional_part11 * (1.0f - distance_fractional_part);
|
||||
|
||||
delay_t delay0;
|
||||
delay_t delay1;
|
||||
|
||||
if(channel == 0) {
|
||||
delay0 =
|
||||
elevations0[elevation_index00].azimuths[azimuth_index000].delay * blend_factor_000 + elevations0[elevation_index00].azimuths[azimuth_index001].delay * blend_factor_001 + elevations0[elevation_index01].azimuths[azimuth_index010].delay * blend_factor_010 + elevations0[elevation_index01].azimuths[azimuth_index011].delay * blend_factor_011;
|
||||
|
||||
delay1 =
|
||||
elevations1[elevation_index10].azimuths[azimuth_index100].delay * blend_factor_100 + elevations1[elevation_index10].azimuths[azimuth_index101].delay * blend_factor_101 + elevations1[elevation_index11].azimuths[azimuth_index110].delay * blend_factor_110 + elevations1[elevation_index11].azimuths[azimuth_index111].delay * blend_factor_111;
|
||||
} else {
|
||||
delay0 =
|
||||
elevations0[elevation_index00].azimuths[azimuth_index000].delay_right * blend_factor_000 + elevations0[elevation_index00].azimuths[azimuth_index001].delay_right * blend_factor_001 + elevations0[elevation_index01].azimuths[azimuth_index010].delay_right * blend_factor_010 + elevations0[elevation_index01].azimuths[azimuth_index011].delay_right * blend_factor_011;
|
||||
|
||||
delay1 =
|
||||
elevations1[elevation_index10].azimuths[azimuth_index100].delay_right * blend_factor_100 + elevations1[elevation_index10].azimuths[azimuth_index101].delay_right * blend_factor_101 + elevations1[elevation_index11].azimuths[azimuth_index110].delay_right * blend_factor_110 + elevations1[elevation_index11].azimuths[azimuth_index111].delay_right * blend_factor_111;
|
||||
}
|
||||
|
||||
ref_data.delay = delay0 + delay1;
|
||||
|
||||
if(ref_data.impulse_response.size() < m_response_length)
|
||||
ref_data.impulse_response.resize(m_response_length);
|
||||
|
||||
for(size_t i = 0, j = channel; i < m_response_length; i++, j += m_channel_count) {
|
||||
float sample0 =
|
||||
elevations0[elevation_index00].azimuths[azimuth_index000].impulse_response[j] * blend_factor_000 + elevations0[elevation_index00].azimuths[azimuth_index001].impulse_response[j] * blend_factor_001 + elevations0[elevation_index01].azimuths[azimuth_index010].impulse_response[j] * blend_factor_010 + elevations0[elevation_index01].azimuths[azimuth_index011].impulse_response[j] * blend_factor_011;
|
||||
float sample1 =
|
||||
elevations1[elevation_index10].azimuths[azimuth_index100].impulse_response[j] * blend_factor_100 + elevations1[elevation_index10].azimuths[azimuth_index101].impulse_response[j] * blend_factor_101 + elevations1[elevation_index11].azimuths[azimuth_index110].impulse_response[j] * blend_factor_110 + elevations1[elevation_index11].azimuths[azimuth_index111].impulse_response[j] * blend_factor_111;
|
||||
|
||||
ref_data.impulse_response[i] = sample0 + sample1;
|
||||
}
|
||||
}
|
||||
|
||||
void HrtfData::get_direction_data(angle_t elevation, angle_t azimuth, distance_t distance, DirectionData& ref_data_left, DirectionData& ref_data_right) const {
|
||||
assert(elevation >= -angle_t(pi * 0.5));
|
||||
assert(elevation <= angle_t(pi * 0.5));
|
||||
assert(azimuth >= -angle_t(2.0 * pi));
|
||||
assert(azimuth <= angle_t(2.0 * pi));
|
||||
|
||||
get_direction_data(elevation, azimuth, distance, 0, ref_data_left);
|
||||
if(m_channel_count == 1) {
|
||||
get_direction_data(elevation, -azimuth, distance, 0, ref_data_right);
|
||||
} else {
|
||||
get_direction_data(elevation, azimuth, distance, 1, ref_data_right);
|
||||
}
|
||||
}
|
||||
|
||||
void HrtfData::sample_direction(angle_t elevation, angle_t azimuth, distance_t distance, uint32_t sample, uint32_t channel, float& value, float& delay) const {
|
||||
assert(elevation >= -angle_t(pi * 0.5));
|
||||
assert(elevation <= angle_t(pi * 0.5));
|
||||
assert(azimuth >= -angle_t(2.0 * pi));
|
||||
assert(azimuth <= angle_t(2.0 * pi));
|
||||
|
||||
size_t distance_index0 = 0;
|
||||
while(distance_index0 < m_distances.size() - 1 &&
|
||||
m_distances[distance_index0].distance > distance) {
|
||||
distance_index0++;
|
||||
}
|
||||
const size_t distance_index1 = std::min(distance_index0 + 1, m_distances.size() - 1);
|
||||
const distance_t distance0 = m_distances[distance_index0].distance;
|
||||
const distance_t distance1 = m_distances[distance_index1].distance;
|
||||
const distance_t distance_delta = distance0 - distance1;
|
||||
const float distance_fractional_part = distance_delta ? (distance - distance1) / distance_delta : 0;
|
||||
|
||||
const auto& elevations0 = m_distances[distance_index0].elevations;
|
||||
const auto& elevations1 = m_distances[distance_index1].elevations;
|
||||
|
||||
const float azimuth_mod = std::fmod(azimuth + angle_t(pi * 2.0), angle_t(pi * 2.0));
|
||||
|
||||
const angle_t elevation_scaled0 = (elevation + angle_t(pi * 0.5)) * (elevations0.size() - 1) / angle_t(pi);
|
||||
const angle_t elevation_scaled1 = (elevation + angle_t(pi * 0.5)) * (elevations1.size() - 1) / angle_t(pi);
|
||||
const size_t elevation_index00 = static_cast<size_t>(elevation_scaled0);
|
||||
const size_t elevation_index10 = static_cast<size_t>(elevation_scaled1);
|
||||
const size_t elevation_index01 = std::min(elevation_index00 + 1, elevations0.size() - 1);
|
||||
const size_t elevation_index11 = std::min(elevation_index10 + 1, elevations1.size() - 1);
|
||||
|
||||
const float elevation_fractional_part0 = std::fmod(elevation_scaled0, 1.0);
|
||||
const float elevation_fractional_part1 = std::fmod(elevation_scaled1, 1.0);
|
||||
|
||||
const angle_t azimuth_scaled00 = azimuth_mod * elevations0[elevation_index00].azimuths.size() / angle_t(2 * pi);
|
||||
const size_t azimuth_index000 = static_cast<size_t>(azimuth_scaled00) % elevations0[elevation_index00].azimuths.size();
|
||||
const size_t azimuth_index001 = static_cast<size_t>(azimuth_scaled00 + 1) % elevations0[elevation_index00].azimuths.size();
|
||||
const float azimuth_fractional_part00 = std::fmod(azimuth_scaled00, 1.0);
|
||||
|
||||
const angle_t azimuth_scaled10 = azimuth_mod * elevations1[elevation_index10].azimuths.size() / angle_t(2 * pi);
|
||||
const size_t azimuth_index100 = static_cast<size_t>(azimuth_scaled10) % elevations1[elevation_index10].azimuths.size();
|
||||
const size_t azimuth_index101 = static_cast<size_t>(azimuth_scaled10 + 1) % elevations1[elevation_index10].azimuths.size();
|
||||
const float azimuth_fractional_part10 = std::fmod(azimuth_scaled10, 1.0);
|
||||
|
||||
const angle_t azimuth_scaled01 = azimuth_mod * elevations0[elevation_index01].azimuths.size() / angle_t(2 * pi);
|
||||
const size_t azimuth_index010 = static_cast<size_t>(azimuth_scaled01) % elevations0[elevation_index01].azimuths.size();
|
||||
const size_t azimuth_index011 = static_cast<size_t>(azimuth_scaled01 + 1) % elevations0[elevation_index01].azimuths.size();
|
||||
const float azimuth_fractional_part01 = std::fmod(azimuth_scaled01, 1.0);
|
||||
|
||||
const angle_t azimuth_scaled11 = azimuth_mod * elevations1[elevation_index11].azimuths.size() / angle_t(2 * pi);
|
||||
const size_t azimuth_index110 = static_cast<size_t>(azimuth_scaled11) % elevations1[elevation_index11].azimuths.size();
|
||||
const size_t azimuth_index111 = static_cast<size_t>(azimuth_scaled11 + 1) % elevations1[elevation_index11].azimuths.size();
|
||||
const float azimuth_fractional_part11 = std::fmod(azimuth_scaled11, 1.0);
|
||||
|
||||
const float blend_factor_000 = (1.0f - elevation_fractional_part0) * (1.0f - azimuth_fractional_part00) * distance_fractional_part;
|
||||
const float blend_factor_001 = (1.0f - elevation_fractional_part0) * azimuth_fractional_part00 * distance_fractional_part;
|
||||
const float blend_factor_010 = elevation_fractional_part0 * (1.0f - azimuth_fractional_part01) * distance_fractional_part;
|
||||
const float blend_factor_011 = elevation_fractional_part0 * azimuth_fractional_part01 * distance_fractional_part;
|
||||
|
||||
const float blend_factor_100 = (1.0f - elevation_fractional_part1) * (1.0f - azimuth_fractional_part10) * (1.0f - distance_fractional_part);
|
||||
const float blend_factor_101 = (1.0f - elevation_fractional_part1) * azimuth_fractional_part10 * (1.0f - distance_fractional_part);
|
||||
const float blend_factor_110 = elevation_fractional_part1 * (1.0f - azimuth_fractional_part11) * (1.0f - distance_fractional_part);
|
||||
const float blend_factor_111 = elevation_fractional_part1 * azimuth_fractional_part11 * (1.0f - distance_fractional_part);
|
||||
|
||||
float delay0;
|
||||
float delay1;
|
||||
|
||||
if(channel == 0) {
|
||||
delay0 =
|
||||
elevations0[elevation_index00].azimuths[azimuth_index000].delay * blend_factor_000 + elevations0[elevation_index00].azimuths[azimuth_index001].delay * blend_factor_001 + elevations0[elevation_index01].azimuths[azimuth_index010].delay * blend_factor_010 + elevations0[elevation_index01].azimuths[azimuth_index011].delay * blend_factor_011;
|
||||
|
||||
delay1 =
|
||||
elevations1[elevation_index10].azimuths[azimuth_index100].delay * blend_factor_100 + elevations1[elevation_index10].azimuths[azimuth_index101].delay * blend_factor_101 + elevations1[elevation_index11].azimuths[azimuth_index110].delay * blend_factor_110 + elevations1[elevation_index11].azimuths[azimuth_index111].delay * blend_factor_111;
|
||||
} else {
|
||||
delay0 =
|
||||
elevations0[elevation_index00].azimuths[azimuth_index000].delay_right * blend_factor_000 + elevations0[elevation_index00].azimuths[azimuth_index001].delay_right * blend_factor_001 + elevations0[elevation_index01].azimuths[azimuth_index010].delay_right * blend_factor_010 + elevations0[elevation_index01].azimuths[azimuth_index011].delay_right * blend_factor_011;
|
||||
|
||||
delay1 =
|
||||
elevations1[elevation_index10].azimuths[azimuth_index100].delay_right * blend_factor_100 + elevations1[elevation_index10].azimuths[azimuth_index101].delay_right * blend_factor_101 + elevations1[elevation_index11].azimuths[azimuth_index110].delay_right * blend_factor_110 + elevations1[elevation_index11].azimuths[azimuth_index111].delay_right * blend_factor_111;
|
||||
}
|
||||
|
||||
delay = delay0 + delay1;
|
||||
|
||||
sample = sample * m_channel_count + channel;
|
||||
|
||||
float value0 =
|
||||
elevations0[elevation_index00].azimuths[azimuth_index000].impulse_response[sample] * blend_factor_000 + elevations0[elevation_index00].azimuths[azimuth_index001].impulse_response[sample] * blend_factor_001 + elevations0[elevation_index01].azimuths[azimuth_index010].impulse_response[sample] * blend_factor_010 + elevations0[elevation_index01].azimuths[azimuth_index011].impulse_response[sample] * blend_factor_011;
|
||||
|
||||
float value1 =
|
||||
elevations1[elevation_index10].azimuths[azimuth_index100].impulse_response[sample] * blend_factor_100 + elevations1[elevation_index10].azimuths[azimuth_index101].impulse_response[sample] * blend_factor_101 + elevations1[elevation_index11].azimuths[azimuth_index110].impulse_response[sample] * blend_factor_110 + elevations1[elevation_index11].azimuths[azimuth_index111].impulse_response[sample] * blend_factor_111;
|
||||
|
||||
value = value0 + value1;
|
||||
}
|
||||
|
||||
void HrtfData::sample_direction(angle_t elevation, angle_t azimuth, distance_t distance, uint32_t sample, float& value_left, float& delay_left, float& value_right, float& delay_right) const {
|
||||
assert(elevation >= -angle_t(pi * 0.5));
|
||||
assert(elevation <= angle_t(pi * 0.5));
|
||||
assert(azimuth >= -angle_t(2.0 * pi));
|
||||
assert(azimuth <= angle_t(2.0 * pi));
|
||||
|
||||
sample_direction(elevation, azimuth, distance, sample, 0, value_left, delay_left);
|
||||
if(m_channel_count == 1) {
|
||||
sample_direction(elevation, -azimuth, distance, sample, 0, value_right, delay_right);
|
||||
} else {
|
||||
sample_direction(elevation, azimuth, distance, sample, 1, value_right, delay_right);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
#pragma once
|
||||
|
||||
#include "HrtfTypes.h"
|
||||
#include "IHrtfData.h"
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
struct ElevationData {
|
||||
std::vector<DirectionData> azimuths;
|
||||
};
|
||||
|
||||
struct DistanceData {
|
||||
distance_t distance;
|
||||
std::vector<ElevationData> elevations;
|
||||
};
|
||||
|
||||
class HrtfData : public IHrtfData {
|
||||
void LoadHrtf00(std::istream& stream);
|
||||
void LoadHrtf01(std::istream& stream);
|
||||
void LoadHrtf02(std::istream& stream);
|
||||
void LoadHrtf03(std::istream& stream);
|
||||
|
||||
public:
|
||||
HrtfData(std::istream& stream);
|
||||
|
||||
void get_direction_data(angle_t elevation, angle_t azimuth, distance_t distance, uint32_t channel, DirectionData& ref_data) const override;
|
||||
void get_direction_data(angle_t elevation, angle_t azimuth, distance_t distance, DirectionData& ref_data_left, DirectionData& ref_data_right) const override;
|
||||
void sample_direction(angle_t elevation, angle_t azimuth, distance_t distance, uint32_t sample, uint32_t channel, float& value, float& delay) const override;
|
||||
void sample_direction(angle_t elevation, angle_t azimuth, distance_t distance, uint32_t sample, float& value_left, float& delay_left, float& value_right, float& delay_right) const override;
|
||||
|
||||
uint32_t get_sample_rate() const override {
|
||||
return m_sample_rate;
|
||||
}
|
||||
uint32_t get_response_length() const override {
|
||||
return m_response_length;
|
||||
}
|
||||
uint32_t get_longest_delay() const override {
|
||||
return m_longest_delay;
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t m_sample_rate;
|
||||
uint32_t m_response_length;
|
||||
uint32_t m_longest_delay;
|
||||
uint32_t m_channel_count;
|
||||
std::vector<DistanceData> m_distances;
|
||||
};
|
|
@ -0,0 +1,14 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
typedef float distance_t;
|
||||
typedef float angle_t;
|
||||
typedef int delay_t;
|
||||
|
||||
struct DirectionData {
|
||||
std::vector<float> impulse_response;
|
||||
delay_t delay;
|
||||
delay_t delay_right;
|
||||
};
|
|
@ -0,0 +1,19 @@
|
|||
#pragma once
|
||||
|
||||
#include "HrtfTypes.h"
|
||||
|
||||
class IHrtfData {
|
||||
public:
|
||||
virtual ~IHrtfData() = default;
|
||||
|
||||
virtual void get_direction_data(angle_t elevation, angle_t azimuth, distance_t distance, uint32_t channel, DirectionData& ref_data) const = 0;
|
||||
virtual void get_direction_data(angle_t elevation, angle_t azimuth, distance_t distance, DirectionData& ref_data_left, DirectionData& ref_data_right) const = 0;
|
||||
// Get only once IR sample at given direction. The delay returned is the delay of IR's beginning, not the sample's!
|
||||
virtual void sample_direction(angle_t elevation, angle_t azimuth, distance_t distance, uint32_t sample, uint32_t channel, float& value, float& delay) const = 0;
|
||||
// Get only once IR sample at given direction for both channels. The delay returned is the delay of IR's beginning, not the sample's!
|
||||
virtual void sample_direction(angle_t elevation, angle_t azimuth, distance_t distance, uint32_t sample, float& value_left, float& delay_left, float& value_right, float& delay_right) const = 0;
|
||||
|
||||
virtual uint32_t get_sample_rate() const = 0;
|
||||
virtual uint32_t get_response_length() const = 0;
|
||||
virtual uint32_t get_longest_delay() const = 0;
|
||||
};
|
|
@ -1,504 +0,0 @@
|
|||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 2.1, February 1999
|
||||
|
||||
Copyright (C) 1991, 1999 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 Lesser GPL. It also counts
|
||||
as the successor of the GNU Library Public License, version 2, hence
|
||||
the version number 2.1.]
|
||||
|
||||
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 Lesser General Public License, applies to some
|
||||
specially designated software packages--typically libraries--of the
|
||||
Free Software Foundation and other authors who decide to use it. You
|
||||
can use it too, but we suggest you first think carefully about whether
|
||||
this license or the ordinary General Public License is the better
|
||||
strategy to use in any particular case, based on the explanations below.
|
||||
|
||||
When we speak of free software, we are referring to freedom of use,
|
||||
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 and use pieces of
|
||||
it in new free programs; and that you are informed that you can do
|
||||
these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
distributors to deny you these rights or to ask you to surrender these
|
||||
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 other code 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.
|
||||
|
||||
We protect your rights with a two-step method: (1) we copyright the
|
||||
library, and (2) we offer you this license, which gives you legal
|
||||
permission to copy, distribute and/or modify the library.
|
||||
|
||||
To protect each distributor, we want to make it very clear that
|
||||
there is no warranty for the free library. Also, if the library is
|
||||
modified by someone else and passed on, the recipients should know
|
||||
that what they have is not the original version, so that the original
|
||||
author's reputation will not be affected by problems that might be
|
||||
introduced by others.
|
||||
|
||||
Finally, software patents pose a constant threat to the existence of
|
||||
any free program. We wish to make sure that a company cannot
|
||||
effectively restrict the users of a free program by obtaining a
|
||||
restrictive license from a patent holder. Therefore, we insist that
|
||||
any patent license obtained for a version of the library must be
|
||||
consistent with the full freedom of use specified in this license.
|
||||
|
||||
Most GNU software, including some libraries, is covered by the
|
||||
ordinary GNU General Public License. This license, the GNU Lesser
|
||||
General Public License, applies to certain designated libraries, and
|
||||
is quite different from the ordinary General Public License. We use
|
||||
this license for certain libraries in order to permit linking those
|
||||
libraries into non-free programs.
|
||||
|
||||
When a program is linked with a library, whether statically or using
|
||||
a shared library, the combination of the two is legally speaking a
|
||||
combined work, a derivative of the original library. The ordinary
|
||||
General Public License therefore permits such linking only if the
|
||||
entire combination fits its criteria of freedom. The Lesser General
|
||||
Public License permits more lax criteria for linking other code with
|
||||
the library.
|
||||
|
||||
We call this license the "Lesser" General Public License because it
|
||||
does Less to protect the user's freedom than the ordinary General
|
||||
Public License. It also provides other free software developers Less
|
||||
of an advantage over competing non-free programs. These disadvantages
|
||||
are the reason we use the ordinary General Public License for many
|
||||
libraries. However, the Lesser license provides advantages in certain
|
||||
special circumstances.
|
||||
|
||||
For example, on rare occasions, there may be a special need to
|
||||
encourage the widest possible use of a certain library, so that it becomes
|
||||
a de-facto standard. To achieve this, non-free programs must be
|
||||
allowed to use the library. A more frequent case is that a free
|
||||
library does the same job as widely used non-free libraries. In this
|
||||
case, there is little to gain by limiting the free library to free
|
||||
software only, so we use the Lesser General Public License.
|
||||
|
||||
In other cases, permission to use a particular library in non-free
|
||||
programs enables a greater number of people to use a large body of
|
||||
free software. For example, permission to use the GNU C Library in
|
||||
non-free programs enables many more people to use the whole GNU
|
||||
operating system, as well as its variant, the GNU/Linux operating
|
||||
system.
|
||||
|
||||
Although the Lesser General Public License is Less protective of the
|
||||
users' freedom, it does ensure that the user of a program that is
|
||||
linked with the Library has the freedom and the wherewithal to run
|
||||
that program using a modified version of the Library.
|
||||
|
||||
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, whereas the latter must
|
||||
be combined with the library in order to run.
|
||||
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License Agreement applies to any software library or other
|
||||
program which contains a notice placed by the copyright holder or
|
||||
other authorized party saying it may be distributed under the terms of
|
||||
this Lesser 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 combine 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) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (1) uses at run time a
|
||||
copy of the library already present on the user's computer system,
|
||||
rather than copying library functions into the executable, and (2)
|
||||
will operate properly with a modified version of the library, if
|
||||
the user installs one, as long as the modified version is
|
||||
interface-compatible with the version that the work was made with.
|
||||
|
||||
c) 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.
|
||||
|
||||
d) 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.
|
||||
|
||||
e) 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 materials to be 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 with
|
||||
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 Lesser 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
|
||||
|
||||
How to Apply These Terms to Your New Libraries
|
||||
|
||||
If you develop a new library, and you want it to be of the greatest
|
||||
possible use to the public, we recommend making it free software that
|
||||
everyone can redistribute and change. You can do so by permitting
|
||||
redistribution under these terms (or, alternatively, under the terms of the
|
||||
ordinary General Public License).
|
||||
|
||||
To apply these terms, attach the following notices to the library. It is
|
||||
safest to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least the
|
||||
"copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the library's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 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
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser 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
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the library, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the
|
||||
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1990
|
||||
Ty Coon, President of Vice
|
||||
|
||||
That's all there is to it!
|
||||
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
SoX resampler plugin for foobar2000 audio player
|
||||
Copyright (C) lvqcl
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 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
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser 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
|
||||
|
||||
|
||||
This software uses code of SoX licensed under the terms of LGPLv2.1
|
||||
Copyright (C) robs@users.sourceforge.net
|
||||
Copyright (C) Chris Bagwell and SoX contributors
|
||||
Copyright (C) Reuben Thomas
|
||||
|
||||
|
||||
This software uses code of FFmpeg licensed under the terms of LGPLv2.1
|
||||
Copyright (C) Fabrice Bellard
|
||||
Copyright (C) Michael Niedermayer <michaelni@gmx.at>
|
||||
Copyright (C) Alex Converse <alex dot converse at gmail dot com>
|
||||
Copyright (C) Loren Merritt
|
||||
Copyright (C) Vitor Sessak
|
||||
Copyright (C) x264 project
|
||||
|
||||
|
||||
This software uses code of General Purpose FFT Package
|
||||
Copyright (C) Takuya OOURA
|
||||
|
||||
|
||||
This software uses code of foobar2000 1.4 SDK
|
||||
Copyright (C) 2001-2018, Peter Pawlowski
|
|
@ -1,193 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2013, 2018 lvqcl
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <memory.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include "lpc.h"
|
||||
|
||||
static void apply_window(float *const data, const size_t data_len) {
|
||||
#if 0
|
||||
if (0) // subtract the mean
|
||||
{
|
||||
double mean = 0;
|
||||
for(int i = 0; i < (int)data_len; i++)
|
||||
mean += data[i];
|
||||
mean /= data_len;
|
||||
|
||||
for(int i = 0; i < (int)data_len; i++)
|
||||
data[i] -= (float)mean;
|
||||
}
|
||||
#endif
|
||||
|
||||
if(1) // Welch window
|
||||
{
|
||||
const float n2 = (data_len + 1) / 2.0f;
|
||||
for(int i = 0; i < (int)data_len; i++) {
|
||||
float k = (i + 1 - n2) / n2;
|
||||
data[data_len - 1 - i] *= 1.0f - k * k;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static float vorbis_lpc_from_data(float *data, float *lpci, int n, int m, double *aut, double *lpc) {
|
||||
double error;
|
||||
double epsilon;
|
||||
int i, j;
|
||||
|
||||
/* autocorrelation, p+1 lag coefficients */
|
||||
j = m + 1;
|
||||
while(j--) {
|
||||
double d = 0; /* double needed for accumulator depth */
|
||||
for(i = j; i < n; i++) d += (double)data[i] * data[i - j];
|
||||
aut[j] = d;
|
||||
}
|
||||
|
||||
/* Generate lpc coefficients from autocorr values */
|
||||
|
||||
/* set our noise floor to about -100dB */
|
||||
error = aut[0] * (1. + 1e-10);
|
||||
epsilon = 1e-9 * aut[0] + 1e-10;
|
||||
|
||||
for(i = 0; i < m; i++) {
|
||||
double r = -aut[i + 1];
|
||||
|
||||
if(error < epsilon) {
|
||||
memset(lpc + i, 0, (m - i) * sizeof(*lpc));
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Sum up this iteration's reflection coefficient; note that in
|
||||
Vorbis we don't save it. If anyone wants to recycle this code
|
||||
and needs reflection coefficients, save the results of 'r' from
|
||||
each iteration. */
|
||||
|
||||
for(j = 0; j < i; j++) r -= lpc[j] * aut[i - j];
|
||||
r /= error;
|
||||
|
||||
/* Update LPC coefficients and total error */
|
||||
|
||||
lpc[i] = r;
|
||||
for(j = 0; j < i / 2; j++) {
|
||||
double tmp = lpc[j];
|
||||
|
||||
lpc[j] += r * lpc[i - 1 - j];
|
||||
lpc[i - 1 - j] += r * tmp;
|
||||
}
|
||||
if(i & 1) lpc[j] += lpc[j] * r;
|
||||
|
||||
error *= 1. - r * r;
|
||||
}
|
||||
|
||||
done:
|
||||
|
||||
/* slightly damp the filter */
|
||||
{
|
||||
double g = .99;
|
||||
double damp = g;
|
||||
for(j = 0; j < m; j++) {
|
||||
lpc[j] *= damp;
|
||||
damp *= g;
|
||||
}
|
||||
}
|
||||
|
||||
for(j = 0; j < m; j++) lpci[j] = (float)lpc[j];
|
||||
|
||||
/* we need the error value to know how big an impulse to hit the
|
||||
filter with later */
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static void vorbis_lpc_predict(float *coeff, float *prime, int m, float *data, long n, float *work) {
|
||||
/* in: coeff[0...m-1] LPC coefficients
|
||||
prime[0...m-1] initial values (allocated size of n+m-1)
|
||||
out: data[0...n-1] data samples */
|
||||
|
||||
long i, j, o, p;
|
||||
float y;
|
||||
|
||||
if(!prime)
|
||||
for(i = 0; i < m; i++)
|
||||
work[i] = 0.f;
|
||||
else
|
||||
for(i = 0; i < m; i++)
|
||||
work[i] = prime[i];
|
||||
|
||||
for(i = 0; i < n; i++) {
|
||||
y = 0;
|
||||
o = i;
|
||||
p = m;
|
||||
for(j = 0; j < m; j++)
|
||||
y -= work[o++] * coeff[--p];
|
||||
|
||||
data[i] = work[o] = y;
|
||||
}
|
||||
}
|
||||
|
||||
void lpc_extrapolate2(float *const data, const size_t data_len, const int nch, const int lpc_order, const size_t extra_bkwd, const size_t extra_fwd, void **extrapolate_buffer, size_t *extrapolate_buffer_size) {
|
||||
const size_t tdata_size = sizeof(float) * (extra_bkwd + data_len + extra_fwd);
|
||||
const size_t aut_size = sizeof(double) * (lpc_order + 1);
|
||||
const size_t lpc_size = sizeof(double) * lpc_order;
|
||||
const size_t lpci_size = sizeof(float) * lpc_order;
|
||||
const size_t work_size = sizeof(float) * (extra_bkwd + lpc_order + extra_fwd);
|
||||
|
||||
const size_t new_size = tdata_size + aut_size + lpc_size + lpci_size + work_size;
|
||||
|
||||
if(new_size > *extrapolate_buffer_size) {
|
||||
*extrapolate_buffer = realloc(*extrapolate_buffer, new_size);
|
||||
*extrapolate_buffer_size = new_size;
|
||||
}
|
||||
|
||||
float *tdata = (float *)(*extrapolate_buffer); // for 1 channel only
|
||||
|
||||
double *aut = (double *)(*extrapolate_buffer + tdata_size);
|
||||
double *lpc = (double *)(*extrapolate_buffer + tdata_size + aut_size);
|
||||
float *lpci = (float *)(*extrapolate_buffer + tdata_size + aut_size + lpc_size);
|
||||
float *work = (float *)(*extrapolate_buffer + tdata_size + aut_size + lpc_size + lpci_size);
|
||||
|
||||
for(int c = 0; c < nch; c++) {
|
||||
if(extra_bkwd) {
|
||||
for(int i = 0; i < (int)data_len; i++)
|
||||
tdata[data_len - 1 - i] = data[i * nch + c];
|
||||
} else {
|
||||
for(int i = 0; i < (int)data_len; i++)
|
||||
tdata[i] = data[i * nch + c];
|
||||
}
|
||||
|
||||
apply_window(tdata, data_len);
|
||||
vorbis_lpc_from_data(tdata, lpci, (int)data_len, lpc_order, aut, lpc);
|
||||
|
||||
// restore after apply_window
|
||||
if(extra_bkwd) {
|
||||
for(int i = 0; i < (int)data_len; i++)
|
||||
tdata[data_len - 1 - i] = data[i * nch + c];
|
||||
} else {
|
||||
for(int i = 0; i < (int)data_len; i++)
|
||||
tdata[i] = data[i * nch + c];
|
||||
}
|
||||
|
||||
vorbis_lpc_predict(lpci, tdata + data_len - lpc_order, lpc_order, tdata + data_len, extra_fwd + extra_bkwd, work);
|
||||
|
||||
if(extra_bkwd) {
|
||||
for(int i = 0; i < extra_bkwd; i++)
|
||||
data[(-i - 1) * nch + c] = tdata[data_len + i];
|
||||
} else {
|
||||
for(int i = 0; i < extra_fwd; i++)
|
||||
data[(i + data_len) * nch + c] = tdata[data_len + i];
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
#ifndef MY_LPC_H
|
||||
#define MY_LPC_H
|
||||
|
||||
/* data - beginning of the data
|
||||
* data_len - length of data (in samples) that are base for extrapolation
|
||||
* nch - number of (interleaved) channels
|
||||
* lpc_order - LPC order
|
||||
* extra_bkwd - number of samples to pre-extrapolate
|
||||
* extra_fwd - number of samples to post-extrapolate
|
||||
*
|
||||
* D = data; N = num_channels; LEN = data_len*N; EX = extra*N
|
||||
*
|
||||
* memory layout when invdir == false:
|
||||
*
|
||||
* [||||||||||||||||||||||||||||||||][||||||||||||||||||||][
|
||||
* ^ D[0] ^ D[LEN] ^ D[LEN+EX]
|
||||
*
|
||||
* memory layout when invdir == true:
|
||||
* ][||||||||||||||||||||][||||||||||||||||||||||||||||||||][
|
||||
* ^ D[0] ^ D[LEN]
|
||||
* ^ D[-1*N-EX] ^ D[-1*N]
|
||||
*
|
||||
*/
|
||||
|
||||
static const size_t LPC_ORDER = 32;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void lpc_extrapolate2(float * const data, const size_t data_len, const int nch, const int lpc_order, const size_t extra_bkwd, const size_t extra_fwd, void ** extrapolate_buffer, size_t * extrapolate_buffer_size);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
static inline void lpc_extrapolate_bkwd(float * const data, const size_t data_len, const size_t prime_len, const int nch, const int lpc_order, const size_t extra_bkwd, void ** extrapolate_buffer, size_t * extrapolate_buffer_size)
|
||||
{
|
||||
(void)data_len;
|
||||
lpc_extrapolate2(data, prime_len, nch, lpc_order, extra_bkwd, 0, extrapolate_buffer, extrapolate_buffer_size);
|
||||
}
|
||||
|
||||
static inline void lpc_extrapolate_fwd(float * const data, const size_t data_len, const size_t prime_len, const int nch, const int lpc_order, const size_t extra_fwd, void ** extrapolate_buffer, size_t * extrapolate_buffer_size)
|
||||
{
|
||||
lpc_extrapolate2(data + (data_len - prime_len)*nch, prime_len, nch, lpc_order, 0, extra_fwd, extrapolate_buffer, extrapolate_buffer_size);
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,55 +0,0 @@
|
|||
/* Copyright (c) 2018 lvqcl. All rights reserved.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 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 Lesser
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser 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
|
||||
*/
|
||||
|
||||
#ifndef UTIL_H_
|
||||
#define UTIL_H_
|
||||
|
||||
#ifndef min
|
||||
#define min(a,b) (((a)<(b))?(a):(b))
|
||||
#endif
|
||||
#ifndef max
|
||||
#define max(a,b) (((a)>(b))?(a):(b))
|
||||
#endif
|
||||
|
||||
static inline unsigned local_gcd(unsigned a, unsigned b)
|
||||
{
|
||||
if (a == 0 || b == 0) return 0;
|
||||
unsigned c = a % b;
|
||||
while (c != 0) { a = b; b = c; c = a % b; }
|
||||
return b;
|
||||
}
|
||||
|
||||
/*
|
||||
In: *r1 and *r2: samplerates;
|
||||
Out: *r1 and *r2: numbers of samples;
|
||||
|
||||
multiply r1 and r2 by n so that durations is 1/N th of second;
|
||||
limit n so that r1 and r2 aren't bigger than M
|
||||
*/
|
||||
static void samples_len(unsigned* r1, unsigned* r2, unsigned N, unsigned M) // example: r1 = 44100, r2 = 48000, N = 20, M = 8192
|
||||
{
|
||||
if (r1 == 0 || r2 == 0 || *r1 == 0 || *r2 == 0) return;
|
||||
unsigned v = local_gcd(*r1, *r2); // v = 300
|
||||
*r1 /= v; *r2 /= v; // r1 = 147; r2 = 160 == 1/300th of second
|
||||
unsigned n = (v + N-1) / N; // n = 300/20 = 15 times
|
||||
unsigned z = max(*r1, *r2); // z = 160
|
||||
if (z*n > M) n = M / z; // 160*15 = 2400 < 8192;; if M == 1024: n = 1024/160 = 6; 160*6 = 960
|
||||
if (n < 1) n = 1;
|
||||
*r1 *= n; *r2 *= n; // r1 = 147*15 = 2205 samples, r2 = 160*15 = 2400 samples
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1 +0,0 @@
|
|||
Subproject commit afd61e7ed76d86a9bc6cb91fd0a9f305f853fe38
|
|
@ -1,164 +0,0 @@
|
|||
//
|
||||
// r8bstate.h
|
||||
// CogAudio Framework
|
||||
//
|
||||
// Created by Christopher Snowhill on 3/3/22.
|
||||
//
|
||||
|
||||
#ifndef r8bstate_h
|
||||
#define r8bstate_h
|
||||
|
||||
#include <Accelerate/Accelerate.h>
|
||||
|
||||
#include "r8bbase.h"
|
||||
|
||||
#include "CDSPResampler.h"
|
||||
|
||||
struct r8bstate {
|
||||
int channelCount;
|
||||
int bufferCapacity;
|
||||
size_t remainder;
|
||||
uint64_t inProcessed;
|
||||
uint64_t outProcessed;
|
||||
double sampleRatio;
|
||||
r8b::CFixedBuffer<double> InBuf;
|
||||
r8b::CFixedBuffer<double> *OutBufs;
|
||||
r8b::CDSPResampler24 **Resamps;
|
||||
r8bstate(int _channelCount, int _bufferCapacity, double srcRate, double dstRate)
|
||||
: channelCount(_channelCount), bufferCapacity(_bufferCapacity), inProcessed(0), outProcessed(0), remainder(0) {
|
||||
InBuf.alloc(bufferCapacity);
|
||||
OutBufs = new r8b::CFixedBuffer<double>[channelCount];
|
||||
Resamps = new r8b::CDSPResampler24 *[channelCount];
|
||||
for(int i = 0; i < channelCount; ++i) {
|
||||
Resamps[i] = new r8b::CDSPResampler24(srcRate, dstRate, bufferCapacity);
|
||||
}
|
||||
sampleRatio = dstRate / srcRate;
|
||||
}
|
||||
|
||||
~r8bstate() {
|
||||
delete[] OutBufs;
|
||||
for(int i = 0; i < channelCount; ++i) {
|
||||
delete Resamps[i];
|
||||
}
|
||||
delete[] Resamps;
|
||||
}
|
||||
|
||||
double latency() {
|
||||
return ((double)inProcessed * sampleRatio) - (double)outProcessed;
|
||||
}
|
||||
|
||||
int resample(const float *input, size_t inCount, size_t *inDone, float *output, size_t outMax) {
|
||||
int ret = 0;
|
||||
int i;
|
||||
if(inDone) *inDone = 0;
|
||||
while(remainder > 0) {
|
||||
size_t blockCount = remainder;
|
||||
if(blockCount > outMax)
|
||||
blockCount = outMax;
|
||||
for(i = 0; i < channelCount; ++i) {
|
||||
vDSP_vdpsp(&OutBufs[i][0], 1, output + i, channelCount, blockCount);
|
||||
}
|
||||
remainder -= blockCount;
|
||||
if(remainder > 0) {
|
||||
for(i = 0; i < channelCount; ++i) {
|
||||
memmove(&OutBufs[i][0], &OutBufs[i][blockCount], remainder * sizeof(double));
|
||||
}
|
||||
}
|
||||
output += channelCount * blockCount;
|
||||
outProcessed += blockCount;
|
||||
outMax -= blockCount;
|
||||
ret += blockCount;
|
||||
if(!outMax)
|
||||
return ret;
|
||||
}
|
||||
while(inCount > 0) {
|
||||
size_t blockCount = inCount;
|
||||
if(blockCount > bufferCapacity)
|
||||
blockCount = bufferCapacity;
|
||||
int outputDone = 0;
|
||||
for(i = 0; i < channelCount; ++i) {
|
||||
double *outputPointer;
|
||||
vDSP_vspdp(input + i, channelCount, &InBuf[0], 1, blockCount);
|
||||
outputDone = Resamps[i]->process(InBuf, (int)blockCount, outputPointer);
|
||||
if(outputDone) {
|
||||
if(outputDone > outMax) {
|
||||
vDSP_vdpsp(outputPointer, 1, output + i, channelCount, outMax);
|
||||
remainder = outputDone - outMax;
|
||||
OutBufs[i].alloc((int)remainder);
|
||||
memcpy(&OutBufs[i][0], outputPointer + outMax, remainder);
|
||||
} else {
|
||||
vDSP_vdpsp(outputPointer, 1, output + i, channelCount, outputDone);
|
||||
}
|
||||
}
|
||||
}
|
||||
size_t outputActual = outputDone - remainder;
|
||||
input += channelCount * blockCount;
|
||||
output += channelCount * outputActual;
|
||||
inCount -= blockCount;
|
||||
if(inDone) *inDone += blockCount;
|
||||
inProcessed += blockCount;
|
||||
outProcessed += outputActual;
|
||||
outMax -= outputActual;
|
||||
ret += outputActual;
|
||||
if(remainder)
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int flush(float *output, size_t outMax) {
|
||||
int ret = 0;
|
||||
int i;
|
||||
if(remainder > 0) {
|
||||
size_t blockCount = remainder;
|
||||
if(blockCount > outMax)
|
||||
blockCount = outMax;
|
||||
for(i = 0; i < channelCount; ++i) {
|
||||
vDSP_vdpsp(&OutBufs[i][0], 1, output + i, channelCount, blockCount);
|
||||
}
|
||||
remainder -= blockCount;
|
||||
if(remainder > 0) {
|
||||
for(i = 0; i < channelCount; ++i) {
|
||||
memmove(&OutBufs[i][0], &OutBufs[i][blockCount], remainder * sizeof(double));
|
||||
}
|
||||
}
|
||||
output += channelCount * blockCount;
|
||||
outProcessed += blockCount;
|
||||
outMax -= blockCount;
|
||||
ret += blockCount;
|
||||
if(!outMax)
|
||||
return ret;
|
||||
}
|
||||
uint64_t outputWanted = ceil(inProcessed * sampleRatio);
|
||||
memset(&InBuf[0], 0, sizeof(double) * bufferCapacity);
|
||||
while(outProcessed < outputWanted) {
|
||||
int outputDone = 0;
|
||||
for(int i = 0; i < channelCount; ++i) {
|
||||
double *outputPointer;
|
||||
outputDone = Resamps[i]->process(InBuf, bufferCapacity, outputPointer);
|
||||
if(outputDone) {
|
||||
if(outputDone > (outputWanted - outProcessed))
|
||||
outputDone = (int)(outputWanted - outProcessed);
|
||||
if(outputDone > outMax) {
|
||||
vDSP_vdpsp(outputPointer, 1, output + i, channelCount, outMax);
|
||||
remainder = outputDone - outMax;
|
||||
OutBufs[i].alloc((int)remainder);
|
||||
memcpy(&OutBufs[i][0], outputPointer + outMax, remainder);
|
||||
} else {
|
||||
vDSP_vdpsp(outputPointer, 1, output + i, channelCount, outputDone);
|
||||
}
|
||||
}
|
||||
}
|
||||
size_t outputActual = outputDone - remainder;
|
||||
outProcessed += outputActual;
|
||||
output += channelCount * outputActual;
|
||||
outMax -= outputActual;
|
||||
ret += outputActual;
|
||||
if(remainder)
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* r8bstate_h */
|
|
@ -0,0 +1,30 @@
|
|||
//
|
||||
// rsstate.cpp
|
||||
// CogAudio Framework
|
||||
//
|
||||
// Created by Christopher Snowhill on 2/4/23.
|
||||
//
|
||||
|
||||
#include "rsstate.h"
|
||||
#include "rsstate.hpp"
|
||||
|
||||
void *rsstate_new(int channelCount, double srcRate, double dstRate) {
|
||||
return (void *)new rsstate(channelCount, srcRate, dstRate);
|
||||
}
|
||||
|
||||
void rsstate_delete(void *state) {
|
||||
delete(rsstate *)state;
|
||||
}
|
||||
|
||||
double rsstate_latency(void *state) {
|
||||
return ((rsstate *)state)->latency();
|
||||
}
|
||||
|
||||
int rsstate_resample(void *state, const float *input, size_t inCount, size_t *inDone,
|
||||
float *output, size_t outMax) {
|
||||
return ((rsstate *)state)->resample(input, inCount, inDone, output, outMax);
|
||||
}
|
||||
|
||||
int rsstate_flush(void *state, float *output, size_t outMax) {
|
||||
return ((rsstate *)state)->flush(output, outMax);
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
//
|
||||
// rsstate.h
|
||||
// CogAudio Framework
|
||||
//
|
||||
// Created by Christopher Snowhill on 2/4/23.
|
||||
//
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifndef rsstate_h
|
||||
#define rsstate_h
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void *rsstate_new(int channelCount, double srcRate, double dstRate);
|
||||
void rsstate_delete(void *);
|
||||
|
||||
double rsstate_latency(void *);
|
||||
|
||||
int rsstate_resample(void *, const float *input, size_t inCount, size_t *inDone,
|
||||
float *output, size_t outMax);
|
||||
|
||||
int rsstate_flush(void *, float *output, size_t outMax);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* rsstate_h */
|
|
@ -0,0 +1,80 @@
|
|||
//
|
||||
// rsstate.hpp
|
||||
// CogAudio Framework
|
||||
//
|
||||
// Created by Christopher Snowhill on 2/3/23.
|
||||
//
|
||||
|
||||
#ifndef rsstate_hpp
|
||||
#define rsstate_hpp
|
||||
|
||||
#include "soxr.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
|
||||
struct rsstate {
|
||||
int channelCount;
|
||||
int bufferCapacity;
|
||||
size_t remainder;
|
||||
uint64_t inProcessed;
|
||||
uint64_t outProcessed;
|
||||
double sampleRatio;
|
||||
double dstRate;
|
||||
std::vector<float> SilenceBuf;
|
||||
soxr_t Resampler;
|
||||
rsstate(int _channelCount, double srcRate, double _dstRate)
|
||||
: channelCount(_channelCount), inProcessed(0), outProcessed(0), remainder(0), dstRate(_dstRate) {
|
||||
SilenceBuf.resize(1024 * channelCount);
|
||||
memset(&SilenceBuf[0], 0, 1024 * channelCount * sizeof(float));
|
||||
Resampler = soxr_create(srcRate, dstRate, channelCount, NULL, NULL, NULL, NULL);
|
||||
sampleRatio = dstRate / srcRate;
|
||||
}
|
||||
|
||||
~rsstate() {
|
||||
soxr_delete(Resampler);
|
||||
}
|
||||
|
||||
double latency() {
|
||||
return (((double)inProcessed * sampleRatio) - (double)outProcessed) / dstRate;
|
||||
}
|
||||
|
||||
int resample(const float *input, size_t inCount, size_t *inDone, float *output, size_t outMax) {
|
||||
size_t outDone = 0;
|
||||
soxr_error_t errmsg = soxr_process(Resampler, (soxr_in_t)input, inCount, inDone, (soxr_out_t)output, outMax, &outDone);
|
||||
if(!errmsg) {
|
||||
inProcessed += *inDone;
|
||||
outProcessed += outDone;
|
||||
return (int)outDone;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int flush(float *output, size_t outMax) {
|
||||
size_t outTotal = 0;
|
||||
uint64_t outputWanted = std::ceil(inProcessed * sampleRatio);
|
||||
while(outProcessed < outputWanted) {
|
||||
size_t outWanted = outputWanted - outProcessed;
|
||||
if(outWanted > outMax) {
|
||||
outWanted = outMax;
|
||||
}
|
||||
size_t outDone = 0;
|
||||
size_t inDone = 0;
|
||||
soxr_error_t errmsg = soxr_process(Resampler, (soxr_in_t)(&SilenceBuf[0]), 1024, &inDone, (soxr_out_t)output, outWanted, &outDone);
|
||||
if(!errmsg) {
|
||||
outProcessed += outDone;
|
||||
outTotal += outDone;
|
||||
output += outDone * channelCount;
|
||||
outMax -= outDone;
|
||||
if(!outMax || outProcessed == outputWanted) {
|
||||
return (int)outTotal;
|
||||
}
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* r8bstate_h */
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// Semaphore.h
|
||||
// CogSemaphore.h
|
||||
// Cog
|
||||
//
|
||||
// Created by Vincent Spader on 8/2/05.
|
|
@ -1,12 +1,12 @@
|
|||
//
|
||||
// Semaphore.m
|
||||
// CogSemaphore.m
|
||||
// Cog
|
||||
//
|
||||
// Created by Vincent Spader on 8/2/05.
|
||||
// Copyright 2005 Vincent Spader. All rights reserved.
|
||||
//
|
||||
|
||||
#import "Semaphore.h"
|
||||
#import <CogAudio/CogSemaphore.h>
|
||||
|
||||
@implementation Semaphore
|
||||
|
|
@ -11,15 +11,20 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
@interface VisualizationController : NSObject {
|
||||
double sampleRate;
|
||||
float visAudio[4096];
|
||||
double latency;
|
||||
float *visAudio;
|
||||
int visAudioCursor, visAudioSize;
|
||||
float *visAudioTemp;
|
||||
}
|
||||
|
||||
+ (VisualizationController *)sharedController;
|
||||
|
||||
- (void)postLatency:(double)latency;
|
||||
|
||||
- (void)postSampleRate:(double)sampleRate;
|
||||
- (void)postVisPCM:(const float *)inPCM amount:(int)amount;
|
||||
- (double)readSampleRate;
|
||||
- (void)copyVisPCM:(float *)outPCM visFFT:(float *)outFFT;
|
||||
- (void)copyVisPCM:(float *)outPCM visFFT:(float *)outFFT latencyOffset:(double)latency;
|
||||
|
||||
@end
|
||||
|
||||
|
|
|
@ -26,7 +26,12 @@ static VisualizationController *_sharedController = nil;
|
|||
- (id)init {
|
||||
self = [super init];
|
||||
if(self) {
|
||||
vDSP_vclr(visAudio, 1, 4096);
|
||||
visAudio = NULL;
|
||||
latency = 0;
|
||||
visAudioTemp = (float *) calloc(sizeof(float), 4096);
|
||||
if(!visAudioTemp) {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
@ -37,32 +42,88 @@ static VisualizationController *_sharedController = nil;
|
|||
|
||||
- (void)postSampleRate:(double)sampleRate {
|
||||
@synchronized(self) {
|
||||
self->sampleRate = sampleRate;
|
||||
if(self->sampleRate != sampleRate) {
|
||||
self->sampleRate = sampleRate;
|
||||
int visAudioSize = (int)(sampleRate * 45.0);
|
||||
void *visAudio = realloc(self->visAudio, visAudioSize * sizeof(float));
|
||||
if(visAudio && visAudioSize) {
|
||||
if(visAudioSize > self->visAudioSize) {
|
||||
bzero(((float *)visAudio) + self->visAudioSize, sizeof(float) * (visAudioSize - self->visAudioSize));
|
||||
}
|
||||
self->visAudio = visAudio;
|
||||
self->visAudioSize = visAudioSize;
|
||||
visAudioCursor %= visAudioSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)postVisPCM:(const float *)inPCM amount:(int)amount {
|
||||
int skipAmount = 0;
|
||||
if(amount > 4096) {
|
||||
skipAmount = amount - 4096;
|
||||
amount = 4096;
|
||||
}
|
||||
@synchronized(self) {
|
||||
cblas_scopy(4096 - amount, visAudio + amount, 1, visAudio, 1);
|
||||
cblas_scopy(amount, inPCM + skipAmount, 1, visAudio + 4096 - amount, 1);
|
||||
int samplesRead = 0;
|
||||
while(amount > 0) {
|
||||
int amountToCopy = (int)(visAudioSize - visAudioCursor);
|
||||
if(amountToCopy > amount) amountToCopy = amount;
|
||||
cblas_scopy(amountToCopy, inPCM + samplesRead, 1, visAudio + visAudioCursor, 1);
|
||||
visAudioCursor = visAudioCursor + amountToCopy;
|
||||
if(visAudioCursor >= visAudioSize) visAudioCursor -= visAudioSize;
|
||||
amount -= amountToCopy;
|
||||
samplesRead += amountToCopy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)postLatency:(double)latency {
|
||||
self->latency = latency;
|
||||
assert(latency < 45.0);
|
||||
}
|
||||
|
||||
- (double)readSampleRate {
|
||||
@synchronized(self) {
|
||||
return sampleRate;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)copyVisPCM:(float *)outPCM visFFT:(float *)outFFT {
|
||||
- (void)copyVisPCM:(float *)outPCM visFFT:(float *)outFFT latencyOffset:(double)latency {
|
||||
if(!outPCM && !outFFT) return;
|
||||
|
||||
if(!visAudio || !visAudioSize) {
|
||||
if(outPCM) bzero(outPCM, sizeof(float) * 4096);
|
||||
if(outFFT) bzero(outFFT, sizeof(float) * 2048);
|
||||
return;
|
||||
}
|
||||
|
||||
if(!outPCM) outPCM = &visAudioTemp[0];
|
||||
|
||||
@synchronized(self) {
|
||||
cblas_scopy(4096, visAudio, 1, outPCM, 1);
|
||||
fft_calculate(visAudio, outFFT, 2048);
|
||||
if(!sampleRate) {
|
||||
bzero(outPCM, 4096 * sizeof(float));
|
||||
if(outFFT) {
|
||||
bzero(outFFT, 2048 * sizeof(float));
|
||||
}
|
||||
return;
|
||||
}
|
||||
int latencySamples = (int)(sampleRate * (self->latency + latency));
|
||||
if(latencySamples < 4096) latencySamples = 4096;
|
||||
int readCursor = visAudioCursor - latencySamples;
|
||||
int samples = 4096;
|
||||
int samplesRead = 0;
|
||||
while(readCursor < 0)
|
||||
readCursor += visAudioSize;
|
||||
while(readCursor >= visAudioSize)
|
||||
readCursor -= visAudioSize;
|
||||
while(samples > 0) {
|
||||
int samplesToRead = (int)(visAudioSize - readCursor);
|
||||
if(samplesToRead > samples) samplesToRead = samples;
|
||||
cblas_scopy(samplesToRead, visAudio + readCursor, 1, outPCM + samplesRead, 1);
|
||||
samplesRead += samplesToRead;
|
||||
readCursor += samplesToRead;
|
||||
samples -= samplesToRead;
|
||||
if(readCursor >= visAudioSize) readCursor -= visAudioSize;
|
||||
}
|
||||
}
|
||||
if(outFFT) {
|
||||
fft_calculate(outPCM, outFFT, 2048);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
//
|
||||
// VisualizationController.swift
|
||||
// CogAudio Framework
|
||||
//
|
||||
// Created by Christopher Snowhill on 6/30/22.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@objc(VisualizationController)
|
||||
class VisualizationController : NSObject {
|
||||
var serialQueue = DispatchQueue(label: "Visualization Queue")
|
||||
var sampleRate = 0.0
|
||||
var latency = 0.0
|
||||
var visAudio: [Float] = Array(repeating: 0.0, count: 44100 * 45)
|
||||
var visAudioCursor = 0
|
||||
var visAudioSize = 0
|
||||
|
||||
private static var sharedVisualizationController: VisualizationController = {
|
||||
let visualizationController = VisualizationController()
|
||||
return visualizationController
|
||||
}()
|
||||
|
||||
@objc
|
||||
class func sharedController() -> VisualizationController {
|
||||
return sharedVisualizationController
|
||||
}
|
||||
|
||||
@objc
|
||||
func postLatency(_ latency: Double) {
|
||||
self.latency = latency
|
||||
}
|
||||
|
||||
@objc
|
||||
func postSampleRate(_ sampleRate: Double) {
|
||||
serialQueue.sync {
|
||||
if(self.sampleRate != sampleRate) {
|
||||
self.sampleRate = sampleRate
|
||||
visAudioSize = (Int)(sampleRate * 45.0)
|
||||
visAudio = Array(repeating: 0.0, count: visAudioSize)
|
||||
visAudioCursor = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
func postVisPCM(_ inPCM: UnsafePointer<Float>?, amount: Int) {
|
||||
serialQueue.sync {
|
||||
let bufferPointer = UnsafeBufferPointer<Float>(start: inPCM, count: amount)
|
||||
var j = self.visAudioCursor
|
||||
let k = self.visAudioSize
|
||||
for i in 0..<amount {
|
||||
let x = bufferPointer[i]
|
||||
self.visAudio[j] = x
|
||||
j += 1; if j >= k { j = 0 }
|
||||
}
|
||||
self.visAudioCursor = j
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
func readSampleRate() -> Double {
|
||||
serialQueue.sync {
|
||||
return self.sampleRate
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
func copyVisPCM(_ outPCM: UnsafeMutablePointer<Float>?, visFFT: UnsafeMutablePointer<Float>?, latencyOffset: Double) {
|
||||
if(self.visAudioSize == 0) {
|
||||
if(outPCM != nil) {
|
||||
let pcmPointer = UnsafeMutableBufferPointer<Float>(start: outPCM, count: 4096)
|
||||
for i in 0...4095 {
|
||||
pcmPointer[i] = 0.0
|
||||
}
|
||||
}
|
||||
if(visFFT != nil) {
|
||||
let fftPointer = UnsafeMutableBufferPointer<Float>(start: visFFT, count: 2048)
|
||||
for i in 0...2047 {
|
||||
fftPointer[i] = 0.0
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var outPCMCopy = Array<Float>(repeating: 0.0, count: 4096)
|
||||
|
||||
serialQueue.sync {
|
||||
let latencySamples = (Int)(self.latency * self.sampleRate)
|
||||
var j = self.visAudioCursor - latencySamples
|
||||
let k = self.visAudioSize
|
||||
if j < 0 { j += k }
|
||||
for i in 0...4095 {
|
||||
let x = self.visAudio[j]
|
||||
outPCMCopy[i] = x
|
||||
j += 1; if j >= k { j = 0 }
|
||||
}
|
||||
}
|
||||
|
||||
if(outPCM != nil) {
|
||||
let pcmPointer = UnsafeMutableBufferPointer<Float>(start: outPCM, count: 4096)
|
||||
for i in 0...4095 {
|
||||
let x = outPCMCopy[i]
|
||||
pcmPointer[i] = x
|
||||
}
|
||||
}
|
||||
|
||||
if(visFFT != nil) {
|
||||
serialQueue.sync {
|
||||
fft_calculate(outPCMCopy, visFFT, 2048)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,18 +1,18 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="20037" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="22113.1" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="20037"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22113.1"/>
|
||||
<plugIn identifier="com.apple.WebKit2IBPlugin" version="22113.1"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="AboutWIndowController" customModule="Cog" customModuleProvider="target">
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="AboutWindowController" customModule="Cog" customModuleProvider="target">
|
||||
<connections>
|
||||
<outlet property="appCopyright" destination="ht5-N1-YXd" id="cUK-kB-F2V"/>
|
||||
<outlet property="appName" destination="Lya-f1-R7S" id="ZVN-sE-MQp"/>
|
||||
<outlet property="appVersion" destination="7dE-CE-MBv" id="qOe-3F-tMG"/>
|
||||
<outlet property="creditsView" destination="iz8-0U-jtY" id="uMm-WQ-Fui"/>
|
||||
<outlet property="imageView" destination="aIO-L9-zMj" id="cIl-Ce-onP"/>
|
||||
<outlet property="creditsView" destination="LZw-Xy-wYl" id="jyQ-Rg-oqx"/>
|
||||
<outlet property="vfxView" destination="2nK-dq-1h6" id="ToT-af-ycD"/>
|
||||
<outlet property="window" destination="F0z-JX-Cv5" id="gIp-Ho-8D9"/>
|
||||
</connections>
|
||||
|
@ -39,41 +39,17 @@
|
|||
<visualEffectView blendingMode="withinWindow" material="fullScreenUI" state="followsWindowActiveState" translatesAutoresizingMaskIntoConstraints="NO" id="2nK-dq-1h6">
|
||||
<rect key="frame" x="387" y="84" width="262" height="230"/>
|
||||
<subviews>
|
||||
<scrollView appearanceType="aqua" borderType="none" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" translatesAutoresizingMaskIntoConstraints="NO" id="HLG-H1-ARK">
|
||||
<wkWebView wantsLayer="YES" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="LZw-Xy-wYl">
|
||||
<rect key="frame" x="0.0" y="0.0" width="262" height="230"/>
|
||||
<clipView key="contentView" drawsBackground="NO" id="VsR-Lq-AhX">
|
||||
<rect key="frame" x="0.0" y="0.0" width="247" height="230"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textView editable="NO" verticallyResizable="YES" findStyle="bar" allowsDocumentBackgroundColorChange="YES" spellingCorrection="YES" smartInsertDelete="YES" id="iz8-0U-jtY">
|
||||
<rect key="frame" x="0.0" y="0.0" width="247" height="230"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<size key="minSize" width="247" height="230"/>
|
||||
<size key="maxSize" width="247" height="10000000"/>
|
||||
<color key="insertionPointColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
</textView>
|
||||
</subviews>
|
||||
</clipView>
|
||||
<scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="YES" id="Jrl-HU-vmJ">
|
||||
<rect key="frame" x="-100" y="-100" width="225" height="15"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
<scroller key="verticalScroller" verticalHuggingPriority="750" horizontal="NO" id="z2b-Sa-SQ9">
|
||||
<rect key="frame" x="247" y="0.0" width="15" height="230"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
</scrollView>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<wkWebViewConfiguration key="configuration" applicationNameForUserAgent="Cog">
|
||||
<audiovisualMediaTypes key="mediaTypesRequiringUserActionForPlayback" none="YES"/>
|
||||
<wkPreferences key="preferences"/>
|
||||
</wkWebViewConfiguration>
|
||||
</wkWebView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="HLG-H1-ARK" firstAttribute="leading" secondItem="2nK-dq-1h6" secondAttribute="leading" id="RIy-GD-jCE"/>
|
||||
<constraint firstItem="HLG-H1-ARK" firstAttribute="top" secondItem="2nK-dq-1h6" secondAttribute="top" id="Ye7-Xs-CXc"/>
|
||||
<constraint firstAttribute="trailing" secondItem="HLG-H1-ARK" secondAttribute="trailing" id="tZ8-S3-bky"/>
|
||||
<constraint firstAttribute="bottom" secondItem="HLG-H1-ARK" secondAttribute="bottom" id="vNr-Wi-0vq"/>
|
||||
</constraints>
|
||||
</visualEffectView>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Lya-f1-R7S">
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Lya-f1-R7S">
|
||||
<rect key="frame" x="385" y="351" width="266" height="31"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" selectable="YES" title="Cog" usesSingleLineMode="YES" id="hzl-Rl-e01">
|
||||
<font key="font" textStyle="largeTitle" name=".SFNS-Regular"/>
|
||||
|
@ -81,7 +57,7 @@
|
|||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="7dE-CE-MBv">
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="7dE-CE-MBv">
|
||||
<rect key="frame" x="385" y="322" width="266" height="21"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" selectable="YES" title="Version..." usesSingleLineMode="YES" id="Wjf-By-C1F">
|
||||
<font key="font" textStyle="title2" name=".SFNS-Regular"/>
|
||||
|
@ -89,12 +65,12 @@
|
|||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ht5-N1-YXd">
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ht5-N1-YXd">
|
||||
<rect key="frame" x="387" y="20" width="264" height="56"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" selectable="YES" title="Copyright..." id="wLU-AJ-J0b">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
|
@ -1,8 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="19529" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="20037" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="19529"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="20037"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
|
@ -853,7 +853,7 @@
|
|||
</connections>
|
||||
</slider>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="sTm-FX-lSj">
|
||||
<rect key="frame" x="172" y="9" width="356" height="16"/>
|
||||
<rect key="frame" x="0.0" y="9" width="701" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" alignment="center" title="Note: You may use right-click to draw an equalizer shape" id="lwG-Tm-rr1">
|
||||
<font key="font" usesAppearanceFont="YES"/>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="15505" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="22113.1" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="15505"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22113.1"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
|
@ -21,13 +21,13 @@
|
|||
<windowStyleMask key="styleMask" titled="YES" closable="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="168" y="357" width="480" height="376"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1920" height="1057"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1920" height="1055"/>
|
||||
<value key="minSize" type="size" width="213" height="107"/>
|
||||
<view key="contentView" id="3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="480" height="376"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="10">
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="10">
|
||||
<rect key="frame" x="80" y="295" width="356" height="22"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" alignment="left" drawsBackground="YES" id="16">
|
||||
|
@ -39,7 +39,7 @@
|
|||
<outlet property="nextKeyView" destination="15" id="30"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="8">
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="8">
|
||||
<rect key="frame" x="17" y="297" width="58" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<textFieldCell key="cell" sendsActionOnEndEditing="YES" alignment="left" title="Subject:" id="18">
|
||||
|
@ -48,7 +48,7 @@
|
|||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="7">
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="7">
|
||||
<rect key="frame" x="17" y="262" width="66" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<textFieldCell key="cell" sendsActionOnEndEditing="YES" alignment="left" title="Message:" id="19">
|
||||
|
@ -60,11 +60,11 @@
|
|||
<scrollView fixedFrame="YES" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="11">
|
||||
<rect key="frame" x="20" y="55" width="440" height="199"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<clipView key="contentView" ambiguous="YES" drawsBackground="NO" id="tK9-bv-5OD">
|
||||
<clipView key="contentView" drawsBackground="NO" id="tK9-bv-5OD">
|
||||
<rect key="frame" x="1" y="1" width="438" height="197"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textView ambiguous="YES" importsGraphics="NO" verticallyResizable="YES" usesFontPanel="YES" findStyle="panel" continuousSpellChecking="YES" usesRuler="YES" smartInsertDelete="YES" id="15">
|
||||
<textView importsGraphics="NO" verticallyResizable="YES" usesFontPanel="YES" findStyle="panel" continuousSpellChecking="YES" usesRuler="YES" smartInsertDelete="YES" id="15">
|
||||
<rect key="frame" x="0.0" y="0.0" width="438" height="197"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
|
@ -115,7 +115,7 @@
|
|||
<rect key="frame" x="444" y="340" width="16" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</progressIndicator>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="5">
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="5">
|
||||
<rect key="frame" x="80" y="337" width="356" height="22"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" drawsBackground="YES" id="21">
|
||||
|
@ -127,7 +127,7 @@
|
|||
<outlet property="nextKeyView" destination="10" id="33"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="4">
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="4">
|
||||
<rect key="frame" x="17" y="339" width="71" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<textFieldCell key="cell" sendsActionOnEndEditing="YES" title="Email:" id="22">
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14313.18" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="22113.1" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14313.18"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22113.1"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
|
@ -32,10 +32,10 @@
|
|||
<rect key="frame" x="0.0" y="0.0" width="300" height="400"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<pathControl focusRingType="none" verticalHuggingPriority="750" allowsExpansionToolTips="YES" id="65">
|
||||
<rect key="frame" x="0.0" y="374" width="300" height="26"/>
|
||||
<pathControl focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" allowsExpansionToolTips="YES" translatesAutoresizingMaskIntoConstraints="NO" id="65">
|
||||
<rect key="frame" x="76" y="374" width="224" height="26"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<pathCell key="cell" selectable="YES" editable="YES" focusRingType="none" alignment="left" pathStyle="popUp" id="66">
|
||||
<pathCell key="cell" selectable="YES" enabled="NO" focusRingType="none" alignment="left" pathStyle="popUp" id="66">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="backgroundColor" name="windowBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</pathCell>
|
||||
|
@ -47,7 +47,7 @@
|
|||
</binding>
|
||||
</connections>
|
||||
</pathControl>
|
||||
<box boxType="custom" borderType="line" title="Box" titlePosition="noTitle" id="147">
|
||||
<box fixedFrame="YES" boxType="custom" borderType="line" title="Box" titlePosition="noTitle" translatesAutoresizingMaskIntoConstraints="NO" id="147">
|
||||
<rect key="frame" x="0.0" y="373" width="300" height="1"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<view key="contentView" id="Dg2-ay-LZH">
|
||||
|
@ -58,7 +58,7 @@
|
|||
<color key="fillColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
<font key="titleFont" metaFont="system"/>
|
||||
</box>
|
||||
<scrollView borderType="none" autohidesScrollers="YES" horizontalLineScroll="23" horizontalPageScroll="10" verticalLineScroll="23" verticalPageScroll="10" usesPredominantAxisScrolling="NO" id="64">
|
||||
<scrollView fixedFrame="YES" borderType="none" autohidesScrollers="YES" horizontalLineScroll="23" horizontalPageScroll="10" verticalLineScroll="23" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="64">
|
||||
<rect key="frame" x="0.0" y="0.0" width="300" height="373"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<clipView key="contentView" drawsBackground="NO" copiesOnScroll="NO" id="OYe-Aa-Spw">
|
||||
|
@ -67,14 +67,13 @@
|
|||
<subviews>
|
||||
<outlineView focusRingType="none" verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" selectionHighlightStyle="sourceList" columnReordering="NO" autosaveColumns="NO" autosaveName="FileTree" rowHeight="18" indentationPerLevel="14" autoresizesOutlineColumn="YES" outlineTableColumn="70" id="69" customClass="FileTreeOutlineView">
|
||||
<rect key="frame" x="0.0" y="0.0" width="300" height="373"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxY="YES"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<size key="intercellSpacing" width="3" height="5"/>
|
||||
<color key="backgroundColor" name="_sourceListBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
|
||||
<tableColumns>
|
||||
<tableColumn editable="NO" width="297" minWidth="16" maxWidth="1000" id="70">
|
||||
<tableColumn editable="NO" width="268" minWidth="16" maxWidth="1000" id="70">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" white="0.33333299" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</tableHeaderCell>
|
||||
|
@ -115,7 +114,19 @@
|
|||
<outlet property="nextKeyView" destination="69" id="104"/>
|
||||
</connections>
|
||||
</scrollView>
|
||||
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Tqu-Wl-fBM">
|
||||
<rect key="frame" x="-2" y="370" width="81" height="32"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="push" title="Choose" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="vRe-3U-Nxj">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="chooseRootFolder:" target="-2" id="aZ6-tK-wqz"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<point key="canvasLocation" x="-310" y="119"/>
|
||||
</customView>
|
||||
<customObject id="94" customClass="FileTreeController">
|
||||
<connections>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="20037" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="22113.1" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="20037"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22113.1"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
|
@ -13,18 +13,18 @@
|
|||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<window title="Info Inspector" separatorStyle="none" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" visibleAtLaunch="NO" frameAutosaveName="InfoInspector" animationBehavior="default" titlebarAppearsTransparent="YES" id="1" customClass="NSPanel">
|
||||
<window title="Info Inspector" separatorStyle="none" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" visibleAtLaunch="NO" animationBehavior="utilityWindow" frameAutosaveName="InfoInspector" titlebarAppearsTransparent="YES" id="1" customClass="NSPanel">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" resizable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/>
|
||||
<rect key="contentRect" x="700" y="80" width="300" height="582"/>
|
||||
<rect key="contentRect" x="700" y="80" width="300" height="604"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1920" height="1055"/>
|
||||
<value key="minSize" type="size" width="240" height="550"/>
|
||||
<value key="minSize" type="size" width="240" height="572"/>
|
||||
<value key="maxSize" type="size" width="400" height="600"/>
|
||||
<view key="contentView" id="2">
|
||||
<rect key="frame" x="0.0" y="0.0" width="300" height="582"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="300" height="604"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="9">
|
||||
<rect key="frame" x="60" y="526" width="45" height="14"/>
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="9">
|
||||
<rect key="frame" x="-2" y="548" width="107" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Artist:" id="10">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -32,8 +32,8 @@
|
|||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="11">
|
||||
<rect key="frame" x="62" y="504" width="43" height="14"/>
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="11">
|
||||
<rect key="frame" x="-2" y="526" width="107" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Album:" id="12">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -41,8 +41,8 @@
|
|||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="13">
|
||||
<rect key="frame" x="67" y="460" width="38" height="14"/>
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="13">
|
||||
<rect key="frame" x="-2" y="482" width="107" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Track:" id="14">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -50,8 +50,8 @@
|
|||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="15">
|
||||
<rect key="frame" x="60" y="438" width="45" height="14"/>
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="15">
|
||||
<rect key="frame" x="-2" y="460" width="107" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Length:" id="16">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -59,17 +59,17 @@
|
|||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="17">
|
||||
<rect key="frame" x="73" y="416" width="32" height="14"/>
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="17">
|
||||
<rect key="frame" x="-2" y="438" width="107" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Year:" id="18">
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Date:" id="18">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="19">
|
||||
<rect key="frame" x="65" y="394" width="40" height="14"/>
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="19">
|
||||
<rect key="frame" x="-2" y="416" width="107" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Genre:" id="20">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -77,8 +77,8 @@
|
|||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="21">
|
||||
<rect key="frame" x="32" y="350" width="73" height="14"/>
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="21">
|
||||
<rect key="frame" x="-2" y="372" width="107" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Sample Rate:" id="22">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -86,8 +86,8 @@
|
|||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="27">
|
||||
<rect key="frame" x="48" y="328" width="57" height="14"/>
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="27">
|
||||
<rect key="frame" x="-2" y="350" width="107" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Channels:" id="28">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -95,8 +95,8 @@
|
|||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="29">
|
||||
<rect key="frame" x="63" y="306" width="42" height="14"/>
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="29">
|
||||
<rect key="frame" x="-2" y="328" width="107" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Bitrate:" id="32">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -104,8 +104,8 @@
|
|||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="30">
|
||||
<rect key="frame" x="15" y="284" width="90" height="14"/>
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="30">
|
||||
<rect key="frame" x="-2" y="306" width="107" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Bits Per Sample:" id="31">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -113,8 +113,8 @@
|
|||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="23">
|
||||
<rect key="frame" x="73" y="482" width="32" height="14"/>
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="23">
|
||||
<rect key="frame" x="-2" y="504" width="107" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Title:" id="24">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -122,10 +122,10 @@
|
|||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="33" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="526" width="170" height="14"/>
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="33" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="548" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" sendsActionOnEndEditing="YES" title="N/A" id="34">
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="34">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
|
@ -134,10 +134,10 @@
|
|||
<binding destination="-2" name="value" keyPath="valueToDisplay.artist" id="108"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="35" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="504" width="170" height="14"/>
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="35" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="526" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" sendsActionOnEndEditing="YES" title="N/A" id="36">
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="36">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
|
@ -146,10 +146,10 @@
|
|||
<binding destination="-2" name="value" keyPath="valueToDisplay.album" id="109"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="37" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="482" width="170" height="14"/>
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="37" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="504" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" sendsActionOnEndEditing="YES" title="N/A" id="38">
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="38">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
|
@ -158,10 +158,10 @@
|
|||
<binding destination="-2" name="value" keyPath="valueToDisplay.title" id="110"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="39" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="460" width="170" height="14"/>
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="39" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="482" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" sendsActionOnEndEditing="YES" title="N/A" id="40">
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="40">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
|
@ -170,10 +170,10 @@
|
|||
<binding destination="-2" name="value" keyPath="valueToDisplay.trackText" id="ZO2-Cd-dfh"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="41" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="438" width="170" height="14"/>
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="41" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="460" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" sendsActionOnEndEditing="YES" title="N/A" id="42">
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="42">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
|
@ -182,22 +182,22 @@
|
|||
<binding destination="-2" name="value" keyPath="valueToDisplay.lengthText" id="ji7-tL-8rb"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="43" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="416" width="170" height="14"/>
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="43" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="438" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" sendsActionOnEndEditing="YES" title="N/A" id="44">
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="44">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<connections>
|
||||
<binding destination="-2" name="value" keyPath="valueToDisplay.yearText" id="miZ-gp-CqU"/>
|
||||
<binding destination="-2" name="value" keyPath="valueToDisplay.date" id="EUq-4E-Lz3"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="45" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="394" width="170" height="14"/>
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="45" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="416" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" sendsActionOnEndEditing="YES" title="N/A" id="46">
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="46">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
|
@ -206,22 +206,26 @@
|
|||
<binding destination="-2" name="value" keyPath="valueToDisplay.genre" id="114"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="49" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="350" width="170" height="14"/>
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="49" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="372" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" sendsActionOnEndEditing="YES" title="N/A" id="50">
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="50">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<connections>
|
||||
<binding destination="-2" name="value" keyPath="valueToDisplay.sampleRate" id="116"/>
|
||||
<binding destination="-2" name="value" keyPath="valueToDisplay.sampleRate" id="jhK-B4-LCu">
|
||||
<dictionary key="options">
|
||||
<string key="NSValueTransformerName">NumberHertzToStringTransformer</string>
|
||||
</dictionary>
|
||||
</binding>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="51" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="328" width="170" height="14"/>
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="51" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="350" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" sendsActionOnEndEditing="YES" title="N/A" id="52">
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="52">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
|
@ -230,10 +234,10 @@
|
|||
<binding destination="-2" name="value" keyPath="valueToDisplay.channels" id="117"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="53" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="306" width="170" height="14"/>
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="53" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="328" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" sendsActionOnEndEditing="YES" title="N/A" id="54">
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="54">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
|
@ -242,10 +246,10 @@
|
|||
<binding destination="-2" name="value" keyPath="valueToDisplay.bitrate" id="118"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="55" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="284" width="170" height="14"/>
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="55" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="306" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" sendsActionOnEndEditing="YES" title="N/A" id="56">
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="56">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
|
@ -254,8 +258,8 @@
|
|||
<binding destination="-2" name="value" keyPath="valueToDisplay.bitsPerSample" id="122"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="QPg-Mb-Urn">
|
||||
<rect key="frame" x="60" y="262" width="45" height="14"/>
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="QPg-Mb-Urn">
|
||||
<rect key="frame" x="-2" y="284" width="107" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Codec:" id="cbq-TT-CZX">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -263,10 +267,10 @@
|
|||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ijS-y2-eCZ" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="262" width="170" height="14"/>
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ijS-y2-eCZ" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="284" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" sendsActionOnEndEditing="YES" title="N/A" id="Yby-OU-cqP">
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="Yby-OU-cqP">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
|
@ -275,8 +279,8 @@
|
|||
<binding destination="-2" name="value" keyPath="valueToDisplay.codec" id="Tle-Vx-BN5"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="bti-s6-SIU">
|
||||
<rect key="frame" x="38" y="240" width="67" height="14"/>
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="bti-s6-SIU">
|
||||
<rect key="frame" x="-2" y="262" width="107" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Encoding:" id="8e7-lp-K5l">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -284,10 +288,10 @@
|
|||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="L4f-rE-CN3" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="240" width="170" height="14"/>
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="L4f-rE-CN3" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="262" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" sendsActionOnEndEditing="YES" title="N/A" id="v14-AG-B9D">
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="v14-AG-B9D">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
|
@ -296,8 +300,8 @@
|
|||
<binding destination="-2" name="value" keyPath="valueToDisplay.encoding" id="BWi-9r-yOR"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="gn9-9b-eV8">
|
||||
<rect key="frame" x="15" y="218" width="90" height="14"/>
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="gn9-9b-eV8">
|
||||
<rect key="frame" x="-2" y="240" width="107" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Cuesheet:" id="Cu5-ia-Z5b">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -305,10 +309,10 @@
|
|||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="WOl-SC-4tu" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="218" width="170" height="14"/>
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="WOl-SC-4tu" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="240" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" sendsActionOnEndEditing="YES" title="N/A" id="UyE-Mc-e39">
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="UyE-Mc-e39">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
|
@ -317,8 +321,8 @@
|
|||
<binding destination="-2" name="value" keyPath="valueToDisplay.cuesheetPresent" id="nPu-MP-igV"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="arA-Pj-ANg">
|
||||
<rect key="frame" x="15" y="196" width="90" height="14"/>
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="arA-Pj-ANg">
|
||||
<rect key="frame" x="-2" y="218" width="107" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="ReplayGain:" id="qBi-M8-kEx">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -326,10 +330,10 @@
|
|||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="2xx-It-i6I" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="196" width="170" height="14"/>
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="2xx-It-i6I" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="218" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" sendsActionOnEndEditing="YES" title="N/A" id="rAo-AP-lVa">
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="rAo-AP-lVa">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
|
@ -339,8 +343,8 @@
|
|||
<binding destination="-2" name="toolTip" keyPath="valueToDisplay.gainInfo" id="BUp-Hu-Szq"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="84">
|
||||
<rect key="frame" x="49" y="372" width="56" height="14"/>
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="84">
|
||||
<rect key="frame" x="-2" y="394" width="107" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Filename:" id="85">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -348,10 +352,10 @@
|
|||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="86" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="372" width="170" height="14"/>
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="86" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="394" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" sendsActionOnEndEditing="YES" title="N/A" id="87">
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="87">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
|
@ -360,22 +364,8 @@
|
|||
<binding destination="-2" name="value" keyPath="valueToDisplay.filename" id="115"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<imageView fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="95">
|
||||
<rect key="frame" x="17" y="17" width="266" height="138"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<imageCell key="cell" enabled="NO" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" id="96"/>
|
||||
<connections>
|
||||
<binding destination="-2" name="value" keyPath="valueToDisplay.albumArt" id="121">
|
||||
<dictionary key="options">
|
||||
<bool key="NSAllowsEditingMultipleValuesSelection" value="NO"/>
|
||||
<bool key="NSConditionallySetsEnabled" value="NO"/>
|
||||
<string key="NSValueTransformerName">MissingAlbumArtTransformer</string>
|
||||
</dictionary>
|
||||
</binding>
|
||||
</connections>
|
||||
</imageView>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="vB6-9J-5qg">
|
||||
<rect key="frame" x="18" y="548" width="87" height="14"/>
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="vB6-9J-5qg">
|
||||
<rect key="frame" x="-2" y="570" width="107" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Album Artist:" id="LFJ-QQ-gGr">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -383,10 +373,10 @@
|
|||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="cj0-Tw-xpq" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="548" width="170" height="14"/>
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="cj0-Tw-xpq" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="570" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" sendsActionOnEndEditing="YES" title="N/A" id="B8w-o8-ZBw">
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="B8w-o8-ZBw">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
|
@ -395,8 +385,8 @@
|
|||
<binding destination="-2" name="value" keyPath="valueToDisplay.albumartist" id="gTS-bf-rHT"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="FWx-Sc-Ocv">
|
||||
<rect key="frame" x="15" y="174" width="90" height="14"/>
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="FWx-Sc-Ocv">
|
||||
<rect key="frame" x="-2" y="196" width="107" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Play Count:" id="fiv-eh-w3c">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -404,10 +394,19 @@
|
|||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="WSs-wC-mWc" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="174" width="170" height="14"/>
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="cd3-Qt-hCm">
|
||||
<rect key="frame" x="-2" y="174" width="107" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Comment:" id="Ule-N3-dKW">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="WSs-wC-mWc" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="196" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" sendsActionOnEndEditing="YES" title="N/A" id="Ial-XI-91y">
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="Ial-XI-91y">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
|
@ -417,6 +416,33 @@
|
|||
<binding destination="-2" name="toolTip" keyPath="valueToDisplay.playCountInfo" id="ydF-ec-fBX"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Ef3-yG-qT1" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="174" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="PPV-dt-9Bp">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<connections>
|
||||
<binding destination="-2" name="value" keyPath="valueToDisplay.comment" id="Esa-PB-mpv"/>
|
||||
<binding destination="-2" name="toolTip" keyPath="valueToDisplay.comment" id="XzQ-nd-jMU"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="RWn-fb-0wT">
|
||||
<rect key="frame" x="0.0" y="20" width="300" height="146"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES"/>
|
||||
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" id="vwy-pi-T4L"/>
|
||||
<connections>
|
||||
<binding destination="-2" name="value" keyPath="valueToDisplay.albumArt" id="bmd-73-heg">
|
||||
<dictionary key="options">
|
||||
<bool key="NSAllowsEditingMultipleValuesSelection" value="NO"/>
|
||||
<bool key="NSConditionallySetsEnabled" value="NO"/>
|
||||
<string key="NSValueTransformerName">MissingAlbumArtTransformer</string>
|
||||
</dictionary>
|
||||
</binding>
|
||||
</connections>
|
||||
</imageView>
|
||||
</subviews>
|
||||
</view>
|
||||
<point key="canvasLocation" x="136" y="131"/>
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="22113.1" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22113.1"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="LyricsWindowController">
|
||||
<connections>
|
||||
<outlet property="window" destination="QvC-M9-y7g" id="eSJ-Nv-PBE"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<window title="Lyrics" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" visibleAtLaunch="NO" frameAutosaveName="LyricsWindow" animationBehavior="default" id="QvC-M9-y7g">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="196" y="240" width="480" height="270"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1920" height="1055"/>
|
||||
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="480" height="270"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<scrollView borderType="none" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" id="O8B-8Z-Mxc">
|
||||
<rect key="frame" x="0.0" y="0.0" width="480" height="270"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<clipView key="contentView" drawsBackground="NO" id="O6S-QV-ThM">
|
||||
<rect key="frame" x="0.0" y="0.0" width="480" height="270"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textView wantsLayer="YES" editable="NO" importsGraphics="NO" richText="NO" verticallyResizable="YES" findStyle="bar" incrementalSearchingEnabled="YES" id="DKA-ld-0Sh">
|
||||
<rect key="frame" x="0.0" y="0.0" width="480" height="270"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
<size key="minSize" width="480" height="270"/>
|
||||
<size key="maxSize" width="480" height="10000000"/>
|
||||
<color key="insertionPointColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
<connections>
|
||||
<binding destination="-2" name="value" keyPath="valueToDisplay.unsyncedlyrics" id="tSj-CA-G4Q">
|
||||
<dictionary key="options">
|
||||
<bool key="NSAllowsEditingMultipleValuesSelection" value="NO"/>
|
||||
<bool key="NSConditionallySetsEditable" value="NO"/>
|
||||
</dictionary>
|
||||
</binding>
|
||||
</connections>
|
||||
</textView>
|
||||
</subviews>
|
||||
</clipView>
|
||||
<scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="YES" id="f77-wI-xOz">
|
||||
<rect key="frame" x="-100" y="-100" width="225" height="15"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
<scroller key="verticalScroller" verticalHuggingPriority="750" horizontal="NO" id="XfW-du-B6L">
|
||||
<rect key="frame" x="464" y="0.0" width="16" height="270"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
</scrollView>
|
||||
</subviews>
|
||||
</view>
|
||||
<point key="canvasLocation" x="126" y="104"/>
|
||||
</window>
|
||||
<userDefaultsController representsSharedInstance="YES" id="t5R-DO-d90"/>
|
||||
</objects>
|
||||
</document>
|
|
@ -1,8 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="20037" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="22113.1" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="20037"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22113.1"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
|
@ -25,17 +25,17 @@
|
|||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<splitView dividerStyle="thin" vertical="YES" translatesAutoresizingMaskIntoConstraints="NO" id="2123">
|
||||
<rect key="frame" x="0.0" y="354" width="1015" height="46"/>
|
||||
<rect key="frame" x="0.0" y="372" width="1135" height="28"/>
|
||||
<subviews>
|
||||
<scrollView fixedFrame="YES" borderType="none" autohidesScrollers="YES" horizontalLineScroll="24" horizontalPageScroll="0.0" verticalLineScroll="24" verticalPageScroll="0.0" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" id="206" userLabel="Scroll View - Playlist View">
|
||||
<rect key="frame" x="0.0" y="0.0" width="1015" height="46"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="1135" height="28"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<clipView key="contentView" drawsBackground="NO" copiesOnScroll="NO" id="KWC-Ti-8KY">
|
||||
<rect key="frame" x="0.0" y="0.0" width="1015" height="46"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="1135" height="28"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<tableView focusRingType="none" verticalHuggingPriority="750" allowsExpansionToolTips="YES" alternatingRowBackgroundColors="YES" autosaveName="Playlist" rowHeight="18" headerView="1517" viewBased="YES" id="207" customClass="PlaylistView">
|
||||
<rect key="frame" x="0.0" y="0.0" width="1015" height="29"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="1135" height="24"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<size key="intercellSpacing" width="3" height="6"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
|
@ -57,7 +57,7 @@
|
|||
<rect key="frame" x="11" y="3" width="69" height="18"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="w5u-JQ-3Hf">
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="w5u-JQ-3Hf">
|
||||
<rect key="frame" x="0.0" y="1" width="69" height="16"/>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" alignment="right" title="Table View Cell" id="FMU-QZ-NdQ">
|
||||
<font key="font" usesAppearanceFont="YES"/>
|
||||
|
@ -102,7 +102,7 @@
|
|||
<rect key="frame" x="0.0" y="3" width="17" height="11"/>
|
||||
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" id="8rO-fU-Njw"/>
|
||||
</imageView>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" allowsExpansionToolTips="YES" translatesAutoresizingMaskIntoConstraints="NO" id="5cp-JI-ogI">
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" allowsExpansionToolTips="YES" translatesAutoresizingMaskIntoConstraints="NO" id="5cp-JI-ogI">
|
||||
<rect key="frame" x="23" y="1" width="4" height="16"/>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="wky-z8-Cj5">
|
||||
<font key="font" metaFont="system"/>
|
||||
|
@ -127,8 +127,8 @@
|
|||
</tableCellView>
|
||||
</prototypeCellViews>
|
||||
</tableColumn>
|
||||
<tableColumn identifier="title" editable="NO" width="125.5" minWidth="96" maxWidth="1024" id="208">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left" title="Title">
|
||||
<tableColumn identifier="rating" editable="NO" width="64" minWidth="48" maxWidth="128" id="208" userLabel="Rating">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left" title="Rating">
|
||||
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" white="0.33333299" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</tableHeaderCell>
|
||||
|
@ -141,11 +141,11 @@
|
|||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
<prototypeCellViews>
|
||||
<tableCellView id="ZCP-Dx-UBV">
|
||||
<rect key="frame" x="106" y="3" width="126" height="18"/>
|
||||
<rect key="frame" x="106" y="3" width="64" height="18"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="exY-Bg-Mjm">
|
||||
<rect key="frame" x="0.0" y="1" width="126" height="16"/>
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="exY-Bg-Mjm">
|
||||
<rect key="frame" x="0.0" y="1" width="64" height="16"/>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="sdo-Sm-KPH">
|
||||
<font key="font" usesAppearanceFont="YES"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
|
@ -171,6 +171,50 @@
|
|||
</binding>
|
||||
</connections>
|
||||
</tableColumn>
|
||||
<tableColumn identifier="title" editable="NO" width="126" minWidth="96" maxWidth="1024" id="XBr-ec-D81">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left" title="Title">
|
||||
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" white="0.33333299" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</tableHeaderCell>
|
||||
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" alignment="left" title="Text Cell" id="20m-a6-e24">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<sortDescriptor key="sortDescriptorPrototype" selector="caseInsensitiveCompare:" sortKey="title"/>
|
||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
<prototypeCellViews>
|
||||
<tableCellView id="ZHl-H1-IIC">
|
||||
<rect key="frame" x="173" y="3" width="126" height="18"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="dQP-wC-mba">
|
||||
<rect key="frame" x="0.0" y="1" width="126" height="16"/>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="VVx-99-roJ">
|
||||
<font key="font" usesAppearanceFont="YES"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="dQP-wC-mba" firstAttribute="leading" secondItem="ZHl-H1-IIC" secondAttribute="leading" constant="2" id="RRE-MM-Fc6"/>
|
||||
<constraint firstItem="dQP-wC-mba" firstAttribute="centerX" secondItem="ZHl-H1-IIC" secondAttribute="centerX" id="T3S-gq-Mcb"/>
|
||||
<constraint firstItem="dQP-wC-mba" firstAttribute="centerY" secondItem="ZHl-H1-IIC" secondAttribute="centerY" id="auz-Po-AgT"/>
|
||||
</constraints>
|
||||
<connections>
|
||||
<outlet property="textField" destination="dQP-wC-mba" id="slW-Fj-66g"/>
|
||||
</connections>
|
||||
</tableCellView>
|
||||
</prototypeCellViews>
|
||||
<connections>
|
||||
<binding destination="218" name="value" keyPath="arrangedObjects.title" id="Qr8-tD-lGt">
|
||||
<dictionary key="options">
|
||||
<bool key="NSConditionallySetsEditable" value="YES"/>
|
||||
</dictionary>
|
||||
</binding>
|
||||
</connections>
|
||||
</tableColumn>
|
||||
<tableColumn identifier="albumartist" editable="NO" width="150" minWidth="96" maxWidth="1024" hidden="YES" id="yGV-gP-Wl6">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left" title="Album Artist">
|
||||
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
|
||||
|
@ -187,8 +231,8 @@
|
|||
<rect key="frame" x="1" y="3" width="0.0" height="18"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="ZWb-jm-i9i">
|
||||
<rect key="frame" x="1" y="1" width="4" height="16"/>
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="ZWb-jm-i9i">
|
||||
<rect key="frame" x="0.0" y="1" width="4" height="16"/>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="3QN-Ok-QPu">
|
||||
<font key="font" usesAppearanceFont="YES"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
|
@ -215,7 +259,7 @@
|
|||
<binding destination="1689" name="fontSize" keyPath="values.fontSize" id="dJs-UO-m5r"/>
|
||||
</connections>
|
||||
</tableColumn>
|
||||
<tableColumn identifier="artist" editable="NO" width="149.5" minWidth="96" maxWidth="1024" id="391">
|
||||
<tableColumn identifier="artist" editable="NO" width="150" minWidth="96" maxWidth="1024" id="391">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left" title="Artist">
|
||||
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
|
||||
|
@ -229,10 +273,10 @@
|
|||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
<prototypeCellViews>
|
||||
<tableCellView id="gpC-Oe-Rog">
|
||||
<rect key="frame" x="235" y="3" width="149" height="18"/>
|
||||
<rect key="frame" x="302" y="3" width="150" height="18"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="1WK-qN-Mgj">
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="1WK-qN-Mgj">
|
||||
<rect key="frame" x="0.0" y="1" width="150" height="16"/>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="71l-3L-S3g">
|
||||
<font key="font" usesAppearanceFont="YES"/>
|
||||
|
@ -259,7 +303,7 @@
|
|||
</binding>
|
||||
</connections>
|
||||
</tableColumn>
|
||||
<tableColumn identifier="album" editable="NO" width="150.5" minWidth="96" maxWidth="1024" id="806">
|
||||
<tableColumn identifier="album" editable="NO" width="151" minWidth="96" maxWidth="1024" id="806">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left" title="Album">
|
||||
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
|
||||
|
@ -273,10 +317,10 @@
|
|||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
<prototypeCellViews>
|
||||
<tableCellView id="1ed-gX-bct">
|
||||
<rect key="frame" x="387" y="3" width="151" height="18"/>
|
||||
<rect key="frame" x="455" y="3" width="151" height="18"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="nEt-s5-vRX">
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="nEt-s5-vRX">
|
||||
<rect key="frame" x="0.0" y="1" width="151" height="16"/>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="moV-3G-GpB">
|
||||
<font key="font" usesAppearanceFont="YES"/>
|
||||
|
@ -303,7 +347,7 @@
|
|||
</binding>
|
||||
</connections>
|
||||
</tableColumn>
|
||||
<tableColumn identifier="length" editable="NO" width="95.5" minWidth="43.62012" maxWidth="96" id="807">
|
||||
<tableColumn identifier="length" editable="NO" width="96" minWidth="43.62012" maxWidth="96" id="807">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="right" title="Length">
|
||||
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
|
||||
|
@ -316,10 +360,10 @@
|
|||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
<prototypeCellViews>
|
||||
<tableCellView id="hhB-nv-e78">
|
||||
<rect key="frame" x="541" y="3" width="95" height="18"/>
|
||||
<rect key="frame" x="609" y="3" width="96" height="18"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="tHy-sM-HDB">
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="tHy-sM-HDB">
|
||||
<rect key="frame" x="0.0" y="1" width="96" height="16"/>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="Igo-5f-yim">
|
||||
<font key="font" usesAppearanceFont="YES"/>
|
||||
|
@ -347,7 +391,7 @@
|
|||
<binding destination="1689" name="fontSize" keyPath="values.fontSize" id="1919"/>
|
||||
</connections>
|
||||
</tableColumn>
|
||||
<tableColumn identifier="year" editable="NO" width="93.5" minWidth="42" maxWidth="96" id="848">
|
||||
<tableColumn identifier="year" editable="NO" width="94" minWidth="42" maxWidth="96" id="848">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="right" title="Year">
|
||||
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
|
||||
|
@ -360,10 +404,10 @@
|
|||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
<prototypeCellViews>
|
||||
<tableCellView id="q93-oh-i5T">
|
||||
<rect key="frame" x="639" y="3" width="94" height="18"/>
|
||||
<rect key="frame" x="708" y="3" width="94" height="18"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="bOi-LI-TDx">
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="bOi-LI-TDx">
|
||||
<rect key="frame" x="0.0" y="1" width="94" height="16"/>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="C2Q-qG-dwX">
|
||||
<font key="font" usesAppearanceFont="YES"/>
|
||||
|
@ -387,7 +431,7 @@
|
|||
<binding destination="1689" name="fontSize" keyPath="values.fontSize" id="1921"/>
|
||||
</connections>
|
||||
</tableColumn>
|
||||
<tableColumn identifier="genre" editable="NO" width="144" minWidth="96" maxWidth="512" id="849">
|
||||
<tableColumn identifier="genre" editable="NO" width="64" minWidth="32" maxWidth="512" id="849">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left" title="Genre">
|
||||
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
|
||||
|
@ -401,11 +445,11 @@
|
|||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
<prototypeCellViews>
|
||||
<tableCellView id="rRl-p9-Awr">
|
||||
<rect key="frame" x="736" y="3" width="144" height="18"/>
|
||||
<rect key="frame" x="805" y="3" width="64" height="18"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="yW6-2w-6mN">
|
||||
<rect key="frame" x="0.0" y="1" width="144" height="16"/>
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="yW6-2w-6mN">
|
||||
<rect key="frame" x="0.0" y="1" width="64" height="16"/>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="js2-sT-U4M">
|
||||
<font key="font" usesAppearanceFont="YES"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
|
@ -428,7 +472,7 @@
|
|||
<binding destination="1689" name="fontSize" keyPath="values.fontSize" id="1922"/>
|
||||
</connections>
|
||||
</tableColumn>
|
||||
<tableColumn identifier="track" editable="NO" width="34.5" minWidth="24" maxWidth="72" id="850">
|
||||
<tableColumn identifier="track" editable="NO" width="35" minWidth="24" maxWidth="72" id="850">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="right" title="№">
|
||||
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
|
||||
|
@ -441,10 +485,10 @@
|
|||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
<prototypeCellViews>
|
||||
<tableCellView id="hgh-VE-5kl">
|
||||
<rect key="frame" x="883" y="3" width="38" height="18"/>
|
||||
<rect key="frame" x="872" y="3" width="39" height="18"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="yEY-MI-d3o">
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="yEY-MI-d3o">
|
||||
<rect key="frame" x="0.0" y="1" width="39" height="16"/>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="tus-lr-RhS">
|
||||
<font key="font" usesAppearanceFont="YES"/>
|
||||
|
@ -484,8 +528,8 @@
|
|||
<rect key="frame" x="1" y="3" width="0.0" height="18"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="QFJ-4l-2O6">
|
||||
<rect key="frame" x="1" y="1" width="4" height="16"/>
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="QFJ-4l-2O6">
|
||||
<rect key="frame" x="0.0" y="1" width="4" height="16"/>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="gKK-cS-RP5">
|
||||
<font key="font" usesAppearanceFont="YES"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
|
@ -528,8 +572,8 @@
|
|||
<rect key="frame" x="1" y="3" width="0.0" height="18"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="Qvd-sk-vRc">
|
||||
<rect key="frame" x="1" y="1" width="4" height="16"/>
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="Qvd-sk-vRc">
|
||||
<rect key="frame" x="0.0" y="1" width="4" height="16"/>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="YwT-z9-2d2">
|
||||
<font key="font" usesAppearanceFont="YES"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
|
@ -572,8 +616,8 @@
|
|||
<rect key="frame" x="1" y="3" width="0.0" height="18"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="gXW-DX-EsQ">
|
||||
<rect key="frame" x="1" y="1" width="4" height="16"/>
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="gXW-DX-EsQ">
|
||||
<rect key="frame" x="0.0" y="1" width="4" height="16"/>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="vaJ-Bc-ebE">
|
||||
<font key="font" usesAppearanceFont="YES"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
|
@ -600,6 +644,94 @@
|
|||
<binding destination="1689" name="fontSize" keyPath="values.fontSize" id="VQf-MC-VR1"/>
|
||||
</connections>
|
||||
</tableColumn>
|
||||
<tableColumn identifier="samplerate" editable="NO" width="64" minWidth="32" maxWidth="1024" hidden="YES" id="et9-zH-awt" userLabel="Sample Rate">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left" title="Sample Rate">
|
||||
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
|
||||
</tableHeaderCell>
|
||||
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" alignment="left" title="Text Cell" id="vAq-NH-dpx">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
<prototypeCellViews>
|
||||
<tableCellView id="gxz-eZ-ydV">
|
||||
<rect key="frame" x="1" y="3" width="0.0" height="18"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="3V9-dD-ecy">
|
||||
<rect key="frame" x="0.0" y="1" width="4" height="16"/>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="2ha-ys-sRi">
|
||||
<font key="font" usesAppearanceFont="YES"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="3V9-dD-ecy" firstAttribute="leading" secondItem="gxz-eZ-ydV" secondAttribute="leading" constant="2" id="dba-hi-HeI"/>
|
||||
<constraint firstAttribute="trailing" secondItem="3V9-dD-ecy" secondAttribute="trailing" constant="-2" id="gKV-3b-yxd"/>
|
||||
<constraint firstItem="3V9-dD-ecy" firstAttribute="centerY" secondItem="gxz-eZ-ydV" secondAttribute="centerY" id="y5e-tN-ftx"/>
|
||||
</constraints>
|
||||
<connections>
|
||||
<outlet property="textField" destination="3V9-dD-ecy" id="4vK-XL-Fur"/>
|
||||
</connections>
|
||||
</tableCellView>
|
||||
</prototypeCellViews>
|
||||
<connections>
|
||||
<binding destination="218" name="value" keyPath="arrangedObjects.sampleRate" id="5lo-d8-LCy">
|
||||
<dictionary key="options">
|
||||
<bool key="NSConditionallySetsEditable" value="YES"/>
|
||||
</dictionary>
|
||||
</binding>
|
||||
<binding destination="1689" name="fontSize" keyPath="values.fontSize" id="36z-Tr-B4G"/>
|
||||
</connections>
|
||||
</tableColumn>
|
||||
<tableColumn identifier="bitspersample" editable="NO" width="64" minWidth="32" maxWidth="1024" hidden="YES" id="3no-xo-TpJ" userLabel="Bits Per Sample">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left" title="Bits Per Sample">
|
||||
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
|
||||
</tableHeaderCell>
|
||||
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" alignment="left" title="Text Cell" id="ols-kE-jvg">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
<prototypeCellViews>
|
||||
<tableCellView id="Shd-2v-psn">
|
||||
<rect key="frame" x="1" y="3" width="0.0" height="18"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="1rT-Fe-Pho">
|
||||
<rect key="frame" x="0.0" y="1" width="4" height="16"/>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="DdU-ro-7Yi">
|
||||
<font key="font" usesAppearanceFont="YES"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" secondItem="1rT-Fe-Pho" secondAttribute="trailing" constant="-2" id="ehR-9P-yhp"/>
|
||||
<constraint firstItem="1rT-Fe-Pho" firstAttribute="leading" secondItem="Shd-2v-psn" secondAttribute="leading" constant="2" id="jGH-kM-uJY"/>
|
||||
<constraint firstItem="1rT-Fe-Pho" firstAttribute="centerY" secondItem="Shd-2v-psn" secondAttribute="centerY" id="kcA-6X-Nhf"/>
|
||||
</constraints>
|
||||
<connections>
|
||||
<outlet property="textField" destination="1rT-Fe-Pho" id="8gM-l3-O0k"/>
|
||||
</connections>
|
||||
</tableCellView>
|
||||
</prototypeCellViews>
|
||||
<connections>
|
||||
<binding destination="218" name="value" keyPath="arrangedObjects.bitsPerSample" id="qVg-Ph-8TQ">
|
||||
<dictionary key="options">
|
||||
<bool key="NSConditionallySetsEditable" value="YES"/>
|
||||
</dictionary>
|
||||
</binding>
|
||||
<binding destination="1689" name="fontSize" keyPath="values.fontSize" id="OsB-l4-dFb"/>
|
||||
</connections>
|
||||
</tableColumn>
|
||||
</tableColumns>
|
||||
<connections>
|
||||
<binding destination="1689" name="rowHeight" keyPath="values.fontSize" id="1927">
|
||||
|
@ -628,7 +760,7 @@
|
|||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
<tableHeaderView key="headerView" wantsLayer="YES" id="1517">
|
||||
<rect key="frame" x="0.0" y="0.0" width="1015" height="17"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="1135" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</tableHeaderView>
|
||||
</scrollView>
|
||||
|
@ -640,8 +772,8 @@
|
|||
<outlet property="delegate" destination="2172" id="2182"/>
|
||||
</connections>
|
||||
</splitView>
|
||||
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="778">
|
||||
<rect key="frame" x="377" y="4" width="261" height="14"/>
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="778">
|
||||
<rect key="frame" x="437" y="4" width="261" height="14"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" alignment="center" title="Total Duration: 00 hours 00 minutes 00 seconds" bezelStyle="round" id="1473">
|
||||
<font key="font" metaFont="controlContent" size="11"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
|
@ -703,7 +835,7 @@
|
|||
</toolbarItem>
|
||||
<toolbarItem implicitItemIdentifier="0D05748D-3258-44F5-9D1C-CBF211C15E2D" label="Search" paletteLabel="Search" sizingBehavior="auto" id="1533" customClass="NSSearchToolbarItem">
|
||||
<nil key="toolTip"/>
|
||||
<searchField key="view" wantsLayer="YES" verticalHuggingPriority="750" id="1531">
|
||||
<searchField key="view" wantsLayer="YES" focusRingType="none" verticalHuggingPriority="750" id="1531">
|
||||
<rect key="frame" x="0.0" y="14" width="79" height="22"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<searchFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" borderStyle="bezel" placeholderString="All" bezelStyle="round" recentsAutosaveName="CogFilter" id="1532">
|
||||
|
@ -768,7 +900,7 @@
|
|||
</toolbarItem>
|
||||
<toolbarItem implicitItemIdentifier="B042D8A5-AFF4-43B2-9DFB-E87A09B7F861" label="Current Time" paletteLabel="Current Time" visibilityPriority="5" sizingBehavior="auto" id="1568">
|
||||
<nil key="toolTip"/>
|
||||
<textField key="view" verticalHuggingPriority="750" id="1566" customClass="TimeField">
|
||||
<textField key="view" focusRingType="none" verticalHuggingPriority="750" id="1566" customClass="TimeField">
|
||||
<rect key="frame" x="15" y="14" width="47" height="19"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" borderStyle="bezel" alignment="center" title="0:00" usesSingleLineMode="YES" bezelStyle="round" id="1567">
|
||||
|
@ -942,16 +1074,22 @@
|
|||
<nil key="toolTip"/>
|
||||
<size key="minSize" width="64" height="26"/>
|
||||
<size key="maxSize" width="64" height="26"/>
|
||||
<customView key="view" id="lVy-2P-05b" customClass="SpectrumView">
|
||||
<customView key="view" id="lVy-2P-05b" customClass="SpectrumViewCG">
|
||||
<rect key="frame" x="0.0" y="14" width="64" height="26"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</customView>
|
||||
<connections>
|
||||
<outlet property="playbackController" destination="705" id="FPc-ek-NRd"/>
|
||||
</connections>
|
||||
</toolbarItem>
|
||||
</allowedToolbarItems>
|
||||
<defaultToolbarItems>
|
||||
<toolbarItem reference="ZH9-ZU-skw"/>
|
||||
<toolbarItem reference="1539"/>
|
||||
<toolbarItem reference="1610"/>
|
||||
<toolbarItem reference="1552"/>
|
||||
<toolbarItem reference="1568"/>
|
||||
<toolbarItem reference="1551"/>
|
||||
<toolbarItem reference="1636"/>
|
||||
<toolbarItem reference="1639"/>
|
||||
<toolbarItem reference="2466"/>
|
||||
|
@ -960,9 +1098,6 @@
|
|||
<toolbarItem reference="1629"/>
|
||||
<toolbarItem reference="1529"/>
|
||||
<toolbarItem reference="NtB-XF-g07"/>
|
||||
<toolbarItem reference="1568"/>
|
||||
<toolbarItem reference="1551"/>
|
||||
<toolbarItem reference="1610"/>
|
||||
<toolbarItem reference="1552"/>
|
||||
<toolbarItem reference="1533"/>
|
||||
</defaultToolbarItems>
|
||||
|
@ -1035,7 +1170,7 @@
|
|||
</toolbarItem>
|
||||
<toolbarItem implicitItemIdentifier="C0FF70A3-EE67-43F6-9956-95B89425CF0E" label="Current Time" paletteLabel="Current Time" visibilityPriority="5" sizingBehavior="auto" id="2274">
|
||||
<nil key="toolTip"/>
|
||||
<textField key="view" verticalHuggingPriority="750" id="2291" customClass="TimeField">
|
||||
<textField key="view" focusRingType="none" verticalHuggingPriority="750" id="2291" customClass="TimeField">
|
||||
<rect key="frame" x="15" y="14" width="47" height="19"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" borderStyle="bezel" alignment="center" title="0:00" usesSingleLineMode="YES" bezelStyle="round" id="2292">
|
||||
|
@ -1198,10 +1333,13 @@
|
|||
<nil key="toolTip"/>
|
||||
<size key="minSize" width="64" height="26"/>
|
||||
<size key="maxSize" width="64" height="26"/>
|
||||
<customView key="view" id="gyu-hn-EUs" customClass="SpectrumView">
|
||||
<customView key="view" id="gyu-hn-EUs" customClass="SpectrumViewCG">
|
||||
<rect key="frame" x="0.0" y="14" width="64" height="26"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</customView>
|
||||
<connections>
|
||||
<outlet property="playbackController" destination="705" id="GWc-77-WHY"/>
|
||||
</connections>
|
||||
</toolbarItem>
|
||||
</allowedToolbarItems>
|
||||
<defaultToolbarItems>
|
||||
|
@ -1242,9 +1380,21 @@
|
|||
<action selector="showWindow:" target="Hd4-Wy-Rfl" id="xfd-8T-SL4"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Check for Updates..." id="302">
|
||||
<connections>
|
||||
<action selector="checkForUpdates:" target="226" id="jEY-i9-qwZ"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="1100">
|
||||
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
|
||||
</menuItem>
|
||||
<menuItem title="Privacy Policy..." id="nb7-FW-egH">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="privacyPolicy:" target="226" id="c3c-kH-5sf"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="0ig-xg-gkg"/>
|
||||
<menuItem title="Donate" id="751">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Donate" id="kue-p2-G0Y">
|
||||
|
@ -1425,6 +1575,11 @@
|
|||
<action selector="toggleWindow:" target="2422" id="2454"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Show Lyrics" keyEquivalent="L" id="Tkb-7M-OiA">
|
||||
<connections>
|
||||
<action selector="toggleWindow:" target="3y4-Q0-Kkl" id="0kv-17-aS6"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Show Spotlight Panel" keyEquivalent="F" id="1853">
|
||||
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
|
||||
<connections>
|
||||
|
@ -2002,6 +2157,7 @@ Gw
|
|||
</declaredKeys>
|
||||
<classReference key="objectClass" className="PlaylistEntry"/>
|
||||
<connections>
|
||||
<outlet property="appController" destination="226" id="lmS-HT-dqn"/>
|
||||
<outlet property="playbackController" destination="705" id="2121"/>
|
||||
<outlet property="playlistLoader" destination="1319" id="1321"/>
|
||||
<outlet property="spotlightWindowController" destination="1675" id="1709"/>
|
||||
|
@ -2036,6 +2192,7 @@ Gw
|
|||
<outlet property="playlistController" destination="218" id="236"/>
|
||||
<outlet property="playlistLoader" destination="1319" id="1322"/>
|
||||
<outlet property="playlistView" destination="207" id="1257"/>
|
||||
<outlet property="preferencesController" destination="1217" id="cps-Ql-RlL"/>
|
||||
<outlet property="randomizeButton" destination="2467" id="swo-wn-Yr8"/>
|
||||
<outlet property="repeatButton" destination="1640" id="twI-AO-RJG"/>
|
||||
<outlet property="showAlbumColumn" destination="1340" id="1350"/>
|
||||
|
@ -2170,13 +2327,8 @@ Gw
|
|||
<menuItem title="Add to Queue" id="1893">
|
||||
<connections>
|
||||
<action selector="toggleQueued:" target="218" id="1934"/>
|
||||
<binding destination="2020" name="enabled" keyPath="selection" id="2068">
|
||||
<binding destination="2020" name="enabled" keyPath="selection" id="tl0-j5-Pok">
|
||||
<dictionary key="options">
|
||||
<integer key="NSMultipleValuesPlaceholder" value="0"/>
|
||||
<integer key="NSNoSelectionPlaceholder" value="0"/>
|
||||
<integer key="NSNotApplicablePlaceholder" value="0"/>
|
||||
<integer key="NSNullPlaceholder" value="0"/>
|
||||
<integer key="NSRaisesForNotApplicableKeys" value="1"/>
|
||||
<string key="NSValueTransformerName">NSIsNotNil</string>
|
||||
</dictionary>
|
||||
</binding>
|
||||
|
@ -2196,6 +2348,11 @@ Gw
|
|||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="stopAfterSelection:" target="218" id="ZRy-Mw-4cY"/>
|
||||
<binding destination="2020" name="enabled" keyPath="selection" id="ytG-h2-5MA">
|
||||
<dictionary key="options">
|
||||
<string key="NSValueTransformerName">NSIsNotNil</string>
|
||||
</dictionary>
|
||||
</binding>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="2047"/>
|
||||
|
@ -2203,12 +2360,8 @@ Gw
|
|||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="searchByArtist:" target="218" id="2118"/>
|
||||
<binding destination="2020" name="enabled" keyPath="selection.artist" id="2066">
|
||||
<binding destination="2020" name="enabled" keyPath="selection.artist" id="gQw-pa-TIE">
|
||||
<dictionary key="options">
|
||||
<integer key="NSMultipleValuesPlaceholder" value="0"/>
|
||||
<integer key="NSNoSelectionPlaceholder" value="0"/>
|
||||
<integer key="NSNotApplicablePlaceholder" value="0"/>
|
||||
<integer key="NSNullPlaceholder" value="0"/>
|
||||
<string key="NSValueTransformerName">NSIsNotNil</string>
|
||||
</dictionary>
|
||||
</binding>
|
||||
|
@ -2218,12 +2371,8 @@ Gw
|
|||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="searchByAlbum:" target="218" id="2119"/>
|
||||
<binding destination="2020" name="enabled" keyPath="selection.album" id="2067">
|
||||
<binding destination="2020" name="enabled" keyPath="selection.album" id="F1G-2E-nhM">
|
||||
<dictionary key="options">
|
||||
<integer key="NSMultipleValuesPlaceholder" value="0"/>
|
||||
<integer key="NSNoSelectionPlaceholder" value="0"/>
|
||||
<integer key="NSNotApplicablePlaceholder" value="0"/>
|
||||
<integer key="NSNullPlaceholder" value="0"/>
|
||||
<string key="NSValueTransformerName">NSIsNotNil</string>
|
||||
</dictionary>
|
||||
</binding>
|
||||
|
@ -2237,7 +2386,29 @@ Gw
|
|||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="reloadTags:" target="218" id="Ghx-F8-uF3"/>
|
||||
<binding destination="2020" name="enabled" keyPath="selection" id="DmN-2M-3dY">
|
||||
<binding destination="2020" name="enabled" keyPath="selection" id="khw-aB-pyx">
|
||||
<dictionary key="options">
|
||||
<string key="NSValueTransformerName">NSIsNotNil</string>
|
||||
</dictionary>
|
||||
</binding>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Reset Play Count" id="5iV-dM-oX2">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="resetPlaycounts:" target="218" id="J9t-hL-TQZ"/>
|
||||
<binding destination="2020" name="enabled" keyPath="selection" id="Ixw-08-sA9">
|
||||
<dictionary key="options">
|
||||
<string key="NSValueTransformerName">NSIsNotNil</string>
|
||||
</dictionary>
|
||||
</binding>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Remove Rating" id="PyI-lF-iuz">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="removeRatings:" target="218" id="f0r-ur-bMZ"/>
|
||||
<binding destination="2020" name="enabled" keyPath="selection" id="BNy-Ih-BJg">
|
||||
<dictionary key="options">
|
||||
<string key="NSValueTransformerName">NSIsNotNil</string>
|
||||
</dictionary>
|
||||
|
@ -2247,12 +2418,8 @@ Gw
|
|||
<menuItem title="Show in Finder" id="1064">
|
||||
<connections>
|
||||
<action selector="showEntryInFinder:" target="218" id="1345"/>
|
||||
<binding destination="2020" name="enabled" keyPath="selection" id="2069">
|
||||
<binding destination="2020" name="enabled" keyPath="selection" id="sJT-oT-8Cw">
|
||||
<dictionary key="options">
|
||||
<integer key="NSMultipleValuesPlaceholder" value="0"/>
|
||||
<integer key="NSNoSelectionPlaceholder" value="0"/>
|
||||
<integer key="NSNotApplicablePlaceholder" value="0"/>
|
||||
<integer key="NSNullPlaceholder" value="0"/>
|
||||
<string key="NSValueTransformerName">NSIsNotNil</string>
|
||||
</dictionary>
|
||||
</binding>
|
||||
|
@ -2262,12 +2429,8 @@ Gw
|
|||
<menuItem title="Remove" id="1360">
|
||||
<connections>
|
||||
<action selector="remove:" target="218" id="1361"/>
|
||||
<binding destination="2020" name="enabled" keyPath="selection" id="2070">
|
||||
<binding destination="2020" name="enabled" keyPath="selection" id="Z62-ts-HSG">
|
||||
<dictionary key="options">
|
||||
<integer key="NSMultipleValuesPlaceholder" value="0"/>
|
||||
<integer key="NSNoSelectionPlaceholder" value="0"/>
|
||||
<integer key="NSNotApplicablePlaceholder" value="0"/>
|
||||
<integer key="NSNullPlaceholder" value="0"/>
|
||||
<string key="NSValueTransformerName">NSIsNotNil</string>
|
||||
</dictionary>
|
||||
</binding>
|
||||
|
@ -2277,12 +2440,8 @@ Gw
|
|||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="trash:" target="218" id="7Vd-Vf-Ym1"/>
|
||||
<binding destination="2020" name="enabled" keyPath="selection" id="pGj-Wj-1bF">
|
||||
<binding destination="2020" name="enabled" keyPath="selection" id="Knx-iC-hoo">
|
||||
<dictionary key="options">
|
||||
<integer key="NSMultipleValuesPlaceholder" value="0"/>
|
||||
<integer key="NSNoSelectionPlaceholder" value="0"/>
|
||||
<integer key="NSNotApplicablePlaceholder" value="0"/>
|
||||
<integer key="NSNullPlaceholder" value="0"/>
|
||||
<string key="NSValueTransformerName">NSIsNotNil</string>
|
||||
</dictionary>
|
||||
</binding>
|
||||
|
@ -2293,12 +2452,8 @@ Gw
|
|||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="showWindow:" target="2422" id="lET-Cq-KED"/>
|
||||
<binding destination="2020" name="enabled" keyPath="selection" id="Ff1-eV-AGk">
|
||||
<binding destination="2020" name="enabled" keyPath="selection" id="pj1-yp-Ojr">
|
||||
<dictionary key="options">
|
||||
<integer key="NSMultipleValuesPlaceholder" value="0"/>
|
||||
<integer key="NSNoSelectionPlaceholder" value="0"/>
|
||||
<integer key="NSNotApplicablePlaceholder" value="0"/>
|
||||
<integer key="NSNullPlaceholder" value="0"/>
|
||||
<string key="NSValueTransformerName">NSIsNotNil</string>
|
||||
</dictionary>
|
||||
</binding>
|
||||
|
@ -2398,6 +2553,13 @@ Gw
|
|||
</connections>
|
||||
</customObject>
|
||||
<customObject id="Hd4-Wy-Rfl" customClass="AboutWindowController" customModule="Cog" customModuleProvider="target"/>
|
||||
<customObject id="3y4-Q0-Kkl" customClass="LyricsWindowController">
|
||||
<connections>
|
||||
<outlet property="appController" destination="226" id="tbl-VH-JHF"/>
|
||||
<outlet property="currentEntryController" destination="1897" id="5k1-yb-UNo"/>
|
||||
<outlet property="playlistSelectionController" destination="2020" id="eLM-rx-mpe"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="deadItemsTemplate" width="20" height="20"/>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="19529" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="22113.1" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="19529"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22113.1"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
|
@ -24,7 +24,7 @@
|
|||
<rect key="frame" x="0.0" y="0.0" width="506" height="100"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<button verticalHuggingPriority="750" fixedFrame="YES" tag="1" translatesAutoresizingMaskIntoConstraints="NO" id="8">
|
||||
<button tag="1" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="8">
|
||||
<rect key="frame" x="408" y="12" width="84" height="32"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<buttonCell key="cell" type="push" title="OK" bezelStyle="rounded" alignment="center" borderStyle="border" tag="1" inset="2" id="23">
|
||||
|
@ -52,7 +52,7 @@ Gw
|
|||
<action selector="doOpenURL:" target="-2" id="15"/>
|
||||
</connections>
|
||||
</button>
|
||||
<comboBox verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="13">
|
||||
<comboBox focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="13">
|
||||
<rect key="frame" x="20" y="56" width="469" height="26"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<comboBoxCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" borderStyle="bezel" alignment="left" drawsBackground="YES" numberOfVisibleItems="5" id="25">
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="20037" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="22113.1" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="20037"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22113.1"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
|
@ -1,8 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="20037" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="22113.1" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="20037"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22113.1"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
|
@ -45,17 +45,17 @@ DQ
|
|||
<rect key="frame" x="20" y="44" width="440" height="228"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<clipView key="contentView" drawsBackground="NO" copiesOnScroll="NO" id="zfU-bI-FkO">
|
||||
<rect key="frame" x="1" y="1" width="423" height="226"/>
|
||||
<rect key="frame" x="1" y="1" width="438" height="226"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<tableView focusRingType="none" verticalHuggingPriority="750" allowsExpansionToolTips="YES" alternatingRowBackgroundColors="YES" autosaveName="CogSpotlightPlaylist" headerView="25" id="28" customClass="PlaylistView">
|
||||
<rect key="frame" x="0.0" y="0.0" width="447" height="203"/>
|
||||
<tableView focusRingType="none" verticalHuggingPriority="750" allowsExpansionToolTips="YES" alternatingRowBackgroundColors="YES" autosaveName="CogSpotlightPlaylist" rowSizeStyle="automatic" headerView="25" viewBased="YES" id="28" customClass="PlaylistView">
|
||||
<rect key="frame" x="0.0" y="0.0" width="438" height="203"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<size key="intercellSpacing" width="3" height="2"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
|
||||
<tableColumns>
|
||||
<tableColumn identifier="title" editable="NO" width="129" minWidth="41" maxWidth="1000" id="36">
|
||||
<tableColumn identifier="title" editable="NO" width="126" minWidth="41" maxWidth="1000" id="36">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left" title="Title">
|
||||
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" white="0.33333299" alpha="1" colorSpace="calibratedWhite"/>
|
||||
|
@ -67,6 +67,26 @@ DQ
|
|||
</textFieldCell>
|
||||
<sortDescriptor key="sortDescriptorPrototype" selector="caseInsensitiveCompare:" sortKey="title"/>
|
||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
<prototypeCellViews>
|
||||
<tableCellView id="vIL-tT-nsx">
|
||||
<rect key="frame" x="1" y="1" width="131" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="dow-05-P5d">
|
||||
<rect key="frame" x="0.0" y="1" width="131" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="nbX-Qx-ta8">
|
||||
<font key="font" usesAppearanceFont="YES"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
<connections>
|
||||
<outlet property="textField" destination="dow-05-P5d" id="dKF-cf-DXu"/>
|
||||
</connections>
|
||||
</tableCellView>
|
||||
</prototypeCellViews>
|
||||
<connections>
|
||||
<binding destination="16" name="value" keyPath="arrangedObjects.title" id="93">
|
||||
<dictionary key="options">
|
||||
|
@ -76,7 +96,7 @@ DQ
|
|||
<binding destination="186" name="fontSize" keyPath="values.fontSize" id="198"/>
|
||||
</connections>
|
||||
</tableColumn>
|
||||
<tableColumn identifier="artist" editable="NO" width="124" minWidth="36" maxWidth="1000" id="34">
|
||||
<tableColumn identifier="artist" editable="NO" width="122" minWidth="36" maxWidth="1000" id="34">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left" title="Artist">
|
||||
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
|
||||
|
@ -88,6 +108,26 @@ DQ
|
|||
</textFieldCell>
|
||||
<sortDescriptor key="sortDescriptorPrototype" selector="caseInsensitiveCompare:" sortKey="artist"/>
|
||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
<prototypeCellViews>
|
||||
<tableCellView id="VrH-Fp-XeF">
|
||||
<rect key="frame" x="135" y="1" width="122" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="f5E-6Y-uFf">
|
||||
<rect key="frame" x="0.0" y="1" width="122" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="zeT-Bx-Fer">
|
||||
<font key="font" usesAppearanceFont="YES"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
<connections>
|
||||
<outlet property="textField" destination="f5E-6Y-uFf" id="V2x-bq-F1X"/>
|
||||
</connections>
|
||||
</tableCellView>
|
||||
</prototypeCellViews>
|
||||
<connections>
|
||||
<binding destination="16" name="value" keyPath="arrangedObjects.artist" id="104">
|
||||
<dictionary key="options">
|
||||
|
@ -97,7 +137,7 @@ DQ
|
|||
<binding destination="186" name="fontSize" keyPath="values.fontSize" id="199"/>
|
||||
</connections>
|
||||
</tableColumn>
|
||||
<tableColumn identifier="album" editable="NO" width="127" minWidth="39" maxWidth="1000" id="33">
|
||||
<tableColumn identifier="album" editable="NO" width="125" minWidth="39" maxWidth="1000" id="33">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left" title="Album">
|
||||
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
|
||||
|
@ -109,6 +149,26 @@ DQ
|
|||
</textFieldCell>
|
||||
<sortDescriptor key="sortDescriptorPrototype" selector="caseInsensitiveCompare:" sortKey="album"/>
|
||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
<prototypeCellViews>
|
||||
<tableCellView id="sZe-dt-0bF">
|
||||
<rect key="frame" x="260" y="1" width="125" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="wXR-zH-kaq">
|
||||
<rect key="frame" x="0.0" y="1" width="125" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="DBh-Jn-CLe">
|
||||
<font key="font" usesAppearanceFont="YES"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
<connections>
|
||||
<outlet property="textField" destination="wXR-zH-kaq" id="zTm-AX-xhO"/>
|
||||
</connections>
|
||||
</tableCellView>
|
||||
</prototypeCellViews>
|
||||
<connections>
|
||||
<binding destination="16" name="value" keyPath="arrangedObjects.album" id="101">
|
||||
<dictionary key="options">
|
||||
|
@ -129,8 +189,28 @@ DQ
|
|||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
<prototypeCellViews>
|
||||
<tableCellView id="w6e-lJ-LQP">
|
||||
<rect key="frame" x="1" y="1" width="0.0" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="kNC-PB-Kja">
|
||||
<rect key="frame" x="0.0" y="1" width="4" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="Uvp-HF-CkO">
|
||||
<font key="font" usesAppearanceFont="YES"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
<connections>
|
||||
<outlet property="textField" destination="kNC-PB-Kja" id="qWQ-5p-Fw9"/>
|
||||
</connections>
|
||||
</tableCellView>
|
||||
</prototypeCellViews>
|
||||
<connections>
|
||||
<binding destination="16" name="value" keyPath="arrangedObjects.spotlightLength" id="gWF-nL-fqJ">
|
||||
<binding destination="16" name="value" keyPath="arrangedObjects.length" id="4eP-Is-O9p">
|
||||
<dictionary key="options">
|
||||
<bool key="NSConditionallySetsEditable" value="YES"/>
|
||||
</dictionary>
|
||||
|
@ -149,6 +229,26 @@ DQ
|
|||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
<prototypeCellViews>
|
||||
<tableCellView id="1lW-Vx-WF3">
|
||||
<rect key="frame" x="1" y="1" width="0.0" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="acc-7f-dXh">
|
||||
<rect key="frame" x="0.0" y="1" width="4" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="9ZK-JX-P74">
|
||||
<font key="font" usesAppearanceFont="YES"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
<connections>
|
||||
<outlet property="textField" destination="acc-7f-dXh" id="Hha-9Z-fS4"/>
|
||||
</connections>
|
||||
</tableCellView>
|
||||
</prototypeCellViews>
|
||||
<connections>
|
||||
<binding destination="16" name="value" keyPath="arrangedObjects.year" id="94">
|
||||
<dictionary key="options">
|
||||
|
@ -170,6 +270,26 @@ DQ
|
|||
</textFieldCell>
|
||||
<sortDescriptor key="sortDescriptorPrototype" selector="caseInsensitiveCompare:" sortKey="genre"/>
|
||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
<prototypeCellViews>
|
||||
<tableCellView id="HJP-mb-r2M">
|
||||
<rect key="frame" x="1" y="1" width="0.0" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="5n5-AC-puO">
|
||||
<rect key="frame" x="0.0" y="1" width="4" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="R66-n6-DSb">
|
||||
<font key="font" usesAppearanceFont="YES"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
<connections>
|
||||
<outlet property="textField" destination="5n5-AC-puO" id="Wno-mK-MDi"/>
|
||||
</connections>
|
||||
</tableCellView>
|
||||
</prototypeCellViews>
|
||||
<connections>
|
||||
<binding destination="16" name="value" keyPath="arrangedObjects.genre" id="102">
|
||||
<dictionary key="options">
|
||||
|
@ -179,7 +299,7 @@ DQ
|
|||
<binding destination="186" name="fontSize" keyPath="values.fontSize" id="212"/>
|
||||
</connections>
|
||||
</tableColumn>
|
||||
<tableColumn identifier="track" editable="NO" width="46" minWidth="8" maxWidth="46" id="29">
|
||||
<tableColumn identifier="track" editable="NO" width="44" minWidth="8" maxWidth="46" id="29">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="right" title="Track">
|
||||
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
|
||||
|
@ -191,6 +311,26 @@ DQ
|
|||
</textFieldCell>
|
||||
<sortDescriptor key="sortDescriptorPrototype" selector="compareTrackNumbers:" sortKey="trackText"/>
|
||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
<prototypeCellViews>
|
||||
<tableCellView id="AdY-0L-7wP">
|
||||
<rect key="frame" x="388" y="1" width="48" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="6ak-Iu-0Dr">
|
||||
<rect key="frame" x="0.0" y="1" width="48" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="5fO-3i-lEM">
|
||||
<font key="font" usesAppearanceFont="YES"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
<connections>
|
||||
<outlet property="textField" destination="6ak-Iu-0Dr" id="Afl-RA-rk1"/>
|
||||
</connections>
|
||||
</tableCellView>
|
||||
</prototypeCellViews>
|
||||
<connections>
|
||||
<binding destination="16" name="value" keyPath="arrangedObjects.trackText" id="VFy-Rw-QP2">
|
||||
<dictionary key="options">
|
||||
|
@ -209,6 +349,7 @@ DQ
|
|||
</dictionary>
|
||||
</binding>
|
||||
<outlet property="dataSource" destination="16" id="151"/>
|
||||
<outlet property="delegate" destination="16" id="LPj-YJ-Alq"/>
|
||||
<outlet property="menu" destination="171" id="176"/>
|
||||
<outlet property="playlistController" destination="16" id="184"/>
|
||||
</connections>
|
||||
|
@ -221,18 +362,18 @@ DQ
|
|||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
<scroller key="verticalScroller" verticalHuggingPriority="750" doubleValue="1" horizontal="NO" id="27">
|
||||
<rect key="frame" x="424" y="24" width="15" height="203"/>
|
||||
<rect key="frame" x="423" y="24" width="16" height="203"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
<tableHeaderView key="headerView" wantsLayer="YES" id="25">
|
||||
<rect key="frame" x="0.0" y="0.0" width="447" height="23"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="438" height="23"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</tableHeaderView>
|
||||
<connections>
|
||||
<outlet property="nextKeyView" destination="88" id="206"/>
|
||||
</connections>
|
||||
</scrollView>
|
||||
<searchField wantsLayer="YES" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="55">
|
||||
<searchField wantsLayer="YES" focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="55">
|
||||
<rect key="frame" x="20" y="282" width="313" height="22"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<searchFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" borderStyle="bezel" bezelStyle="round" id="56">
|
||||
|
@ -245,10 +386,10 @@ DQ
|
|||
<outlet property="nextKeyView" destination="5" id="204"/>
|
||||
</connections>
|
||||
</searchField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="79">
|
||||
<rect key="frame" x="174" y="13" width="108" height="17"/>
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="79">
|
||||
<rect key="frame" x="18" y="13" width="264" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Search Location:" id="80">
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Search Location:" id="80">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
|
|
|
@ -2,16 +2,15 @@
|
|||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.temporary-exception.mach-lookup.global-name</key>
|
||||
<array>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)-spks</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)-spki</string>
|
||||
</array>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.assets.movies.read-only</key>
|
||||
<true/>
|
||||
<key>com.apple.security.assets.music.read-only</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.downloads.read-only</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.user-selected.read-write</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>HPDBookType</key>
|
||||
<string>3</string>
|
||||
<key>HPDBookTitle</key>
|
||||
<string>Cog Help</string>
|
||||
<key>HPDBookKBURL</key>
|
||||
<string>https://bitbucket.org/kode54/cog</string>
|
||||
<key>HPDBookKBProduct</key>
|
||||
<string>cog1</string>
|
||||
<key>HPDBookIndexPath</key>
|
||||
<string>Cog.helpindex</string>
|
||||
<key>HPDBookIconPath</key>
|
||||
<string>shrd/icon.png</string>
|
||||
<key>HPDBookAccessPath</key>
|
||||
<string>Cog.html</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>hbwr</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>BNDL</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>Cog</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>org.cogx.cog.help</string>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en_us</string>
|
||||
</dict>
|
||||
</plist>
|
|
@ -1,137 +0,0 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
|
||||
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
|
||||
|
||||
<head>
|
||||
|
||||
<title>Cog Help</title>
|
||||
|
||||
<meta name="AppleTitle" content="Cog Help"/>
|
||||
<META NAME="AppleIcon" content="../shrd/icon.png">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
|
||||
<style type="text/css">
|
||||
|
||||
html, body, button, input, select, textarea
|
||||
{ font-family: sans-serif;
|
||||
font-size: 14px;
|
||||
color: #5f5c52}
|
||||
|
||||
h1, h2, h3, strong, b
|
||||
{ font-weight: 600;
|
||||
color: #423f37}
|
||||
|
||||
a:link, a:visited { color: black; }
|
||||
a:hover { text-decoration: none; }
|
||||
|
||||
td { width: 100px; font-weight: bold; padding-top: 10px; }
|
||||
td.icon { width: auto; }
|
||||
|
||||
</style>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="header" style="clear: both;"><img src="../shrd/logo.png" style="width: 220px; " />
|
||||
|
||||
<ul style="float: right; list-style-type: none; margin-right: 20px;">
|
||||
<li><a href="http://cogx.org/">Cog homepage</a></li>
|
||||
<li><a href="http://cogx.org/forums/">Cog forums</a></li>
|
||||
<li><a href="mailto:vspader@users.sourceforge.net">Developer email</a></li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="formats" style="float: right; width: 45%; ">
|
||||
|
||||
<h2>Supported codecs</h2>
|
||||
|
||||
<ul>
|
||||
<li>Ogg Vorbis</li>
|
||||
<li>FLAC</li>
|
||||
<li>AAC</li>
|
||||
<li>MP3</li>
|
||||
<li>WAV</li>
|
||||
<li>AIFF</li>
|
||||
<li>Musepack</li>
|
||||
<li>Monkey's Audio</li>
|
||||
<li>Shorten</li>
|
||||
<li>WavPack</li>
|
||||
<li>Apple Lossless</li>
|
||||
<li>WMA Standard, Pro, Lossless, and Voice</li>
|
||||
<li>TrueAudio</li>
|
||||
<li>MIDI Sequences</li>
|
||||
<li>Sequenced Module formats (IT, XM, S3M, MOD, STM, PTM, MTM, 669, PSM, AM, J2B, DSM, AMF, OKT/OKTA, and UMX)</li>
|
||||
<li>Emulated Console formats supported by the Game_Music_Emu library (AY, GBS, HES, KSS, NSF/NSFE, SAP, SGC, SPC, and VGM/VGZ)</li>
|
||||
<li>Many Emulated Console formats utilizing the PSF format (PSF, PSF2, SSF, DSF, QSF, GSF, NCSF, 2SF, and their respective mini variants)</li>
|
||||
<li>Any codec supported by Mac OS X's Core Audio API</li>
|
||||
</ul>
|
||||
|
||||
<h2>Supported tags</h2>
|
||||
|
||||
<ul>
|
||||
<li>Vorbis comments/FLAC tags</li>
|
||||
<li>ID3v1.0, 1.1, 2.3+</li>
|
||||
<li>APEv1 and v2</li>
|
||||
</ul>
|
||||
|
||||
<h2>Supported Archive formats</h2>
|
||||
|
||||
<ul>
|
||||
<li>ZIP</li>
|
||||
<li>GZip</li>
|
||||
<li>RAR (also renamed to RSN)</li>
|
||||
<li>7-Zip (also renamed to VGM7Z)</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="buttons">
|
||||
|
||||
<h2>Controls</h2>
|
||||
|
||||
<table>
|
||||
|
||||
<tr>
|
||||
<td class="icon"><img src="../shrd/playback.png"/></td>
|
||||
<td>Play/Pause/Previous/Next</td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td class="icon"><img src="../shrd/files.png"/></td>
|
||||
<td>Show/hide<br>File Drawer</td>
|
||||
<td class="icon"><img src="../shrd/repeat.png"/></td>
|
||||
<td>Repeat on/off</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="icon"><img src="../shrd/info.png"/></td>
|
||||
<td>Show/hide<br>Info Drawer</td>
|
||||
<td class="icon"><img src="../shrd/shuffle.png"/></td>
|
||||
<td>Shuffle on/off</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
|
||||
<h2>General features</h2>
|
||||
|
||||
<ul>
|
||||
<li>Last.fm support</li>
|
||||
<li>Growl support</li>
|
||||
<li>M3U and PLS playlist</li>
|
||||
<li>HTTP streaming</li>
|
||||
<li>Output device selection</li>
|
||||
<li>Gapless playback</li>
|
||||
<li>Logarithmic volume control</li>
|
||||
<li>Smart shuffle</li>
|
||||
<li>Seeking</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
Before Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 352 B |
Before Width: | Height: | Size: 274 KiB |
Before Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 441 B |
Before Width: | Height: | Size: 96 KiB |
Before Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 410 B |