Compare commits
762 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b6cedd0f8f | ||
|
|
f11e159c44 | ||
|
|
f9c14db284 | ||
|
|
2a13cb9e68 | ||
|
|
bfb66906b7 | ||
|
|
7872799eb8 | ||
|
|
4b8730e2a6 | ||
|
|
316e80d7e5 | ||
|
|
29bd67638c | ||
|
|
03a3113d34 | ||
|
|
9c8ccf7a9f | ||
|
|
cc6189e162 | ||
|
|
97a58c2988 | ||
|
|
3651ce2659 | ||
|
|
cc1a6debb1 | ||
|
|
fcdfd34914 | ||
|
|
9807da71dc | ||
|
|
08d6d60f57 | ||
|
|
574058bec2 | ||
|
|
b382b5a204 | ||
|
|
e51781ee1d | ||
|
|
84233e46cd | ||
|
|
8d26f5d78a | ||
|
|
137f110e1d | ||
|
|
25119a555c | ||
|
|
f569aa6e99 | ||
|
|
9823f3683c | ||
|
|
abe2930647 | ||
|
|
0a9143f00a | ||
|
|
819b46cc01 | ||
|
|
a3ce9e3b20 | ||
|
|
7526a43cf0 | ||
|
|
17699297f3 | ||
|
|
4460fc4308 | ||
|
|
8f6adbd77f | ||
|
|
a048b1aa21 | ||
|
|
4f84675024 | ||
|
|
6b7f4477b0 | ||
|
|
ec9a4ef88a | ||
|
|
2f7bd3a3d1 | ||
|
|
08114e2cbe | ||
|
|
0ef06e400d | ||
|
|
bdc313cd1f | ||
|
|
9b432f3fc9 | ||
|
|
7fd8bc5633 | ||
|
|
cc3f0226ef | ||
|
|
1f10a359b5 | ||
|
|
16e6437dc4 | ||
|
|
ed21ac0efb | ||
|
|
0e11072070 | ||
|
|
232dcf3a29 | ||
|
|
65329ccc49 | ||
|
|
7d38ab6b0e | ||
|
|
5b8a0f4ef2 | ||
|
|
ba05aaa3ef | ||
|
|
0317262f85 | ||
|
|
1f202e12fc | ||
|
|
c9ab557aaa | ||
|
|
d1facbbcd4 | ||
|
|
a6840ade89 | ||
|
|
57b89bef0a | ||
|
|
28943adac8 | ||
|
|
337174617f | ||
|
|
816f66a726 | ||
|
|
38e80f2ef9 | ||
|
|
c1924c1f8b | ||
|
|
12b7590041 | ||
|
|
d084f13edf | ||
|
|
4a51e86fd5 | ||
|
|
bbcc5ee5d1 | ||
|
|
0af197fa98 | ||
|
|
9f208ee7d2 | ||
|
|
15eb546c3d | ||
|
|
1787739128 | ||
|
|
6d5c55dd2b | ||
|
|
f9463d1149 | ||
|
|
d01366d069 | ||
|
|
ea4615ea65 | ||
|
|
928e798d86 | ||
|
|
0dd5f73508 | ||
|
|
72f444ffc5 | ||
|
|
10fc130dbc | ||
|
|
65395f8af2 | ||
|
|
8048e91117 | ||
|
|
19375e55b8 | ||
|
|
4549665e08 | ||
|
|
f8b42408e2 | ||
|
|
077cd0b9fa | ||
|
|
8a336b3a6e | ||
|
|
ab7a84c40f | ||
|
|
d92e43d215 | ||
|
|
974618fc18 | ||
|
|
88c2c3e12b | ||
|
|
26d35504c8 | ||
|
|
24eec2687c | ||
|
|
e06bb0f9f3 | ||
|
|
691cf421e3 | ||
|
|
0161126153 | ||
|
|
729cf52b68 | ||
|
|
0bf89c18f9 | ||
|
|
40d3f83719 | ||
|
|
a36a7fe4b2 | ||
|
|
717a8f2771 | ||
|
|
ca0389a7b6 | ||
|
|
25cad0bedf | ||
|
|
76a96e39bd | ||
|
|
29ef6c134e | ||
|
|
fc1e011246 | ||
|
|
dbe92512ae | ||
|
|
d8a771e24f | ||
|
|
8a47e86685 | ||
|
|
fc91a74d05 | ||
|
|
22d9c41357 | ||
|
|
0289f4c06e | ||
|
|
21ff75b92e | ||
|
|
df0913727c | ||
|
|
fb99136cc1 | ||
|
|
b68a380e35 | ||
|
|
6027fc61dd | ||
|
|
edbc6190e4 | ||
|
|
be55de91c1 | ||
|
|
7c55bab17b | ||
|
|
dec07ff3a5 | ||
|
|
5742b20fa3 | ||
|
|
ce4e964f09 | ||
|
|
d28a82ff73 | ||
|
|
ff39f8c458 | ||
|
|
f24cb8dc96 | ||
|
|
d273a7fcb4 | ||
|
|
4e813c2540 | ||
|
|
2dd298f772 | ||
|
|
4d9063f16c | ||
|
|
136b1561c1 | ||
|
|
5a933beb16 | ||
|
|
7e2a34a825 | ||
|
|
b4600a289d | ||
|
|
e74ce1f4cc | ||
|
|
ba75777c0d | ||
|
|
92c0b4493a | ||
|
|
803231c558 | ||
|
|
120909f8d6 | ||
|
|
f0f23cbd0b | ||
|
|
939a35ee97 | ||
|
|
18e1785e57 | ||
|
|
bef2709834 | ||
|
|
c063e2fc30 | ||
|
|
6e62ce15b5 | ||
|
|
19221e1fcc | ||
|
|
c25d6cb551 | ||
|
|
86920a3083 | ||
|
|
68c39290ec | ||
|
|
563f56d2a2 | ||
|
|
4174333b40 | ||
|
|
a74445f9e4 | ||
|
|
859d8cc86c | ||
|
|
d8d2f1c801 | ||
|
|
85308462eb | ||
|
|
d84f8b14c4 | ||
|
|
0f4889923d | ||
|
|
2093816f2d | ||
|
|
7de751f882 | ||
|
|
6da476aebb | ||
|
|
7140d25a87 | ||
|
|
cb413a99f0 | ||
|
|
e6d21518d2 | ||
|
|
19e647ae84 | ||
|
|
494f5c71bb | ||
|
|
c4ad80f3b9 | ||
|
|
460fa6c8e6 | ||
|
|
5a62ffe178 | ||
|
|
c867d7bdf2 | ||
|
|
167cf7c659 | ||
|
|
73a3ec3f63 | ||
|
|
f8981248dc | ||
|
|
b90b2469b5 | ||
|
|
2de9ca144d | ||
|
|
f1a89a1c55 | ||
|
|
3909aa4ab7 | ||
|
|
10e95f5388 | ||
|
|
21bb2af7ea | ||
|
|
9b7b4071bf | ||
|
|
2ec267fc4f | ||
|
|
e6b954e9e8 | ||
|
|
b2ce0f3934 | ||
|
|
01d3a91e40 | ||
|
|
cf0b87298d | ||
|
|
02c69c1686 | ||
|
|
9a53ffa6d0 | ||
|
|
d22e23937f | ||
|
|
7b820e10e0 | ||
|
|
d795fee579 | ||
|
|
80418162d4 | ||
|
|
32c83a6eee | ||
|
|
d0b8cf0275 | ||
|
|
74fa6da8c2 | ||
|
|
f199f96f7b | ||
|
|
bfc13549e9 | ||
|
|
7660b07fa8 | ||
|
|
b72054c2f7 | ||
|
|
9dcbc38231 | ||
|
|
dbb50cf580 | ||
|
|
da0782d036 | ||
|
|
778801992d | ||
|
|
f410ff0ed2 | ||
|
|
23685253a8 | ||
|
|
21ad2cab07 | ||
|
|
c5a14422ee | ||
|
|
c12deb4b56 | ||
|
|
792d98dfd5 | ||
|
|
12aa3b19d5 | ||
|
|
4b3f782ece | ||
|
|
0016a41474 | ||
|
|
e115981b9f | ||
|
|
94fbe58fb7 | ||
|
|
90962d0391 | ||
|
|
7092de8809 | ||
|
|
81a0a724ab | ||
|
|
34297feed4 | ||
|
|
4407f8ac68 | ||
|
|
3de691fa0d | ||
|
|
e7f388999d | ||
|
|
8d66913a8e | ||
|
|
b30b5b6474 | ||
|
|
19b8412d67 | ||
|
|
24d2555c5e | ||
|
|
322564bc42 | ||
|
|
12bff7aa9e | ||
|
|
d87a84db85 | ||
|
|
b38017eb1c | ||
|
|
2d99870ec2 | ||
|
|
6f7a4aa234 | ||
|
|
49add7e0f8 | ||
|
|
c2708078db | ||
|
|
a46e68f145 | ||
|
|
a4f8d46d69 | ||
|
|
94d6bc4bca | ||
|
|
ec78631691 | ||
|
|
cd8f9792ab | ||
|
|
fd9c868c40 | ||
|
|
6282500305 | ||
|
|
7b5bc6d236 | ||
|
|
3e45ec7353 | ||
|
|
5051f4e38c | ||
|
|
5532e3349a | ||
|
|
4ea7a09583 | ||
|
|
affba5a29f | ||
|
|
d486055fc8 | ||
|
|
52b7cd68c1 | ||
|
|
afdf4bf108 | ||
|
|
a33dbac984 | ||
|
|
ff48107b1a | ||
|
|
3afaaed1fb | ||
|
|
7ca6299fba | ||
|
|
cba7f5b801 | ||
|
|
d75ed8aeee | ||
|
|
b9b2984235 | ||
|
|
c2b7892e6c | ||
|
|
6ce07c0a4b | ||
|
|
008e217a79 | ||
|
|
5847e52fbc | ||
|
|
e9baa25b46 | ||
|
|
b6213e1ed8 | ||
|
|
59ac3a0bb8 | ||
|
|
9b63972878 | ||
|
|
4955e24f12 | ||
|
|
16fb654b98 | ||
|
|
eda88b3078 | ||
|
|
2435277e49 | ||
|
|
7caded7c95 | ||
|
|
df6cdf9a80 | ||
|
|
25be63ef2c | ||
|
|
b0a042369f | ||
|
|
d8d68dcf71 | ||
|
|
133fb661b3 | ||
|
|
d5f2b375a6 | ||
|
|
f280d3b64c | ||
|
|
e20261fa66 | ||
|
|
b6a41d417d | ||
|
|
80f3052c2b | ||
|
|
62060e0c04 | ||
|
|
f3f7c57f10 | ||
|
|
82d61909f6 | ||
|
|
03c68d1dc3 | ||
|
|
05f593e40c | ||
|
|
5719c6cfc2 | ||
|
|
1ba8ff06e1 | ||
|
|
fae5d09001 | ||
|
|
3eecb412c6 | ||
|
|
b022be49a1 | ||
|
|
7238eccfd2 | ||
|
|
e3b8ccff9e | ||
|
|
c1aa3c9d4d | ||
|
|
00345c94a3 | ||
|
|
20001e6a26 | ||
|
|
c06fdc7760 | ||
|
|
8e28eba959 | ||
|
|
47ff1a2dd8 | ||
|
|
00ff0f532f | ||
|
|
2e0732c75b | ||
|
|
842247de54 | ||
|
|
52840ce8ae | ||
|
|
f640d2574b | ||
|
|
8598aad9e2 | ||
|
|
5dc82aadc7 | ||
|
|
2f8411a658 | ||
|
|
fcce9b62d5 | ||
|
|
5a077d2f52 | ||
|
|
36f8ea8df0 | ||
|
|
fe623d93a1 | ||
|
|
d41b8a7c24 | ||
|
|
f016a82a32 | ||
|
|
d872aa4a6c | ||
|
|
2c7262ced4 | ||
|
|
01e21ea212 | ||
|
|
0eae04ab83 | ||
|
|
e42afcb434 | ||
|
|
fad4be419a | ||
|
|
9768d17b9b | ||
|
|
e36bae2ab6 | ||
|
|
f8b5b1db9c | ||
|
|
9ccf1a31bc | ||
|
|
36d23b5dc2 | ||
|
|
9d27335d7d | ||
|
|
a913cee7a4 | ||
|
|
eeeba3d2b7 | ||
|
|
b8a56776da | ||
|
|
0e9fe58c06 | ||
|
|
6cf7635005 | ||
|
|
6c7ea3a16d | ||
|
|
4ded0d03d0 | ||
|
|
ac45ad740d | ||
|
|
f010c840e6 | ||
|
|
90b59908c6 | ||
|
|
13c6693cdd | ||
|
|
cd56c960a6 | ||
|
|
a51423a4d6 | ||
|
|
80a25ec71c | ||
|
|
51209179d1 | ||
|
|
b3937caa10 | ||
|
|
8927ba7c73 | ||
|
|
5f4c867618 | ||
|
|
fced4178be | ||
|
|
da0944c814 | ||
|
|
7aa14219b8 | ||
|
|
09a3494183 | ||
|
|
8924d81e0a | ||
|
|
b599fbf88d | ||
|
|
578a72f560 | ||
|
|
6b1e298d43 | ||
|
|
698665c4cc | ||
|
|
caa3e4d07a | ||
|
|
7606baa20b | ||
|
|
fde6be3f97 | ||
|
|
abdc70121d | ||
|
|
879a0524fc | ||
|
|
a529b34f99 | ||
|
|
e8daeb5f30 | ||
|
|
f610e8ba1e | ||
|
|
b3245c967b | ||
|
|
0af9da2214 | ||
|
|
db68f80048 | ||
|
|
b0efd80e42 | ||
|
|
3e12e0b84d | ||
|
|
5465a45dc6 | ||
|
|
53bd7d6ae2 | ||
|
|
d66751b6ac | ||
|
|
95de6d0afc | ||
|
|
147daa7681 | ||
|
|
ece925858a | ||
|
|
657b05d077 | ||
|
|
cf3bceeb46 | ||
|
|
6735e5eaaa | ||
|
|
7b9e71df40 | ||
|
|
fae61f3d87 | ||
|
|
a57419150e | ||
|
|
378257b7bf | ||
|
|
885b92a0a1 | ||
|
|
58371c36d3 | ||
|
|
d39064b209 | ||
|
|
f497da7967 | ||
|
|
03931cb232 | ||
|
|
4eeb02d9d4 | ||
|
|
8ff5ae3555 | ||
|
|
053a5e9dbe | ||
|
|
5a1a88bf33 | ||
|
|
f19b18d806 | ||
|
|
f6e4701d6b | ||
|
|
c3d686e472 | ||
|
|
6c7ff870e8 | ||
|
|
38bac83ddd | ||
|
|
4d8c77cf3e | ||
|
|
0a147697d2 | ||
|
|
21837e7464 | ||
|
|
d747962e24 | ||
|
|
49f09d1b3a | ||
|
|
783d6e69b1 | ||
|
|
6452ff78c0 | ||
|
|
a430d39849 | ||
|
|
33c222555f | ||
|
|
00f7e0fe62 | ||
|
|
44a31ede74 | ||
|
|
fb3ee2aa8c | ||
|
|
58e5dd9186 | ||
|
|
27ec46c64e | ||
|
|
12a5d777e5 | ||
|
|
276a94f9e8 | ||
|
|
ccc3c86900 | ||
|
|
124415363f | ||
|
|
de850b39f2 | ||
|
|
379f73b6ca | ||
|
|
159b778fa9 | ||
|
|
007f7a0ce1 | ||
|
|
54fa642693 | ||
|
|
2fbc6f9193 | ||
|
|
262c6fd8ab | ||
|
|
7fcb53c7d0 | ||
|
|
d0e1101bfb | ||
|
|
a8bb4ae6d1 | ||
|
|
d743307e59 | ||
|
|
e92e9eb45c | ||
|
|
aaefa356ae | ||
|
|
3a7da2bc98 | ||
|
|
94b211e3b7 | ||
|
|
9ff5b90605 | ||
|
|
a033b74f7f | ||
|
|
bb4e81b00a | ||
|
|
5f2e0b79cd | ||
|
|
64a7fef7c1 | ||
|
|
09dbc5c84e | ||
|
|
e71e0791bc | ||
|
|
2a4be39c9a | ||
|
|
3cde191afb | ||
|
|
02a8d9a243 | ||
|
|
7f4e313a78 | ||
|
|
44e3d90dcb | ||
|
|
945eb3ccc6 | ||
|
|
793008852a | ||
|
|
7e791ee5e4 | ||
|
|
1413490579 | ||
|
|
57154e5d0b | ||
|
|
57a1556e23 | ||
|
|
e248e92ca1 | ||
|
|
fa5adcf08b | ||
|
|
87a51afd99 | ||
|
|
4efaa1f350 | ||
|
|
1d329600af | ||
|
|
c2a0e51984 | ||
|
|
ebd59e38ce | ||
|
|
f75b384c17 | ||
|
|
5419622c74 | ||
|
|
ab40011954 | ||
|
|
1b658e9b40 | ||
|
|
49605f9c23 | ||
|
|
d9b6b808f7 | ||
|
|
1607dd329d | ||
|
|
74337b2699 | ||
|
|
d6a684bbe7 | ||
|
|
feeb997f62 | ||
|
|
26da3bf9a8 | ||
|
|
82046afd9f | ||
|
|
97704deea0 | ||
|
|
64b2a18ff3 | ||
|
|
cf16edceec | ||
|
|
f6008737d1 | ||
|
|
61de8c4717 | ||
|
|
83ad83e656 | ||
|
|
2afb13c580 | ||
|
|
e48a963503 | ||
|
|
c846a0626f | ||
|
|
a6b1d09ff1 | ||
|
|
3603e146cc | ||
|
|
9ab597c0e9 | ||
|
|
3251e9f845 | ||
|
|
3e7b24a3de | ||
|
|
6989cd4d40 | ||
|
|
c7205a512e | ||
|
|
c0befa0f49 | ||
|
|
2223024383 | ||
|
|
216d9a1686 | ||
|
|
1f9bff6182 | ||
|
|
0f7d42ab83 | ||
|
|
60334f24f1 | ||
|
|
d7d099d2d7 | ||
|
|
514d4170be | ||
|
|
6b1c1853b8 | ||
|
|
ced318637f | ||
|
|
c2c7c37ef6 | ||
|
|
7e5041eac5 | ||
|
|
4b7db87444 | ||
|
|
22aba74ed9 | ||
|
|
5283dd2f9e | ||
|
|
8bc69ba0a4 | ||
|
|
90207c6184 | ||
|
|
678590fc4f | ||
|
|
9170aba906 | ||
|
|
59a92bb172 | ||
|
|
f12cf8a6ce | ||
|
|
26cb75f93f | ||
|
|
e8b05cd7c2 | ||
|
|
4930c07e9c | ||
|
|
39d4002491 | ||
|
|
e095b139ae | ||
|
|
088cf97ebf | ||
|
|
be90b30b8f | ||
|
|
0ff3041e89 | ||
|
|
9d2c2c0e47 | ||
|
|
b35f2b7cb4 | ||
|
|
1988c5fe9a | ||
|
|
aad99ce3a4 | ||
|
|
1d7209693a | ||
|
|
4017732d71 | ||
|
|
c09f2a1121 | ||
|
|
9467a0700a | ||
|
|
ef74faeef4 | ||
|
|
2891ad7efe | ||
|
|
364ffc4659 | ||
|
|
8aa82274ff | ||
|
|
71ca037ca8 | ||
|
|
341aac8ff7 | ||
|
|
99ccfb8e94 | ||
|
|
4ed884de55 | ||
|
|
f9caa75aa7 | ||
|
|
37a7bc43fd | ||
|
|
cdd6ac1d8b | ||
|
|
235e0efc09 | ||
|
|
2f4f93d3ba | ||
|
|
f25a1b70f3 | ||
|
|
6ec8edd5e2 | ||
|
|
8beeced7c2 | ||
|
|
f4add84a13 | ||
|
|
c507d0ed7e | ||
|
|
3ff193f42d | ||
|
|
8104050658 | ||
|
|
b53f0b4c1b | ||
|
|
7698c05dd5 | ||
|
|
18734345a1 | ||
|
|
ad54ee4bda | ||
|
|
f8a4b7b1db | ||
|
|
e6966df9ac | ||
|
|
8e17fc0edb | ||
|
|
2cdb8c022d | ||
|
|
04822346a6 | ||
|
|
6fcfc2da34 | ||
|
|
62c7b8cdc1 | ||
|
|
d2d899a8d0 | ||
|
|
921e09227f | ||
|
|
39c274a515 | ||
|
|
5db65b6e6e | ||
|
|
c71fc5575f | ||
|
|
2ebf264feb | ||
|
|
aa20cf5138 | ||
|
|
5f96a8991a | ||
|
|
47e0d6ae19 | ||
|
|
e3e2d8d2f6 | ||
|
|
f57e057f2a | ||
|
|
dc0df8cc61 | ||
|
|
c30d7a2089 | ||
|
|
03bffb3319 | ||
|
|
65dc93ed37 | ||
|
|
8ed04b9c6e | ||
|
|
e6a9327bae | ||
|
|
658c506653 | ||
|
|
ad4a4281b4 | ||
|
|
6b0070ec56 | ||
|
|
c9cf6baf4b | ||
|
|
85a25a0a39 | ||
|
|
e192d254f9 | ||
|
|
9e2b9d7cf4 | ||
|
|
cd545213e4 | ||
|
|
935d2ff02c | ||
|
|
b7d6218b89 | ||
|
|
d86754b0e6 | ||
|
|
b97674a404 | ||
|
|
f91522fc14 | ||
|
|
9b4ef00278 | ||
|
|
43270c7763 | ||
|
|
9a98fb399c | ||
|
|
c255b0249f | ||
|
|
b0e71f6f18 | ||
|
|
7e59377daf | ||
|
|
73aa369e6d | ||
|
|
731d7e5d6b | ||
|
|
bc5e1c7df9 | ||
|
|
1a15f9b581 | ||
|
|
136d6052d8 | ||
|
|
811b270bae | ||
|
|
7870e3cfce | ||
|
|
4874af1d5a | ||
|
|
1b7ead0479 | ||
|
|
ac97984314 | ||
|
|
8c778e8afb | ||
|
|
2b6bbce1d9 | ||
|
|
b8e0a9c1dd | ||
|
|
4c3c3de065 | ||
|
|
a74801a0af | ||
|
|
cbe8217790 | ||
|
|
e1cdf34955 | ||
|
|
fefc20c52a | ||
|
|
ad1e574cfe | ||
|
|
c25d4b7dad | ||
|
|
207aa88daf | ||
|
|
3be08a3e63 | ||
|
|
7d91ffbafa | ||
|
|
128ba084ad | ||
|
|
0defff8f7c | ||
|
|
6e3497e4c4 | ||
|
|
1c309b2c89 | ||
|
|
35507e7fbb | ||
|
|
bc439829cf | ||
|
|
93f433f388 | ||
|
|
f9e99e2a33 | ||
|
|
94dcf7c3f3 | ||
|
|
c3bb29182e | ||
|
|
b11f6d5b3b | ||
|
|
d2b900f7c3 | ||
|
|
bf2b5d8882 | ||
|
|
e7878bdb8f | ||
|
|
0670550521 | ||
|
|
f5df923c51 | ||
|
|
f1f62816b5 | ||
|
|
db462690b3 | ||
|
|
c28685c700 | ||
|
|
1f1780597c | ||
|
|
1590251dad | ||
|
|
d25c17342b | ||
|
|
e105022185 | ||
|
|
16931917b7 | ||
|
|
e2e316a079 | ||
|
|
1dd2151a20 | ||
|
|
c7367ad46a | ||
|
|
21045411e7 | ||
|
|
128e8834e8 | ||
|
|
7433772606 | ||
|
|
8967f5f090 | ||
|
|
3b51a6e2a9 | ||
|
|
c5cb635b4e | ||
|
|
1e9f8d707e | ||
|
|
69a0aa4bd8 | ||
|
|
581fa88055 | ||
|
|
f991ae5aed | ||
|
|
44825ece04 | ||
|
|
eac2c5c020 | ||
|
|
a593e97227 | ||
|
|
ecad786f50 | ||
|
|
9012d33c05 | ||
|
|
268751815f | ||
|
|
6529b170e6 | ||
|
|
5ce465cb30 | ||
|
|
48f1df2fd6 | ||
|
|
3e6ddf560a | ||
|
|
0051533ac8 | ||
|
|
e69ac7ca28 | ||
|
|
9c8abb8edf | ||
|
|
9ee434f275 | ||
|
|
4826d9fbf0 | ||
|
|
3fd7b8ed3c | ||
|
|
ebc8d483d9 | ||
|
|
9821d3595a | ||
|
|
f21be9d10c | ||
|
|
d221036cde | ||
|
|
1dbff48ebb | ||
|
|
43e56fc433 | ||
|
|
b3b562f4bb | ||
|
|
784ac996d3 | ||
|
|
fb6a95078d | ||
|
|
189e7b8bc9 | ||
|
|
55967ad27c | ||
|
|
e2c82af4d6 | ||
|
|
8712923bec | ||
|
|
416635179b | ||
|
|
c1b635e036 | ||
|
|
2860d8f1de | ||
|
|
c1fb07b4c7 | ||
|
|
5036c2231c | ||
|
|
c848666e17 | ||
|
|
55aef98a30 | ||
|
|
7cbbf799dc | ||
|
|
30a4e0297c | ||
|
|
5f68f51693 | ||
|
|
8e6aaf29e0 | ||
|
|
cde0e74a2e | ||
|
|
2a573cbab3 | ||
|
|
941275a1b9 | ||
|
|
c5884ec498 | ||
|
|
da06cf52ec | ||
|
|
07e8f489c1 | ||
|
|
baed2ac031 | ||
|
|
cdab138b2f | ||
|
|
06524edfb3 | ||
|
|
0e40550427 | ||
|
|
e08b6ade9e | ||
|
|
36bc4944f9 | ||
|
|
a0be95d634 | ||
|
|
faf8d4c4ad | ||
|
|
8af740caa8 | ||
|
|
69fa3521f9 | ||
|
|
991a96d3dc | ||
|
|
b10bf06305 | ||
|
|
7f54b30fbe | ||
|
|
ee79043536 | ||
|
|
c9e6611b92 | ||
|
|
73d128d89a | ||
|
|
5a240acd86 | ||
|
|
7dc4a5cf87 | ||
|
|
6d835297b2 | ||
|
|
b44c29e235 | ||
|
|
8da9a9cf27 | ||
|
|
a3617626f7 | ||
|
|
e44832ea9e | ||
|
|
f841e78dcf | ||
|
|
e7a8d48cca | ||
|
|
69e8d0e32f | ||
|
|
830d1a6bf9 | ||
|
|
e9b72b442a | ||
|
|
77ac7eca18 | ||
|
|
86d4198ffd | ||
|
|
37dfe31ac2 | ||
|
|
584e1c48e6 | ||
|
|
15766ceb97 | ||
|
|
6e79f28b69 | ||
|
|
e9632d206b | ||
|
|
a0f55bfcb5 | ||
|
|
3c32f1da6e | ||
|
|
7e13c1b22a | ||
|
|
2fc52e673f | ||
|
|
48001a660b | ||
|
|
5a6daf79b6 | ||
|
|
ae4fd9f7df | ||
|
|
7dfa8776fd | ||
|
|
a836796fcc | ||
|
|
fb131972d4 | ||
|
|
140ce358fa | ||
|
|
dd92f2dccb | ||
|
|
d62004eadf | ||
|
|
abc21e9692 | ||
|
|
a13b0d5d91 | ||
|
|
12f8c75c2d | ||
|
|
bdc1920166 | ||
|
|
8cb0d57ffe | ||
|
|
7344689263 | ||
|
|
f521ca1118 | ||
|
|
6712f1383e | ||
|
|
d756fd4a29 | ||
|
|
e070ef1b7f | ||
|
|
ea7786a002 | ||
|
|
3eec07fac1 | ||
|
|
46afa76af8 | ||
|
|
6cc1ba64d8 | ||
|
|
225596481f | ||
|
|
4ef3b3b332 | ||
|
|
e7ac768b5b | ||
|
|
b1e8833daa | ||
|
|
155d71dc80 | ||
|
|
94ebf17134 | ||
|
|
d6fe2edf0d | ||
|
|
867cd7e583 | ||
|
|
1a2471a32d | ||
|
|
057dd9c01d | ||
|
|
5a2c0e15e9 | ||
|
|
ae5ae24f6f | ||
|
|
7a2f3fe840 | ||
|
|
b46788c81a |
50
.github/workflows/jekyll-gh-pages.yml
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
# Sample workflow for building and deploying a Jekyll site to GitHub Pages
|
||||
name: Deploy Jekyll with GitHub Pages dependencies preinstalled
|
||||
|
||||
on:
|
||||
# Runs on pushes targeting the default branch
|
||||
push:
|
||||
branches: ["master"]
|
||||
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
# Allow one concurrent deployment
|
||||
concurrency:
|
||||
group: "pages"
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
# Build job
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v2
|
||||
- name: Build with Jekyll
|
||||
uses: actions/jekyll-build-pages@v1
|
||||
with:
|
||||
source: ./docs
|
||||
destination: ./_site
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v1
|
||||
|
||||
# Deployment job
|
||||
deploy:
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v1
|
||||
10
.gitignore
vendored
@@ -26,3 +26,13 @@ osx/.DS_Store
|
||||
osx/runvimix
|
||||
|
||||
*.autosave
|
||||
|
||||
flatpak/.flatpak-builder
|
||||
|
||||
flatpak/repo/
|
||||
|
||||
flatpak/build/
|
||||
|
||||
build/
|
||||
.vscode/
|
||||
.cache/
|
||||
|
||||
2
.gitmodules
vendored
@@ -21,4 +21,4 @@
|
||||
url = https://github.com/Ableton/link.git
|
||||
[submodule "ext/tfd"]
|
||||
path = ext/tfd
|
||||
url = https://git.code.sf.net/p/tinyfiledialogs/code
|
||||
url = https://git.code.sf.net/p/tinyfiledialogs/code
|
||||
737
CMakeLists.txt
@@ -1,966 +0,0 @@
|
||||
/*
|
||||
* This file is part of vimix - video live mixer
|
||||
*
|
||||
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||
*
|
||||
* 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 3 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, see <https://www.gnu.org/licenses/>.
|
||||
**/
|
||||
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
#include <iomanip>
|
||||
|
||||
#include "osc/OscOutboundPacketStream.h"
|
||||
|
||||
#include "defines.h"
|
||||
#include "Log.h"
|
||||
#include "Settings.h"
|
||||
#include "BaseToolkit.h"
|
||||
#include "Mixer.h"
|
||||
#include "Source.h"
|
||||
#include "SourceCallback.h"
|
||||
#include "ActionManager.h"
|
||||
#include "SystemToolkit.h"
|
||||
#include "tinyxml2Toolkit.h"
|
||||
#include "Metronome.h"
|
||||
|
||||
#include "UserInterfaceManager.h"
|
||||
#include "RenderingManager.h"
|
||||
#include <GLFW/glfw3.h>
|
||||
|
||||
#include "ControlManager.h"
|
||||
|
||||
#ifndef NDEBUG
|
||||
#define CONTROL_DEBUG
|
||||
#endif
|
||||
|
||||
#define CONTROL_OSC_MSG "OSC: "
|
||||
|
||||
//bool Control::input_active[INPUT_MAX]{};
|
||||
//float Control::input_values[INPUT_MAX]{};
|
||||
//std::mutex Control::input_access_;
|
||||
|
||||
|
||||
void Control::RequestListener::ProcessMessage( const osc::ReceivedMessage& m,
|
||||
const IpEndpointName& remoteEndpoint )
|
||||
{
|
||||
char sender[IpEndpointName::ADDRESS_AND_PORT_STRING_LENGTH];
|
||||
remoteEndpoint.AddressAndPortAsString(sender);
|
||||
|
||||
try{
|
||||
#ifdef CONTROL_DEBUG
|
||||
Log::Info(CONTROL_OSC_MSG "received '%s' from %s", FullMessage(m).c_str(), sender);
|
||||
#endif
|
||||
// Preprocessing with Translator
|
||||
std::string address_pattern = Control::manager().translate(m.AddressPattern());
|
||||
|
||||
// structured OSC address
|
||||
std::list<std::string> address = BaseToolkit::splitted(address_pattern, OSC_SEPARATOR);
|
||||
//
|
||||
// A wellformed OSC address is in the form '/vimix/target/attribute {arguments}'
|
||||
// First test: should have 3 elements and start with APP_NAME ('vimix')
|
||||
//
|
||||
if (address.size() > 2 && address.front().compare(OSC_PREFIX) == 0 ){
|
||||
// done with the first part of the OSC address
|
||||
address.pop_front();
|
||||
// next part of the OSC message is the target
|
||||
std::string target = address.front();
|
||||
// next part of the OSC message is the attribute
|
||||
address.pop_front();
|
||||
std::string attribute = address.front();
|
||||
// Log target: just print text in log window
|
||||
if ( target.compare(OSC_INFO) == 0 )
|
||||
{
|
||||
if ( attribute.compare(OSC_INFO_NOTIFY) == 0) {
|
||||
Log::Notify(CONTROL_OSC_MSG "Received '%s' from %s", FullMessage(m).c_str(), sender);
|
||||
}
|
||||
else if ( attribute.compare(OSC_INFO_LOG) == 0) {
|
||||
Log::Info(CONTROL_OSC_MSG "Received '%s' from %s", FullMessage(m).c_str(), sender);
|
||||
}
|
||||
}
|
||||
// Output target: concerns attributes of the rendering output
|
||||
else if ( target.compare(OSC_OUTPUT) == 0 )
|
||||
{
|
||||
if ( Control::manager().receiveOutputAttribute(attribute, m.ArgumentStream())) {
|
||||
// send the global status
|
||||
Control::manager().sendOutputStatus(remoteEndpoint);
|
||||
}
|
||||
}
|
||||
// Multitouch target: user input on 'Multitouch' tab
|
||||
else if ( target.compare(OSC_MULTITOUCH) == 0 )
|
||||
{
|
||||
Control::manager().receiveMultitouchAttribute(attribute, m.ArgumentStream());
|
||||
}
|
||||
// Session target: concerns attributes of the session
|
||||
else if ( target.compare(OSC_SESSION) == 0 )
|
||||
{
|
||||
if ( Control::manager().receiveSessionAttribute(attribute, m.ArgumentStream()) ) {
|
||||
// send the global status
|
||||
Control::manager().sendOutputStatus(remoteEndpoint);
|
||||
// send the status of all sources
|
||||
Control::manager().sendSourcesStatus(remoteEndpoint, m.ArgumentStream());
|
||||
}
|
||||
}
|
||||
// ALL sources target: apply attribute to all sources of the session
|
||||
else if ( target.compare(OSC_ALL) == 0 )
|
||||
{
|
||||
// Loop over selected sources
|
||||
for (SourceList::iterator it = Mixer::manager().session()->begin(); it != Mixer::manager().session()->end(); ++it) {
|
||||
// apply attributes
|
||||
if ( Control::manager().receiveSourceAttribute( *it, attribute, m.ArgumentStream()) && Mixer::manager().currentSource() == *it)
|
||||
// and send back feedback if needed
|
||||
Control::manager().sendSourceAttibutes(remoteEndpoint, OSC_CURRENT);
|
||||
}
|
||||
}
|
||||
// Selected sources target: apply attribute to all sources of the selection
|
||||
else if ( target.compare(OSC_SELECTED) == 0 )
|
||||
{
|
||||
// Loop over selected sources
|
||||
for (SourceList::iterator it = Mixer::selection().begin(); it != Mixer::selection().end(); ++it) {
|
||||
// apply attributes
|
||||
if ( Control::manager().receiveSourceAttribute( *it, attribute, m.ArgumentStream()) && Mixer::manager().currentSource() == *it)
|
||||
// and send back feedback if needed
|
||||
Control::manager().sendSourceAttibutes(remoteEndpoint, OSC_CURRENT);
|
||||
}
|
||||
}
|
||||
// Current source target: apply attribute to the current sources
|
||||
else if ( target.compare(OSC_CURRENT) == 0 )
|
||||
{
|
||||
int sourceid = -1;
|
||||
if ( attribute.compare(OSC_SYNC) == 0) {
|
||||
// send the status of all sources
|
||||
Control::manager().sendSourcesStatus(remoteEndpoint, m.ArgumentStream());
|
||||
}
|
||||
else if ( attribute.compare(OSC_NEXT) == 0) {
|
||||
// set current to NEXT
|
||||
Mixer::manager().setCurrentNext();
|
||||
// send the status of all sources
|
||||
Control::manager().sendSourcesStatus(remoteEndpoint, m.ArgumentStream());
|
||||
}
|
||||
else if ( attribute.compare(OSC_PREVIOUS) == 0) {
|
||||
// set current to PREVIOUS
|
||||
Mixer::manager().setCurrentPrevious();
|
||||
// send the status of all sources
|
||||
Control::manager().sendSourcesStatus(remoteEndpoint, m.ArgumentStream());
|
||||
}
|
||||
else if ( BaseToolkit::is_a_number( attribute.substr(1), &sourceid) ){
|
||||
// set current to given INDEX
|
||||
Mixer::manager().setCurrentIndex(sourceid);
|
||||
// send the status of all sources
|
||||
Control::manager().sendSourcesStatus(remoteEndpoint, m.ArgumentStream());
|
||||
}
|
||||
// all other attributes operate on current source
|
||||
else {
|
||||
// apply attributes to current source
|
||||
if ( Control::manager().receiveSourceAttribute( Mixer::manager().currentSource(), attribute, m.ArgumentStream()) )
|
||||
// and send back feedback if needed
|
||||
Control::manager().sendSourceAttibutes(remoteEndpoint, OSC_CURRENT);
|
||||
}
|
||||
}
|
||||
// General case: try to identify the target
|
||||
else {
|
||||
// try to find source by index
|
||||
Source *s = nullptr;
|
||||
int sourceid = -1;
|
||||
if ( BaseToolkit::is_a_number(target.substr(1), &sourceid) )
|
||||
s = Mixer::manager().sourceAtIndex(sourceid);
|
||||
|
||||
// if failed, try to find source by name
|
||||
if (s == nullptr)
|
||||
s = Mixer::manager().findSource(target.substr(1));
|
||||
|
||||
// if a source with the given target name or index was found
|
||||
if (s) {
|
||||
// apply attributes to source
|
||||
if ( Control::manager().receiveSourceAttribute(s, attribute, m.ArgumentStream()) )
|
||||
// and send back feedback if needed
|
||||
Control::manager().sendSourceAttibutes(remoteEndpoint, target, s);
|
||||
}
|
||||
else
|
||||
Log::Info(CONTROL_OSC_MSG "Unknown target '%s' requested by %s.", target.c_str(), sender);
|
||||
}
|
||||
}
|
||||
else {
|
||||
Log::Info(CONTROL_OSC_MSG "Unknown osc message '%s' sent by %s.", m.AddressPattern(), sender);
|
||||
}
|
||||
}
|
||||
catch( osc::Exception& e ){
|
||||
// any parsing errors such as unexpected argument types, or
|
||||
// missing arguments get thrown as exceptions.
|
||||
Log::Info(CONTROL_OSC_MSG "Ignoring error in message '%s' from %s : %s", m.AddressPattern(), sender, e.what());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::string Control::RequestListener::FullMessage( const osc::ReceivedMessage& m )
|
||||
{
|
||||
// build a string with the address pattern of the message
|
||||
std::ostringstream message;
|
||||
message << m.AddressPattern() << " ";
|
||||
|
||||
// try to fill the string with the arguments
|
||||
std::ostringstream arguments;
|
||||
try{
|
||||
// loop over all arguments
|
||||
osc::ReceivedMessage::const_iterator arg = m.ArgumentsBegin();
|
||||
while (arg != m.ArgumentsEnd()) {
|
||||
if( arg->IsBool() ){
|
||||
bool a = (arg++)->AsBoolUnchecked();
|
||||
message << (a ? "T" : "F");
|
||||
}
|
||||
else if( arg->IsInt32() ){
|
||||
int a = (arg++)->AsInt32Unchecked();
|
||||
message << "i";
|
||||
arguments << " " << a;
|
||||
}
|
||||
else if( arg->IsFloat() ){
|
||||
float a = (arg++)->AsFloatUnchecked();
|
||||
message << "f";
|
||||
arguments << " " << std::fixed << std::setprecision(2) << a;
|
||||
}
|
||||
else if( arg->IsString() ){
|
||||
const char *a = (arg++)->AsStringUnchecked();
|
||||
message << "s";
|
||||
arguments << " " << a;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch( osc::Exception& e ){
|
||||
// any parsing errors such as unexpected argument types, or
|
||||
// missing arguments get thrown as exceptions.
|
||||
Log::Info(CONTROL_OSC_MSG "Ignoring error in message '%s': %s", m.AddressPattern(), e.what());
|
||||
}
|
||||
|
||||
// append list of arguments to the message string
|
||||
message << arguments.str();
|
||||
|
||||
// returns the full message
|
||||
return message.str();
|
||||
}
|
||||
|
||||
|
||||
Control::Control() : receiver_(nullptr)
|
||||
{
|
||||
for (size_t i = 0; i < INPUT_MULTITOUCH_COUNT; ++i) {
|
||||
multitouch_active[i] = false;
|
||||
multitouch_values[i] = glm::vec2(0.f);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < INPUT_MAX; ++i) {
|
||||
input_active[i] = false;
|
||||
input_values[i] = 0.f;
|
||||
}
|
||||
}
|
||||
|
||||
Control::~Control()
|
||||
{
|
||||
terminate();
|
||||
}
|
||||
|
||||
std::string Control::translate (std::string addresspattern)
|
||||
{
|
||||
auto it_translation = translation_.find(addresspattern);
|
||||
if ( it_translation != translation_.end() )
|
||||
return it_translation->second;
|
||||
else
|
||||
return addresspattern;
|
||||
}
|
||||
|
||||
void Control::loadOscConfig()
|
||||
{
|
||||
// reset translations
|
||||
translation_.clear();
|
||||
|
||||
// load osc config file
|
||||
tinyxml2::XMLDocument xmlDoc;
|
||||
tinyxml2::XMLError eResult = xmlDoc.LoadFile(Settings::application.control.osc_filename.c_str());
|
||||
|
||||
// the only reason to return false is if the file does not exist or is empty
|
||||
if (eResult == tinyxml2::XML_ERROR_FILE_NOT_FOUND
|
||||
| eResult == tinyxml2::XML_ERROR_FILE_COULD_NOT_BE_OPENED
|
||||
| eResult == tinyxml2::XML_ERROR_FILE_READ_ERROR
|
||||
| eResult == tinyxml2::XML_ERROR_EMPTY_DOCUMENT )
|
||||
resetOscConfig();
|
||||
|
||||
// found the file, could open and read it
|
||||
else if (eResult != tinyxml2::XML_SUCCESS)
|
||||
Log::Warning(CONTROL_OSC_MSG "Error while parsing Translator: %s", xmlDoc.ErrorIDToName(eResult));
|
||||
|
||||
// no XML parsing error
|
||||
else {
|
||||
// parse all entries 'osc'
|
||||
tinyxml2::XMLElement* osc = xmlDoc.FirstChildElement("osc");
|
||||
for( ; osc ; osc=osc->NextSiblingElement()) {
|
||||
// get the 'from' entry
|
||||
tinyxml2::XMLElement* from = osc->FirstChildElement("from");
|
||||
if (from) {
|
||||
const char *str_from = from->GetText();
|
||||
if (str_from) {
|
||||
// get the 'to' entry
|
||||
tinyxml2::XMLElement* to = osc->FirstChildElement("to");
|
||||
if (to) {
|
||||
const char *str_to = to->GetText();
|
||||
// if could get both; add to translator
|
||||
if (str_to)
|
||||
translation_[str_from] = str_to;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Log::Info(CONTROL_OSC_MSG "Loaded %d translation%s.", translation_.size(), translation_.size()>1?"s":"");
|
||||
}
|
||||
|
||||
void Control::resetOscConfig()
|
||||
{
|
||||
// generate a template xml translation dictionary
|
||||
tinyxml2::XMLDocument xmlDoc;
|
||||
tinyxml2::XMLDeclaration *pDec = xmlDoc.NewDeclaration();
|
||||
xmlDoc.InsertFirstChild(pDec);
|
||||
tinyxml2::XMLComment *pComment = xmlDoc.NewComment("The OSC translator converts OSC address patterns into other ones.\n"
|
||||
"Complete the dictionary by adding as many <osc> translations as you want.\n"
|
||||
"Each <osc> should contain a <from> pattern to translate into a <to> pattern.\n"
|
||||
"More at https://github.com/brunoherbelin/vimix/wiki/Open-Sound-Control-API.");
|
||||
xmlDoc.InsertEndChild(pComment);
|
||||
tinyxml2::XMLElement *from = xmlDoc.NewElement( "from" );
|
||||
from->InsertFirstChild( xmlDoc.NewText("/example/osc/message") );
|
||||
tinyxml2::XMLElement *to = xmlDoc.NewElement( "to" );
|
||||
to->InsertFirstChild( xmlDoc.NewText("/vimix/info/log") );
|
||||
tinyxml2::XMLElement *osc = xmlDoc.NewElement("osc");
|
||||
osc->InsertEndChild(from);
|
||||
osc->InsertEndChild(to);
|
||||
xmlDoc.InsertEndChild(osc);
|
||||
|
||||
// save xml in osc config file
|
||||
xmlDoc.SaveFile(Settings::application.control.osc_filename.c_str());
|
||||
|
||||
// reset and fill translation with default example
|
||||
translation_.clear();
|
||||
translation_["/example/osc/message"] = "/vimix/info/log";
|
||||
}
|
||||
|
||||
bool Control::init()
|
||||
{
|
||||
//
|
||||
// terminate before init (allows calling init() multiple times)
|
||||
//
|
||||
terminate();
|
||||
|
||||
//
|
||||
// set keyboard callback
|
||||
//
|
||||
GLFWwindow *main = Rendering::manager().mainWindow().window();
|
||||
GLFWwindow *output = Rendering::manager().outputWindow().window();
|
||||
glfwSetKeyCallback( main, Control::keyboardCalback);
|
||||
glfwSetKeyCallback( output, Control::keyboardCalback);
|
||||
|
||||
//
|
||||
// load OSC Translator
|
||||
//
|
||||
loadOscConfig();
|
||||
|
||||
//
|
||||
// launch OSC listener
|
||||
//
|
||||
try {
|
||||
// try to create listenning socket
|
||||
// through exception runtime if fails
|
||||
receiver_ = new UdpListeningReceiveSocket( IpEndpointName( IpEndpointName::ANY_ADDRESS,
|
||||
Settings::application.control.osc_port_receive ), &listener_ );
|
||||
// listen for answers in a separate thread
|
||||
std::thread(listen).detach();
|
||||
|
||||
// inform user
|
||||
IpEndpointName ip = receiver_->LocalEndpointFor( IpEndpointName( NetworkToolkit::hostname().c_str(),
|
||||
Settings::application.control.osc_port_receive ));
|
||||
static char *addresseip = (char *)malloc(IpEndpointName::ADDRESS_AND_PORT_STRING_LENGTH);
|
||||
ip.AddressAndPortAsString(addresseip);
|
||||
Log::Info(CONTROL_OSC_MSG "Listening to UDP messages sent to %s", addresseip);
|
||||
}
|
||||
catch (const std::runtime_error &e) {
|
||||
// arg, the receiver could not be initialized
|
||||
// (often because the port was not available)
|
||||
receiver_ = nullptr;
|
||||
Log::Warning(CONTROL_OSC_MSG "The port %d is already used by another program; %s", Settings::application.control.osc_port_receive, e.what());
|
||||
}
|
||||
|
||||
return receiver_ != nullptr;
|
||||
}
|
||||
|
||||
void Control::update()
|
||||
{
|
||||
// read joystick buttons
|
||||
int num_buttons = 0;
|
||||
const unsigned char *state_buttons = glfwGetJoystickButtons(GLFW_JOYSTICK_1, &num_buttons );
|
||||
// map to Control input array
|
||||
for (int b = 0; b < num_buttons; ++b) {
|
||||
input_access_.lock();
|
||||
input_active[INPUT_JOYSTICK_FIRST_BUTTON + b] = state_buttons[b] == GLFW_PRESS;
|
||||
input_values[INPUT_JOYSTICK_FIRST_BUTTON + b] = state_buttons[b] == GLFW_PRESS ? 1.f : 0.f;
|
||||
input_access_.unlock();
|
||||
}
|
||||
|
||||
// read joystick axis
|
||||
int num_axis = 0;
|
||||
const float *state_axis = glfwGetJoystickAxes(GLFW_JOYSTICK_1, &num_axis );
|
||||
for (int a = 0; a < num_axis; ++a) {
|
||||
input_access_.lock();
|
||||
input_active[INPUT_JOYSTICK_FIRST_AXIS + a] = ABS(state_axis[a]) > 0.02 ? true : false;
|
||||
input_values[INPUT_JOYSTICK_FIRST_AXIS + a] = state_axis[a];
|
||||
input_access_.unlock();
|
||||
}
|
||||
|
||||
// multitouch input needs to be cleared when no more OSC input comes in
|
||||
for (int m = 0; m < INPUT_MULTITOUCH_COUNT; ++m) {
|
||||
if ( multitouch_active[m] > 0 )
|
||||
multitouch_active[m] -= 1;
|
||||
else {
|
||||
input_access_.lock();
|
||||
input_active[INPUT_MULTITOUCH_FIRST + m] = false;
|
||||
input_values[INPUT_MULTITOUCH_FIRST + m] = 0.f;
|
||||
multitouch_values[m] = glm::vec2(0.f);
|
||||
input_access_.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
// draft : react to metronome
|
||||
// int p = (int) Metronome::manager().phase();
|
||||
// static bool bip = false;
|
||||
// static int t = 2;
|
||||
// if (!bip) {
|
||||
// if (p + 1 == t){
|
||||
// g_print("bip");
|
||||
// bip = true;
|
||||
// }
|
||||
// }
|
||||
// else {
|
||||
// if (p + 1 != t){
|
||||
// bip = false;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
void Control::listen()
|
||||
{
|
||||
if (Control::manager().receiver_)
|
||||
Control::manager().receiver_->Run();
|
||||
|
||||
Control::manager().receiver_end_.notify_all();
|
||||
}
|
||||
|
||||
void Control::terminate()
|
||||
{
|
||||
if ( receiver_ != nullptr ) {
|
||||
|
||||
// request termination of receiver
|
||||
receiver_->AsynchronousBreak();
|
||||
|
||||
// wait for the receiver_end_ notification
|
||||
std::mutex mtx;
|
||||
std::unique_lock<std::mutex> lck(mtx);
|
||||
// if waited more than 2 seconds, its dead :(
|
||||
if ( receiver_end_.wait_for(lck,std::chrono::seconds(2)) == std::cv_status::timeout)
|
||||
Log::Warning(CONTROL_OSC_MSG "Failed to terminate; try again.");
|
||||
|
||||
// delete receiver and ready to initialize
|
||||
delete receiver_;
|
||||
receiver_ = nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
bool Control::receiveOutputAttribute(const std::string &attribute,
|
||||
osc::ReceivedMessageArgumentStream arguments)
|
||||
{
|
||||
bool need_feedback = false;
|
||||
|
||||
try {
|
||||
if ( attribute.compare(OSC_SYNC) == 0) {
|
||||
need_feedback = true;
|
||||
}
|
||||
/// e.g. '/vimix/output/enable' or '/vimix/output/enable 1.0' or '/vimix/output/enable 0.0'
|
||||
else if ( attribute.compare(OSC_OUTPUT_ENABLE) == 0) {
|
||||
float on = 1.f;
|
||||
if ( !arguments.Eos()) {
|
||||
arguments >> on >> osc::EndMessage;
|
||||
}
|
||||
Settings::application.render.disabled = on < 0.5f;
|
||||
}
|
||||
/// e.g. '/vimix/output/disable' or '/vimix/output/disable 1.0' or '/vimix/output/disable 0.0'
|
||||
else if ( attribute.compare(OSC_OUTPUT_DISABLE) == 0) {
|
||||
float on = 1.f;
|
||||
if ( !arguments.Eos()) {
|
||||
arguments >> on >> osc::EndMessage;
|
||||
}
|
||||
Settings::application.render.disabled = on > 0.5f;
|
||||
}
|
||||
/// e.g. '/vimix/output/fading f 0.2' or '/vimix/output/fading ff 1.0 300.f'
|
||||
else if ( attribute.compare(OSC_OUTPUT_FADING) == 0) {
|
||||
float f = 0.f, d = 0.f;
|
||||
// first argument is fading value
|
||||
arguments >> f;
|
||||
if (arguments.Eos())
|
||||
arguments >> osc::EndMessage;
|
||||
// if a second argument is given, it is a duration
|
||||
else
|
||||
arguments >> d >> osc::EndMessage;
|
||||
Mixer::manager().session()->setFadingTarget(f, d);
|
||||
}
|
||||
/// e.g. '/vimix/output/fadein' or '/vimix/output/fadein f 300.f'
|
||||
else if ( attribute.compare(OSC_OUTPUT_FADE_IN) == 0) {
|
||||
float f = 0.f;
|
||||
// if argument is given, it is a duration
|
||||
if (!arguments.Eos())
|
||||
arguments >> f >> osc::EndMessage;
|
||||
Mixer::manager().session()->setFadingTarget( Mixer::manager().session()->fading() - f * 0.01);
|
||||
need_feedback = true;
|
||||
}
|
||||
else if ( attribute.compare(OSC_OUTPUT_FADE_OUT) == 0) {
|
||||
float f = 0.f;
|
||||
// if argument is given, it is a duration
|
||||
if (!arguments.Eos())
|
||||
arguments >> f >> osc::EndMessage;
|
||||
Mixer::manager().session()->setFadingTarget( Mixer::manager().session()->fading() + f * 0.01);
|
||||
need_feedback = true;
|
||||
}
|
||||
#ifdef CONTROL_DEBUG
|
||||
else {
|
||||
Log::Info(CONTROL_OSC_MSG "Ignoring attribute '%s' for target 'output'", attribute.c_str());
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
catch (osc::MissingArgumentException &e) {
|
||||
Log::Info(CONTROL_OSC_MSG "Missing argument for attribute '%s' for target 'output'", attribute.c_str());
|
||||
}
|
||||
catch (osc::ExcessArgumentException &e) {
|
||||
Log::Info(CONTROL_OSC_MSG "Too many arguments for attribute '%s' for target 'output'", attribute.c_str());
|
||||
}
|
||||
catch (osc::WrongArgumentTypeException &e) {
|
||||
Log::Info(CONTROL_OSC_MSG "Invalid argument for attribute '%s' for target 'output'", attribute.c_str());
|
||||
}
|
||||
|
||||
return need_feedback;
|
||||
}
|
||||
|
||||
bool Control::receiveSourceAttribute(Source *target, const std::string &attribute,
|
||||
osc::ReceivedMessageArgumentStream arguments)
|
||||
{
|
||||
bool send_feedback = false;
|
||||
|
||||
if (target == nullptr)
|
||||
return send_feedback;
|
||||
|
||||
try {
|
||||
/// e.g. '/vimix/current/play' or '/vimix/current/play T' or '/vimix/current/play F'
|
||||
if ( attribute.compare(OSC_SOURCE_PLAY) == 0) {
|
||||
float on = 1.f;
|
||||
if ( !arguments.Eos()) {
|
||||
arguments >> on >> osc::EndMessage;
|
||||
}
|
||||
target->call( new Play(on > 0.5f) );
|
||||
}
|
||||
/// e.g. '/vimix/current/pause' or '/vimix/current/pause T' or '/vimix/current/pause F'
|
||||
else if ( attribute.compare(OSC_SOURCE_PAUSE) == 0) {
|
||||
float on = 1.f;
|
||||
if ( !arguments.Eos()) {
|
||||
arguments >> on >> osc::EndMessage;
|
||||
}
|
||||
target->call( new Play(on < 0.5f) );
|
||||
}
|
||||
/// e.g. '/vimix/current/replay'
|
||||
else if ( attribute.compare(OSC_SOURCE_REPLAY) == 0) {
|
||||
target->call( new RePlay() );
|
||||
}
|
||||
/// e.g. '/vimix/current/alpha f 0.3'
|
||||
else if ( attribute.compare(OSC_SOURCE_LOCK) == 0) {
|
||||
float x = 1.f;
|
||||
arguments >> x >> osc::EndMessage;
|
||||
target->call( new Lock(x > 0.5f ? true : false) );
|
||||
}
|
||||
/// e.g. '/vimix/current/alpha f 0.3'
|
||||
else if ( attribute.compare(OSC_SOURCE_ALPHA) == 0) {
|
||||
float x = 1.f;
|
||||
arguments >> x >> osc::EndMessage;
|
||||
target->call( new SetAlpha(x), true );
|
||||
}
|
||||
/// e.g. '/vimix/current/alpha f 0.3'
|
||||
else if ( attribute.compare(OSC_SOURCE_LOOM) == 0) {
|
||||
float x = 1.f;
|
||||
arguments >> x >> osc::EndMessage;
|
||||
target->call( new Loom(x, 0.f), true );
|
||||
// this will require to send feedback status about source
|
||||
send_feedback = true;
|
||||
}
|
||||
/// e.g. '/vimix/current/transparency f 0.7'
|
||||
else if ( attribute.compare(OSC_SOURCE_TRANSPARENCY) == 0) {
|
||||
float x = 0.f;
|
||||
arguments >> x >> osc::EndMessage;
|
||||
target->call( new SetAlpha(1.f - x), true );
|
||||
}
|
||||
/// e.g. '/vimix/current/depth f 5.0'
|
||||
else if ( attribute.compare(OSC_SOURCE_DEPTH) == 0) {
|
||||
float x = 0.f;
|
||||
arguments >> x >> osc::EndMessage;
|
||||
target->call( new SetDepth(x), true );
|
||||
}
|
||||
/// e.g. '/vimix/current/translation ff 10.0 2.2'
|
||||
else if ( attribute.compare(OSC_SOURCE_GRAB) == 0) {
|
||||
float x = 0.f, y = 0.f;
|
||||
arguments >> x >> y >> osc::EndMessage;
|
||||
target->call( new Grab( x, y, 0.f), true );
|
||||
}
|
||||
/// e.g. '/vimix/current/scale ff 10.0 2.2'
|
||||
else if ( attribute.compare(OSC_SOURCE_RESIZE) == 0) {
|
||||
float x = 0.f, y = 0.f;
|
||||
arguments >> x >> y >> osc::EndMessage;
|
||||
target->call( new Resize( x, y, 0.f), true );
|
||||
}
|
||||
/// e.g. '/vimix/current/turn f 1.0'
|
||||
else if ( attribute.compare(OSC_SOURCE_TURN) == 0) {
|
||||
float x = 0.f, y = 0.f;
|
||||
arguments >> x;
|
||||
if (arguments.Eos())
|
||||
arguments >> osc::EndMessage;
|
||||
else // ignore second argument
|
||||
arguments >> y >> osc::EndMessage;
|
||||
target->call( new Turn( x, 0.f), true );
|
||||
}
|
||||
/// e.g. '/vimix/current/reset'
|
||||
else if ( attribute.compare(OSC_SOURCE_RESET) == 0) {
|
||||
target->call( new ResetGeometry(), true );
|
||||
}
|
||||
#ifdef CONTROL_DEBUG
|
||||
else {
|
||||
Log::Info(CONTROL_OSC_MSG "Ignoring attribute '%s' for target %s.", attribute.c_str(), target->name().c_str());
|
||||
}
|
||||
#endif
|
||||
|
||||
// overwrite value if source locked
|
||||
if (target->locked())
|
||||
send_feedback = true;
|
||||
|
||||
}
|
||||
catch (osc::MissingArgumentException &e) {
|
||||
Log::Info(CONTROL_OSC_MSG "Missing argument for attribute '%s' for target %s.", attribute.c_str(), target->name().c_str());
|
||||
}
|
||||
catch (osc::ExcessArgumentException &e) {
|
||||
Log::Info(CONTROL_OSC_MSG "Too many arguments for attribute '%s' for target %s.", attribute.c_str(), target->name().c_str());
|
||||
}
|
||||
catch (osc::WrongArgumentTypeException &e) {
|
||||
Log::Info(CONTROL_OSC_MSG "Invalid argument for attribute '%s' for target %s.", attribute.c_str(), target->name().c_str());
|
||||
}
|
||||
|
||||
return send_feedback;
|
||||
}
|
||||
|
||||
bool Control::receiveSessionAttribute(const std::string &attribute,
|
||||
osc::ReceivedMessageArgumentStream arguments)
|
||||
{
|
||||
bool send_feedback = false;
|
||||
|
||||
try {
|
||||
if ( attribute.compare(OSC_SYNC) == 0) {
|
||||
send_feedback = true;
|
||||
}
|
||||
else if ( attribute.compare(OSC_SESSION_VERSION) == 0) {
|
||||
float v = 0.f;
|
||||
arguments >> v >> osc::EndMessage;
|
||||
size_t id = (int) ceil(v);
|
||||
std::list<uint64_t> snapshots = Action::manager().snapshots();
|
||||
if ( id < snapshots.size() ) {
|
||||
for (size_t i = 0; i < id; ++i)
|
||||
snapshots.pop_back();
|
||||
uint64_t snap = snapshots.back();
|
||||
Action::manager().restore(snap);
|
||||
}
|
||||
send_feedback = true;
|
||||
}
|
||||
#ifdef CONTROL_DEBUG
|
||||
else {
|
||||
Log::Info(CONTROL_OSC_MSG "Ignoring attribute '%s' for target 'session'", attribute.c_str());
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
catch (osc::MissingArgumentException &e) {
|
||||
Log::Info(CONTROL_OSC_MSG "Missing argument for attribute '%s' for target 'session'", attribute.c_str());
|
||||
}
|
||||
catch (osc::ExcessArgumentException &e) {
|
||||
Log::Info(CONTROL_OSC_MSG "Too many arguments for attribute '%s' for target 'session'", attribute.c_str());
|
||||
}
|
||||
catch (osc::WrongArgumentTypeException &e) {
|
||||
Log::Info(CONTROL_OSC_MSG "Invalid argument for attribute '%s' for target 'session'", attribute.c_str());
|
||||
}
|
||||
|
||||
return send_feedback;
|
||||
}
|
||||
|
||||
void Control::receiveMultitouchAttribute(const std::string &attribute,
|
||||
osc::ReceivedMessageArgumentStream arguments)
|
||||
{
|
||||
try {
|
||||
// address should be in the form /vimix/multitouch/i
|
||||
int t = -1;
|
||||
if ( BaseToolkit::is_a_number(attribute.substr(1), &t) && t >= 0 && t < INPUT_MULTITOUCH_COUNT )
|
||||
{
|
||||
// get value inputs
|
||||
float x = 0.f, y = 0.f;
|
||||
if ( !arguments.Eos())
|
||||
arguments >> x >> y >> osc::EndMessage;
|
||||
|
||||
input_access_.lock();
|
||||
|
||||
// if the touch was already pressed
|
||||
if ( multitouch_active[t] > 0 ) {
|
||||
// active value decreases with the distance from original press position
|
||||
input_values[INPUT_MULTITOUCH_FIRST + t] = 1.f - glm::distance(Control::multitouch_values[t], glm::vec2(x, y)) / M_SQRT2;
|
||||
}
|
||||
// first time touch is pressed
|
||||
else {
|
||||
// store original press position
|
||||
multitouch_values[t] = glm::vec2(x, y);
|
||||
// active value is 1.f at first press (full)
|
||||
input_values[INPUT_MULTITOUCH_FIRST + t] = 1.f;
|
||||
}
|
||||
// keep track of button press
|
||||
multitouch_active[t] = 3;
|
||||
// set array of active input
|
||||
input_active[INPUT_MULTITOUCH_FIRST + t] = true;
|
||||
|
||||
input_access_.unlock();
|
||||
}
|
||||
}
|
||||
catch (osc::MissingArgumentException &e) {
|
||||
Log::Info(CONTROL_OSC_MSG "Missing argument for attribute '%s' for target %s.", attribute.c_str(), OSC_MULTITOUCH);
|
||||
}
|
||||
catch (osc::ExcessArgumentException &e) {
|
||||
Log::Info(CONTROL_OSC_MSG "Too many arguments for attribute '%s' for target %s.", attribute.c_str(), OSC_MULTITOUCH);
|
||||
}
|
||||
catch (osc::WrongArgumentTypeException &e) {
|
||||
Log::Info(CONTROL_OSC_MSG "Invalid argument for attribute '%s' for target %s.", attribute.c_str(), OSC_MULTITOUCH);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Control::sendSourceAttibutes(const IpEndpointName &remoteEndpoint, std::string target, Source *s)
|
||||
{
|
||||
// default values
|
||||
char name[21] = {"\0"};
|
||||
float lock = 0.f;
|
||||
float play = 0.f;
|
||||
float depth = 0.f;
|
||||
float alpha = 0.f;
|
||||
|
||||
// get source or current source
|
||||
Source *_s = s;
|
||||
if ( target.compare(OSC_CURRENT) == 0 )
|
||||
_s = Mixer::manager().currentSource();
|
||||
|
||||
// fill values if the source is valid
|
||||
if (_s!=nullptr) {
|
||||
strncpy(name, _s->name().c_str(), 20);
|
||||
lock = _s->locked() ? 1.f : 0.f;
|
||||
play = _s->playing() ? 1.f : 0.f;
|
||||
depth = _s->depth();
|
||||
alpha = _s->alpha();
|
||||
}
|
||||
|
||||
// build socket to send message to indicated endpoint
|
||||
UdpTransmitSocket socket( IpEndpointName( remoteEndpoint.address, Settings::application.control.osc_port_send ) );
|
||||
|
||||
// build messages packet
|
||||
char buffer[IP_MTU_SIZE];
|
||||
osc::OutboundPacketStream p( buffer, IP_MTU_SIZE );
|
||||
|
||||
// create bundle
|
||||
p.Clear();
|
||||
p << osc::BeginBundle();
|
||||
|
||||
/// name
|
||||
std::string address = std::string(OSC_PREFIX) + target + OSC_SOURCE_NAME;
|
||||
p << osc::BeginMessage( address.c_str() ) << name << osc::EndMessage;
|
||||
/// Play status
|
||||
address = std::string(OSC_PREFIX) + target + OSC_SOURCE_LOCK;
|
||||
p << osc::BeginMessage( address.c_str() ) << lock << osc::EndMessage;
|
||||
/// Play status
|
||||
address = std::string(OSC_PREFIX) + target + OSC_SOURCE_PLAY;
|
||||
p << osc::BeginMessage( address.c_str() ) << play << osc::EndMessage;
|
||||
/// Depth
|
||||
address = std::string(OSC_PREFIX) + target + OSC_SOURCE_DEPTH;
|
||||
p << osc::BeginMessage( address.c_str() ) << depth << osc::EndMessage;
|
||||
/// Alpha
|
||||
address = std::string(OSC_PREFIX) + target + OSC_SOURCE_ALPHA;
|
||||
p << osc::BeginMessage( address.c_str() ) << alpha << osc::EndMessage;
|
||||
|
||||
// send bundle
|
||||
p << osc::EndBundle;
|
||||
socket.Send( p.Data(), p.Size() );
|
||||
}
|
||||
|
||||
|
||||
void Control::sendSourcesStatus(const IpEndpointName &remoteEndpoint, osc::ReceivedMessageArgumentStream arguments)
|
||||
{
|
||||
// (if an argument is given, it indicates the number of sources to update)
|
||||
float N = 0.f;
|
||||
if ( !arguments.Eos())
|
||||
arguments >> N >> osc::EndMessage;
|
||||
|
||||
// build socket to send message to indicated endpoint
|
||||
UdpTransmitSocket socket( IpEndpointName( remoteEndpoint.address, Settings::application.control.osc_port_send ) );
|
||||
|
||||
// build messages packet
|
||||
char buffer[IP_MTU_SIZE];
|
||||
osc::OutboundPacketStream p( buffer, IP_MTU_SIZE );
|
||||
|
||||
p.Clear();
|
||||
p << osc::BeginBundle();
|
||||
|
||||
int i = 0;
|
||||
char oscaddr[128];
|
||||
int index_current = Mixer::manager().indexCurrentSource();
|
||||
for (; i < Mixer::manager().numSource(); ++i) {
|
||||
// send status of currently selected
|
||||
sprintf(oscaddr, OSC_PREFIX OSC_CURRENT "/%d", i);
|
||||
p << osc::BeginMessage( oscaddr ) << (index_current == i ? 1.f : 0.f) << osc::EndMessage;
|
||||
|
||||
// send status of alpha
|
||||
sprintf(oscaddr, OSC_PREFIX "/%d" OSC_SOURCE_ALPHA, i);
|
||||
p << osc::BeginMessage( oscaddr ) << Mixer::manager().sourceAtIndex(i)->alpha() << osc::EndMessage;
|
||||
}
|
||||
|
||||
for (; i < (int) N ; ++i) {
|
||||
// reset status of currently selected
|
||||
sprintf(oscaddr, OSC_PREFIX OSC_CURRENT "/%d", i);
|
||||
p << osc::BeginMessage( oscaddr ) << 0.f << osc::EndMessage;
|
||||
|
||||
// reset status of alpha
|
||||
sprintf(oscaddr, OSC_PREFIX "/%d" OSC_SOURCE_ALPHA, i);
|
||||
p << osc::BeginMessage( oscaddr ) << 0.f << osc::EndMessage;
|
||||
}
|
||||
|
||||
p << osc::EndBundle;
|
||||
socket.Send( p.Data(), p.Size() );
|
||||
|
||||
// send status of current source
|
||||
sendSourceAttibutes(remoteEndpoint, OSC_CURRENT);
|
||||
}
|
||||
|
||||
|
||||
void Control::sendOutputStatus(const IpEndpointName &remoteEndpoint)
|
||||
{
|
||||
// build socket to send message to indicated endpoint
|
||||
UdpTransmitSocket socket( IpEndpointName( remoteEndpoint.address, Settings::application.control.osc_port_send ) );
|
||||
|
||||
// build messages packet
|
||||
char buffer[IP_MTU_SIZE];
|
||||
osc::OutboundPacketStream p( buffer, IP_MTU_SIZE );
|
||||
|
||||
p.Clear();
|
||||
p << osc::BeginBundle();
|
||||
|
||||
/// output attributes
|
||||
p << osc::BeginMessage( OSC_PREFIX OSC_OUTPUT OSC_OUTPUT_ENABLE );
|
||||
p << (Settings::application.render.disabled ? 0.f : 1.f);
|
||||
p << osc::EndMessage;
|
||||
p << osc::BeginMessage( OSC_PREFIX OSC_OUTPUT OSC_OUTPUT_FADING );
|
||||
p << Mixer::manager().session()->fading();
|
||||
p << osc::EndMessage;
|
||||
|
||||
p << osc::EndBundle;
|
||||
socket.Send( p.Data(), p.Size() );
|
||||
}
|
||||
|
||||
|
||||
void Control::keyboardCalback(GLFWwindow* window, int key, int, int action, int mods)
|
||||
{
|
||||
if (UserInterface::manager().keyboardAvailable() && !mods )
|
||||
{
|
||||
Control::manager().input_access_.lock();
|
||||
if (key >= GLFW_KEY_A && key <= GLFW_KEY_Z) {
|
||||
Control::manager().input_active[INPUT_KEYBOARD_FIRST + key - GLFW_KEY_A] = action > GLFW_RELEASE;
|
||||
Control::manager().input_values[INPUT_KEYBOARD_FIRST + key - GLFW_KEY_A] = action > GLFW_RELEASE ? 1.f : 0.f;
|
||||
}
|
||||
else if (key >= GLFW_KEY_KP_0 && key <= GLFW_KEY_KP_EQUAL) {
|
||||
Control::manager().input_active[INPUT_NUMPAD_FIRST + key - GLFW_KEY_KP_0] = action > GLFW_RELEASE;
|
||||
Control::manager().input_values[INPUT_NUMPAD_FIRST + key - GLFW_KEY_KP_0] = action > GLFW_RELEASE ? 1.f : 0.f;
|
||||
}
|
||||
else if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS )
|
||||
{
|
||||
static GLFWwindow *output = Rendering::manager().outputWindow().window();
|
||||
if (window==output)
|
||||
Rendering::manager().outputWindow().exitFullscreen();
|
||||
}
|
||||
Control::manager().input_access_.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
bool Control::inputActive (uint id)
|
||||
{
|
||||
input_access_.lock();
|
||||
const bool ret = input_active[MIN(id,INPUT_MAX)];
|
||||
input_access_.unlock();
|
||||
|
||||
return ret && !Settings::application.mapping.disabled;
|
||||
}
|
||||
|
||||
float Control::inputValue (uint id)
|
||||
{
|
||||
input_access_.lock();
|
||||
const float ret = input_values[MIN(id,INPUT_MAX)];
|
||||
input_access_.unlock();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string Control::inputLabel(uint id)
|
||||
{
|
||||
std::string label;
|
||||
|
||||
if ( id >= INPUT_KEYBOARD_FIRST && id <= INPUT_KEYBOARD_LAST )
|
||||
{
|
||||
label = std::string( 1, 'A' + (char) (id -INPUT_KEYBOARD_FIRST) );
|
||||
}
|
||||
else if ( id >= INPUT_NUMPAD_FIRST && id <= INPUT_NUMPAD_LAST )
|
||||
{
|
||||
static std::vector< std::string > pad_labels = { "0", "1", "2", "3", "4", "5",
|
||||
"6", "7", "8", "9", ".", "/", "*", "-", "+"
|
||||
};
|
||||
label = pad_labels[id -INPUT_NUMPAD_FIRST];
|
||||
}
|
||||
else if ( id >= INPUT_JOYSTICK_FIRST && id <= INPUT_JOYSTICK_LAST )
|
||||
{
|
||||
static std::vector< std::string > joystick_labels = { "Button A", "Button B", "Button X", "Button Y",
|
||||
"Left bumper", "Right bumper", "Back", "Start",
|
||||
"Guide", "Left thumb", "Right thumb",
|
||||
"Up", "Right", "Down", "Left",
|
||||
"Left Axis X", "Left Axis Y", "Left Trigger",
|
||||
"Right Axis X", "Right Axis Y", "Right Trigger" };
|
||||
label = joystick_labels[id - INPUT_JOYSTICK_FIRST];
|
||||
}
|
||||
else if ( id >= INPUT_MULTITOUCH_FIRST && id <= INPUT_MULTITOUCH_LAST )
|
||||
{
|
||||
label = std::string( "Multitouch ") + std::to_string(id - INPUT_MULTITOUCH_FIRST);
|
||||
}
|
||||
else if ( id >= INPUT_CUSTOM_FIRST && id <= INPUT_CUSTOM_LAST )
|
||||
{
|
||||
label = std::string( "Custom ") + std::to_string(id - INPUT_CUSTOM_FIRST);
|
||||
}
|
||||
|
||||
return label;
|
||||
}
|
||||
149
DeviceSource.h
@@ -1,149 +0,0 @@
|
||||
#ifndef DEVICESOURCE_H
|
||||
#define DEVICESOURCE_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
|
||||
#include "GstToolkit.h"
|
||||
#include "StreamSource.h"
|
||||
|
||||
class DeviceSource : public StreamSource
|
||||
{
|
||||
public:
|
||||
DeviceSource(uint64_t id = 0);
|
||||
~DeviceSource();
|
||||
|
||||
// Source interface
|
||||
bool failed() const override;
|
||||
void accept (Visitor& v) override;
|
||||
void setActive (bool on) override;
|
||||
|
||||
// StreamSource interface
|
||||
Stream *stream() const override { return stream_; }
|
||||
|
||||
// specific interface
|
||||
void setDevice(const std::string &devicename);
|
||||
inline std::string device() const { return device_; }
|
||||
void unplug() { unplugged_ = true; }
|
||||
|
||||
glm::ivec2 icon() const override;
|
||||
std::string info() const override;
|
||||
|
||||
private:
|
||||
std::string device_;
|
||||
std::atomic<bool> unplugged_;
|
||||
void unsetDevice();
|
||||
};
|
||||
|
||||
struct DeviceConfig {
|
||||
gint width;
|
||||
gint height;
|
||||
gint fps_numerator;
|
||||
gint fps_denominator;
|
||||
std::string stream;
|
||||
std::string format;
|
||||
|
||||
DeviceConfig() {
|
||||
width = 0;
|
||||
height = 0;
|
||||
fps_numerator = 30;
|
||||
fps_denominator = 1;
|
||||
stream = "";
|
||||
format = "";
|
||||
}
|
||||
|
||||
inline DeviceConfig& operator = (const DeviceConfig& b)
|
||||
{
|
||||
if (this != &b) {
|
||||
this->width = b.width;
|
||||
this->height = b.height;
|
||||
this->fps_numerator = b.fps_numerator;
|
||||
this->fps_denominator = b.fps_denominator;
|
||||
this->stream = b.stream;
|
||||
this->format = b.format;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline bool operator < (const DeviceConfig b) const
|
||||
{
|
||||
int formatscore = this->format.find("R") != std::string::npos ? 2 : 1; // best score for RGBx
|
||||
int b_formatscore = b.format.find("R") != std::string::npos ? 2 : 1;
|
||||
float fps = static_cast<float>(this->fps_numerator) / static_cast<float>(this->fps_denominator);
|
||||
float b_fps = static_cast<float>(b.fps_numerator) / static_cast<float>(b.fps_denominator);
|
||||
return ( fps * static_cast<float>(this->height * formatscore) < b_fps * static_cast<float>(b.height * b_formatscore));
|
||||
}
|
||||
};
|
||||
|
||||
struct better_device_comparator
|
||||
{
|
||||
inline bool operator () (const DeviceConfig a, const DeviceConfig b) const
|
||||
{
|
||||
return (a < b);
|
||||
}
|
||||
};
|
||||
|
||||
typedef std::set<DeviceConfig, better_device_comparator> DeviceConfigSet;
|
||||
|
||||
struct DeviceHandle {
|
||||
|
||||
std::string name;
|
||||
std::string pipeline;
|
||||
DeviceConfigSet configs;
|
||||
|
||||
Stream *stream;
|
||||
std::list<DeviceSource *> connected_sources;
|
||||
|
||||
DeviceHandle() {
|
||||
stream = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
class Device
|
||||
{
|
||||
friend class DeviceSource;
|
||||
|
||||
Device();
|
||||
Device(Device const& copy) = delete;
|
||||
Device& operator=(Device const& copy) = delete;
|
||||
|
||||
public:
|
||||
|
||||
static Device& manager()
|
||||
{
|
||||
// The only instance
|
||||
static Device _instance;
|
||||
return _instance;
|
||||
}
|
||||
|
||||
int numDevices () ;
|
||||
std::string name (int index) ;
|
||||
std::string description (int index) ;
|
||||
DeviceConfigSet config (int index) ;
|
||||
|
||||
int index (const std::string &device);
|
||||
bool exists (const std::string &device) ;
|
||||
|
||||
static gboolean callback_device_monitor (GstBus *, GstMessage *, gpointer);
|
||||
static DeviceConfigSet getDeviceConfigs(const std::string &src_description);
|
||||
|
||||
private:
|
||||
|
||||
static void launchMonitoring(Device *d);
|
||||
static bool initialized();
|
||||
void remove(GstDevice *device);
|
||||
void add(GstDevice *device);
|
||||
|
||||
std::mutex access_;
|
||||
std::vector< DeviceHandle > handles_;
|
||||
|
||||
GstDeviceMonitor *monitor_;
|
||||
std::condition_variable monitor_initialization_;
|
||||
bool monitor_initialized_;
|
||||
bool monitor_unplug_event_;
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif // DEVICESOURCE_H
|
||||
@@ -1,624 +0,0 @@
|
||||
/*
|
||||
* This file is part of vimix - video live mixer
|
||||
*
|
||||
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||
*
|
||||
* 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 3 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, see <https://www.gnu.org/licenses/>.
|
||||
**/
|
||||
|
||||
|
||||
// multiplatform implementation of system dialogs
|
||||
//
|
||||
// 'TinyFileDialog' from https://github.com/native-toolkit/tinyfiledialogs.git
|
||||
// is the prefered solution, but it is not compatible with linux snap packaging
|
||||
// because of 'zenity' access rights nightmare :(
|
||||
// Thus this re-implementation of native GTK+ dialogs for linux
|
||||
|
||||
#include "defines.h"
|
||||
#include "Settings.h"
|
||||
#include "SystemToolkit.h"
|
||||
#include "DialogToolkit.h"
|
||||
|
||||
#if defined(LINUX)
|
||||
#define USE_TINYFILEDIALOG 0
|
||||
#else
|
||||
#define USE_TINYFILEDIALOG 1
|
||||
#endif
|
||||
|
||||
#if USE_TINYFILEDIALOG
|
||||
#include "tinyfiledialogs.h"
|
||||
#else
|
||||
#include <stdio.h>
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
static bool gtk_init_ok = false;
|
||||
static int window_x = -1, window_y = -1;
|
||||
|
||||
void add_filter_any_file_dialog( GtkWidget *dialog)
|
||||
{
|
||||
/* append a wildcard option */
|
||||
GtkFileFilter *filter;
|
||||
filter = gtk_file_filter_new();
|
||||
gtk_file_filter_set_name( filter, "Any file" );
|
||||
gtk_file_filter_add_pattern( filter, "*" );
|
||||
gtk_file_chooser_add_filter( GTK_FILE_CHOOSER(dialog), filter );
|
||||
}
|
||||
|
||||
void add_filter_file_dialog( GtkWidget *dialog, int const countfilterPatterns, char const * const * const filterPatterns, char const * const filterDescription)
|
||||
{
|
||||
GtkFileFilter *filter;
|
||||
filter = gtk_file_filter_new();
|
||||
gtk_file_filter_set_name( filter, filterDescription );
|
||||
for (int i = 0; i < countfilterPatterns; i++ ) {
|
||||
gtk_file_filter_add_pattern( filter, g_ascii_strdown(filterPatterns[i], -1) );
|
||||
gtk_file_filter_add_pattern( filter, g_ascii_strup(filterPatterns[i], -1) );
|
||||
}
|
||||
gtk_file_chooser_add_filter( GTK_FILE_CHOOSER(dialog), filter );
|
||||
}
|
||||
|
||||
void wait_for_event(void)
|
||||
{
|
||||
while ( gtk_events_pending() )
|
||||
gtk_main_iteration();
|
||||
}
|
||||
|
||||
bool gtk_init()
|
||||
{
|
||||
if (!gtk_init_ok) {
|
||||
if ( gtk_init_check( NULL, NULL ) )
|
||||
gtk_init_ok = true;
|
||||
}
|
||||
return gtk_init_ok;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
// globals
|
||||
const std::chrono::milliseconds timeout = std::chrono::milliseconds(4);
|
||||
bool DialogToolkit::FileDialog::busy_ = false;
|
||||
|
||||
//
|
||||
// FileDialog common functions
|
||||
//
|
||||
DialogToolkit::FileDialog::FileDialog(const std::string &name) : id_(name)
|
||||
{
|
||||
if ( Settings::application.dialogRecentFolder.count(id_) == 0 )
|
||||
Settings::application.dialogRecentFolder[id_] = SystemToolkit::home_path();
|
||||
}
|
||||
|
||||
bool DialogToolkit::FileDialog::closed()
|
||||
{
|
||||
if ( !promises_.empty() ) {
|
||||
// check that file dialog thread finished
|
||||
if (promises_.back().wait_for(timeout) == std::future_status::ready ) {
|
||||
// get the filename from this file dialog
|
||||
path_ = promises_.back().get();
|
||||
if (!path_.empty()) {
|
||||
// save path location
|
||||
Settings::application.dialogRecentFolder[id_] = SystemToolkit::path_filename(path_);
|
||||
}
|
||||
// done with this file dialog
|
||||
promises_.pop_back();
|
||||
busy_ = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// type specific implementations
|
||||
//
|
||||
std::string openImageFileDialog(const std::string &label, const std::string &path);
|
||||
void DialogToolkit::OpenImageDialog::open()
|
||||
{
|
||||
if ( !busy_ && promises_.empty() ) {
|
||||
promises_.emplace_back( std::async(std::launch::async, openImageFileDialog, id_,
|
||||
Settings::application.dialogRecentFolder[id_]) );
|
||||
busy_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
std::string openSessionFileDialog(const std::string &label, const std::string &path);
|
||||
void DialogToolkit::OpenSessionDialog::open()
|
||||
{
|
||||
if ( !busy_ && promises_.empty() ) {
|
||||
promises_.emplace_back( std::async(std::launch::async, openSessionFileDialog, id_,
|
||||
Settings::application.dialogRecentFolder[id_]) );
|
||||
busy_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
std::string openMediaFileDialog(const std::string &label, const std::string &path);
|
||||
void DialogToolkit::OpenMediaDialog::open()
|
||||
{
|
||||
if ( !busy_ && promises_.empty() ) {
|
||||
promises_.emplace_back( std::async(std::launch::async, openMediaFileDialog, id_,
|
||||
Settings::application.dialogRecentFolder[id_]) );
|
||||
busy_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
std::string saveSessionFileDialog(const std::string &label, const std::string &path);
|
||||
void DialogToolkit::SaveSessionDialog::open()
|
||||
{
|
||||
if ( !busy_ && promises_.empty() ) {
|
||||
promises_.emplace_back( std::async(std::launch::async, saveSessionFileDialog, id_,
|
||||
Settings::application.dialogRecentFolder[id_]) );
|
||||
busy_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void DialogToolkit::SaveSessionDialog::setFolder(std::string path)
|
||||
{
|
||||
Settings::application.dialogRecentFolder[id_] = SystemToolkit::path_filename( path );
|
||||
}
|
||||
|
||||
std::string openFolderDialog(const std::string &label, const std::string &path);
|
||||
void DialogToolkit::OpenFolderDialog::open()
|
||||
{
|
||||
if ( !busy_ && promises_.empty() ) {
|
||||
promises_.emplace_back( std::async(std::launch::async, openFolderDialog, id_,
|
||||
Settings::application.dialogRecentFolder[id_]) );
|
||||
busy_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
std::list<std::string> selectImagesFileDialog(const std::string &label,const std::string &path);
|
||||
void DialogToolkit::MultipleImagesDialog::open()
|
||||
{
|
||||
if ( !busy_ && promisedlist_.empty() ) {
|
||||
promisedlist_.emplace_back( std::async(std::launch::async, selectImagesFileDialog, id_,
|
||||
Settings::application.dialogRecentFolder[id_]) );
|
||||
busy_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool DialogToolkit::MultipleImagesDialog::closed()
|
||||
{
|
||||
if ( !promisedlist_.empty() ) {
|
||||
// check that file dialog thread finished
|
||||
if (promisedlist_.back().wait_for(timeout) == std::future_status::ready ) {
|
||||
// get the filename from this file dialog
|
||||
std::list<std::string> list = promisedlist_.back().get();
|
||||
if (!list.empty()) {
|
||||
// selected a filenames
|
||||
pathlist_ = list;
|
||||
path_ = list.front();
|
||||
// save path location
|
||||
Settings::application.dialogRecentFolder[id_] = SystemToolkit::path_filename(path_);
|
||||
}
|
||||
else {
|
||||
pathlist_.clear();
|
||||
path_.clear();
|
||||
}
|
||||
// done with this file dialog
|
||||
promisedlist_.pop_back();
|
||||
busy_ = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
// CALLBACKS
|
||||
//
|
||||
//
|
||||
|
||||
std::string saveSessionFileDialog(const std::string &label, const std::string &path)
|
||||
{
|
||||
std::string filename = "";
|
||||
char const * save_pattern[1] = { VIMIX_FILE_PATTERN };
|
||||
|
||||
#if USE_TINYFILEDIALOG
|
||||
char const * save_file_name;
|
||||
|
||||
save_file_name = tinyfd_saveFileDialog( label.c_str(), path.c_str(), 1, save_pattern, "vimix session");
|
||||
|
||||
if (save_file_name)
|
||||
filename = std::string(save_file_name);
|
||||
#else
|
||||
if (!gtk_init()) {
|
||||
DialogToolkit::ErrorDialog("Could not initialize GTK+ for dialog");
|
||||
return filename;
|
||||
}
|
||||
|
||||
GtkWidget *dialog = gtk_file_chooser_dialog_new( label.c_str(), NULL,
|
||||
GTK_FILE_CHOOSER_ACTION_SAVE,
|
||||
"_Cancel", GTK_RESPONSE_CANCEL,
|
||||
"_Save", GTK_RESPONSE_ACCEPT, NULL );
|
||||
gtk_file_chooser_set_do_overwrite_confirmation( GTK_FILE_CHOOSER(dialog), TRUE );
|
||||
|
||||
// set file filters
|
||||
add_filter_file_dialog(dialog, 1, save_pattern, "vimix session");
|
||||
add_filter_any_file_dialog(dialog);
|
||||
|
||||
// Set the default path
|
||||
gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER(dialog), path.c_str() );
|
||||
|
||||
// ensure front and centered
|
||||
gtk_window_set_keep_above( GTK_WINDOW(dialog), TRUE );
|
||||
if (window_x > 0 && window_y > 0)
|
||||
gtk_window_move( GTK_WINDOW(dialog), window_x, window_y);
|
||||
|
||||
// display and get filename
|
||||
if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT ) {
|
||||
|
||||
char *save_file_name = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog) );
|
||||
if (save_file_name) {
|
||||
filename = std::string(save_file_name);
|
||||
g_free( save_file_name );
|
||||
}
|
||||
}
|
||||
|
||||
// remember position
|
||||
gtk_window_get_position( GTK_WINDOW(dialog), &window_x, &window_y);
|
||||
|
||||
// done
|
||||
gtk_widget_destroy(dialog);
|
||||
wait_for_event();
|
||||
#endif
|
||||
|
||||
if (!filename.empty() && !SystemToolkit::has_extension(filename, VIMIX_FILE_EXT ) )
|
||||
filename += std::string(".") + VIMIX_FILE_EXT;
|
||||
|
||||
return filename;
|
||||
}
|
||||
|
||||
|
||||
std::string openSessionFileDialog(const std::string &label, const std::string &path)
|
||||
{
|
||||
std::string filename = "";
|
||||
std::string startpath = SystemToolkit::file_exists(path) ? path : SystemToolkit::home_path();
|
||||
char const * open_pattern[1] = { VIMIX_FILE_PATTERN };
|
||||
|
||||
#if USE_TINYFILEDIALOG
|
||||
char const * open_file_name;
|
||||
open_file_name = tinyfd_openFileDialog( label.c_str(), startpath.c_str(), 1, open_pattern, "vimix session", 0);
|
||||
|
||||
if (open_file_name)
|
||||
filename = std::string(open_file_name);
|
||||
#else
|
||||
if (!gtk_init()) {
|
||||
DialogToolkit::ErrorDialog("Could not initialize GTK+ for dialog");
|
||||
return filename;
|
||||
}
|
||||
|
||||
GtkWidget *dialog = gtk_file_chooser_dialog_new( label.c_str(), NULL,
|
||||
GTK_FILE_CHOOSER_ACTION_OPEN,
|
||||
"_Cancel", GTK_RESPONSE_CANCEL,
|
||||
"_Open", GTK_RESPONSE_ACCEPT, NULL );
|
||||
|
||||
// set file filters
|
||||
add_filter_file_dialog(dialog, 1, open_pattern, "vimix session");
|
||||
add_filter_any_file_dialog(dialog);
|
||||
|
||||
// Set the default path
|
||||
gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER(dialog), startpath.c_str() );
|
||||
|
||||
// ensure front and centered
|
||||
gtk_window_set_keep_above( GTK_WINDOW(dialog), TRUE );
|
||||
if (window_x > 0 && window_y > 0)
|
||||
gtk_window_move( GTK_WINDOW(dialog), window_x, window_y);
|
||||
|
||||
// display and get filename
|
||||
if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT ) {
|
||||
|
||||
char *open_file_name = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog) );
|
||||
if (open_file_name) {
|
||||
filename = std::string(open_file_name);
|
||||
g_free( open_file_name );
|
||||
}
|
||||
}
|
||||
|
||||
// remember position
|
||||
gtk_window_get_position( GTK_WINDOW(dialog), &window_x, &window_y);
|
||||
|
||||
// done
|
||||
gtk_widget_destroy(dialog);
|
||||
wait_for_event();
|
||||
#endif
|
||||
|
||||
return filename;
|
||||
}
|
||||
|
||||
|
||||
std::string openMediaFileDialog(const std::string &label, const std::string &path)
|
||||
{
|
||||
std::string filename = "";
|
||||
std::string startpath = SystemToolkit::file_exists(path) ? path : SystemToolkit::home_path();
|
||||
char const * open_pattern[26] = { MEDIA_FILES_PATTERN };
|
||||
|
||||
#if USE_TINYFILEDIALOG
|
||||
char const * open_file_name;
|
||||
open_file_name = tinyfd_openFileDialog( label.c_str(), startpath.c_str(), 26, open_pattern, "All supported formats", 0);
|
||||
|
||||
if (open_file_name)
|
||||
filename = std::string(open_file_name);
|
||||
#else
|
||||
|
||||
if (!gtk_init()) {
|
||||
DialogToolkit::ErrorDialog("Could not initialize GTK+ for dialog");
|
||||
return filename;
|
||||
}
|
||||
|
||||
GtkWidget *dialog = gtk_file_chooser_dialog_new( label.c_str(), NULL,
|
||||
GTK_FILE_CHOOSER_ACTION_OPEN,
|
||||
"_Cancel", GTK_RESPONSE_CANCEL,
|
||||
"_Open", GTK_RESPONSE_ACCEPT, NULL );
|
||||
|
||||
// set file filters
|
||||
add_filter_file_dialog(dialog, 26, open_pattern, "Supported formats (videos, images, sessions)");
|
||||
add_filter_any_file_dialog(dialog);
|
||||
|
||||
// Set the default path
|
||||
gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER(dialog), startpath.c_str() );
|
||||
|
||||
// ensure front and centered
|
||||
gtk_window_set_keep_above( GTK_WINDOW(dialog), TRUE );
|
||||
if (window_x > 0 && window_y > 0)
|
||||
gtk_window_move( GTK_WINDOW(dialog), window_x, window_y);
|
||||
|
||||
// display and get filename
|
||||
if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT ) {
|
||||
|
||||
char *open_file_name = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog) );
|
||||
if (open_file_name) {
|
||||
filename = std::string(open_file_name);
|
||||
g_free( open_file_name );
|
||||
}
|
||||
}
|
||||
|
||||
// remember position
|
||||
gtk_window_get_position( GTK_WINDOW(dialog), &window_x, &window_y);
|
||||
|
||||
// done
|
||||
gtk_widget_destroy(dialog);
|
||||
wait_for_event();
|
||||
#endif
|
||||
|
||||
return filename;
|
||||
}
|
||||
|
||||
|
||||
std::string openImageFileDialog(const std::string &label, const std::string &path)
|
||||
{
|
||||
std::string filename = "";
|
||||
std::string startpath = SystemToolkit::file_exists(path) ? path : SystemToolkit::home_path();
|
||||
char const * open_pattern[5] = { IMAGES_FILES_PATTERN };
|
||||
|
||||
#if USE_TINYFILEDIALOG
|
||||
char const * open_file_name;
|
||||
open_file_name = tinyfd_openFileDialog( label.c_str(), startpath.c_str(), 5, open_pattern, "Image (JPG, PNG, BMP, PPM, GIF)", 0);
|
||||
|
||||
if (open_file_name)
|
||||
filename = std::string(open_file_name);
|
||||
#else
|
||||
|
||||
if (!gtk_init()) {
|
||||
DialogToolkit::ErrorDialog("Could not initialize GTK+ for dialog");
|
||||
return filename;
|
||||
}
|
||||
|
||||
GtkWidget *dialog = gtk_file_chooser_dialog_new( label.c_str(), NULL,
|
||||
GTK_FILE_CHOOSER_ACTION_OPEN,
|
||||
"_Cancel", GTK_RESPONSE_CANCEL,
|
||||
"_Open", GTK_RESPONSE_ACCEPT, NULL );
|
||||
|
||||
// set file filters
|
||||
add_filter_file_dialog(dialog, 5, open_pattern, "Image (JPG, PNG, BMP, PPM, GIF)");
|
||||
add_filter_any_file_dialog(dialog);
|
||||
|
||||
// Set the default path
|
||||
gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER(dialog), startpath.c_str() );
|
||||
|
||||
// ensure front and centered
|
||||
gtk_window_set_keep_above( GTK_WINDOW(dialog), TRUE );
|
||||
if (window_x > 0 && window_y > 0)
|
||||
gtk_window_move( GTK_WINDOW(dialog), window_x, window_y);
|
||||
|
||||
// display and get filename
|
||||
if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT ) {
|
||||
|
||||
char *open_file_name = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog) );
|
||||
if (open_file_name) {
|
||||
filename = std::string(open_file_name);
|
||||
g_free( open_file_name );
|
||||
}
|
||||
}
|
||||
|
||||
// remember position
|
||||
gtk_window_get_position( GTK_WINDOW(dialog), &window_x, &window_y);
|
||||
|
||||
// done
|
||||
gtk_widget_destroy(dialog);
|
||||
wait_for_event();
|
||||
#endif
|
||||
|
||||
return filename;
|
||||
}
|
||||
|
||||
|
||||
std::string openFolderDialog(const std::string &label, const std::string &path)
|
||||
{
|
||||
std::string foldername = "";
|
||||
std::string startpath = SystemToolkit::file_exists(path) ? path : SystemToolkit::home_path();
|
||||
|
||||
#if USE_TINYFILEDIALOG
|
||||
char const * open_folder_name;
|
||||
open_folder_name = tinyfd_selectFolderDialog(label.c_str(), startpath.c_str());
|
||||
|
||||
if (open_folder_name)
|
||||
foldername = std::string(open_folder_name);
|
||||
#else
|
||||
if (!gtk_init()) {
|
||||
DialogToolkit::ErrorDialog("Could not initialize GTK+ for dialog");
|
||||
return foldername;
|
||||
}
|
||||
|
||||
GtkWidget *dialog = gtk_file_chooser_dialog_new( label.c_str(), NULL,
|
||||
GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
|
||||
"_Cancel", GTK_RESPONSE_CANCEL,
|
||||
"_Select", GTK_RESPONSE_ACCEPT, NULL );
|
||||
gtk_file_chooser_set_do_overwrite_confirmation( GTK_FILE_CHOOSER(dialog), TRUE );
|
||||
|
||||
// Set the default path
|
||||
gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER(dialog), startpath.c_str() );
|
||||
|
||||
// ensure front and centered
|
||||
gtk_window_set_keep_above( GTK_WINDOW(dialog), TRUE );
|
||||
if (window_x > 0 && window_y > 0)
|
||||
gtk_window_move( GTK_WINDOW(dialog), window_x, window_y);
|
||||
|
||||
// display and get filename
|
||||
if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT ) {
|
||||
|
||||
char *open_folder_name = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog) );
|
||||
if (open_folder_name) {
|
||||
foldername = std::string(open_folder_name);
|
||||
g_free( open_folder_name );
|
||||
}
|
||||
}
|
||||
|
||||
// remember position
|
||||
gtk_window_get_position( GTK_WINDOW(dialog), &window_x, &window_y);
|
||||
|
||||
// done
|
||||
gtk_widget_destroy(dialog);
|
||||
wait_for_event();
|
||||
#endif
|
||||
|
||||
return foldername;
|
||||
}
|
||||
|
||||
|
||||
std::list<std::string> selectImagesFileDialog(const std::string &label,const std::string &path)
|
||||
{
|
||||
std::list<std::string> files;
|
||||
|
||||
std::string startpath = SystemToolkit::file_exists(path) ? path : SystemToolkit::home_path();
|
||||
char const * open_pattern[6] = { "*.jpg", "*.png", "*.tif" };
|
||||
|
||||
#if USE_TINYFILEDIALOG
|
||||
char const * open_file_names;
|
||||
open_file_names = tinyfd_openFileDialog(label.c_str(), startpath.c_str(), 3, open_pattern, "Images (JPG, PNG, TIF)", 1);
|
||||
|
||||
if (open_file_names) {
|
||||
|
||||
const std::string& str (open_file_names);
|
||||
const std::string& delimiters = "|";
|
||||
// Skip delimiters at beginning.
|
||||
std::string::size_type lastPos = str.find_first_not_of(delimiters, 0);
|
||||
|
||||
// Find first non-delimiter.
|
||||
std::string::size_type pos = str.find_first_of(delimiters, lastPos);
|
||||
|
||||
while (std::string::npos != pos || std::string::npos != lastPos) {
|
||||
// Found a token, add it to the vector.
|
||||
files.push_back(str.substr(lastPos, pos - lastPos));
|
||||
|
||||
// Skip delimiters.
|
||||
lastPos = str.find_first_not_of(delimiters, pos);
|
||||
|
||||
// Find next non-delimiter.
|
||||
pos = str.find_first_of(delimiters, lastPos);
|
||||
}
|
||||
}
|
||||
#else
|
||||
|
||||
if (!gtk_init()) {
|
||||
DialogToolkit::ErrorDialog("Could not initialize GTK+ for dialog");
|
||||
return files;
|
||||
}
|
||||
|
||||
GtkWidget *dialog = gtk_file_chooser_dialog_new( label.c_str(), NULL,
|
||||
GTK_FILE_CHOOSER_ACTION_OPEN,
|
||||
"_Cancel", GTK_RESPONSE_CANCEL,
|
||||
"_Open", GTK_RESPONSE_ACCEPT, NULL );
|
||||
|
||||
// set file filters
|
||||
add_filter_file_dialog(dialog, 3, open_pattern, "Images (JPG, PNG, TIF)");
|
||||
add_filter_any_file_dialog(dialog);
|
||||
|
||||
// multiple files
|
||||
gtk_file_chooser_set_select_multiple( GTK_FILE_CHOOSER(dialog), true );
|
||||
|
||||
// Set the default path
|
||||
gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER(dialog), startpath.c_str() );
|
||||
|
||||
// ensure front and centered
|
||||
gtk_window_set_keep_above( GTK_WINDOW(dialog), TRUE );
|
||||
if (window_x > 0 && window_y > 0)
|
||||
gtk_window_move( GTK_WINDOW(dialog), window_x, window_y);
|
||||
|
||||
// display and get filename
|
||||
if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT ) {
|
||||
|
||||
GSList *open_file_names = gtk_file_chooser_get_filenames( GTK_FILE_CHOOSER(dialog) );
|
||||
|
||||
while (open_file_names) {
|
||||
files.push_back( (char *) open_file_names->data );
|
||||
open_file_names = open_file_names->next;
|
||||
// g_free( open_file_names->data );
|
||||
}
|
||||
|
||||
g_slist_free( open_file_names );
|
||||
}
|
||||
|
||||
// remember position
|
||||
gtk_window_get_position( GTK_WINDOW(dialog), &window_x, &window_y);
|
||||
|
||||
// done
|
||||
gtk_widget_destroy(dialog);
|
||||
wait_for_event();
|
||||
#endif
|
||||
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
void DialogToolkit::ErrorDialog(const char* message)
|
||||
{
|
||||
#if USE_TINYFILEDIALOG
|
||||
tinyfd_messageBox( APP_TITLE, message, "ok", "error", 0);
|
||||
#else
|
||||
if (!gtk_init()) {
|
||||
return;
|
||||
}
|
||||
|
||||
GtkWidget *dialog = gtk_message_dialog_new( NULL, GTK_DIALOG_DESTROY_WITH_PARENT,
|
||||
GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
|
||||
"Error: %s", message);
|
||||
|
||||
gtk_window_set_keep_above( GTK_WINDOW(dialog), TRUE );
|
||||
static int x = 0, y = 0;
|
||||
if (x != 0)
|
||||
gtk_window_move( GTK_WINDOW(dialog), x, y);
|
||||
|
||||
// show
|
||||
gtk_dialog_run( GTK_DIALOG(dialog) );
|
||||
|
||||
// remember position
|
||||
gtk_window_get_position( GTK_WINDOW(dialog), &x, &y);
|
||||
|
||||
// done
|
||||
gtk_widget_destroy( dialog );
|
||||
wait_for_event();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
#ifndef DIALOGTOOLKIT_H
|
||||
#define DIALOGTOOLKIT_H
|
||||
|
||||
#include <string>
|
||||
#include <list>
|
||||
#include <vector>
|
||||
#include <future>
|
||||
|
||||
namespace DialogToolkit
|
||||
{
|
||||
|
||||
void ErrorDialog(const char* message);
|
||||
|
||||
class FileDialog
|
||||
{
|
||||
protected:
|
||||
std::string id_;
|
||||
std::string directory_;
|
||||
std::string path_;
|
||||
std::vector< std::future<std::string> >promises_;
|
||||
static bool busy_;
|
||||
|
||||
public:
|
||||
FileDialog(const std::string &name);
|
||||
|
||||
virtual void open() = 0;
|
||||
virtual bool closed();
|
||||
inline std::string path() const { return path_; }
|
||||
|
||||
static bool busy() { return busy_; }
|
||||
};
|
||||
|
||||
class OpenImageDialog : public FileDialog
|
||||
{
|
||||
public:
|
||||
OpenImageDialog(const std::string &name) : FileDialog(name) {}
|
||||
void open();
|
||||
};
|
||||
|
||||
class OpenSessionDialog : public FileDialog
|
||||
{
|
||||
public:
|
||||
OpenSessionDialog(const std::string &name) : FileDialog(name) {}
|
||||
void open();
|
||||
};
|
||||
|
||||
class OpenMediaDialog : public FileDialog
|
||||
{
|
||||
public:
|
||||
OpenMediaDialog(const std::string &name) : FileDialog(name) {}
|
||||
void open();
|
||||
};
|
||||
|
||||
class SaveSessionDialog : public FileDialog
|
||||
{
|
||||
public:
|
||||
SaveSessionDialog(const std::string &name) : FileDialog(name) {}
|
||||
void setFolder(std::string path);
|
||||
void open();
|
||||
};
|
||||
|
||||
class OpenFolderDialog : public FileDialog
|
||||
{
|
||||
public:
|
||||
OpenFolderDialog(const std::string &name) : FileDialog(name) {}
|
||||
void open();
|
||||
};
|
||||
|
||||
class MultipleImagesDialog : public FileDialog
|
||||
{
|
||||
std::list<std::string> pathlist_;
|
||||
std::vector< std::future< std::list<std::string> > > promisedlist_;
|
||||
public:
|
||||
MultipleImagesDialog(const std::string &name) : FileDialog(name) {}
|
||||
void open() override;
|
||||
bool closed() override;
|
||||
inline std::list<std::string> images() const { return pathlist_; }
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endif // DIALOGTOOLKIT_H
|
||||
@@ -1,29 +0,0 @@
|
||||
#ifndef DRAWVISITOR_H
|
||||
#define DRAWVISITOR_H
|
||||
|
||||
#include "GlmToolkit.h"
|
||||
#include "Visitor.h"
|
||||
|
||||
class DrawVisitor : public Visitor
|
||||
{
|
||||
glm::mat4 modelview_;
|
||||
glm::mat4 projection_;
|
||||
std::vector<Node *> targets_;
|
||||
bool force_;
|
||||
int num_duplicat_;
|
||||
glm::mat4 transform_duplicat_;
|
||||
|
||||
public:
|
||||
DrawVisitor(Node *nodetodraw, glm::mat4 projection, bool force = false);
|
||||
DrawVisitor(const std::vector<Node *> &nodestodraw, glm::mat4 projection, bool force = false);
|
||||
|
||||
void loop(int num, glm::mat4 transform);
|
||||
|
||||
void visit(Scene& n) override;
|
||||
void visit(Node& n) override;
|
||||
void visit(Primitive& ) override;
|
||||
void visit(Group& n) override;
|
||||
void visit(Switch& n) override;
|
||||
};
|
||||
|
||||
#endif // DRAWVISITOR_H
|
||||
1232
FileDialog.cpp
112
FileDialog.h
@@ -1,112 +0,0 @@
|
||||
#ifndef __IMGUI_FILE_DIALOG_H_
|
||||
#define __IMGUI_FILE_DIALOG_H_
|
||||
|
||||
#include "imgui.h"
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <future>
|
||||
#include <functional>
|
||||
|
||||
#define MAX_FILE_DIALOG_NAME_BUFFER 1024
|
||||
|
||||
struct FileInfoStruct
|
||||
{
|
||||
char type = ' ';
|
||||
std::string filePath;
|
||||
std::string fileName;
|
||||
std::string ext;
|
||||
};
|
||||
|
||||
class FileDialog
|
||||
{
|
||||
private:
|
||||
std::vector<FileInfoStruct> m_FileList;
|
||||
std::map<std::string, ImVec4> m_FilterColor;
|
||||
std::string m_SelectedFileName;
|
||||
std::string m_SelectedExt;
|
||||
std::string m_CurrentPath;
|
||||
std::vector<std::string> m_CurrentPath_Decomposition;
|
||||
std::string m_Name;
|
||||
bool m_ShowDialog;
|
||||
bool m_ShowDrives;
|
||||
|
||||
public:
|
||||
static char FileNameBuffer[MAX_FILE_DIALOG_NAME_BUFFER];
|
||||
static char DirectoryNameBuffer[MAX_FILE_DIALOG_NAME_BUFFER];
|
||||
static char SearchBuffer[MAX_FILE_DIALOG_NAME_BUFFER];
|
||||
static int FilterIndex;
|
||||
bool IsOk;
|
||||
bool m_AnyWindowsHovered;
|
||||
bool m_CreateDirectoryMode;
|
||||
|
||||
private:
|
||||
std::string dlg_key;
|
||||
std::string dlg_name;
|
||||
const char *dlg_filters;
|
||||
std::string dlg_path;
|
||||
std::string dlg_defaultFileName;
|
||||
std::string dlg_defaultExt;
|
||||
std::function<void(std::string, bool*)> dlg_optionsPane;
|
||||
size_t dlg_optionsPaneWidth;
|
||||
std::string searchTag;
|
||||
std::string dlg_userString;
|
||||
|
||||
public:
|
||||
static FileDialog* Instance()
|
||||
{
|
||||
static FileDialog *_instance = new FileDialog();
|
||||
return _instance;
|
||||
}
|
||||
|
||||
static void RenderCurrent();
|
||||
// Open an Open File dialog for TEXT file
|
||||
static void SetCurrentOpenText();
|
||||
// Open an Open File dialog for IMAGE file
|
||||
static void SetCurrentOpenImage();
|
||||
// Open an Open File dialog for MEDIA file
|
||||
static void SetCurrentOpenMedia();
|
||||
|
||||
protected:
|
||||
|
||||
FileDialog(); // Prevent construction
|
||||
FileDialog(const FileDialog&) = delete; // Prevent construction by copying
|
||||
FileDialog& operator =(const FileDialog&) = delete; // Prevent assignment
|
||||
~FileDialog(); // Prevent unwanted destruction
|
||||
|
||||
public:
|
||||
void OpenDialog(const std::string& vKey, const char* vName, const char* vFilters,
|
||||
const std::string& vPath, const std::string& vDefaultFileName,
|
||||
std::function<void(std::string, bool*)> vOptionsPane, size_t vOptionsPaneWidth = 250, const std::string& vUserString = "");
|
||||
void OpenDialog(const std::string& vKey, const char* vName, const char* vFilters,
|
||||
const std::string& vDefaultFileName,
|
||||
std::function<void(std::string, bool*)> vOptionsPane, size_t vOptionsPaneWidth = 250, const std::string& vUserString = "");
|
||||
void OpenDialog(const std::string& vKey, const char* vName, const char* vFilters,
|
||||
const std::string& vPath, const std::string& vDefaultFileName, const std::string& vUserString = "");
|
||||
void OpenDialog(const std::string& vKey, const char* vName, const char* vFilters,
|
||||
const std::string& vFilePathName, const std::string& vUserString = "");
|
||||
|
||||
void CloseDialog(const std::string& vKey);
|
||||
bool Render(const std::string& vKey, ImVec2 geometry);
|
||||
std::string GetFilepathName();
|
||||
std::string GetCurrentPath();
|
||||
std::string GetCurrentFileName();
|
||||
std::string GetCurrentFilter();
|
||||
std::string GetUserString();
|
||||
|
||||
void SetFilterColor(const std::string &vFilter, ImVec4 vColor);
|
||||
bool GetFilterColor(const std::string &vFilter, ImVec4 *vColor);
|
||||
void ClearFilterColor();
|
||||
|
||||
private:
|
||||
void SetPath(const std::string& vPath);
|
||||
void ScanDir(const std::string& vPath);
|
||||
void SetCurrentDir(const std::string& vPath);
|
||||
bool CreateDir(const std::string& vPath);
|
||||
void ComposeNewPath(std::vector<std::string>::iterator vIter);
|
||||
void GetDrives();
|
||||
};
|
||||
|
||||
|
||||
#endif // __IMGUI_FILE_DIALOG_H_
|
||||
@@ -1,103 +0,0 @@
|
||||
/*
|
||||
* This file is part of vimix - Live video mixer
|
||||
*
|
||||
* **Copyright** (C) 2020-2021 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||
*
|
||||
* 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 3 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, see <https://www.gnu.org/licenses/>.
|
||||
**/
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "Log.h"
|
||||
#include "Scene.h"
|
||||
#include "GarbageVisitor.h"
|
||||
|
||||
|
||||
GarbageVisitor::GarbageVisitor(Node *nodetocollect) : Visitor()
|
||||
{
|
||||
targets_.push_front(nodetocollect);
|
||||
current_ = nullptr;
|
||||
found_ = false;
|
||||
}
|
||||
|
||||
GarbageVisitor::GarbageVisitor(Source *sourcetocollect) : Visitor()
|
||||
{
|
||||
targets_.push_front(sourcetocollect->group(View::MIXING));
|
||||
targets_.push_front(sourcetocollect->group(View::GEOMETRY));
|
||||
targets_.push_front(sourcetocollect->group(View::RENDERING));
|
||||
current_ = nullptr;
|
||||
found_ = false;
|
||||
}
|
||||
|
||||
|
||||
void GarbageVisitor::visit(Node &n)
|
||||
{
|
||||
// found the target
|
||||
// if (n.id() == target_->id())
|
||||
// if ( &n == target_ )
|
||||
if ( std::count(targets_.begin(), targets_.end(), &n) )
|
||||
{
|
||||
// end recursive
|
||||
found_ = true;
|
||||
|
||||
// take the node out of the Tree
|
||||
if (current_)
|
||||
current_->detach(&n);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
GarbageVisitor::~GarbageVisitor()
|
||||
{
|
||||
// actually delete the Node
|
||||
|
||||
}
|
||||
|
||||
|
||||
void GarbageVisitor::visit(Group &n)
|
||||
{
|
||||
// no need to go further if the node was found (or is this group)
|
||||
if (found_)
|
||||
return;
|
||||
|
||||
// current group
|
||||
current_ = &n;
|
||||
|
||||
// loop over members of a group
|
||||
// and stop when found
|
||||
for (NodeSet::iterator node = n.begin(); !found_ && node != n.end(); ++node) {
|
||||
// visit the child node
|
||||
(*node)->accept(*this);
|
||||
// un-stack recursive browsing
|
||||
current_ = &n;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void GarbageVisitor::visit(Scene &n)
|
||||
{
|
||||
n.root()->accept(*this);
|
||||
}
|
||||
|
||||
void GarbageVisitor::visit(Switch &n)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void GarbageVisitor::visit(Primitive &n)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
#ifndef GARBAGEVISITOR_H
|
||||
#define GARBAGEVISITOR_H
|
||||
|
||||
#include <list>
|
||||
|
||||
#include "Source.h"
|
||||
#include "Visitor.h"
|
||||
|
||||
|
||||
class GarbageVisitor : public Visitor
|
||||
{
|
||||
Group *current_;
|
||||
std::list<Node *> targets_;
|
||||
bool found_;
|
||||
|
||||
public:
|
||||
GarbageVisitor(Source *sourcetocollect);
|
||||
GarbageVisitor(Node *nodetocollect);
|
||||
~GarbageVisitor();
|
||||
|
||||
void visit(Scene& n) override;
|
||||
void visit(Node& n) override;
|
||||
void visit(Group& n) override;
|
||||
void visit(Switch& n) override;
|
||||
void visit(Primitive& n) override;
|
||||
|
||||
};
|
||||
|
||||
#endif // GARBAGEVISITOR_H
|
||||
1148
GeometryView.cpp
35
GstToolkit.h
@@ -1,35 +0,0 @@
|
||||
#ifndef __GSTGUI_TOOLKIT_H_
|
||||
#define __GSTGUI_TOOLKIT_H_
|
||||
|
||||
#include <gst/gst.h>
|
||||
|
||||
#include <string>
|
||||
#include <list>
|
||||
|
||||
namespace GstToolkit
|
||||
{
|
||||
|
||||
typedef enum {
|
||||
TIME_STRING_FIXED = 0,
|
||||
TIME_STRING_ADJUSTED,
|
||||
TIME_STRING_MINIMAL,
|
||||
TIME_STRING_READABLE
|
||||
} time_string_mode;
|
||||
|
||||
std::string time_to_string(guint64 t, time_string_mode m = TIME_STRING_ADJUSTED);
|
||||
std::string filename_to_uri(std::string filename);
|
||||
|
||||
std::string gst_version();
|
||||
|
||||
std::list<std::string> all_plugins();
|
||||
std::list<std::string> enable_gpu_decoding_plugins(bool enable = true);
|
||||
std::string used_gpu_decoding_plugins(GstElement *gstbin);
|
||||
std::string used_decoding_plugins(GstElement *gstbin);
|
||||
|
||||
std::list<std::string> all_plugin_features(std::string pluginname);
|
||||
bool has_feature (std::string name);
|
||||
bool enable_feature (std::string name, bool enable);
|
||||
|
||||
}
|
||||
|
||||
#endif // __GSTGUI_TOOLKIT_H_
|
||||
1267
ImGuiVisitor.cpp
399
InfoVisitor.cpp
@@ -1,399 +0,0 @@
|
||||
/*
|
||||
* This file is part of vimix - video live mixer
|
||||
*
|
||||
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||
*
|
||||
* 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 3 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, see <https://www.gnu.org/licenses/>.
|
||||
**/
|
||||
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
#include <glm/gtc/constants.hpp>
|
||||
#include <glm/gtc/matrix_access.hpp>
|
||||
|
||||
#include <tinyxml2.h>
|
||||
#include "tinyxml2Toolkit.h"
|
||||
|
||||
#include "defines.h"
|
||||
#include "Log.h"
|
||||
#include "Scene.h"
|
||||
#include "Primitives.h"
|
||||
#include "ImageShader.h"
|
||||
#include "ImageProcessingShader.h"
|
||||
#include "MediaPlayer.h"
|
||||
#include "MediaSource.h"
|
||||
#include "CloneSource.h"
|
||||
#include "RenderSource.h"
|
||||
#include "SessionSource.h"
|
||||
#include "PatternSource.h"
|
||||
#include "DeviceSource.h"
|
||||
#include "NetworkSource.h"
|
||||
#include "SrtReceiverSource.h"
|
||||
#include "MultiFileSource.h"
|
||||
#include "SessionCreator.h"
|
||||
#include "SessionVisitor.h"
|
||||
#include "Settings.h"
|
||||
#include "Mixer.h"
|
||||
#include "ActionManager.h"
|
||||
#include "BaseToolkit.h"
|
||||
#include "UserInterfaceManager.h"
|
||||
#include "SystemToolkit.h"
|
||||
|
||||
#include "InfoVisitor.h"
|
||||
|
||||
|
||||
|
||||
InfoVisitor::InfoVisitor() : brief_(true), current_id_(0)
|
||||
{
|
||||
}
|
||||
|
||||
void InfoVisitor::visit(Node &n)
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << std::fixed << std::setprecision(1);
|
||||
oss << "Pos ( " << n.translation_.x << ", " << n.translation_.y << " )" << std::endl;
|
||||
oss << "Scale ( " << n.scale_.x << ", " << n.scale_.y << " )" << std::endl;
|
||||
oss << "Angle " << std::setprecision(2) << n.rotation_.z * 180.f / M_PI << "\u00B0" << std::endl;
|
||||
|
||||
if (!brief_) {
|
||||
oss << n.crop_.x << ", " << n.crop_.y << " Crop" << std::endl;
|
||||
}
|
||||
|
||||
information_ = oss.str();
|
||||
}
|
||||
|
||||
void InfoVisitor::visit(Group &)
|
||||
{
|
||||
}
|
||||
|
||||
void InfoVisitor::visit(Switch &)
|
||||
{
|
||||
}
|
||||
|
||||
void InfoVisitor::visit(Scene &)
|
||||
{
|
||||
}
|
||||
|
||||
void InfoVisitor::visit(Primitive &)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void InfoVisitor::visit(MediaPlayer &mp)
|
||||
{
|
||||
// do not ask twice
|
||||
if (current_id_ == mp.id())
|
||||
return;
|
||||
|
||||
std::ostringstream oss;
|
||||
if (brief_) {
|
||||
oss << SystemToolkit::filename(mp.filename()) << std::endl;
|
||||
oss << mp.media().codec_name.substr(0, mp.media().codec_name.find_first_of(" (,")) << ", ";
|
||||
oss << mp.width() << " x " << mp.height();
|
||||
if (!mp.isImage())
|
||||
oss << ", " << std::fixed << std::setprecision(0) << mp.frameRate() << "fps";
|
||||
}
|
||||
else {
|
||||
oss << mp.filename() << std::endl;
|
||||
oss << mp.media().codec_name << std::endl;
|
||||
oss << mp.width() << " x " << mp.height() ;
|
||||
if (!mp.isImage())
|
||||
oss << ", " << std::fixed << std::setprecision(1) << mp.frameRate() << " fps";
|
||||
}
|
||||
|
||||
information_ = oss.str();
|
||||
|
||||
// remember (except if codec was not identified yet)
|
||||
if ( !mp.media().codec_name.empty() )
|
||||
current_id_ = mp.id();
|
||||
}
|
||||
|
||||
void InfoVisitor::visit(Stream &n)
|
||||
{
|
||||
std::ostringstream oss;
|
||||
if (brief_) {
|
||||
oss << BaseToolkit::splitted(n.description(), '!').front();
|
||||
}
|
||||
else {
|
||||
oss << n.description();
|
||||
}
|
||||
information_ = oss.str();
|
||||
}
|
||||
|
||||
|
||||
void InfoVisitor::visit (MediaSource& s)
|
||||
{
|
||||
s.mediaplayer()->accept(*this);
|
||||
}
|
||||
|
||||
void InfoVisitor::visit (SessionFileSource& s)
|
||||
{
|
||||
if (current_id_ == s.id() || s.session() == nullptr)
|
||||
return;
|
||||
|
||||
std::ostringstream oss;
|
||||
if (s.session()->frame()){
|
||||
uint N = s.session()->size();
|
||||
std::string numsource = std::to_string(N) + " source" + (N>1 ? "s" : "");
|
||||
uint T = s.session()->numSources();
|
||||
if (T>N)
|
||||
numsource += " (" + std::to_string(T) + " total)";
|
||||
if (brief_) {
|
||||
oss << SystemToolkit::filename(s.path()) << std::endl;
|
||||
oss << numsource << ", " ;
|
||||
oss << "RGB, " << s.session()->frame()->width() << " x " << s.session()->frame()->height();
|
||||
}
|
||||
else {
|
||||
oss << s.path() << std::endl;
|
||||
oss << "MIX session (" << numsource << "), RGB" << std::endl;
|
||||
oss << s.session()->frame()->width() << " x " << s.session()->frame()->height();
|
||||
}
|
||||
}
|
||||
|
||||
information_ = oss.str();
|
||||
current_id_ = s.id();
|
||||
}
|
||||
|
||||
void InfoVisitor::visit (SessionGroupSource& s)
|
||||
{
|
||||
if (current_id_ == s.id() || s.session() == nullptr)
|
||||
return;
|
||||
|
||||
std::ostringstream oss;
|
||||
|
||||
if (!brief_) oss << "Group of ";
|
||||
uint N = s.session()->size();
|
||||
oss << N << " source" << (N>1 ? "s" : "");
|
||||
uint T = s.session()->numSources();
|
||||
if (T>N)
|
||||
oss << " (" << std::to_string(T) << " total)";
|
||||
|
||||
if (s.session()->frame()){
|
||||
if (brief_) {
|
||||
oss << ", RGB, " << s.session()->frame()->width() << " x " << s.session()->frame()->height();
|
||||
}
|
||||
else {
|
||||
oss << std::endl;
|
||||
oss << "RGB" << std::endl;
|
||||
oss << s.session()->frame()->width() << " x " << s.session()->frame()->height();
|
||||
}
|
||||
}
|
||||
|
||||
information_ = oss.str();
|
||||
current_id_ = s.id();
|
||||
}
|
||||
|
||||
void InfoVisitor::visit (RenderSource& s)
|
||||
{
|
||||
if (current_id_ == s.id())
|
||||
return;
|
||||
|
||||
std::ostringstream oss;
|
||||
|
||||
if (s.frame()){
|
||||
if (brief_) {
|
||||
oss << (s.frame()->flags() & FrameBuffer::FrameBuffer_alpha ? "RGBA, " : "RGB, ");
|
||||
oss << s.frame()->width() << " x " << s.frame()->height();
|
||||
}
|
||||
else {
|
||||
oss << "Rendering Output (";
|
||||
oss << RenderSource::rendering_provenance_label[s.renderingProvenance()] << ") " << std::endl;
|
||||
oss << (s.frame()->flags() & FrameBuffer::FrameBuffer_alpha ? "RGBA" : "RGB") << std::endl;
|
||||
oss << s.frame()->width() << " x " << s.frame()->height();
|
||||
}
|
||||
}
|
||||
|
||||
information_ = oss.str();
|
||||
current_id_ = s.id();
|
||||
}
|
||||
|
||||
void InfoVisitor::visit (CloneSource& s)
|
||||
{
|
||||
if (current_id_ == s.id() || s.origin() == nullptr)
|
||||
return;
|
||||
|
||||
std::ostringstream oss;
|
||||
|
||||
if (s.frame()){
|
||||
if (brief_) {
|
||||
oss << (s.frame()->flags() & FrameBuffer::FrameBuffer_alpha ? "RGBA, " : "RGB, ");
|
||||
oss << s.frame()->width() << " x " << s.frame()->height();
|
||||
}
|
||||
else {
|
||||
if (s.origin())
|
||||
oss << "Clone of '" << s.origin()->name() << "' " << std::endl;
|
||||
oss << (s.frame()->flags() & FrameBuffer::FrameBuffer_alpha ? "RGBA, " : "RGB, ");
|
||||
oss << FrameBufferFilter::type_label[s.filter()->type()] << " filter" << std::endl;
|
||||
oss << s.frame()->width() << " x " << s.frame()->height();
|
||||
}
|
||||
}
|
||||
|
||||
information_ = oss.str();
|
||||
current_id_ = s.id();
|
||||
}
|
||||
|
||||
void InfoVisitor::visit (PatternSource& s)
|
||||
{
|
||||
if (current_id_ == s.id())
|
||||
return;
|
||||
|
||||
std::ostringstream oss;
|
||||
if (s.pattern()) {
|
||||
if (brief_) {
|
||||
oss << "RGBA, " << s.pattern()->width() << " x " << s.pattern()->height();
|
||||
}
|
||||
else {
|
||||
oss << Pattern::get(s.pattern()->type()).label << " pattern" << std::endl;
|
||||
oss << "RGBA" << std::endl;
|
||||
oss << s.pattern()->width() << " x " << s.pattern()->height();
|
||||
}
|
||||
}
|
||||
|
||||
information_ = oss.str();
|
||||
current_id_ = s.id();
|
||||
}
|
||||
|
||||
void InfoVisitor::visit (DeviceSource& s)
|
||||
{
|
||||
if (current_id_ == s.id())
|
||||
return;
|
||||
|
||||
std::ostringstream oss;
|
||||
|
||||
DeviceConfigSet confs = Device::manager().config( Device::manager().index(s.device()));
|
||||
if ( !confs.empty()) {
|
||||
DeviceConfig best = *confs.rbegin();
|
||||
float fps = static_cast<float>(best.fps_numerator) / static_cast<float>(best.fps_denominator);
|
||||
|
||||
if (brief_) {
|
||||
oss << best.stream << " " << best.format << ", ";
|
||||
oss << best.width << " x " << best.height << ", ";
|
||||
oss << std::fixed << std::setprecision(0) << fps << "fps";
|
||||
}
|
||||
else {
|
||||
oss << s.device() << std::endl;
|
||||
oss << Device::manager().description( Device::manager().index(s.device()));
|
||||
oss << ", " << best.stream << " " << best.format << std::endl;
|
||||
oss << best.width << " x " << best.height << ", ";
|
||||
oss << std::fixed << std::setprecision(1) << fps << " fps";
|
||||
}
|
||||
}
|
||||
|
||||
information_ = oss.str();
|
||||
current_id_ = s.id();
|
||||
}
|
||||
|
||||
void InfoVisitor::visit (NetworkSource& s)
|
||||
{
|
||||
if (current_id_ == s.id())
|
||||
return;
|
||||
|
||||
NetworkStream *ns = s.networkStream();
|
||||
|
||||
std::ostringstream oss;
|
||||
if (brief_) {
|
||||
oss << NetworkToolkit::stream_protocol_label[ns->protocol()] << std::endl;
|
||||
oss << "IP " << ns->serverAddress() << std::endl;
|
||||
oss << ns->resolution().x << " x " << ns->resolution().y;
|
||||
}
|
||||
else {
|
||||
oss << s.connection() << std::endl;
|
||||
oss << NetworkToolkit::stream_protocol_label[ns->protocol()];
|
||||
oss << " shared from IP " << ns->serverAddress() << std::endl;
|
||||
oss << ns->resolution().x << " x " << ns->resolution().y;
|
||||
}
|
||||
|
||||
information_ = oss.str();
|
||||
current_id_ = s.id();
|
||||
}
|
||||
|
||||
|
||||
void InfoVisitor::visit (MultiFileSource& s)
|
||||
{
|
||||
if (current_id_ == s.id())
|
||||
return;
|
||||
|
||||
std::ostringstream oss;
|
||||
if (brief_) {
|
||||
oss << s.sequence().max - s.sequence().min + 1 << " images [";
|
||||
oss << s.sequence().min << " - " << s.sequence().max << "]" << std::endl;
|
||||
oss << s.sequence().codec << ", ";
|
||||
oss << s.sequence().width << " x " << s.sequence().height;
|
||||
}
|
||||
else {
|
||||
oss << s.sequence().location << " [";
|
||||
oss << s.sequence().min << " - " << s.sequence().max << "]" << std::endl;
|
||||
oss << s.sequence().max - s.sequence().min + 1 << " ";
|
||||
oss << s.sequence().codec << " images" << std::endl;
|
||||
oss << s.sequence().width << " x " << s.sequence().height << ", " << s.framerate() << " fps";
|
||||
}
|
||||
|
||||
information_ = oss.str();
|
||||
current_id_ = s.id();
|
||||
}
|
||||
|
||||
void InfoVisitor::visit (GenericStreamSource& s)
|
||||
{
|
||||
if (current_id_ == s.id())
|
||||
return;
|
||||
|
||||
std::ostringstream oss;
|
||||
if (s.stream()) {
|
||||
std::string src_element = s.gstElements().front();
|
||||
|
||||
if (brief_) {
|
||||
src_element = src_element.substr(0, src_element.find(" "));
|
||||
oss << "gstreamer '" << src_element << "'" << std::endl;
|
||||
oss << "RGBA, " << s.stream()->width() << " x " << s.stream()->height();
|
||||
}
|
||||
else {
|
||||
oss << "gstreamer '" << src_element << "'" << std::endl;
|
||||
oss << "RGBA" << std::endl;
|
||||
oss << s.stream()->width() << " x " << s.stream()->height();
|
||||
}
|
||||
}
|
||||
else
|
||||
oss << "Undefined";
|
||||
|
||||
information_ = oss.str();
|
||||
current_id_ = s.id();
|
||||
}
|
||||
|
||||
void InfoVisitor::visit (SrtReceiverSource& s)
|
||||
{
|
||||
if (current_id_ == s.id())
|
||||
return;
|
||||
|
||||
std::ostringstream oss;
|
||||
|
||||
if (s.stream()) {
|
||||
if (brief_)
|
||||
oss << s.uri() << std::endl;
|
||||
else {
|
||||
oss << "SRT Receiver " << s.uri() << std::endl;
|
||||
oss << "H264 (" << s.stream()->decoderName() << ")" << std::endl;
|
||||
oss << s.stream()->width() << " x " << s.stream()->height();
|
||||
}
|
||||
}
|
||||
else
|
||||
oss << "Undefined";
|
||||
|
||||
information_ = oss.str();
|
||||
current_id_ = s.id();
|
||||
}
|
||||
31
Loopback.h
@@ -1,31 +0,0 @@
|
||||
#ifndef LOOPBACK_H
|
||||
#define LOOPBACK_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <gst/pbutils/pbutils.h>
|
||||
#include <gst/app/gstappsrc.h>
|
||||
|
||||
#include "FrameGrabber.h"
|
||||
|
||||
|
||||
class Loopback : public FrameGrabber
|
||||
{
|
||||
static std::string system_loopback_pipeline;
|
||||
static std::string system_loopback_name;
|
||||
static bool system_loopback_initialized;
|
||||
|
||||
std::string init(GstCaps *caps) override;
|
||||
void terminate() override;
|
||||
|
||||
public:
|
||||
|
||||
Loopback();
|
||||
|
||||
static bool systemLoopbackInitialized();
|
||||
static bool initializeSystemLoopback();
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif // LOOPBACK_H
|
||||
1351
MediaPlayer.cpp
114
README.md
@@ -1,5 +1,8 @@
|
||||
# vimix
|
||||
Live Video Mixing
|
||||
__Live Video Mixing__
|
||||
|
||||
<img src=docs/vimix_screenshot.png width="800">
|
||||
|
||||
|
||||
vimix performs graphical mixing and blending of several movie clips and
|
||||
computer generated graphics, with image processing effects in real-time.
|
||||
@@ -8,7 +11,7 @@ Its intuitive and hands-on user interface gives direct control on image opacity
|
||||
shape for producing live graphics during concerts and VJ-ing sessions.
|
||||
|
||||
The output image is typically projected full-screen on an external
|
||||
monitor or a projector, but can be streamed live (SRT) or recorded (no audio).
|
||||
monitor or a projector, and can be streamed live (SRT, Shmdata) or recorded (without audio).
|
||||
|
||||
vimix is the successor for GLMixer - https://sourceforge.net/projects/glmixer/
|
||||
|
||||
@@ -17,47 +20,82 @@ vimix is the successor for GLMixer - https://sourceforge.net/projects/glmixer/
|
||||
GPL-3.0-or-later
|
||||
See [LICENSE](https://github.com/brunoherbelin/vimix/blob/master/LICENSE)
|
||||
|
||||
# Install
|
||||
# Install vimix
|
||||
|
||||
Check the [Quick Installation Guide](https://github.com/brunoherbelin/vimix/wiki/Quick-Installation-Guide)
|
||||
|
||||
### Linux
|
||||
|
||||
Download and install a release package from https://snapcraft.io/vimix
|
||||
Download and install a released [flatpak package](https://flathub.org/apps/details/io.github.brunoherbelin.Vimix)
|
||||
|
||||
$ snap install vimix
|
||||
flatpak install --user vimix
|
||||
|
||||
NB: You'll need to setup the snap permissions.
|
||||
NB: Build your flatpak package to get the latest beta version; instructions are [here](https://github.com/brunoherbelin/vimix/tree/master/flatpak).
|
||||
|
||||
|
||||
[](https://snapcraft.io/vimix)
|
||||
|
||||
Download and install a released [snap package](https://snapcraft.io/vimix)
|
||||
|
||||
snap install vimix
|
||||
|
||||
Install the stable debian package (slower release frequency)
|
||||
|
||||
sudo apt install vimix
|
||||
|
||||
### Mac OSX
|
||||
|
||||
Download and open a release package from https://github.com/brunoherbelin/vimix/releases
|
||||
|
||||
NB: You'll need to accept the exception in OSX security preference.
|
||||
|
||||
# Build vimix
|
||||
|
||||
## Clone
|
||||
|
||||
$ git clone --recursive https://github.com/brunoherbelin/vimix.git
|
||||
git clone --recursive https://github.com/brunoherbelin/vimix.git
|
||||
|
||||
This will create the directory 'vimix', download the latest version of vimix code,
|
||||
and (recursively) clone all the internal git dependencies.
|
||||
|
||||
To only update a cloned git copy:
|
||||
|
||||
$ git pull
|
||||
|
||||
## Compile
|
||||
|
||||
First time after git clone:
|
||||
|
||||
$ mkdir vimix-build
|
||||
$ cd vimix-build
|
||||
$ cmake -DCMAKE_BUILD_TYPE=Release ../vimix
|
||||
mkdir vimix-build
|
||||
cd vimix-build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release ../vimix
|
||||
cmake --build .
|
||||
|
||||
Compile (or re-compile after pull):
|
||||
This will create the directory 'vimix-build', configure the program for build, and compile vimix.
|
||||
If successful, the compilation will have produced the executable `vimix` in the `src` directory.
|
||||
You can run vimix with `./src/vimix` :
|
||||
|
||||
...
|
||||
[100%] Built target vimix
|
||||
./src/vimix
|
||||
|
||||
$ cmake --build .
|
||||
## Update clone and re-compile
|
||||
|
||||
### Dependencies
|
||||
Run these commands from the `vimix-build` directory if you did 'Clone' and 'Compile' previously and only want to get the latest update and rebuild.
|
||||
|
||||
git -C ../vimix/ pull
|
||||
cmake --build .
|
||||
|
||||
This will pull the latest commit from git and recompile.
|
||||
|
||||
## Try the Beta branch
|
||||
|
||||
Run this commands from the `vimix-build` directory before runing 'Update clone and re-compile above'
|
||||
|
||||
git -C ../vimix/ checkout beta
|
||||
|
||||
It should say;
|
||||
|
||||
branch 'beta' set up to track 'origin/beta'.
|
||||
Switched to a new branch 'beta'
|
||||
|
||||
## Dependencies
|
||||
|
||||
**Compiling tools:**
|
||||
|
||||
@@ -79,38 +117,28 @@ Optionnal:
|
||||
- stb
|
||||
- TinyXML2
|
||||
- AbletonLink
|
||||
- Shmdata
|
||||
|
||||
#### Install Dependencies
|
||||
### Install Dependencies
|
||||
|
||||
**Ubuntu**
|
||||
#### Ubuntu
|
||||
|
||||
$ apt-get install build-essential cmake libpng-dev libglfw3-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev gstreamer1.0-libav libicu-dev libgtk-3-dev
|
||||
apt-get install build-essential cmake libpng-dev libglfw3-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev gstreamer1.0-libav libicu-dev libgtk-3-dev
|
||||
|
||||
Optionnal:
|
||||
|
||||
$ apt-get install libglm-dev libstb-dev libtinyxml2-dev ableton-link-dev
|
||||
|
||||
**OSX with Brew**
|
||||
|
||||
$ brew install cmake libpng glfw gstreamer gst-libav gst-plugins-bad gst-plugins-base gst-plugins-good gst-plugins-ugly icu4c
|
||||
|
||||
|
||||
#### Generate snap
|
||||
|
||||
To generate the snap (from vimix directory):
|
||||
|
||||
$ snapcraft
|
||||
apt-get install libglm-dev libstb-dev libtinyxml2-dev ableton-link-dev
|
||||
|
||||
To install the locally created snap:
|
||||
|
||||
$ snap install --dangerous vimix_0.5_amd64.snap
|
||||
|
||||
### Memcheck
|
||||
> Follow these instructions to [install Shmdata](https://github.com/nicobou/shmdata/blob/develop/doc/install-from-sources.md).
|
||||
|
||||
git clone https://gitlab.com/sat-metalab/shmdata.git
|
||||
mkdir shmdata-build
|
||||
cd shmdata-build
|
||||
cmake -DCMAKE_INSTALL_PREFIX:PATH=/usr -DCMAKE_BUILD_TYPE=Release -DWITH_PYTHON=0 -DWITH_SDCRASH=0 -DWITH_SDFLOW=0 ../shmdata-build
|
||||
cmake --build . --target package
|
||||
sudo dpkg -i ./libshmdata_1.3*_amd64.deb
|
||||
|
||||
To generate memory usage plots in [massif format](https://valgrind.org/docs/manual/ms-manual.html):
|
||||
#### OSX with Brew
|
||||
|
||||
brew install cmake libpng glfw gstreamer icu4c
|
||||
|
||||
$ G_SLICE=always-malloc valgrind --tool=massif ./vimix
|
||||
|
||||
To check for memory leaks:
|
||||
|
||||
$ G_SLICE=always-malloc valgrind --leak-check=full --log-file=vimix_mem.txt ./vimix
|
||||
|
||||
@@ -1,996 +0,0 @@
|
||||
/*
|
||||
* This file is part of vimix - video live mixer
|
||||
*
|
||||
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||
*
|
||||
* 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 3 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, see <https://www.gnu.org/licenses/>.
|
||||
**/
|
||||
|
||||
#include <cstring>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <chrono>
|
||||
#include <stdlib.h>
|
||||
|
||||
// Desktop OpenGL function loader
|
||||
#include <glad/glad.h> // Initialized with gladLoadGLLoader()
|
||||
|
||||
// Include glfw3.h after our OpenGL definitions
|
||||
#define GLFW_INCLUDE_GLEXT
|
||||
#include <GLFW/glfw3.h>
|
||||
|
||||
#ifdef APPLE
|
||||
#include "osx/CocoaToolkit.h"
|
||||
#define GLFW_EXPOSE_NATIVE_COCOA
|
||||
#define GLFW_EXPOSE_NATIVE_NSGL
|
||||
#else
|
||||
#define GLFW_EXPOSE_NATIVE_X11
|
||||
#define GLFW_EXPOSE_NATIVE_GLX
|
||||
#endif
|
||||
#include <GLFW/glfw3native.h>
|
||||
|
||||
#include <glm/gtc/matrix_transform.hpp> // glm::translate, glm::rotate, glm::scale
|
||||
#include <glm/ext/matrix_clip_space.hpp> // glm::perspective
|
||||
|
||||
// Include GStreamer
|
||||
#include <gst/gl/gl.h>
|
||||
#include <gst/gl/gstglcontext.h>
|
||||
|
||||
#ifdef GLFW_EXPOSE_NATIVE_COCOA
|
||||
//#include <gst/gl/cocoa/gstgldisplay_cocoa.h>
|
||||
#endif
|
||||
#ifdef GLFW_EXPOSE_NATIVE_GLX
|
||||
#include <gst/gl/x11/gstgldisplay_x11.h>
|
||||
#endif
|
||||
|
||||
// standalone image loader
|
||||
#include <stb_image.h>
|
||||
|
||||
// vimix
|
||||
#include "defines.h"
|
||||
#include "Log.h"
|
||||
#include "Resource.h"
|
||||
#include "Settings.h"
|
||||
#include "Primitives.h"
|
||||
#include "Mixer.h"
|
||||
#include "SystemToolkit.h"
|
||||
#include "GstToolkit.h"
|
||||
#include "UserInterfaceManager.h"
|
||||
|
||||
#include "RenderingManager.h"
|
||||
|
||||
#ifdef USE_GST_OPENGL_SYNC_HANDLER
|
||||
//
|
||||
// Discarded because not working under OSX - kept in case it would become useful
|
||||
//
|
||||
// Linking pipeline to the rendering instance ensures the opengl contexts
|
||||
// created by gstreamer inside plugins (e.g. glsinkbin) is the same
|
||||
//
|
||||
static GstGLContext *global_gl_context = NULL;
|
||||
static GstGLDisplay *global_display = NULL;
|
||||
|
||||
static GstBusSyncReply bus_sync_handler( GstBus *, GstMessage * msg, gpointer )
|
||||
{
|
||||
if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_NEED_CONTEXT) {
|
||||
const gchar* contextType;
|
||||
gst_message_parse_context_type(msg, &contextType);
|
||||
|
||||
if (!g_strcmp0(contextType, GST_GL_DISPLAY_CONTEXT_TYPE)) {
|
||||
GstContext *displayContext = gst_context_new(GST_GL_DISPLAY_CONTEXT_TYPE, TRUE);
|
||||
gst_context_set_gl_display(displayContext, global_display);
|
||||
gst_element_set_context(GST_ELEMENT(msg->src), displayContext);
|
||||
gst_context_unref (displayContext);
|
||||
|
||||
g_info ("Managed %s\n", contextType);
|
||||
}
|
||||
if (!g_strcmp0(contextType, "gst.gl.app_context")) {
|
||||
GstContext *appContext = gst_context_new("gst.gl.app_context", TRUE);
|
||||
GstStructure* structure = gst_context_writable_structure(appContext);
|
||||
gst_structure_set(structure, "context", GST_TYPE_GL_CONTEXT, global_gl_context, nullptr);
|
||||
gst_element_set_context(GST_ELEMENT(msg->src), appContext);
|
||||
gst_context_unref (appContext);
|
||||
|
||||
g_info ("Managed %s\n", contextType);
|
||||
}
|
||||
}
|
||||
|
||||
gst_message_unref (msg);
|
||||
|
||||
return GST_BUS_DROP;
|
||||
}
|
||||
|
||||
void Rendering::LinkPipeline( GstPipeline *pipeline )
|
||||
{
|
||||
// capture bus signals to force a unique opengl context for all GST elements
|
||||
GstBus* m_bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
|
||||
gst_bus_set_sync_handler (m_bus, (GstBusSyncHandler) bus_sync_handler, pipeline, NULL);
|
||||
gst_object_unref (m_bus);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
std::map<GLFWwindow *, RenderingWindow*> GLFW_window_;
|
||||
|
||||
static void glfw_error_callback(int error, const char* description)
|
||||
{
|
||||
Log::Error("Glfw Error %d: %s", error, description);
|
||||
}
|
||||
|
||||
static void WindowRefreshCallback( GLFWwindow * )
|
||||
{
|
||||
Rendering::manager().draw();
|
||||
}
|
||||
|
||||
static void WindowResizeCallback( GLFWwindow *w, int width, int height)
|
||||
{
|
||||
if (Rendering::manager().mainWindow().window() == w) {
|
||||
// UI manager tries to keep windows in the workspace
|
||||
WorkspaceWindow::notifyWorkspaceSizeChanged(GLFW_window_[w]->previous_size.x, GLFW_window_[w]->previous_size.y, width, height);
|
||||
GLFW_window_[w]->previous_size = glm::vec2(width, height);
|
||||
}
|
||||
|
||||
int id = GLFW_window_[w]->index();
|
||||
if (!Settings::application.windows[id].fullscreen) {
|
||||
Settings::application.windows[id].w = width;
|
||||
Settings::application.windows[id].h = height;
|
||||
}
|
||||
}
|
||||
|
||||
static void WindowMoveCallback( GLFWwindow *w, int x, int y)
|
||||
{
|
||||
int id = GLFW_window_[w]->index();
|
||||
if (!Settings::application.windows[id].fullscreen) {
|
||||
Settings::application.windows[id].x = x;
|
||||
Settings::application.windows[id].y = y;
|
||||
}
|
||||
}
|
||||
|
||||
static void WindowToggleFullscreen( GLFWwindow *w, int button, int action, int)
|
||||
{
|
||||
if (button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_PRESS)
|
||||
{
|
||||
static double seconds = 0.f;
|
||||
// detect double clic
|
||||
if ( glfwGetTime() - seconds < 0.2f ) {
|
||||
// toggle fullscreen
|
||||
GLFW_window_[w]->toggleFullscreen();
|
||||
}
|
||||
// for next clic detection
|
||||
seconds = glfwGetTime();
|
||||
}
|
||||
}
|
||||
|
||||
static void WindowCloseCallback( GLFWwindow* w )
|
||||
{
|
||||
if (!UserInterface::manager().TryClose())
|
||||
glfwSetWindowShouldClose(w, GLFW_FALSE);
|
||||
}
|
||||
|
||||
void Rendering::close()
|
||||
{
|
||||
glfwSetWindowShouldClose(main_.window(), GLFW_TRUE);
|
||||
}
|
||||
|
||||
Rendering::Rendering()
|
||||
{
|
||||
// main_window_ = nullptr;
|
||||
request_screenshot_ = false;
|
||||
}
|
||||
|
||||
bool Rendering::init()
|
||||
{
|
||||
// Setup window
|
||||
glfwSetErrorCallback(glfw_error_callback);
|
||||
if (!glfwInit()){
|
||||
Log::Error("Failed to Initialize GLFW.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Decide GL+GLSL versions GL 3.3 + GLSL 150
|
||||
glsl_version = "#version 150";
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
|
||||
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only
|
||||
#if __APPLE__
|
||||
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // Required on Mac
|
||||
#endif
|
||||
|
||||
//
|
||||
// OpenGL Multisampling main window
|
||||
//
|
||||
glfwWindowHint(GLFW_SAMPLES, Settings::application.render.multisampling);
|
||||
main_.init(0);
|
||||
// set application icon
|
||||
main_.setIcon("images/vimix_256x256.png");
|
||||
// additional window callbacks for main window
|
||||
glfwSetWindowCloseCallback( main_.window(), WindowCloseCallback );
|
||||
glfwSetWindowRefreshCallback( main_.window(), WindowRefreshCallback );
|
||||
glfwSetDropCallback( main_.window(), Rendering::FileDropped);
|
||||
|
||||
//
|
||||
// Gstreamer setup
|
||||
//
|
||||
std::string plugins_path = SystemToolkit::cwd_path() + "gstreamer-1.0";
|
||||
std::string plugins_scanner = SystemToolkit::cwd_path() + "gst-plugin-scanner" ;
|
||||
if ( SystemToolkit::file_exists(plugins_path)) {
|
||||
Log::Info("Found Gstreamer plugins in %s", plugins_path.c_str());
|
||||
g_setenv ("GST_PLUGIN_SYSTEM_PATH", plugins_path.c_str(), TRUE);
|
||||
g_setenv ("GST_PLUGIN_SCANNER", plugins_scanner.c_str(), TRUE);
|
||||
}
|
||||
std::string frei0r_path = SystemToolkit::cwd_path() + "frei0r-1" ;
|
||||
if ( SystemToolkit::file_exists(frei0r_path)) {
|
||||
Log::Info("Found Frei0r plugins in %s", frei0r_path.c_str());
|
||||
g_setenv ("FREI0R_PATH", frei0r_path.c_str(), TRUE);
|
||||
}
|
||||
g_setenv ("GST_GL_API", "opengl3", TRUE);
|
||||
gst_init (NULL, NULL);
|
||||
|
||||
// increase selection rank for GPU decoding plugins
|
||||
std::list<std::string> gpuplugins = GstToolkit::enable_gpu_decoding_plugins(Settings::application.render.gpu_decoding);
|
||||
if (Settings::application.render.gpu_decoding) {
|
||||
if (gpuplugins.size() > 0) {
|
||||
Log::Info("Found the following GPU decoding plugin(s):");
|
||||
int i = 1;
|
||||
for(auto it = gpuplugins.rbegin(); it != gpuplugins.rend(); it++, ++i)
|
||||
Log::Info("%d. %s", i, (*it).c_str());
|
||||
}
|
||||
else {
|
||||
Log::Info("No GPU decoding plugin found.");
|
||||
}
|
||||
}
|
||||
#ifdef SYNC_GSTREAMER_OPENGL_CONTEXT
|
||||
#if GST_GL_HAVE_PLATFORM_WGL
|
||||
global_gl_context = gst_gl_context_new_wrapped (display, (guintptr) wglGetCurrentContext (),
|
||||
GST_GL_PLATFORM_WGL, GST_GL_API_OPENGL);
|
||||
#elif GST_GL_HAVE_PLATFORM_CGL
|
||||
// global_display = GST_GL_DISPLAY ( glfwGetCocoaMonitor(main_.window()) );
|
||||
global_display = GST_GL_DISPLAY (gst_gl_display_cocoa_new ());
|
||||
|
||||
global_gl_context = gst_gl_context_new_wrapped (global_display,
|
||||
(guintptr) 0,
|
||||
GST_GL_PLATFORM_CGL, GST_GL_API_OPENGL);
|
||||
#elif GST_GL_HAVE_PLATFORM_GLX
|
||||
global_display = (GstGLDisplay*) gst_gl_display_x11_new_with_display( glfwGetX11Display() );
|
||||
global_gl_context = gst_gl_context_new_wrapped (global_display,
|
||||
(guintptr) glfwGetGLXContext(main_.window()),
|
||||
GST_GL_PLATFORM_GLX, GST_GL_API_OPENGL);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
//
|
||||
// output window
|
||||
//
|
||||
glfwWindowHint(GLFW_SAMPLES, 0); // no need for multisampling in displaying output
|
||||
output_.init(1, main_.window());
|
||||
output_.setIcon("images/vimix_256x256.png");
|
||||
// special callbacks for user input in output window
|
||||
glfwSetMouseButtonCallback( output_.window(), WindowToggleFullscreen);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void Rendering::show()
|
||||
{
|
||||
// show output window
|
||||
output_.show();
|
||||
|
||||
// show main window
|
||||
main_.show();
|
||||
|
||||
// show menu on first show
|
||||
UserInterface::manager().showPannel(NAV_MENU);
|
||||
}
|
||||
|
||||
bool Rendering::isActive()
|
||||
{
|
||||
return !glfwWindowShouldClose(main_.window());
|
||||
}
|
||||
|
||||
|
||||
void Rendering::pushFrontDrawCallback(RenderingCallback function)
|
||||
{
|
||||
draw_callbacks_.push_front(function);
|
||||
}
|
||||
|
||||
void Rendering::pushBackDrawCallback(RenderingCallback function)
|
||||
{
|
||||
draw_callbacks_.push_back(function);
|
||||
}
|
||||
|
||||
void Rendering::draw()
|
||||
{
|
||||
// Poll and handle events (inputs, window resize, etc.)
|
||||
// You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs.
|
||||
// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application.
|
||||
// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application.
|
||||
// Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags.
|
||||
glfwPollEvents();
|
||||
|
||||
// change windows fullscreen mode if requested
|
||||
main_.toggleFullscreen_();
|
||||
output_.toggleFullscreen_();
|
||||
|
||||
// change main window title if requested
|
||||
if (!main_new_title_.empty()) {
|
||||
main_.setTitle(main_new_title_);
|
||||
main_new_title_.clear();
|
||||
}
|
||||
|
||||
// operate on main window context
|
||||
main_.makeCurrent();
|
||||
|
||||
// draw
|
||||
std::list<Rendering::RenderingCallback>::iterator iter;
|
||||
for (iter=draw_callbacks_.begin(); iter != draw_callbacks_.end(); ++iter)
|
||||
{
|
||||
(*iter)();
|
||||
}
|
||||
|
||||
// perform screenshot if requested
|
||||
if (request_screenshot_) {
|
||||
screenshot_.captureGL(main_.width(), main_.height());
|
||||
request_screenshot_ = false;
|
||||
}
|
||||
|
||||
// software framerate limiter < 62 FPS
|
||||
{
|
||||
static GTimer *timer = g_timer_new ();
|
||||
double elapsed = g_timer_elapsed (timer, NULL) * 1000000.0;
|
||||
if ( (elapsed < 16000.0) && (elapsed > 0.0) )
|
||||
g_usleep( 16000 - (gulong)elapsed );
|
||||
g_timer_start(timer);
|
||||
}
|
||||
|
||||
// swap GL buffers
|
||||
glfwSwapBuffers(main_.window());
|
||||
|
||||
// draw output window (and swap buffer output)
|
||||
output_.draw( Mixer::manager().session()->frame() );
|
||||
|
||||
}
|
||||
|
||||
void Rendering::terminate()
|
||||
{
|
||||
// close window
|
||||
glfwDestroyWindow(output_.window());
|
||||
glfwDestroyWindow(main_.window());
|
||||
// glfwTerminate();
|
||||
}
|
||||
|
||||
void Rendering::pushAttrib(RenderingAttrib ra)
|
||||
{
|
||||
// push it to top of pile
|
||||
draw_attributes_.push_front(ra);
|
||||
|
||||
// apply Changes to OpenGL
|
||||
glViewport(0, 0, ra.viewport.x, ra.viewport.y);
|
||||
glClearColor(ra.clear_color.r, ra.clear_color.g, ra.clear_color.b, ra.clear_color.a);
|
||||
}
|
||||
|
||||
void Rendering::popAttrib()
|
||||
{
|
||||
// pops the top of the pile
|
||||
if (draw_attributes_.size() > 0)
|
||||
draw_attributes_.pop_front();
|
||||
|
||||
// set attribute element to default
|
||||
RenderingAttrib ra = currentAttrib();
|
||||
|
||||
// apply Changes to OpenGL
|
||||
glViewport(0, 0, ra.viewport.x, ra.viewport.y);
|
||||
glClearColor(ra.clear_color.r, ra.clear_color.g, ra.clear_color.b, ra.clear_color.a);
|
||||
}
|
||||
|
||||
RenderingAttrib Rendering::currentAttrib()
|
||||
{
|
||||
// default rendering attrib is the main window's
|
||||
RenderingAttrib ra = main_.attribs();
|
||||
|
||||
// but if there is an element at top, return it
|
||||
if (draw_attributes_.size() > 0)
|
||||
ra = draw_attributes_.front();
|
||||
return ra;
|
||||
}
|
||||
|
||||
glm::mat4 Rendering::Projection()
|
||||
{
|
||||
static glm::mat4 projection = glm::ortho(-SCENE_UNIT, SCENE_UNIT, -SCENE_UNIT, SCENE_UNIT, -SCENE_DEPTH, 1.f);
|
||||
glm::mat4 scale = glm::scale(glm::identity<glm::mat4>(), glm::vec3(1.f, main_.aspectRatio(), 1.f));
|
||||
|
||||
return projection * scale;
|
||||
}
|
||||
|
||||
|
||||
glm::vec3 Rendering::unProject(glm::vec2 screen_coordinate, glm::mat4 modelview)
|
||||
{
|
||||
glm::vec3 coordinates = glm::vec3( screen_coordinate.x, main_.height() - screen_coordinate.y, 0.f);
|
||||
glm::vec4 viewport = glm::vec4( 0.f, 0.f, main_.width(), main_.height());
|
||||
|
||||
// Log::Info("unproject %d x %d", main_window_attributes_.viewport.x, main_window_attributes_.viewport.y);
|
||||
|
||||
glm::vec3 point = glm::unProject(coordinates, modelview, Projection(), viewport);
|
||||
|
||||
return point;
|
||||
}
|
||||
|
||||
|
||||
glm::vec2 Rendering::project(glm::vec3 scene_coordinate, glm::mat4 modelview, bool to_framebuffer)
|
||||
{
|
||||
glm::vec4 viewport;
|
||||
if (to_framebuffer)
|
||||
viewport= glm::vec4( 0.f, 0.f, main_.width(), main_.height());
|
||||
else
|
||||
viewport= glm::vec4( 0.f, 0.f, main_.width() / main_.dpiScale(), main_.height() / main_.dpiScale());
|
||||
glm::vec3 P = glm::project( scene_coordinate, modelview, Projection(), viewport );
|
||||
|
||||
return glm::vec2(P.x, viewport.w - P.y);
|
||||
}
|
||||
|
||||
void Rendering::FileDropped(GLFWwindow *, int path_count, const char* paths[])
|
||||
{
|
||||
int i = 0;
|
||||
for (; i < path_count; ++i) {
|
||||
std::string filename(paths[i]);
|
||||
if (filename.empty())
|
||||
break;
|
||||
// try to create a source
|
||||
Mixer::manager().addSource ( Mixer::manager().createSourceFile( filename ) );
|
||||
}
|
||||
if (i>0) {
|
||||
UserInterface::manager().showPannel();
|
||||
Rendering::manager().mainWindow().show();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Screenshot *Rendering::currentScreenshot()
|
||||
{
|
||||
return &screenshot_;
|
||||
}
|
||||
|
||||
void Rendering::requestScreenshot()
|
||||
{
|
||||
request_screenshot_ = true;
|
||||
}
|
||||
|
||||
|
||||
// RAM usage in GPU
|
||||
// returns { CurAvailMemoryInKB, TotalMemoryInKB }
|
||||
// MAX values means the info in not available
|
||||
|
||||
#define GL_GPU_MEM_INFO_TOTAL_AVAILABLE_MEM_NVX 0x9048
|
||||
#define GL_GPU_MEM_INFO_CURRENT_AVAILABLE_MEM_NVX 0x9049
|
||||
#define GL_TEXTURE_FREE_MEMORY_ATI 0x87FC
|
||||
|
||||
glm::ivec2 Rendering::getGPUMemoryInformation()
|
||||
{
|
||||
glm::ivec2 ret(INT_MAX, INT_MAX);
|
||||
|
||||
// Detect method to get info
|
||||
static int meminfomode = -1;
|
||||
if (meminfomode<0) {
|
||||
// initialized
|
||||
meminfomode = 0;
|
||||
GLint numExtensions = 0;
|
||||
glGetIntegerv( GL_NUM_EXTENSIONS, &numExtensions );
|
||||
for (int i = 0; i < numExtensions; ++i){
|
||||
const GLubyte *ccc = glGetStringi(GL_EXTENSIONS, i);
|
||||
// NVIDIA extension available
|
||||
if ( strcmp( (const char*)ccc, "GL_NVX_gpu_memory_info") == 0 ){
|
||||
meminfomode = 1;
|
||||
break;
|
||||
}
|
||||
// ATI extension available
|
||||
else if ( strcmp( (const char*)ccc, "GL_ATI_meminfo") == 0 ){
|
||||
meminfomode = 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// NVIDIA
|
||||
if (meminfomode == 1) {
|
||||
static GLint nTotalMemoryInKB = -1;
|
||||
if (nTotalMemoryInKB<0)
|
||||
glGetIntegerv( GL_GPU_MEM_INFO_TOTAL_AVAILABLE_MEM_NVX, &nTotalMemoryInKB );
|
||||
ret.y = nTotalMemoryInKB;
|
||||
|
||||
glGetIntegerv( GL_GPU_MEM_INFO_CURRENT_AVAILABLE_MEM_NVX, &ret.x );
|
||||
}
|
||||
// ATI
|
||||
else if (meminfomode == 2) {
|
||||
GLint memInMB[4] = { 0, 0, 0, 0 };
|
||||
glGetIntegerv( GL_TEXTURE_FREE_MEMORY_ATI, &memInMB[0] );
|
||||
ret.x = memInMB[3] ;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
bool Rendering::shouldHaveEnoughMemory(glm::vec3 resolution, int flags)
|
||||
{
|
||||
glm::ivec2 RAM = getGPUMemoryInformation();
|
||||
|
||||
// approximation of RAM needed for such FBO
|
||||
GLint framebufferMemoryInKB = ( resolution.x * resolution.x *
|
||||
((flags & FrameBuffer::FrameBuffer_alpha)?4:3) * ((flags & FrameBuffer::FrameBuffer_multisampling)?2:1) ) / 1024;
|
||||
|
||||
return ( RAM.x > framebufferMemoryInKB * 3 );
|
||||
}
|
||||
|
||||
|
||||
|
||||
// custom surface with a new VAO
|
||||
class WindowSurface : public Primitive {
|
||||
|
||||
public:
|
||||
WindowSurface(Shader *s = new ImageShader);
|
||||
};
|
||||
|
||||
WindowSurface::WindowSurface(Shader *s) : Primitive(s)
|
||||
{
|
||||
points_ = std::vector<glm::vec3> { glm::vec3( -1.f, -1.f, 0.f ), glm::vec3( -1.f, 1.f, 0.f ),
|
||||
glm::vec3( 1.f, -1.f, 0.f ), glm::vec3( 1.f, 1.f, 0.f ) };
|
||||
colors_ = std::vector<glm::vec4> { glm::vec4( 1.f, 1.f, 1.f , 1.f ), glm::vec4( 1.f, 1.f, 1.f, 1.f ),
|
||||
glm::vec4( 1.f, 1.f, 1.f, 1.f ), glm::vec4( 1.f, 1.f, 1.f, 1.f ) };
|
||||
texCoords_ = std::vector<glm::vec2> { glm::vec2( 0.f, 1.f ), glm::vec2( 0.f, 0.f ),
|
||||
glm::vec2( 1.f, 1.f ), glm::vec2( 1.f, 0.f ) };
|
||||
indices_ = std::vector<uint> { 0, 1, 2, 3 };
|
||||
drawMode_ = GL_TRIANGLE_STRIP;
|
||||
}
|
||||
|
||||
|
||||
RenderingWindow::RenderingWindow() : window_(nullptr), master_(nullptr),
|
||||
index_(-1), dpi_scale_(1.f), textureid_(0), fbo_(0), surface_(nullptr), request_toggle_fullscreen_(false)
|
||||
{
|
||||
}
|
||||
|
||||
RenderingWindow::~RenderingWindow()
|
||||
{
|
||||
if (surface_ != nullptr)
|
||||
delete surface_;
|
||||
if (fbo_ != 0)
|
||||
glDeleteFramebuffers(1, &fbo_);
|
||||
}
|
||||
|
||||
void RenderingWindow::setTitle(const std::string &title)
|
||||
{
|
||||
std::string fulltitle;
|
||||
if ( title.empty() )
|
||||
fulltitle = Settings::application.windows[index_].name;
|
||||
else
|
||||
fulltitle = title + std::string(" - " APP_NAME);
|
||||
|
||||
glfwSetWindowTitle(window_, fulltitle.c_str());
|
||||
}
|
||||
|
||||
void RenderingWindow::setIcon(const std::string &resource)
|
||||
{
|
||||
size_t fpsize = 0;
|
||||
const char *fp = Resource::getData(resource, &fpsize);
|
||||
if (fp != nullptr) {
|
||||
GLFWimage icon;
|
||||
icon.pixels = stbi_load_from_memory( (const stbi_uc*)fp, fpsize, &icon.width, &icon.height, nullptr, 4 );
|
||||
glfwSetWindowIcon( window_, 1, &icon );
|
||||
free( icon.pixels );
|
||||
}
|
||||
}
|
||||
|
||||
bool RenderingWindow::isFullscreen ()
|
||||
{
|
||||
// return (glfwGetWindowMonitor(window_) != nullptr);
|
||||
return Settings::application.windows[index_].fullscreen;
|
||||
}
|
||||
|
||||
GLFWmonitor *RenderingWindow::monitorAt(int x, int y)
|
||||
{
|
||||
// default to primary monitor
|
||||
GLFWmonitor *mo = glfwGetPrimaryMonitor();
|
||||
|
||||
// list all monitors
|
||||
int count_monitors = 0;
|
||||
GLFWmonitor** monitors = glfwGetMonitors(&count_monitors);
|
||||
// if there is more than one monitor
|
||||
if (count_monitors > 1) {
|
||||
// pick at the coordinates given or at pos of window
|
||||
// try every monitor
|
||||
int i = 0;
|
||||
for (; i < count_monitors; i++) {
|
||||
int workarea_x, workarea_y, workarea_width, workarea_height;
|
||||
#if GLFW_VERSION_MINOR > 2
|
||||
glfwGetMonitorWorkarea(monitors[i], &workarea_x, &workarea_y, &workarea_width, &workarea_height);
|
||||
#else
|
||||
glfwGetMonitorPos(monitors[i], &workarea_x, &workarea_y);
|
||||
const GLFWvidmode *vm = glfwGetVideoMode(monitors[i]);
|
||||
workarea_width = vm->width;
|
||||
workarea_height = vm->height;
|
||||
#endif
|
||||
if ( x >= workarea_x && x <= workarea_x + workarea_width &&
|
||||
y >= workarea_y && y <= workarea_y + workarea_height)
|
||||
break;
|
||||
}
|
||||
// found the monitor containing this point !
|
||||
if ( i < count_monitors)
|
||||
mo = monitors[i];
|
||||
}
|
||||
|
||||
return mo;
|
||||
}
|
||||
|
||||
GLFWmonitor *RenderingWindow::monitorNamed(const std::string &name)
|
||||
{
|
||||
// default to primary monitor
|
||||
GLFWmonitor *mo = glfwGetPrimaryMonitor();
|
||||
|
||||
// list all monitors
|
||||
int count_monitors = 0;
|
||||
GLFWmonitor** monitors = glfwGetMonitors(&count_monitors);
|
||||
// if there is more than one monitor
|
||||
if (count_monitors > 1) {
|
||||
// pick at the coordinates given or at pos of window
|
||||
// try every monitor
|
||||
int i = 0;
|
||||
for (; i < count_monitors; i++) {
|
||||
if ( std::string( glfwGetMonitorName(monitors[i])) == name )
|
||||
break;
|
||||
}
|
||||
// found the monitor with this name
|
||||
if ( i < count_monitors)
|
||||
mo = monitors[i];
|
||||
}
|
||||
|
||||
return mo;
|
||||
}
|
||||
|
||||
GLFWmonitor *RenderingWindow::monitor()
|
||||
{
|
||||
// pick at the coordinates given or at pos of window
|
||||
int x, y;
|
||||
glfwGetWindowPos(window_, &x, &y);
|
||||
return monitorAt(x, y);
|
||||
}
|
||||
|
||||
void RenderingWindow::setFullscreen_(GLFWmonitor *mo)
|
||||
{
|
||||
// done request
|
||||
request_toggle_fullscreen_ = false;
|
||||
|
||||
// disable fullscreen mode
|
||||
if (mo == nullptr) {
|
||||
// store fullscreen mode
|
||||
Settings::application.windows[index_].fullscreen = false;
|
||||
|
||||
// set to window mode
|
||||
glfwSetInputMode( window_, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
|
||||
glfwSetWindowMonitor( window_, nullptr, Settings::application.windows[index_].x,
|
||||
Settings::application.windows[index_].y,
|
||||
Settings::application.windows[index_].w,
|
||||
Settings::application.windows[index_].h, 0 );
|
||||
}
|
||||
// set fullscreen mode
|
||||
else {
|
||||
// store fullscreen mode
|
||||
Settings::application.windows[index_].fullscreen = true;
|
||||
Settings::application.windows[index_].monitor = glfwGetMonitorName(mo);
|
||||
|
||||
// set to fullscreen mode
|
||||
const GLFWvidmode * mode = glfwGetVideoMode(mo);
|
||||
glfwSetInputMode( window_, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
|
||||
glfwSetWindowMonitor( window_, mo, 0, 0, mode->width, mode->height, mode->refreshRate);
|
||||
}
|
||||
|
||||
// Enable vsync on output window only (i.e. not 0 if has a master)
|
||||
// Workaround for disabled vsync in fullscreen (https://github.com/glfw/glfw/issues/1072)
|
||||
glfwSwapInterval( nullptr == master_ ? 0 : Settings::application.render.vsync);
|
||||
|
||||
}
|
||||
|
||||
void RenderingWindow::exitFullscreen()
|
||||
{
|
||||
if (isFullscreen()) {
|
||||
// exit fullscreen
|
||||
request_toggle_fullscreen_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void RenderingWindow::toggleFullscreen()
|
||||
{
|
||||
request_toggle_fullscreen_ = true;
|
||||
}
|
||||
|
||||
void RenderingWindow::toggleFullscreen_()
|
||||
{
|
||||
if (request_toggle_fullscreen_) {
|
||||
|
||||
// if in fullscreen mode
|
||||
if (glfwGetWindowMonitor(window_) != nullptr) {
|
||||
// exit fullscreen
|
||||
setFullscreen_(nullptr);
|
||||
}
|
||||
// not in fullscreen mode
|
||||
else {
|
||||
// enter fullscreen in monitor where the window is
|
||||
setFullscreen_(monitor());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int RenderingWindow::width()
|
||||
{
|
||||
return window_attributes_.viewport.x;
|
||||
}
|
||||
|
||||
int RenderingWindow::height()
|
||||
{
|
||||
return window_attributes_.viewport.y;
|
||||
}
|
||||
|
||||
int RenderingWindow::pixelsforRealHeight(float milimeters)
|
||||
{
|
||||
GLFWmonitor *mo = monitor();
|
||||
|
||||
int mm_w = 0;
|
||||
int mm_h = 0;
|
||||
glfwGetMonitorPhysicalSize(mo, &mm_w, &mm_h);
|
||||
|
||||
float pixels = milimeters;
|
||||
if (mm_h > 0)
|
||||
pixels *= static_cast<float>(glfwGetVideoMode(mo)->height) / static_cast<float>(mm_h);
|
||||
else
|
||||
pixels *= 5; // something reasonnable if monitor's physical size is unknown
|
||||
|
||||
return static_cast<int>( round(pixels) );
|
||||
}
|
||||
|
||||
float RenderingWindow::aspectRatio()
|
||||
{
|
||||
return static_cast<float>(window_attributes_.viewport.x) / static_cast<float>(window_attributes_.viewport.y);
|
||||
}
|
||||
|
||||
bool RenderingWindow::init(int index, GLFWwindow *share)
|
||||
{
|
||||
index_ = index;
|
||||
master_ = share;
|
||||
|
||||
// access Settings
|
||||
Settings::WindowConfig winset = Settings::application.windows[index_];
|
||||
|
||||
// do not show at creation
|
||||
glfwWindowHint(GLFW_FOCUSED, GLFW_FALSE);
|
||||
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
|
||||
glfwWindowHint(GLFW_AUTO_ICONIFY, GLFW_FALSE);
|
||||
|
||||
// create the window
|
||||
window_ = glfwCreateWindow(winset.w, winset.h, winset.name.c_str(), NULL, master_);
|
||||
if (window_ == NULL){
|
||||
Log::Error("Failed to create GLFW Window %d", index_);
|
||||
return false;
|
||||
}
|
||||
|
||||
// ensure minimal window size
|
||||
glfwSetWindowSizeLimits(window_, 800, 500, GLFW_DONT_CARE, GLFW_DONT_CARE);
|
||||
|
||||
previous_size = glm::vec2(winset.w, winset.h);
|
||||
|
||||
// set initial position
|
||||
glfwSetWindowPos(window_, winset.x, winset.y);
|
||||
|
||||
/// CALLBACKS
|
||||
// store global ref to pointers (used by callbacks)
|
||||
GLFW_window_[window_] = this;
|
||||
// window position and resize callbacks
|
||||
glfwSetWindowPosCallback( window_, WindowMoveCallback );
|
||||
glfwSetWindowSizeCallback( window_, WindowResizeCallback );
|
||||
|
||||
// take opengl context ownership
|
||||
glfwMakeContextCurrent(window_);
|
||||
|
||||
//
|
||||
// Initialize OpenGL loader on first call
|
||||
//
|
||||
static bool glad_initialized = false;
|
||||
if ( !glad_initialized ) {
|
||||
bool err = gladLoadGLLoader((GLADloadproc) glfwGetProcAddress) == 0;
|
||||
if (err) {
|
||||
Log::Error("Failed to initialize GLAD OpenGL loader.");
|
||||
return false;
|
||||
}
|
||||
glad_initialized = true;
|
||||
}
|
||||
|
||||
// get rendering area
|
||||
glfwGetFramebufferSize(window_, &(window_attributes_.viewport.x), &(window_attributes_.viewport.y));
|
||||
// DPI scaling (retina)
|
||||
dpi_scale_ = float(window_attributes_.viewport.y) / float(winset.h);
|
||||
|
||||
// We decide for byte aligned textures all over
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT,1);
|
||||
// This hint can improve the speed of texturing when perspective-correct texture coordinate interpolation isn't needed
|
||||
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);
|
||||
// fast mipmaps (we are not really using mipmaps anyway)
|
||||
glHint(GL_GENERATE_MIPMAP_HINT, GL_FASTEST);
|
||||
// acurate derivative for shader
|
||||
glHint(GL_FRAGMENT_SHADER_DERIVATIVE_HINT, GL_NICEST);
|
||||
glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
|
||||
|
||||
// if not main window
|
||||
if ( master_ != NULL ) {
|
||||
// Enable vsync on output window
|
||||
glfwSwapInterval(Settings::application.render.vsync);
|
||||
// no need for multisampling
|
||||
glDisable(GL_MULTISAMPLE);
|
||||
// clear to black
|
||||
window_attributes_.clear_color = glm::vec4(0.f, 0.f, 0.f, 1.f);
|
||||
// give back context ownership
|
||||
glfwMakeContextCurrent(master_);
|
||||
}
|
||||
else {
|
||||
// Disable vsync on main window
|
||||
glfwSwapInterval(0);
|
||||
// Enable Antialiasing multisampling
|
||||
if (Settings::application.render.multisampling > 0) {
|
||||
glEnable(GL_MULTISAMPLE);
|
||||
glHint(GL_MULTISAMPLE_FILTER_HINT_NV, GL_NICEST);
|
||||
}
|
||||
// clear to grey
|
||||
window_attributes_.clear_color = glm::vec4(COLOR_BGROUND, 1.f);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void RenderingWindow::show()
|
||||
{
|
||||
glfwShowWindow(window_);
|
||||
|
||||
if ( Settings::application.windows[index_].fullscreen ) {
|
||||
GLFWmonitor *mo = monitorNamed(Settings::application.windows[index_].monitor);
|
||||
setFullscreen_(mo);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
void RenderingWindow::makeCurrent()
|
||||
{
|
||||
// handle window resize
|
||||
glfwGetFramebufferSize(window_, &(window_attributes_.viewport.x), &(window_attributes_.viewport.y));
|
||||
|
||||
// ensure main context is current
|
||||
glfwMakeContextCurrent(window_);
|
||||
|
||||
// set and clear
|
||||
glViewport(0, 0, window_attributes_.viewport.x, window_attributes_.viewport.y);
|
||||
glClearColor(window_attributes_.clear_color.r, window_attributes_.clear_color.g,
|
||||
window_attributes_.clear_color.b, window_attributes_.clear_color.a);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
}
|
||||
|
||||
// TODO update parameters for draw on resize event (not every frame)
|
||||
|
||||
void RenderingWindow::draw(FrameBuffer *fb)
|
||||
{
|
||||
if (!window_ || !fb)
|
||||
return;
|
||||
|
||||
// only draw if window is not iconified
|
||||
if( !glfwGetWindowAttrib(window_, GLFW_ICONIFIED ) ) {
|
||||
|
||||
// update viewport (could be done with callback)
|
||||
glfwGetFramebufferSize(window_, &(window_attributes_.viewport.x), &(window_attributes_.viewport.y));
|
||||
|
||||
// take context ownership
|
||||
glfwMakeContextCurrent(window_);
|
||||
|
||||
// setup attribs
|
||||
Rendering::manager().pushAttrib(window_attributes_);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
// blit framebuffer
|
||||
if (Settings::application.render.blit) {
|
||||
|
||||
if ( textureid_ != fb->texture()) {
|
||||
|
||||
textureid_ = fb->texture();
|
||||
|
||||
// create a new fbo in this opengl context
|
||||
if (fbo_ != 0)
|
||||
glDeleteFramebuffers(1, &fbo_);
|
||||
glGenFramebuffers(1, &fbo_);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo_);
|
||||
|
||||
// attach the 2D texture to local FBO
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureid_, 0);
|
||||
#ifndef NDEBUG
|
||||
Log::Info("Blit to output window enabled.");
|
||||
#endif
|
||||
}
|
||||
|
||||
// if not disabled
|
||||
if (!Settings::application.render.disabled) {
|
||||
|
||||
// calculate scaling factor of frame buffer inside window
|
||||
int rx, ry, rw, rh;
|
||||
float renderingAspectRatio = fb->aspectRatio();
|
||||
if (aspectRatio() < renderingAspectRatio) {
|
||||
int nh = (int)( float(window_attributes_.viewport.x) / renderingAspectRatio);
|
||||
rx = 0;
|
||||
ry = (window_attributes_.viewport.y - nh) / 2;
|
||||
rw = window_attributes_.viewport.x;
|
||||
rh = (window_attributes_.viewport.y + nh) / 2;
|
||||
} else {
|
||||
int nw = (int)( float(window_attributes_.viewport.y) * renderingAspectRatio );
|
||||
rx = (window_attributes_.viewport.x - nw) / 2;
|
||||
ry = 0;
|
||||
rw = (window_attributes_.viewport.x + nw) / 2;
|
||||
rh = window_attributes_.viewport.y;
|
||||
}
|
||||
|
||||
// select fbo texture read target
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo_);
|
||||
|
||||
// select screen target
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
|
||||
// blit operation from fbo (containing texture) to screen
|
||||
glBlitFramebuffer(0, fb->height(), fb->width(), 0, rx, ry, rw, rh, GL_COLOR_BUFFER_BIT, GL_LINEAR);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// draw geometry
|
||||
else if (!Settings::application.render.disabled)
|
||||
{
|
||||
// VAO is not shared between multiple contexts of different windows
|
||||
// so we have to create a new VAO for rendering the surface in this window
|
||||
if (surface_ == 0)
|
||||
surface_ = new WindowSurface;
|
||||
|
||||
// calculate scaling factor of frame buffer inside window
|
||||
float windowAspectRatio = aspectRatio();
|
||||
float renderingAspectRatio = fb->aspectRatio();
|
||||
glm::vec3 scale;
|
||||
if (windowAspectRatio < renderingAspectRatio)
|
||||
scale = glm::vec3(1.f, windowAspectRatio / renderingAspectRatio, 1.f);
|
||||
else
|
||||
scale = glm::vec3(renderingAspectRatio / windowAspectRatio, 1.f, 1.f);
|
||||
|
||||
// make sure previous shader in another glcontext is disabled
|
||||
ShadingProgram::enduse();
|
||||
|
||||
// draw
|
||||
glBindTexture(GL_TEXTURE_2D, fb->texture());
|
||||
// surface->shader()->color.a = 0.4f; // TODO alpha blending ?
|
||||
static glm::mat4 projection = glm::ortho(-1.f, 1.f, -1.f, 1.f, -1.f, 1.f);
|
||||
surface_->draw(glm::scale(glm::identity<glm::mat4>(), scale), projection);
|
||||
|
||||
// done drawing (unload shader from this glcontext)
|
||||
ShadingProgram::enduse();
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
// restore attribs
|
||||
Rendering::manager().popAttrib();
|
||||
|
||||
// swap buffer
|
||||
glfwSwapBuffers(window_);
|
||||
}
|
||||
|
||||
// give back context ownership
|
||||
glfwMakeContextCurrent(master_);
|
||||
}
|
||||
|
||||
642
Settings.cpp
@@ -1,642 +0,0 @@
|
||||
/*
|
||||
* This file is part of vimix - video live mixer
|
||||
*
|
||||
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||
*
|
||||
* 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 3 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, see <https://www.gnu.org/licenses/>.
|
||||
**/
|
||||
|
||||
#include <iostream>
|
||||
#include <locale>
|
||||
using namespace std;
|
||||
|
||||
#include <tinyxml2.h>
|
||||
#include "tinyxml2Toolkit.h"
|
||||
using namespace tinyxml2;
|
||||
|
||||
#include "defines.h"
|
||||
#include "SystemToolkit.h"
|
||||
#include "Settings.h"
|
||||
|
||||
|
||||
Settings::Application Settings::application;
|
||||
string settingsFilename = "";
|
||||
|
||||
|
||||
XMLElement *save_history(Settings::History &h, const char *nodename, XMLDocument &xmlDoc)
|
||||
{
|
||||
XMLElement *pElement = xmlDoc.NewElement( nodename );
|
||||
pElement->SetAttribute("path", h.path.c_str());
|
||||
pElement->SetAttribute("autoload", h.load_at_start);
|
||||
pElement->SetAttribute("autosave", h.save_on_exit);
|
||||
pElement->SetAttribute("valid", h.front_is_valid);
|
||||
for(auto it = h.filenames.cbegin();
|
||||
it != h.filenames.cend(); ++it) {
|
||||
XMLElement *fileNode = xmlDoc.NewElement("path");
|
||||
XMLText *text = xmlDoc.NewText( (*it).c_str() );
|
||||
fileNode->InsertEndChild( text );
|
||||
pElement->InsertFirstChild(fileNode);
|
||||
}
|
||||
return pElement;
|
||||
}
|
||||
|
||||
|
||||
void Settings::Save(uint64_t runtime)
|
||||
{
|
||||
// impose C locale for all app
|
||||
setlocale(LC_ALL, "C");
|
||||
|
||||
XMLDocument xmlDoc;
|
||||
XMLDeclaration *pDec = xmlDoc.NewDeclaration();
|
||||
xmlDoc.InsertFirstChild(pDec);
|
||||
|
||||
XMLElement *pRoot = xmlDoc.NewElement(application.name.c_str());
|
||||
xmlDoc.InsertEndChild(pRoot);
|
||||
#ifdef VIMIX_VERSION_MAJOR
|
||||
pRoot->SetAttribute("major", VIMIX_VERSION_MAJOR);
|
||||
pRoot->SetAttribute("minor", VIMIX_VERSION_MINOR);
|
||||
#endif
|
||||
// runtime
|
||||
if (runtime>0)
|
||||
pRoot->SetAttribute("runtime", runtime + application.total_runtime);
|
||||
|
||||
string comment = "Settings for " + application.name;
|
||||
XMLComment *pComment = xmlDoc.NewComment(comment.c_str());
|
||||
pRoot->InsertEndChild(pComment);
|
||||
|
||||
// Windows
|
||||
{
|
||||
XMLElement *windowsNode = xmlDoc.NewElement( "Windows" );
|
||||
|
||||
for (int i = 0; i < (int) application.windows.size(); ++i)
|
||||
{
|
||||
const Settings::WindowConfig& w = application.windows[i];
|
||||
|
||||
XMLElement *window = xmlDoc.NewElement( "Window" );
|
||||
window->SetAttribute("name", w.name.c_str());
|
||||
window->SetAttribute("id", i);
|
||||
window->SetAttribute("x", w.x);
|
||||
window->SetAttribute("y", w.y);
|
||||
window->SetAttribute("w", w.w);
|
||||
window->SetAttribute("h", w.h);
|
||||
window->SetAttribute("f", w.fullscreen);
|
||||
window->SetAttribute("m", w.monitor.c_str());
|
||||
windowsNode->InsertEndChild(window);
|
||||
}
|
||||
|
||||
pRoot->InsertEndChild(windowsNode);
|
||||
}
|
||||
|
||||
// General application preferences
|
||||
XMLElement *applicationNode = xmlDoc.NewElement( "Application" );
|
||||
applicationNode->SetAttribute("scale", application.scale);
|
||||
applicationNode->SetAttribute("accent_color", application.accent_color);
|
||||
applicationNode->SetAttribute("smooth_transition", application.smooth_transition);
|
||||
applicationNode->SetAttribute("save_snapshot", application.save_version_snapshot);
|
||||
applicationNode->SetAttribute("smooth_cursor", application.smooth_cursor);
|
||||
applicationNode->SetAttribute("action_history_follow_view", application.action_history_follow_view);
|
||||
applicationNode->SetAttribute("show_tooptips", application.show_tooptips);
|
||||
applicationNode->SetAttribute("accept_connections", application.accept_connections);
|
||||
applicationNode->SetAttribute("pannel_history_mode", application.pannel_current_session_mode);
|
||||
applicationNode->SetAttribute("stream_protocol", application.stream_protocol);
|
||||
applicationNode->SetAttribute("broadcast_port", application.broadcast_port);
|
||||
applicationNode->SetAttribute("custom_connect_ip", application.custom_connect_ip.c_str());
|
||||
applicationNode->SetAttribute("custom_connect_port", application.custom_connect_port.c_str());
|
||||
pRoot->InsertEndChild(applicationNode);
|
||||
|
||||
// Widgets
|
||||
XMLElement *widgetsNode = xmlDoc.NewElement( "Widgets" );
|
||||
widgetsNode->SetAttribute("preview", application.widget.preview);
|
||||
widgetsNode->SetAttribute("preview_view", application.widget.preview_view);
|
||||
widgetsNode->SetAttribute("inputs", application.widget.inputs);
|
||||
widgetsNode->SetAttribute("inputs_view", application.widget.inputs_view);
|
||||
widgetsNode->SetAttribute("timer", application.widget.timer);
|
||||
widgetsNode->SetAttribute("timer_view", application.widget.timer_view);
|
||||
widgetsNode->SetAttribute("media_player", application.widget.media_player);
|
||||
widgetsNode->SetAttribute("media_player_view", application.widget.media_player_view);
|
||||
widgetsNode->SetAttribute("timeline_editmode", application.widget.media_player_timeline_editmode);
|
||||
widgetsNode->SetAttribute("media_player_slider", application.widget.media_player_slider);
|
||||
widgetsNode->SetAttribute("shader_editor", application.widget.shader_editor);
|
||||
widgetsNode->SetAttribute("shader_editor_view", application.widget.shader_editor_view);
|
||||
widgetsNode->SetAttribute("stats", application.widget.stats);
|
||||
widgetsNode->SetAttribute("stats_mode", application.widget.stats_mode);
|
||||
widgetsNode->SetAttribute("stats_corner", application.widget.stats_corner);
|
||||
widgetsNode->SetAttribute("logs", application.widget.logs);
|
||||
widgetsNode->SetAttribute("toolbox", application.widget.toolbox);
|
||||
pRoot->InsertEndChild(widgetsNode);
|
||||
|
||||
// Render
|
||||
XMLElement *RenderNode = xmlDoc.NewElement( "Render" );
|
||||
RenderNode->SetAttribute("vsync", application.render.vsync);
|
||||
RenderNode->SetAttribute("multisampling", application.render.multisampling);
|
||||
RenderNode->SetAttribute("blit", application.render.blit);
|
||||
RenderNode->SetAttribute("gpu_decoding", application.render.gpu_decoding);
|
||||
RenderNode->SetAttribute("ratio", application.render.ratio);
|
||||
RenderNode->SetAttribute("res", application.render.res);
|
||||
pRoot->InsertEndChild(RenderNode);
|
||||
|
||||
// Record
|
||||
XMLElement *RecordNode = xmlDoc.NewElement( "Record" );
|
||||
RecordNode->SetAttribute("path", application.record.path.c_str());
|
||||
RecordNode->SetAttribute("profile", application.record.profile);
|
||||
RecordNode->SetAttribute("timeout", application.record.timeout);
|
||||
RecordNode->SetAttribute("delay", application.record.delay);
|
||||
RecordNode->SetAttribute("resolution_mode", application.record.resolution_mode);
|
||||
RecordNode->SetAttribute("framerate_mode", application.record.framerate_mode);
|
||||
RecordNode->SetAttribute("buffering_mode", application.record.buffering_mode);
|
||||
RecordNode->SetAttribute("priority_mode", application.record.priority_mode);
|
||||
RecordNode->SetAttribute("naming_mode", application.record.naming_mode);
|
||||
pRoot->InsertEndChild(RecordNode);
|
||||
|
||||
// Transition
|
||||
XMLElement *TransitionNode = xmlDoc.NewElement( "Transition" );
|
||||
TransitionNode->SetAttribute("cross_fade", application.transition.cross_fade);
|
||||
TransitionNode->SetAttribute("duration", application.transition.duration);
|
||||
TransitionNode->SetAttribute("profile", application.transition.profile);
|
||||
pRoot->InsertEndChild(TransitionNode);
|
||||
|
||||
// Source
|
||||
XMLElement *SourceConfNode = xmlDoc.NewElement( "Source" );
|
||||
SourceConfNode->SetAttribute("new_type", application.source.new_type);
|
||||
SourceConfNode->SetAttribute("ratio", application.source.ratio);
|
||||
SourceConfNode->SetAttribute("res", application.source.res);
|
||||
SourceConfNode->SetAttribute("capture_naming", application.source.capture_naming);
|
||||
SourceConfNode->SetAttribute("capture_path", application.source.capture_path.c_str());
|
||||
pRoot->InsertEndChild(SourceConfNode);
|
||||
|
||||
// Brush
|
||||
XMLElement *BrushNode = xmlDoc.NewElement( "Brush" );
|
||||
BrushNode->InsertEndChild( XMLElementFromGLM(&xmlDoc, application.brush) );
|
||||
pRoot->InsertEndChild(BrushNode);
|
||||
|
||||
// bloc views
|
||||
{
|
||||
XMLElement *viewsNode = xmlDoc.NewElement( "Views" );
|
||||
// save current view only if [mixing, geometry, layers, appearance]
|
||||
int v = application.current_view > 4 ? 1 : application.current_view;
|
||||
viewsNode->SetAttribute("current", v);
|
||||
viewsNode->SetAttribute("workspace", application.current_workspace);
|
||||
|
||||
map<int, Settings::ViewConfig>::iterator iter;
|
||||
for (iter=application.views.begin(); iter != application.views.end(); ++iter)
|
||||
{
|
||||
const Settings::ViewConfig& view_config = iter->second;
|
||||
|
||||
XMLElement *view = xmlDoc.NewElement( "View" );
|
||||
view->SetAttribute("name", view_config.name.c_str());
|
||||
view->SetAttribute("id", iter->first);
|
||||
|
||||
XMLElement *scale = xmlDoc.NewElement("default_scale");
|
||||
scale->InsertEndChild( XMLElementFromGLM(&xmlDoc, view_config.default_scale) );
|
||||
view->InsertEndChild(scale);
|
||||
XMLElement *translation = xmlDoc.NewElement("default_translation");
|
||||
translation->InsertEndChild( XMLElementFromGLM(&xmlDoc, view_config.default_translation) );
|
||||
view->InsertEndChild(translation);
|
||||
|
||||
viewsNode->InsertEndChild(view);
|
||||
}
|
||||
|
||||
pRoot->InsertEndChild(viewsNode);
|
||||
}
|
||||
|
||||
// bloc history
|
||||
{
|
||||
XMLElement *recent = xmlDoc.NewElement( "Recent" );
|
||||
|
||||
// recent session filenames
|
||||
recent->InsertEndChild( save_history(application.recentSessions, "Session", xmlDoc));
|
||||
|
||||
// recent session folders
|
||||
recent->InsertEndChild( save_history(application.recentFolders, "Folder", xmlDoc));
|
||||
|
||||
// recent import media uri
|
||||
recent->InsertEndChild( save_history(application.recentImport, "Import", xmlDoc));
|
||||
|
||||
// recent import folders
|
||||
recent->InsertEndChild( save_history(application.recentImportFolders, "ImportFolder", xmlDoc));
|
||||
|
||||
// recent recordings
|
||||
recent->InsertEndChild( save_history(application.recentRecordings, "Record", xmlDoc));
|
||||
|
||||
// recent dialog path
|
||||
XMLElement *recentdialogpath = xmlDoc.NewElement( "Dialog" );
|
||||
for(auto it = application.dialogRecentFolder.cbegin();
|
||||
it != application.dialogRecentFolder.cend(); ++it) {
|
||||
XMLElement *pathNode = xmlDoc.NewElement("path");
|
||||
pathNode->SetAttribute("label", (*it).first.c_str() );
|
||||
XMLText *text = xmlDoc.NewText( (*it).second.c_str() );
|
||||
pathNode->InsertEndChild( text );
|
||||
recentdialogpath->InsertFirstChild(pathNode);
|
||||
}
|
||||
recent->InsertEndChild(recentdialogpath);
|
||||
|
||||
pRoot->InsertEndChild(recent);
|
||||
}
|
||||
|
||||
// Timer Metronome
|
||||
XMLElement *timerConfNode = xmlDoc.NewElement( "Timer" );
|
||||
timerConfNode->SetAttribute("mode", application.timer.mode);
|
||||
timerConfNode->SetAttribute("link_enabled", application.timer.link_enabled);
|
||||
timerConfNode->SetAttribute("link_tempo", application.timer.link_tempo);
|
||||
timerConfNode->SetAttribute("link_quantum", application.timer.link_quantum);
|
||||
timerConfNode->SetAttribute("link_start_stop_sync", application.timer.link_start_stop_sync);
|
||||
timerConfNode->SetAttribute("stopwatch_duration", application.timer.stopwatch_duration);
|
||||
pRoot->InsertEndChild(timerConfNode);
|
||||
|
||||
// Inputs mapping
|
||||
XMLElement *mappingConfNode = xmlDoc.NewElement( "Mapping" );
|
||||
mappingConfNode->SetAttribute("mode", application.mapping.mode);
|
||||
mappingConfNode->SetAttribute("current", application.mapping.current);
|
||||
mappingConfNode->SetAttribute("disabled", application.mapping.disabled);
|
||||
pRoot->InsertEndChild(mappingConfNode);
|
||||
|
||||
// Controller
|
||||
XMLElement *controlConfNode = xmlDoc.NewElement( "Control" );
|
||||
controlConfNode->SetAttribute("osc_port_receive", application.control.osc_port_receive);
|
||||
controlConfNode->SetAttribute("osc_port_send", application.control.osc_port_send);
|
||||
|
||||
// First save : create filename
|
||||
if (settingsFilename.empty())
|
||||
settingsFilename = SystemToolkit::full_filename(SystemToolkit::settings_path(), APP_SETTINGS);
|
||||
|
||||
XMLError eResult = xmlDoc.SaveFile(settingsFilename.c_str());
|
||||
XMLResultError(eResult);
|
||||
|
||||
}
|
||||
|
||||
|
||||
void load_history(Settings::History &h, const char *nodename, XMLElement *root)
|
||||
{
|
||||
XMLElement * pElement = root->FirstChildElement(nodename);
|
||||
if (pElement)
|
||||
{
|
||||
// list of path
|
||||
h.filenames.clear();
|
||||
XMLElement* path = pElement->FirstChildElement("path");
|
||||
for( ; path ; path = path->NextSiblingElement())
|
||||
{
|
||||
const char *p = path->GetText();
|
||||
if (p)
|
||||
h.push( std::string (p) );
|
||||
}
|
||||
// path attribute
|
||||
const char *path_ = pElement->Attribute("path");
|
||||
if (path_)
|
||||
h.path = std::string(path_);
|
||||
else
|
||||
h.path = SystemToolkit::home_path();
|
||||
// other attritutes
|
||||
pElement->QueryBoolAttribute("autoload", &h.load_at_start);
|
||||
pElement->QueryBoolAttribute("autosave", &h.save_on_exit);
|
||||
pElement->QueryBoolAttribute("valid", &h.front_is_valid);
|
||||
}
|
||||
}
|
||||
|
||||
void Settings::Load()
|
||||
{
|
||||
// impose C locale for all app
|
||||
setlocale(LC_ALL, "C");
|
||||
|
||||
// set filenames from settings path
|
||||
application.control.osc_filename = SystemToolkit::full_filename(SystemToolkit::settings_path(), OSC_CONFIG_FILE);
|
||||
settingsFilename = SystemToolkit::full_filename(SystemToolkit::settings_path(), APP_SETTINGS);
|
||||
|
||||
// try to load settings file
|
||||
XMLDocument xmlDoc;
|
||||
XMLError eResult = xmlDoc.LoadFile(settingsFilename.c_str());
|
||||
|
||||
// do not warn if non existing file
|
||||
if (eResult == XML_ERROR_FILE_NOT_FOUND)
|
||||
return;
|
||||
// warn and return on other error
|
||||
else if (XMLResultError(eResult))
|
||||
return;
|
||||
|
||||
// first element should be called by the application name
|
||||
XMLElement *pRoot = xmlDoc.FirstChildElement(application.name.c_str());
|
||||
if (pRoot == nullptr)
|
||||
return;
|
||||
|
||||
// runtime
|
||||
pRoot->QueryUnsigned64Attribute("runtime", &application.total_runtime);
|
||||
|
||||
// General application preferences
|
||||
XMLElement * applicationNode = pRoot->FirstChildElement("Application");
|
||||
if (applicationNode != nullptr) {
|
||||
applicationNode->QueryFloatAttribute("scale", &application.scale);
|
||||
applicationNode->QueryIntAttribute("accent_color", &application.accent_color);
|
||||
applicationNode->QueryBoolAttribute("smooth_transition", &application.smooth_transition);
|
||||
applicationNode->QueryBoolAttribute("save_snapshot", &application.save_version_snapshot);
|
||||
applicationNode->QueryBoolAttribute("smooth_cursor", &application.smooth_cursor);
|
||||
applicationNode->QueryBoolAttribute("action_history_follow_view", &application.action_history_follow_view);
|
||||
applicationNode->QueryBoolAttribute("show_tooptips", &application.show_tooptips);
|
||||
applicationNode->QueryBoolAttribute("accept_connections", &application.accept_connections);
|
||||
applicationNode->QueryIntAttribute("pannel_history_mode", &application.pannel_current_session_mode);
|
||||
applicationNode->QueryIntAttribute("stream_protocol", &application.stream_protocol);
|
||||
applicationNode->QueryIntAttribute("broadcast_port", &application.broadcast_port);
|
||||
const char *ip = applicationNode->Attribute("custom_connect_ip");
|
||||
if (ip)
|
||||
application.custom_connect_ip = std::string(ip);
|
||||
else
|
||||
application.custom_connect_ip = "127.0.0.1";
|
||||
const char *p = applicationNode->Attribute("custom_connect_port");
|
||||
if (p)
|
||||
application.custom_connect_port = std::string(p);
|
||||
else
|
||||
application.custom_connect_port = "8888";
|
||||
}
|
||||
|
||||
// Widgets
|
||||
XMLElement * widgetsNode = pRoot->FirstChildElement("Widgets");
|
||||
if (widgetsNode != nullptr) {
|
||||
widgetsNode->QueryBoolAttribute("preview", &application.widget.preview);
|
||||
widgetsNode->QueryIntAttribute("preview_view", &application.widget.preview_view);
|
||||
widgetsNode->QueryBoolAttribute("timer", &application.widget.timer);
|
||||
widgetsNode->QueryIntAttribute("timer_view", &application.widget.timer_view);
|
||||
widgetsNode->QueryBoolAttribute("inputs", &application.widget.inputs);
|
||||
widgetsNode->QueryIntAttribute("inputs_view", &application.widget.inputs_view);
|
||||
widgetsNode->QueryBoolAttribute("media_player", &application.widget.media_player);
|
||||
widgetsNode->QueryIntAttribute("media_player_view", &application.widget.media_player_view);
|
||||
widgetsNode->QueryBoolAttribute("timeline_editmode", &application.widget.media_player_timeline_editmode);
|
||||
widgetsNode->QueryFloatAttribute("media_player_slider", &application.widget.media_player_slider);
|
||||
widgetsNode->QueryBoolAttribute("shader_editor", &application.widget.shader_editor);
|
||||
widgetsNode->QueryIntAttribute("shader_editor_view", &application.widget.shader_editor_view);
|
||||
widgetsNode->QueryBoolAttribute("stats", &application.widget.stats);
|
||||
widgetsNode->QueryIntAttribute("stats_mode", &application.widget.stats_mode);
|
||||
widgetsNode->QueryIntAttribute("stats_corner", &application.widget.stats_corner);
|
||||
widgetsNode->QueryBoolAttribute("logs", &application.widget.logs);
|
||||
widgetsNode->QueryBoolAttribute("toolbox", &application.widget.toolbox);
|
||||
}
|
||||
|
||||
// Render
|
||||
XMLElement * rendernode = pRoot->FirstChildElement("Render");
|
||||
if (rendernode != nullptr) {
|
||||
rendernode->QueryIntAttribute("vsync", &application.render.vsync);
|
||||
rendernode->QueryIntAttribute("multisampling", &application.render.multisampling);
|
||||
rendernode->QueryBoolAttribute("blit", &application.render.blit);
|
||||
rendernode->QueryBoolAttribute("gpu_decoding", &application.render.gpu_decoding);
|
||||
rendernode->QueryIntAttribute("ratio", &application.render.ratio);
|
||||
rendernode->QueryIntAttribute("res", &application.render.res);
|
||||
}
|
||||
|
||||
// Record
|
||||
XMLElement * recordnode = pRoot->FirstChildElement("Record");
|
||||
if (recordnode != nullptr) {
|
||||
recordnode->QueryIntAttribute("profile", &application.record.profile);
|
||||
recordnode->QueryUnsignedAttribute("timeout", &application.record.timeout);
|
||||
recordnode->QueryIntAttribute("delay", &application.record.delay);
|
||||
recordnode->QueryIntAttribute("resolution_mode", &application.record.resolution_mode);
|
||||
recordnode->QueryIntAttribute("framerate_mode", &application.record.framerate_mode);
|
||||
recordnode->QueryIntAttribute("buffering_mode", &application.record.buffering_mode);
|
||||
recordnode->QueryIntAttribute("priority_mode", &application.record.priority_mode);
|
||||
recordnode->QueryIntAttribute("naming_mode", &application.record.naming_mode);
|
||||
|
||||
const char *path_ = recordnode->Attribute("path");
|
||||
if (path_)
|
||||
application.record.path = std::string(path_);
|
||||
else
|
||||
application.record.path = SystemToolkit::home_path();
|
||||
}
|
||||
|
||||
// Source
|
||||
XMLElement * sourceconfnode = pRoot->FirstChildElement("Source");
|
||||
if (sourceconfnode != nullptr) {
|
||||
sourceconfnode->QueryIntAttribute("new_type", &application.source.new_type);
|
||||
sourceconfnode->QueryIntAttribute("ratio", &application.source.ratio);
|
||||
sourceconfnode->QueryIntAttribute("res", &application.source.res);
|
||||
|
||||
sourceconfnode->QueryIntAttribute("capture_naming", &application.source.capture_naming);
|
||||
const char *path_ = sourceconfnode->Attribute("capture_path");
|
||||
if (path_)
|
||||
application.source.capture_path = std::string(path_);
|
||||
else
|
||||
application.source.capture_path = SystemToolkit::home_path();
|
||||
}
|
||||
|
||||
// Transition
|
||||
XMLElement * transitionnode = pRoot->FirstChildElement("Transition");
|
||||
if (transitionnode != nullptr) {
|
||||
transitionnode->QueryBoolAttribute("cross_fade", &application.transition.cross_fade);
|
||||
transitionnode->QueryFloatAttribute("duration", &application.transition.duration);
|
||||
transitionnode->QueryIntAttribute("profile", &application.transition.profile);
|
||||
}
|
||||
|
||||
// Windows
|
||||
{
|
||||
XMLElement * pElement = pRoot->FirstChildElement("Windows");
|
||||
if (pElement)
|
||||
{
|
||||
XMLElement* windowNode = pElement->FirstChildElement("Window");
|
||||
for( ; windowNode ; windowNode=windowNode->NextSiblingElement())
|
||||
{
|
||||
Settings::WindowConfig w;
|
||||
windowNode->QueryIntAttribute("x", &w.x); // If this fails, original value is left as-is
|
||||
windowNode->QueryIntAttribute("y", &w.y);
|
||||
windowNode->QueryIntAttribute("w", &w.w);
|
||||
windowNode->QueryIntAttribute("h", &w.h);
|
||||
windowNode->QueryBoolAttribute("f", &w.fullscreen);
|
||||
const char *text = windowNode->Attribute("m");
|
||||
if (text)
|
||||
w.monitor = std::string(text);
|
||||
|
||||
int i = 0;
|
||||
windowNode->QueryIntAttribute("id", &i);
|
||||
w.name = application.windows[i].name; // keep only original name
|
||||
application.windows[i] = w;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Brush
|
||||
XMLElement * brushnode = pRoot->FirstChildElement("Brush");
|
||||
if (brushnode != nullptr) {
|
||||
tinyxml2::XMLElementToGLM( brushnode->FirstChildElement("vec3"), application.brush);
|
||||
}
|
||||
|
||||
// bloc views
|
||||
{
|
||||
XMLElement * pElement = pRoot->FirstChildElement("Views");
|
||||
if (pElement)
|
||||
{
|
||||
application.views.clear(); // trash existing list
|
||||
pElement->QueryIntAttribute("current", &application.current_view);
|
||||
pElement->QueryIntAttribute("workspace", &application.current_workspace);
|
||||
|
||||
XMLElement* viewNode = pElement->FirstChildElement("View");
|
||||
for( ; viewNode ; viewNode=viewNode->NextSiblingElement())
|
||||
{
|
||||
int id = 0;
|
||||
viewNode->QueryIntAttribute("id", &id);
|
||||
application.views[id].name = viewNode->Attribute("name");
|
||||
|
||||
XMLElement* scaleNode = viewNode->FirstChildElement("default_scale");
|
||||
tinyxml2::XMLElementToGLM( scaleNode->FirstChildElement("vec3"),
|
||||
application.views[id].default_scale);
|
||||
|
||||
XMLElement* translationNode = viewNode->FirstChildElement("default_translation");
|
||||
tinyxml2::XMLElementToGLM( translationNode->FirstChildElement("vec3"),
|
||||
application.views[id].default_translation);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// bloc history of recent
|
||||
{
|
||||
XMLElement * pElement = pRoot->FirstChildElement("Recent");
|
||||
if (pElement)
|
||||
{
|
||||
// recent session filenames
|
||||
load_history(application.recentSessions, "Session", pElement);
|
||||
|
||||
// recent session folders
|
||||
load_history(application.recentFolders, "Folder", pElement);
|
||||
|
||||
// recent media uri
|
||||
load_history(application.recentImport, "Import", pElement);
|
||||
|
||||
// recent import folders
|
||||
load_history(application.recentImportFolders, "ImportFolder", pElement);
|
||||
|
||||
// recent recordings
|
||||
load_history(application.recentRecordings, "Record", pElement);
|
||||
|
||||
// recent dialog path
|
||||
XMLElement * pDialog = pElement->FirstChildElement("Dialog");
|
||||
if (pDialog)
|
||||
{
|
||||
application.dialogRecentFolder.clear();
|
||||
XMLElement* path = pDialog->FirstChildElement("path");
|
||||
for( ; path ; path = path->NextSiblingElement())
|
||||
{
|
||||
const char *l = path->Attribute("label");
|
||||
const char *p = path->GetText();
|
||||
if (l && p)
|
||||
application.dialogRecentFolder[ std::string(l)] = std::string (p);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Timer Metronome
|
||||
XMLElement * timerconfnode = pRoot->FirstChildElement("Timer");
|
||||
if (timerconfnode != nullptr) {
|
||||
timerconfnode->QueryUnsigned64Attribute("mode", &application.timer.mode);
|
||||
timerconfnode->QueryBoolAttribute("link_enabled", &application.timer.link_enabled);
|
||||
timerconfnode->QueryDoubleAttribute("link_tempo", &application.timer.link_tempo);
|
||||
timerconfnode->QueryDoubleAttribute("link_quantum", &application.timer.link_quantum);
|
||||
timerconfnode->QueryBoolAttribute("link_start_stop_sync", &application.timer.link_start_stop_sync);
|
||||
timerconfnode->QueryUnsigned64Attribute("stopwatch_duration", &application.timer.stopwatch_duration);
|
||||
}
|
||||
|
||||
// Mapping
|
||||
XMLElement * mappingconfnode = pRoot->FirstChildElement("Mapping");
|
||||
if (mappingconfnode != nullptr) {
|
||||
mappingconfnode->QueryUnsigned64Attribute("mode", &application.mapping.mode);
|
||||
mappingconfnode->QueryUnsignedAttribute("current", &application.mapping.current);
|
||||
mappingconfnode->QueryBoolAttribute("disabled", &application.mapping.disabled);
|
||||
}
|
||||
|
||||
// bloc Controller
|
||||
XMLElement *controlconfnode = pRoot->FirstChildElement("Control");
|
||||
if (controlconfnode != nullptr) {
|
||||
controlconfnode->QueryIntAttribute("osc_port_receive", &application.control.osc_port_receive);
|
||||
controlconfnode->QueryIntAttribute("osc_port_send", &application.control.osc_port_send);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Settings::History::push(const string &filename)
|
||||
{
|
||||
if (filename.empty()) {
|
||||
front_is_valid = false;
|
||||
return;
|
||||
}
|
||||
filenames.remove(filename);
|
||||
filenames.push_front(filename);
|
||||
if (filenames.size() > MAX_RECENT_HISTORY)
|
||||
filenames.pop_back();
|
||||
front_is_valid = true;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
void Settings::History::remove(const std::string &filename)
|
||||
{
|
||||
if (filename.empty())
|
||||
return;
|
||||
if (filenames.front() == filename)
|
||||
front_is_valid = false;
|
||||
filenames.remove(filename);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
void Settings::History::validate()
|
||||
{
|
||||
for (auto fit = filenames.begin(); fit != filenames.end();) {
|
||||
if ( SystemToolkit::file_exists( *fit ))
|
||||
++fit;
|
||||
else
|
||||
fit = filenames.erase(fit);
|
||||
}
|
||||
}
|
||||
|
||||
void Settings::Lock()
|
||||
{
|
||||
|
||||
std::string lockfile = SystemToolkit::full_filename(SystemToolkit::settings_path(), "lock");
|
||||
application.fresh_start = false;
|
||||
|
||||
FILE *file = fopen(lockfile.c_str(), "r");
|
||||
int l = 0;
|
||||
if (file) {
|
||||
if ( fscanf(file, "%d", &l) < 1)
|
||||
l = 0;
|
||||
fclose(file);
|
||||
}
|
||||
|
||||
// not locked or file not existing
|
||||
if ( l < 1 ) {
|
||||
file = fopen(lockfile.c_str(), "w");
|
||||
if (file) {
|
||||
fprintf(file, "1");
|
||||
fclose(file);
|
||||
}
|
||||
application.fresh_start = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Settings::Unlock()
|
||||
{
|
||||
std::string lockfile = SystemToolkit::full_filename(SystemToolkit::settings_path(), "lock");
|
||||
FILE *file = fopen(lockfile.c_str(), "w");
|
||||
if (file) {
|
||||
fprintf(file, "0");
|
||||
fclose(file);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Settings::Check()
|
||||
{
|
||||
XMLDocument xmlDoc;
|
||||
XMLError eResult = xmlDoc.LoadFile(settingsFilename.c_str());
|
||||
if (XMLResultError(eResult)) {
|
||||
return;
|
||||
}
|
||||
xmlDoc.Print();
|
||||
}
|
||||
|
||||
@@ -1,693 +0,0 @@
|
||||
/*
|
||||
* This file is part of vimix - video live mixer
|
||||
*
|
||||
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||
*
|
||||
* 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 3 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, see <https://www.gnu.org/licenses/>.
|
||||
**/
|
||||
|
||||
#include "defines.h"
|
||||
#include "Source.h"
|
||||
#include "UpdateCallback.h"
|
||||
#include "Visitor.h"
|
||||
#include "Log.h"
|
||||
|
||||
#include "SourceCallback.h"
|
||||
|
||||
|
||||
SourceCallback *SourceCallback::create(CallbackType type)
|
||||
{
|
||||
SourceCallback *loadedcallback = nullptr;
|
||||
|
||||
switch (type) {
|
||||
case SourceCallback::CALLBACK_ALPHA:
|
||||
loadedcallback = new SetAlpha;
|
||||
break;
|
||||
case SourceCallback::CALLBACK_LOOM:
|
||||
loadedcallback = new Loom;
|
||||
break;
|
||||
case SourceCallback::CALLBACK_GEOMETRY:
|
||||
loadedcallback = new SetGeometry;
|
||||
break;
|
||||
case SourceCallback::CALLBACK_GRAB:
|
||||
loadedcallback = new Grab;
|
||||
break;
|
||||
case SourceCallback::CALLBACK_RESIZE:
|
||||
loadedcallback = new Resize;
|
||||
break;
|
||||
case SourceCallback::CALLBACK_TURN:
|
||||
loadedcallback = new Turn;
|
||||
break;
|
||||
case SourceCallback::CALLBACK_DEPTH:
|
||||
loadedcallback = new SetDepth;
|
||||
break;
|
||||
case SourceCallback::CALLBACK_PLAY:
|
||||
loadedcallback = new Play;
|
||||
break;
|
||||
case SourceCallback::CALLBACK_REPLAY:
|
||||
loadedcallback = new RePlay;
|
||||
break;
|
||||
case SourceCallback::CALLBACK_RESETGEO:
|
||||
loadedcallback = new ResetGeometry;
|
||||
break;
|
||||
case SourceCallback::CALLBACK_LOCK:
|
||||
loadedcallback = new Lock;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return loadedcallback;
|
||||
}
|
||||
|
||||
|
||||
bool SourceCallback::overlap( SourceCallback *a, SourceCallback *b)
|
||||
{
|
||||
bool ret = false;
|
||||
|
||||
if (a->type() == b->type()) {
|
||||
|
||||
// same type means overlap
|
||||
ret = true;
|
||||
|
||||
// but there are some exceptions..
|
||||
switch (a->type()) {
|
||||
case SourceCallback::CALLBACK_GRAB:
|
||||
{
|
||||
const Grab *_a = static_cast<Grab*>(a);
|
||||
const Grab *_b = static_cast<Grab*>(b);
|
||||
// there is no overlap if the X or Y of a vector is zero
|
||||
if ( ABS(_a->value().x) < EPSILON || ABS(_b->value().x) < EPSILON )
|
||||
ret = false;
|
||||
else if ( ABS(_a->value().y) < EPSILON || ABS(_b->value().y) < EPSILON )
|
||||
ret = false;
|
||||
}
|
||||
break;
|
||||
case SourceCallback::CALLBACK_RESIZE:
|
||||
{
|
||||
const Resize *_a = static_cast<Resize*>(a);
|
||||
const Resize *_b = static_cast<Resize*>(b);
|
||||
if ( ABS(_a->value().x) < EPSILON || ABS(_b->value().x) < EPSILON )
|
||||
ret = false;
|
||||
else if ( ABS(_a->value().y) < EPSILON || ABS(_b->value().y) < EPSILON )
|
||||
ret = false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
SourceCallback::SourceCallback(): status_(PENDING), delay_(0.f), elapsed_(0.f)
|
||||
{
|
||||
}
|
||||
|
||||
void SourceCallback::accept(Visitor& v)
|
||||
{
|
||||
v.visit(*this);
|
||||
}
|
||||
|
||||
void SourceCallback::update (Source *s, float dt)
|
||||
{
|
||||
if (s != nullptr) {
|
||||
// time passed
|
||||
elapsed_ += dt;
|
||||
// wait for delay to start
|
||||
if ( status_ == PENDING && elapsed_ > delay_ )
|
||||
status_ = READY;
|
||||
}
|
||||
// invalid
|
||||
else
|
||||
status_ = FINISHED;
|
||||
|
||||
}
|
||||
|
||||
void ResetGeometry::update(Source *s, float dt)
|
||||
{
|
||||
SourceCallback::update(s, dt);
|
||||
|
||||
if (s->locked())
|
||||
status_ = FINISHED;
|
||||
|
||||
// apply when ready
|
||||
if ( status_ == READY ){
|
||||
|
||||
s->group(View::GEOMETRY)->scale_ = glm::vec3(1.f);
|
||||
s->group(View::GEOMETRY)->rotation_.z = 0;
|
||||
s->group(View::GEOMETRY)->crop_ = glm::vec3(1.f);
|
||||
s->group(View::GEOMETRY)->translation_ = glm::vec3(0.f);
|
||||
s->touch();
|
||||
|
||||
status_ = FINISHED;
|
||||
}
|
||||
}
|
||||
|
||||
SourceCallback *ResetGeometry::clone() const
|
||||
{
|
||||
return new ResetGeometry;
|
||||
}
|
||||
|
||||
|
||||
SetAlpha::SetAlpha(float alpha, float ms, bool revert) : SourceCallback(),
|
||||
duration_(ms), alpha_(alpha), bidirectional_(revert)
|
||||
{
|
||||
alpha_ = CLAMP(alpha_, 0.f, 1.f);
|
||||
start_ = glm::vec2();
|
||||
target_ = glm::vec2();
|
||||
}
|
||||
|
||||
void SetAlpha::update(Source *s, float dt)
|
||||
{
|
||||
SourceCallback::update(s, dt);
|
||||
|
||||
if (s->locked())
|
||||
status_ = FINISHED;
|
||||
|
||||
// set start position on first time it is ready
|
||||
if ( status_ == READY ){
|
||||
// initial mixing view position
|
||||
start_ = glm::vec2(s->group(View::MIXING)->translation_);
|
||||
|
||||
// step in diagonal by default
|
||||
glm::vec2 step = glm::normalize(glm::vec2(1.f, 1.f));
|
||||
// step in direction of source translation if possible
|
||||
if ( glm::length(start_) > DELTA_ALPHA)
|
||||
step = glm::normalize(start_);
|
||||
//
|
||||
// target mixing view position
|
||||
//
|
||||
// special case Alpha = 0
|
||||
if (alpha_ < DELTA_ALPHA) {
|
||||
target_ = step;
|
||||
}
|
||||
// special case Alpha = 1
|
||||
else if (alpha_ > 1.f - DELTA_ALPHA) {
|
||||
target_ = step * 0.005f;
|
||||
}
|
||||
// general case
|
||||
else {
|
||||
// converge to reduce the difference of alpha using dichotomic algorithm
|
||||
target_ = start_;
|
||||
float delta = 1.f;
|
||||
do {
|
||||
target_ += step * (delta / 2.f);
|
||||
delta = SourceCore::alphaFromCordinates(target_.x, target_.y) - alpha_;
|
||||
} while (glm::abs(delta) > DELTA_ALPHA);
|
||||
}
|
||||
|
||||
status_ = ACTIVE;
|
||||
}
|
||||
|
||||
if ( status_ == ACTIVE ) {
|
||||
// time passed since start
|
||||
float progress = elapsed_ - delay_;
|
||||
|
||||
// perform movement
|
||||
if ( ABS(duration_) > 0.f)
|
||||
s->group(View::MIXING)->translation_ = glm::vec3(start_ + (progress/duration_)*(target_ - start_), s->group(View::MIXING)->translation_.z);
|
||||
|
||||
// time-out
|
||||
if ( progress > duration_ ) {
|
||||
// apply alpha to target
|
||||
s->group(View::MIXING)->translation_ = glm::vec3(target_, s->group(View::MIXING)->translation_.z);
|
||||
// done
|
||||
status_ = FINISHED;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void SetAlpha::multiply (float factor)
|
||||
{
|
||||
alpha_ *= factor;
|
||||
}
|
||||
|
||||
SourceCallback *SetAlpha::clone() const
|
||||
{
|
||||
return new SetAlpha(alpha_, duration_, bidirectional_);
|
||||
}
|
||||
|
||||
SourceCallback *SetAlpha::reverse(Source *s) const
|
||||
{
|
||||
return bidirectional_ ? new SetAlpha(s->alpha(), duration_) : nullptr;
|
||||
}
|
||||
|
||||
void SetAlpha::accept(Visitor& v)
|
||||
{
|
||||
SourceCallback::accept(v);
|
||||
v.visit(*this);
|
||||
}
|
||||
|
||||
Lock::Lock(bool on) : lock_(on)
|
||||
{
|
||||
}
|
||||
|
||||
void Lock::update(Source *s, float)
|
||||
{
|
||||
if (s)
|
||||
s->setLocked(lock_);
|
||||
|
||||
status_ = FINISHED;
|
||||
}
|
||||
|
||||
SourceCallback *Lock::clone() const
|
||||
{
|
||||
return new Lock(lock_);
|
||||
}
|
||||
|
||||
|
||||
Loom::Loom(float speed, float ms) : SourceCallback(),
|
||||
speed_(speed), duration_(ms)
|
||||
{
|
||||
pos_ = glm::vec2();
|
||||
step_ = glm::normalize(glm::vec2(1.f, 1.f)); // step in diagonal by default
|
||||
}
|
||||
|
||||
void Loom::update(Source *s, float dt)
|
||||
{
|
||||
SourceCallback::update(s, dt);
|
||||
|
||||
if (s->locked())
|
||||
status_ = FINISHED;
|
||||
|
||||
// set start on first time it is ready
|
||||
if ( status_ == READY ){
|
||||
// initial position
|
||||
pos_ = glm::vec2(s->group(View::MIXING)->translation_);
|
||||
// step in direction of source translation if possible
|
||||
if ( glm::length(pos_) > DELTA_ALPHA)
|
||||
step_ = glm::normalize(pos_);
|
||||
status_ = ACTIVE;
|
||||
}
|
||||
|
||||
if ( status_ == ACTIVE ) {
|
||||
// time passed since start
|
||||
float progress = elapsed_ - delay_;
|
||||
|
||||
// move target by speed vector (in the direction of step_, amplitude of speed * time (in second))
|
||||
pos_ -= step_ * ( speed_ * dt * 0.001f );
|
||||
|
||||
// apply alpha if pos in range [0 MIXING_MIN_THRESHOLD]
|
||||
float l = glm::length( pos_ );
|
||||
if ( (l > 0.01f && speed_ > 0.f ) || (l < MIXING_MIN_THRESHOLD && speed_ < 0.f ) )
|
||||
s->group(View::MIXING)->translation_ = glm::vec3(pos_, s->group(View::MIXING)->translation_.z);
|
||||
else
|
||||
status_ = FINISHED;
|
||||
|
||||
// time-out
|
||||
if ( progress > duration_ )
|
||||
// done
|
||||
status_ = FINISHED;
|
||||
}
|
||||
}
|
||||
|
||||
void Loom::multiply (float factor)
|
||||
{
|
||||
speed_ *= factor;
|
||||
}
|
||||
|
||||
SourceCallback *Loom::clone() const
|
||||
{
|
||||
return new Loom(speed_, duration_);
|
||||
}
|
||||
|
||||
SourceCallback *Loom::reverse(Source *) const
|
||||
{
|
||||
return new Loom(speed_, 0.f);
|
||||
}
|
||||
|
||||
void Loom::accept(Visitor& v)
|
||||
{
|
||||
SourceCallback::accept(v);
|
||||
v.visit(*this);
|
||||
}
|
||||
|
||||
|
||||
SetDepth::SetDepth(float target, float ms, bool revert) : SourceCallback(),
|
||||
duration_(ms), start_(0.f), target_(target), bidirectional_(revert)
|
||||
{
|
||||
target_ = CLAMP(target_, MIN_DEPTH, MAX_DEPTH);
|
||||
}
|
||||
|
||||
void SetDepth::update(Source *s, float dt)
|
||||
{
|
||||
SourceCallback::update(s, dt);
|
||||
|
||||
if (s->locked())
|
||||
status_ = FINISHED;
|
||||
|
||||
// set start position on first time it is ready
|
||||
if ( status_ == READY ){
|
||||
start_ = s->group(View::LAYER)->translation_.z;
|
||||
status_ = ACTIVE;
|
||||
}
|
||||
|
||||
if ( status_ == ACTIVE ) {
|
||||
// time passed since start
|
||||
float progress = elapsed_ - delay_;
|
||||
|
||||
// perform movement
|
||||
if ( ABS(duration_) > 0.f)
|
||||
s->group(View::LAYER)->translation_.z = start_ + (progress/duration_) * (target_ - start_);
|
||||
|
||||
// time-out
|
||||
if ( progress > duration_ ) {
|
||||
// apply depth to target
|
||||
s->group(View::LAYER)->translation_.z = target_;
|
||||
// ensure reordering of view
|
||||
++View::need_deep_update_;
|
||||
// done
|
||||
status_ = FINISHED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SetDepth::multiply (float factor)
|
||||
{
|
||||
target_ *= factor;
|
||||
}
|
||||
|
||||
SourceCallback *SetDepth::clone() const
|
||||
{
|
||||
return new SetDepth(target_, duration_, bidirectional_);
|
||||
}
|
||||
|
||||
SourceCallback *SetDepth::reverse(Source *s) const
|
||||
{
|
||||
return bidirectional_ ? new SetDepth(s->depth(), duration_) : nullptr;
|
||||
}
|
||||
|
||||
void SetDepth::accept(Visitor& v)
|
||||
{
|
||||
SourceCallback::accept(v);
|
||||
v.visit(*this);
|
||||
}
|
||||
|
||||
Play::Play(bool on, bool revert) : SourceCallback(), play_(on), bidirectional_(revert)
|
||||
{
|
||||
}
|
||||
|
||||
void Play::update(Source *s, float dt)
|
||||
{
|
||||
SourceCallback::update(s, dt);
|
||||
|
||||
// toggle play status when ready
|
||||
if ( status_ == READY ){
|
||||
|
||||
if (s->playing() != play_)
|
||||
// call play function
|
||||
s->play(play_);
|
||||
|
||||
status_ = FINISHED;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
SourceCallback *Play::clone() const
|
||||
{
|
||||
return new Play(play_, bidirectional_);
|
||||
}
|
||||
|
||||
SourceCallback *Play::reverse(Source *s) const
|
||||
{
|
||||
return bidirectional_ ? new Play(s->playing()) : nullptr;
|
||||
}
|
||||
|
||||
void Play::accept(Visitor& v)
|
||||
{
|
||||
SourceCallback::accept(v);
|
||||
v.visit(*this);
|
||||
}
|
||||
|
||||
RePlay::RePlay() : SourceCallback()
|
||||
{
|
||||
}
|
||||
|
||||
void RePlay::update(Source *s, float dt)
|
||||
{
|
||||
SourceCallback::update(s, dt);
|
||||
|
||||
// apply when ready
|
||||
if ( status_ == READY ){
|
||||
|
||||
// call replay function
|
||||
s->replay();
|
||||
|
||||
status_ = FINISHED;
|
||||
}
|
||||
}
|
||||
|
||||
SourceCallback *RePlay::clone() const
|
||||
{
|
||||
return new RePlay;
|
||||
}
|
||||
|
||||
|
||||
SetGeometry::SetGeometry(const Group *g, float ms, bool revert) : SourceCallback(),
|
||||
duration_(ms), bidirectional_(revert)
|
||||
{
|
||||
setTarget(g);
|
||||
}
|
||||
|
||||
void SetGeometry::getTarget (Group *g) const
|
||||
{
|
||||
if (g!=nullptr)
|
||||
g->copyTransform(&target_);
|
||||
}
|
||||
|
||||
void SetGeometry::setTarget (const Group *g)
|
||||
{
|
||||
if (g!=nullptr)
|
||||
target_.copyTransform(g);
|
||||
}
|
||||
|
||||
void SetGeometry::update(Source *s, float dt)
|
||||
{
|
||||
SourceCallback::update(s, dt);
|
||||
|
||||
if (s->locked())
|
||||
status_ = FINISHED;
|
||||
|
||||
// set start position on first time it is ready
|
||||
if ( status_ == READY ){
|
||||
start_.copyTransform(s->group(View::GEOMETRY));
|
||||
status_ = ACTIVE;
|
||||
}
|
||||
|
||||
if ( status_ == ACTIVE ) {
|
||||
// time passed since start
|
||||
float progress = elapsed_ - delay_;
|
||||
|
||||
// perform movement
|
||||
if ( ABS(duration_) > 0.f){
|
||||
float ratio = progress / duration_;
|
||||
Group intermediate;
|
||||
intermediate.translation_ = (1.f - ratio) * start_.translation_ + ratio * target_.translation_;
|
||||
intermediate.scale_ = (1.f - ratio) * start_.scale_ + ratio * target_.scale_;
|
||||
intermediate.rotation_ = (1.f - ratio) * start_.rotation_ + ratio * target_.rotation_;
|
||||
// apply geometry
|
||||
s->group(View::GEOMETRY)->copyTransform(&intermediate);
|
||||
s->touch();
|
||||
}
|
||||
|
||||
// time-out
|
||||
if ( progress > duration_ ) {
|
||||
// apply target
|
||||
s->group(View::GEOMETRY)->copyTransform(&target_);
|
||||
s->touch();
|
||||
// done
|
||||
status_ = FINISHED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SetGeometry::multiply (float factor)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
SourceCallback *SetGeometry::clone() const
|
||||
{
|
||||
return new SetGeometry(&target_, duration_, bidirectional_);
|
||||
}
|
||||
|
||||
SourceCallback *SetGeometry::reverse(Source *s) const
|
||||
{
|
||||
return bidirectional_ ? new SetGeometry( s->group(View::GEOMETRY), duration_) : nullptr;
|
||||
}
|
||||
|
||||
void SetGeometry::accept(Visitor& v)
|
||||
{
|
||||
SourceCallback::accept(v);
|
||||
v.visit(*this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
Grab::Grab(float dx, float dy, float ms) : SourceCallback(),
|
||||
speed_(glm::vec2(dx, dy)), duration_(ms)
|
||||
{
|
||||
}
|
||||
|
||||
void Grab::update(Source *s, float dt)
|
||||
{
|
||||
SourceCallback::update(s, dt);
|
||||
|
||||
if (s->locked())
|
||||
status_ = FINISHED;
|
||||
|
||||
// set start on first time it is ready
|
||||
if ( status_ == READY ) {
|
||||
// initial position
|
||||
pos_ = glm::vec2(s->group(View::GEOMETRY)->translation_);
|
||||
status_ = ACTIVE;
|
||||
}
|
||||
|
||||
if ( status_ == ACTIVE ) {
|
||||
|
||||
// move target by speed vector * time (in second)
|
||||
pos_ += speed_ * ( dt * 0.001f);
|
||||
s->group(View::GEOMETRY)->translation_ = glm::vec3(pos_, s->group(View::GEOMETRY)->translation_.z);
|
||||
|
||||
// time-out
|
||||
if ( (elapsed_ - delay_) > duration_ )
|
||||
// done
|
||||
status_ = FINISHED;
|
||||
}
|
||||
}
|
||||
|
||||
void Grab::multiply (float factor)
|
||||
{
|
||||
speed_ *= factor;
|
||||
}
|
||||
|
||||
SourceCallback *Grab::clone() const
|
||||
{
|
||||
return new Grab(speed_.x, speed_.y, duration_);
|
||||
}
|
||||
|
||||
SourceCallback *Grab::reverse(Source *) const
|
||||
{
|
||||
return new Grab(speed_.x, speed_.y, 0.f);
|
||||
}
|
||||
|
||||
void Grab::accept(Visitor& v)
|
||||
{
|
||||
SourceCallback::accept(v);
|
||||
v.visit(*this);
|
||||
}
|
||||
|
||||
Resize::Resize(float dx, float dy, float ms) : SourceCallback(),
|
||||
speed_(glm::vec2(dx, dy)), duration_(ms)
|
||||
{
|
||||
}
|
||||
|
||||
void Resize::update(Source *s, float dt)
|
||||
{
|
||||
SourceCallback::update(s, dt);
|
||||
|
||||
if (s->locked())
|
||||
status_ = FINISHED;
|
||||
|
||||
if ( status_ == READY )
|
||||
status_ = ACTIVE;
|
||||
|
||||
if ( status_ == ACTIVE ) {
|
||||
// move target by speed vector * time (in second)
|
||||
glm::vec2 scale = glm::vec2(s->group(View::GEOMETRY)->scale_) + speed_ * ( dt * 0.001f );
|
||||
s->group(View::GEOMETRY)->scale_ = glm::vec3(scale, s->group(View::GEOMETRY)->scale_.z);
|
||||
|
||||
// time-out
|
||||
if ( (elapsed_ - delay_) > duration_ )
|
||||
status_ = FINISHED;
|
||||
}
|
||||
}
|
||||
|
||||
void Resize::multiply (float factor)
|
||||
{
|
||||
speed_ *= factor;
|
||||
}
|
||||
|
||||
SourceCallback *Resize::clone() const
|
||||
{
|
||||
return new Resize(speed_.x, speed_.y, duration_);
|
||||
}
|
||||
|
||||
SourceCallback *Resize::reverse(Source *) const
|
||||
{
|
||||
return new Resize(speed_.x, speed_.y, 0.f);
|
||||
}
|
||||
|
||||
void Resize::accept(Visitor& v)
|
||||
{
|
||||
SourceCallback::accept(v);
|
||||
v.visit(*this);
|
||||
}
|
||||
|
||||
Turn::Turn(float speed, float ms) : SourceCallback(),
|
||||
speed_(speed), duration_(ms)
|
||||
{
|
||||
}
|
||||
|
||||
void Turn::update(Source *s, float dt)
|
||||
{
|
||||
SourceCallback::update(s, dt);
|
||||
|
||||
if (s->locked())
|
||||
status_ = FINISHED;
|
||||
|
||||
// set start on first time it is ready
|
||||
if ( status_ == READY ){
|
||||
// initial position
|
||||
angle_ = s->group(View::GEOMETRY)->rotation_.z;
|
||||
status_ = ACTIVE;
|
||||
}
|
||||
|
||||
if ( status_ == ACTIVE ) {
|
||||
|
||||
// perform movement
|
||||
angle_ -= speed_ * ( dt * 0.001f );
|
||||
s->group(View::GEOMETRY)->rotation_.z = angle_;
|
||||
|
||||
// timeout
|
||||
if ( (elapsed_ - delay_) > duration_ )
|
||||
// done
|
||||
status_ = FINISHED;
|
||||
}
|
||||
}
|
||||
|
||||
void Turn::multiply (float factor)
|
||||
{
|
||||
speed_ *= factor;
|
||||
}
|
||||
|
||||
SourceCallback *Turn::clone() const
|
||||
{
|
||||
return new Turn(speed_, duration_);
|
||||
}
|
||||
|
||||
SourceCallback *Turn::reverse(Source *) const
|
||||
{
|
||||
return new Turn(speed_, 0.f);
|
||||
}
|
||||
|
||||
void Turn::accept(Visitor& v)
|
||||
{
|
||||
SourceCallback::accept(v);
|
||||
v.visit(*this);
|
||||
}
|
||||
280
SourceCallback.h
@@ -1,280 +0,0 @@
|
||||
#ifndef SOURCECALLBACK_H
|
||||
#define SOURCECALLBACK_H
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "Scene.h"
|
||||
|
||||
class Visitor;
|
||||
class Source;
|
||||
|
||||
class SourceCallback
|
||||
{
|
||||
public:
|
||||
|
||||
typedef enum {
|
||||
CALLBACK_GENERIC = 0,
|
||||
CALLBACK_ALPHA = 1,
|
||||
CALLBACK_LOOM = 2,
|
||||
CALLBACK_GEOMETRY = 3,
|
||||
CALLBACK_GRAB = 4,
|
||||
CALLBACK_RESIZE = 5,
|
||||
CALLBACK_TURN = 6,
|
||||
CALLBACK_DEPTH = 7,
|
||||
CALLBACK_PLAY = 8,
|
||||
CALLBACK_REPLAY = 9,
|
||||
CALLBACK_RESETGEO = 10,
|
||||
CALLBACK_LOCK = 11
|
||||
} CallbackType;
|
||||
|
||||
static SourceCallback *create(CallbackType type);
|
||||
static bool overlap(SourceCallback *a, SourceCallback *b);
|
||||
|
||||
SourceCallback();
|
||||
virtual ~SourceCallback() {}
|
||||
|
||||
virtual void update (Source *, float);
|
||||
virtual void multiply (float) {};
|
||||
virtual SourceCallback *clone () const = 0;
|
||||
virtual SourceCallback *reverse (Source *) const { return nullptr; }
|
||||
virtual CallbackType type () const { return CALLBACK_GENERIC; }
|
||||
virtual void accept (Visitor& v);
|
||||
|
||||
inline bool finished () const { return status_ > ACTIVE; }
|
||||
inline void reset () { status_ = PENDING; }
|
||||
inline void delay (float milisec) { delay_ = milisec;}
|
||||
|
||||
protected:
|
||||
|
||||
typedef enum {
|
||||
PENDING = 0,
|
||||
READY,
|
||||
ACTIVE,
|
||||
FINISHED
|
||||
} state;
|
||||
|
||||
state status_;
|
||||
float delay_;
|
||||
float elapsed_;
|
||||
};
|
||||
|
||||
class SetAlpha : public SourceCallback
|
||||
{
|
||||
float duration_;
|
||||
float alpha_;
|
||||
glm::vec2 start_;
|
||||
glm::vec2 target_;
|
||||
bool bidirectional_;
|
||||
|
||||
public:
|
||||
SetAlpha (float alpha = 0.f, float ms = 0.f, bool revert = false);
|
||||
|
||||
float value () const { return alpha_; }
|
||||
void setValue (float a) { alpha_ = a; }
|
||||
float duration () const { return duration_; }
|
||||
void setDuration (float ms) { duration_ = ms; }
|
||||
bool bidirectional () const { return bidirectional_; }
|
||||
void setBidirectional (bool on) { bidirectional_ = on; }
|
||||
|
||||
void update (Source *s, float) override;
|
||||
void multiply (float factor) override;
|
||||
SourceCallback *clone () const override;
|
||||
SourceCallback *reverse(Source *s) const override;
|
||||
CallbackType type () const override { return CALLBACK_ALPHA; }
|
||||
void accept (Visitor& v) override;
|
||||
};
|
||||
|
||||
class Loom : public SourceCallback
|
||||
{
|
||||
float speed_;
|
||||
glm::vec2 pos_;
|
||||
glm::vec2 step_;
|
||||
float duration_;
|
||||
|
||||
public:
|
||||
Loom (float speed = 0.f, float ms = FLT_MAX);
|
||||
|
||||
float value () const { return speed_; }
|
||||
void setValue (float d) { speed_ = d; }
|
||||
float duration () const { return duration_; }
|
||||
void setDuration (float ms) { duration_ = ms; }
|
||||
|
||||
void update (Source *s, float) override;
|
||||
void multiply (float factor) override;
|
||||
SourceCallback *clone() const override;
|
||||
SourceCallback *reverse(Source *) const override;
|
||||
CallbackType type () const override { return CALLBACK_LOOM; }
|
||||
void accept (Visitor& v) override;
|
||||
};
|
||||
|
||||
class Lock : public SourceCallback
|
||||
{
|
||||
bool lock_;
|
||||
|
||||
public:
|
||||
Lock (bool on = true);
|
||||
|
||||
bool value () const { return lock_;}
|
||||
void setValue (bool on) { lock_ = on;}
|
||||
|
||||
void update (Source *s, float) override;
|
||||
SourceCallback *clone() const override;
|
||||
CallbackType type () const override { return CALLBACK_LOCK; }
|
||||
};
|
||||
|
||||
class SetDepth : public SourceCallback
|
||||
{
|
||||
float duration_;
|
||||
float start_;
|
||||
float target_;
|
||||
bool bidirectional_;
|
||||
|
||||
public:
|
||||
SetDepth (float depth = 0.f, float ms = 0.f, bool revert = false);
|
||||
|
||||
float value () const { return target_;}
|
||||
void setValue (float d) { target_ = d; }
|
||||
float duration () const { return duration_;}
|
||||
void setDuration (float ms) { duration_ = ms; }
|
||||
bool bidirectional () const { return bidirectional_;}
|
||||
void setBidirectional (bool on) { bidirectional_ = on; }
|
||||
|
||||
void update (Source *s, float dt) override;
|
||||
void multiply (float factor) override;
|
||||
SourceCallback *clone () const override;
|
||||
SourceCallback *reverse(Source *s) const override;
|
||||
CallbackType type () const override { return CALLBACK_DEPTH; }
|
||||
void accept (Visitor& v) override;
|
||||
};
|
||||
|
||||
class Play : public SourceCallback
|
||||
{
|
||||
bool play_;
|
||||
bool bidirectional_;
|
||||
|
||||
public:
|
||||
Play (bool on = true, bool revert = false);
|
||||
|
||||
bool value () const { return play_;}
|
||||
void setValue (bool on) { play_ = on; }
|
||||
bool bidirectional () const { return bidirectional_;}
|
||||
void setBidirectional (bool on) { bidirectional_ = on; }
|
||||
|
||||
void update (Source *s, float dt) override;
|
||||
SourceCallback *clone() const override;
|
||||
SourceCallback *reverse(Source *s) const override;
|
||||
CallbackType type () const override { return CALLBACK_PLAY; }
|
||||
void accept (Visitor& v) override;
|
||||
};
|
||||
|
||||
class RePlay : public SourceCallback
|
||||
{
|
||||
public:
|
||||
RePlay();
|
||||
|
||||
void update(Source *s, float dt) override;
|
||||
SourceCallback *clone() const override;
|
||||
CallbackType type () const override { return CALLBACK_REPLAY; }
|
||||
};
|
||||
|
||||
class ResetGeometry : public SourceCallback
|
||||
{
|
||||
public:
|
||||
ResetGeometry () : SourceCallback() {}
|
||||
void update (Source *s, float dt) override;
|
||||
SourceCallback *clone () const override;
|
||||
CallbackType type () const override { return CALLBACK_RESETGEO; }
|
||||
};
|
||||
|
||||
class SetGeometry : public SourceCallback
|
||||
{
|
||||
float duration_;
|
||||
Group start_;
|
||||
Group target_;
|
||||
bool bidirectional_;
|
||||
|
||||
public:
|
||||
SetGeometry (const Group *g = nullptr, float ms = 0.f, bool revert = false);
|
||||
|
||||
void getTarget (Group *g) const;
|
||||
void setTarget (const Group *g);
|
||||
float duration () const { return duration_;}
|
||||
void setDuration (float ms) { duration_ = ms; }
|
||||
bool bidirectional () const { return bidirectional_;}
|
||||
void setBidirectional (bool on) { bidirectional_ = on; }
|
||||
|
||||
void update (Source *s, float dt) override;
|
||||
void multiply (float factor) override;
|
||||
SourceCallback *clone () const override;
|
||||
SourceCallback *reverse(Source *s) const override;
|
||||
CallbackType type () const override { return CALLBACK_GEOMETRY; }
|
||||
void accept (Visitor& v) override;
|
||||
};
|
||||
|
||||
class Grab : public SourceCallback
|
||||
{
|
||||
glm::vec2 speed_;
|
||||
glm::vec2 pos_;
|
||||
float duration_;
|
||||
|
||||
public:
|
||||
Grab(float dx = 0.f, float dy = 0.f, float ms = FLT_MAX);
|
||||
|
||||
glm::vec2 value () const { return speed_; }
|
||||
void setValue (glm::vec2 d) { speed_ = d; }
|
||||
float duration () const { return duration_; }
|
||||
void setDuration (float ns) { duration_ = ns; }
|
||||
|
||||
void update (Source *s, float) override;
|
||||
void multiply (float factor) override;
|
||||
SourceCallback *clone () const override;
|
||||
SourceCallback *reverse(Source *) const override;
|
||||
CallbackType type () const override { return CALLBACK_GRAB; }
|
||||
void accept (Visitor& v) override;
|
||||
};
|
||||
|
||||
class Resize : public SourceCallback
|
||||
{
|
||||
glm::vec2 speed_;
|
||||
float duration_;
|
||||
|
||||
public:
|
||||
Resize(float dx = 0.f, float dy = 0.f, float ms = FLT_MAX);
|
||||
|
||||
glm::vec2 value () const { return speed_; }
|
||||
void setValue (glm::vec2 d) { speed_ = d; }
|
||||
float duration () const { return duration_; }
|
||||
void setDuration (float ms) { duration_ = ms; }
|
||||
|
||||
void update (Source *s, float) override;
|
||||
void multiply (float factor) override;
|
||||
SourceCallback *clone () const override;
|
||||
SourceCallback *reverse(Source *) const override;
|
||||
CallbackType type () const override { return CALLBACK_RESIZE; }
|
||||
void accept (Visitor& v) override;
|
||||
};
|
||||
|
||||
class Turn : public SourceCallback
|
||||
{
|
||||
float speed_;
|
||||
float angle_;
|
||||
float duration_;
|
||||
|
||||
public:
|
||||
Turn(float speed = 0.f, float ms = FLT_MAX);
|
||||
|
||||
float value () const { return speed_; }
|
||||
void setValue (float d) { speed_ = d; }
|
||||
float duration () const { return duration_; }
|
||||
void setDuration (float ms) { duration_ = ms; }
|
||||
|
||||
void update (Source *s, float) override;
|
||||
void multiply (float factor) override;
|
||||
SourceCallback *clone () const override;
|
||||
SourceCallback *reverse(Source *) const override;
|
||||
CallbackType type () const override { return CALLBACK_TURN; }
|
||||
void accept (Visitor& v) override;
|
||||
};
|
||||
|
||||
|
||||
#endif // SOURCECALLBACK_H
|
||||
@@ -1,56 +0,0 @@
|
||||
|
||||
#include "Log.h"
|
||||
#include "Decorations.h"
|
||||
#include "Visitor.h"
|
||||
|
||||
#include "SrtReceiverSource.h"
|
||||
|
||||
SrtReceiverSource::SrtReceiverSource(uint64_t id) : StreamSource(id)
|
||||
{
|
||||
// create stream
|
||||
stream_ = new Stream;
|
||||
|
||||
// set symbol
|
||||
symbol_ = new Symbol(Symbol::RECEIVE, glm::vec3(0.75f, 0.75f, 0.01f));
|
||||
symbol_->scale_.y = 1.5f;
|
||||
}
|
||||
|
||||
|
||||
void SrtReceiverSource::setConnection(const std::string &ip, const std::string &port)
|
||||
{
|
||||
// TODO add check on wellformed IP and PORT
|
||||
ip_ = ip;
|
||||
port_ = port;
|
||||
Log::Notify("Creating Source SRT receiving from '%s'", uri().c_str());
|
||||
|
||||
std::string description = "srtsrc uri=" + uri() + " ! tsdemux ! decodebin ! videoconvert";
|
||||
|
||||
// open gstreamer
|
||||
stream_->open(description);
|
||||
stream_->play(true);
|
||||
|
||||
// will be ready after init and one frame rendered
|
||||
ready_ = false;
|
||||
}
|
||||
|
||||
std::string SrtReceiverSource::uri() const
|
||||
{
|
||||
return std::string("srt://") + ip_ + ":" + port_;
|
||||
}
|
||||
|
||||
void SrtReceiverSource::accept(Visitor& v)
|
||||
{
|
||||
Source::accept(v);
|
||||
if (!failed())
|
||||
v.visit(*this);
|
||||
}
|
||||
|
||||
glm::ivec2 SrtReceiverSource::icon() const
|
||||
{
|
||||
return glm::ivec2(ICON_SOURCE_SRT);
|
||||
}
|
||||
|
||||
std::string SrtReceiverSource::info() const
|
||||
{
|
||||
return "SRT receiver";
|
||||
}
|
||||
@@ -1,515 +0,0 @@
|
||||
#ifndef __UI_MANAGER_H_
|
||||
#define __UI_MANAGER_H_
|
||||
|
||||
#include <string>
|
||||
#include <array>
|
||||
#include <list>
|
||||
#include <future>
|
||||
|
||||
#include <gst/gstutils.h>
|
||||
|
||||
#define NAV_COUNT 68
|
||||
#define NAV_MAX 64
|
||||
#define NAV_NEW 65
|
||||
#define NAV_MENU 66
|
||||
#define NAV_TRANS 67
|
||||
|
||||
#ifdef APPLE
|
||||
#define CTRL_MOD "Cmd+"
|
||||
#define ALT_MOD "Option+"
|
||||
#else
|
||||
#define CTRL_MOD "Ctrl+"
|
||||
#define ALT_MOD "Alt+"
|
||||
#endif
|
||||
|
||||
#define IMGUI_TITLE_MEDIAPLAYER ICON_FA_PLAY_CIRCLE " Player"
|
||||
#define IMGUI_TITLE_TIMER ICON_FA_CLOCK " Timer"
|
||||
#define IMGUI_TITLE_INPUT_MAPPING ICON_FA_HAND_PAPER " Inputs Mapping"
|
||||
#define IMGUI_TITLE_HELP ICON_FA_LIFE_RING " Help"
|
||||
#define IMGUI_TITLE_TOOLBOX ICON_FA_HAMSA " Guru Toolbox"
|
||||
#define IMGUI_TITLE_SHADEREDITOR ICON_FA_CODE " Shader Editor"
|
||||
#define IMGUI_TITLE_PREVIEW ICON_FA_DESKTOP " Output"
|
||||
|
||||
#define MENU_NEW_FILE ICON_FA_FILE " New"
|
||||
#define SHORTCUT_NEW_FILE CTRL_MOD "W"
|
||||
#define MENU_OPEN_FILE ICON_FA_FILE_UPLOAD " Open"
|
||||
#define SHORTCUT_OPEN_FILE CTRL_MOD "O"
|
||||
#define MENU_REOPEN_FILE ICON_FA_FILE_UPLOAD " Re-open"
|
||||
#define SHORTCUT_REOPEN_FILE CTRL_MOD "Shift+O"
|
||||
#define MENU_SAVE_FILE ICON_FA_FILE_DOWNLOAD " Save"
|
||||
#define SHORTCUT_SAVE_FILE CTRL_MOD "S"
|
||||
#define MENU_SAVEAS_FILE ICON_FA_FILE_DOWNLOAD " Save as"
|
||||
#define MENU_SAVE_ON_EXIT ICON_FA_LEVEL_DOWN_ALT " Save on exit"
|
||||
#define MENU_OPEN_ON_START ICON_FA_LEVEL_UP_ALT " Open last on start"
|
||||
#define SHORTCUT_SAVEAS_FILE CTRL_MOD "Shift+S"
|
||||
#define SHORTCUT_HELP CTRL_MOD "H"
|
||||
#define SHORTCUT_LOGS CTRL_MOD "L"
|
||||
#define MENU_QUIT ICON_FA_POWER_OFF " Quit"
|
||||
#define SHORTCUT_QUIT CTRL_MOD "Q"
|
||||
#define MENU_CUT ICON_FA_CUT " Cut"
|
||||
#define SHORTCUT_CUT CTRL_MOD "X"
|
||||
#define MENU_COPY ICON_FA_COPY " Copy"
|
||||
#define SHORTCUT_COPY CTRL_MOD "C"
|
||||
#define MENU_DELETE ICON_FA_ERASER " Delete"
|
||||
#define SHORTCUT_DELETE "Del"
|
||||
#define MENU_PASTE ICON_FA_PASTE " Paste"
|
||||
#define SHORTCUT_PASTE CTRL_MOD "V"
|
||||
#define MENU_SELECTALL ICON_FA_TH_LIST " Select all"
|
||||
#define SHORTCUT_SELECTALL CTRL_MOD "A"
|
||||
#define MENU_UNDO ICON_FA_UNDO " Undo"
|
||||
#define SHORTCUT_UNDO CTRL_MOD "Z"
|
||||
#define MENU_REDO ICON_FA_REDO " Redo"
|
||||
#define SHORTCUT_REDO CTRL_MOD "Shift+Z"
|
||||
#define MENU_RECORD ICON_FA_CIRCLE " Record"
|
||||
#define SHORTCUT_RECORD CTRL_MOD "R"
|
||||
#define MENU_RECORDCONT ICON_FA_STOP_CIRCLE " Save & continue"
|
||||
#define SHORTCUT_RECORDCONT CTRL_MOD "Alt+R"
|
||||
#define MENU_CAPTUREFRAME ICON_FA_CAMERA_RETRO " Capture frame"
|
||||
#define SHORTCUT_CAPTUREFRAME "F11"
|
||||
#define MENU_OUTPUTDISABLE ICON_FA_EYE_SLASH " Disable"
|
||||
#define SHORTCUT_OUTPUTDISABLE "F12"
|
||||
#define MENU_OUTPUTFULLSCREEN ICON_FA_EXPAND_ALT " Fullscreen window"
|
||||
#define SHORTCUT_OUTPUTFULLSCREEN CTRL_MOD "F"
|
||||
#define MENU_CLOSE ICON_FA_TIMES " Close"
|
||||
|
||||
#define TOOLTIP_NOTE "New sticky note "
|
||||
#define SHORTCUT_NOTE CTRL_MOD "Shift+N"
|
||||
#define TOOLTIP_METRICS "Metrics "
|
||||
#define SHORTCUT_METRICS CTRL_MOD "M"
|
||||
#define TOOLTIP_PLAYER "Player "
|
||||
#define SHORTCUT_PLAYER CTRL_MOD "P"
|
||||
#define TOOLTIP_OUTPUT "Output "
|
||||
#define SHORTCUT_OUTPUT CTRL_MOD "D"
|
||||
#define TOOLTIP_TIMER "Timer "
|
||||
#define SHORTCUT_TIMER CTRL_MOD "T"
|
||||
#define TOOLTIP_INPUTS "Inputs mapping "
|
||||
#define SHORTCUT_INPUTS CTRL_MOD "I"
|
||||
#define TOOLTIP_SHADEREDITOR "Shader Editor "
|
||||
#define SHORTCUT_SHADEREDITOR CTRL_MOD "E"
|
||||
#define TOOLTIP_FULLSCREEN "Fullscreen "
|
||||
#define SHORTCUT_FULLSCREEN CTRL_MOD "Shift+F"
|
||||
#define TOOLTIP_MAIN "Main menu "
|
||||
#define SHORTCUT_MAIN "HOME"
|
||||
#define TOOLTIP_NEW_SOURCE "New source "
|
||||
#define SHORTCUT_NEW_SOURCE "INS"
|
||||
#define TOOLTIP_HIDE "Hide windows "
|
||||
#define TOOLTIP_SHOW "Show windows "
|
||||
#define SHORTCUT_HIDE "ESC"
|
||||
|
||||
#include "SourceList.h"
|
||||
#include "InfoVisitor.h"
|
||||
#include "DialogToolkit.h"
|
||||
#include "SessionParser.h"
|
||||
#include "ImageFilter.h"
|
||||
#include "Screenshot.h"
|
||||
|
||||
|
||||
struct ImVec2;
|
||||
class MediaPlayer;
|
||||
class FrameBufferImage;
|
||||
class FrameGrabber;
|
||||
class VideoRecorder;
|
||||
class VideoBroadcast;
|
||||
|
||||
class SourcePreview {
|
||||
|
||||
Source *source_;
|
||||
std::string label_;
|
||||
bool reset_;
|
||||
|
||||
public:
|
||||
SourcePreview();
|
||||
|
||||
void setSource(Source *s = nullptr, const std::string &label = "");
|
||||
Source *getSource();
|
||||
|
||||
void Render(float width);
|
||||
bool ready() const;
|
||||
inline bool filled() const { return source_ != nullptr; }
|
||||
};
|
||||
|
||||
class Thumbnail
|
||||
{
|
||||
float aspect_ratio_;
|
||||
uint texture_;
|
||||
|
||||
public:
|
||||
Thumbnail();
|
||||
~Thumbnail();
|
||||
|
||||
void reset();
|
||||
void fill (const FrameBufferImage *image);
|
||||
bool filled();
|
||||
void Render(float width);
|
||||
};
|
||||
|
||||
class Navigator
|
||||
{
|
||||
// geometry left bar & pannel
|
||||
float width_;
|
||||
float height_;
|
||||
float pannel_width_;
|
||||
float padding_width_;
|
||||
|
||||
// behavior pannel
|
||||
bool show_config_;
|
||||
bool pannel_visible_;
|
||||
bool view_pannel_visible;
|
||||
bool selected_button[NAV_COUNT];
|
||||
int pattern_type;
|
||||
bool custom_pipeline;
|
||||
bool custom_connected;
|
||||
void clearButtonSelection();
|
||||
void clearNewPannel();
|
||||
void applyButtonSelection(int index);
|
||||
|
||||
// side pannels
|
||||
void RenderSourcePannel(Source *s);
|
||||
void RenderMainPannel();
|
||||
void RenderMainPannelVimix();
|
||||
void RenderMainPannelSettings();
|
||||
void RenderTransitionPannel();
|
||||
void RenderNewPannel();
|
||||
void RenderViewPannel(ImVec2 draw_pos, ImVec2 draw_size);
|
||||
|
||||
public:
|
||||
|
||||
typedef enum {
|
||||
SOURCE_FILE = 0,
|
||||
SOURCE_SEQUENCE,
|
||||
SOURCE_CONNECTED,
|
||||
SOURCE_GENERATED,
|
||||
SOURCE_INTERNAL,
|
||||
SOURCE_TYPES
|
||||
} NewSourceType;
|
||||
|
||||
Navigator();
|
||||
void Render();
|
||||
|
||||
bool pannelVisible() { return pannel_visible_; }
|
||||
void hidePannel();
|
||||
void showPannelSource(int index);
|
||||
void togglePannelMenu();
|
||||
void togglePannelNew();
|
||||
void showConfig();
|
||||
|
||||
typedef enum {
|
||||
MEDIA_RECENT = 0,
|
||||
MEDIA_RECORDING,
|
||||
MEDIA_FOLDER
|
||||
} MediaCreateMode;
|
||||
void setNewMedia(MediaCreateMode mode, std::string path = std::string());
|
||||
|
||||
|
||||
private:
|
||||
// for new source panel
|
||||
SourcePreview new_source_preview_;
|
||||
std::list<std::string> sourceSequenceFiles;
|
||||
std::list<std::string> sourceMediaFiles;
|
||||
std::string sourceMediaFileCurrent;
|
||||
MediaCreateMode new_media_mode;
|
||||
bool new_media_mode_changed;
|
||||
};
|
||||
|
||||
class ToolBox
|
||||
{
|
||||
bool show_demo_window;
|
||||
bool show_icons_window;
|
||||
bool show_sandbox;
|
||||
|
||||
public:
|
||||
ToolBox();
|
||||
|
||||
void Render();
|
||||
};
|
||||
|
||||
class HelperToolbox
|
||||
{
|
||||
SessionParser parser_;
|
||||
|
||||
public:
|
||||
HelperToolbox();
|
||||
|
||||
void Render();
|
||||
|
||||
};
|
||||
|
||||
class WorkspaceWindow
|
||||
{
|
||||
static std::list<WorkspaceWindow *> windows_;
|
||||
|
||||
public:
|
||||
WorkspaceWindow(const char* name);
|
||||
|
||||
// global access to Workspace control
|
||||
static bool clear() { return clear_workspace_enabled; }
|
||||
static void toggleClearRestoreWorkspace();
|
||||
static void clearWorkspace();
|
||||
static void restoreWorkspace(bool instantaneous = false);
|
||||
static void notifyWorkspaceSizeChanged(int prev_width, int prev_height, int curr_width, int curr_height);
|
||||
|
||||
// for inherited classes
|
||||
virtual void Update();
|
||||
virtual bool Visible() const { return true; }
|
||||
|
||||
protected:
|
||||
|
||||
static bool clear_workspace_enabled;
|
||||
|
||||
const char *name_;
|
||||
struct ImGuiProperties *impl_;
|
||||
};
|
||||
|
||||
class SourceController : public WorkspaceWindow
|
||||
{
|
||||
float min_width_;
|
||||
float h_space_;
|
||||
float v_space_;
|
||||
float scrollbar_;
|
||||
float timeline_height_;
|
||||
float mediaplayer_height_;
|
||||
float buttons_width_;
|
||||
float buttons_height_;
|
||||
|
||||
bool play_toggle_request_, replay_request_, capture_request_;
|
||||
bool pending_;
|
||||
std::string active_label_;
|
||||
int active_selection_;
|
||||
InfoVisitor info_;
|
||||
SourceList selection_;
|
||||
|
||||
// re-usable ui parts
|
||||
void DrawButtonBar(ImVec2 bottom, float width);
|
||||
const char *SourcePlayIcon(Source *s);
|
||||
bool SourceButton(Source *s, ImVec2 framesize);
|
||||
|
||||
// Render the sources dynamically selected
|
||||
void RenderSelectedSources();
|
||||
|
||||
// Render a stored selection
|
||||
bool selection_context_menu_;
|
||||
MediaPlayer *selection_mediaplayer_;
|
||||
double selection_target_slower_;
|
||||
double selection_target_faster_;
|
||||
void RenderSelectionContextMenu();
|
||||
void RenderSelection(size_t i);
|
||||
|
||||
// Render a single source
|
||||
void RenderSingleSource(Source *s);
|
||||
|
||||
// Render a single media player
|
||||
MediaPlayer *mediaplayer_active_;
|
||||
bool mediaplayer_edit_fading_;
|
||||
bool mediaplayer_mode_;
|
||||
bool mediaplayer_slider_pressed_;
|
||||
float mediaplayer_timeline_zoom_;
|
||||
void RenderMediaPlayer(MediaSource *ms);
|
||||
|
||||
// dialog to select frame capture location
|
||||
DialogToolkit::OpenFolderDialog *captureFolderDialog;
|
||||
Screenshot capture;
|
||||
|
||||
public:
|
||||
SourceController();
|
||||
|
||||
inline void Play() { play_toggle_request_ = true; }
|
||||
inline void Replay() { replay_request_= true; }
|
||||
inline void Capture(){ capture_request_= true; }
|
||||
void resetActiveSelection();
|
||||
|
||||
void setVisible(bool on);
|
||||
void Render();
|
||||
|
||||
// from WorkspaceWindow
|
||||
void Update() override;
|
||||
bool Visible() const override;
|
||||
};
|
||||
|
||||
class OutputPreview : public WorkspaceWindow
|
||||
{
|
||||
// frame grabbers
|
||||
VideoRecorder *video_recorder_;
|
||||
VideoBroadcast *video_broadcaster_;
|
||||
|
||||
// delayed trigger for recording
|
||||
std::vector< std::future<VideoRecorder *> > _video_recorders;
|
||||
|
||||
#if defined(LINUX)
|
||||
FrameGrabber *webcam_emulator_;
|
||||
#endif
|
||||
|
||||
// dialog to select record location
|
||||
DialogToolkit::OpenFolderDialog *recordFolderDialog;
|
||||
|
||||
public:
|
||||
OutputPreview();
|
||||
|
||||
void ToggleRecord(bool save_and_continue = false);
|
||||
inline bool isRecording() const { return video_recorder_ != nullptr; }
|
||||
|
||||
void ToggleBroadcast();
|
||||
inline bool isBroadcasting() const { return video_broadcaster_ != nullptr; }
|
||||
|
||||
void Render();
|
||||
void setVisible(bool on);
|
||||
|
||||
// from WorkspaceWindow
|
||||
void Update() override;
|
||||
bool Visible() const override;
|
||||
};
|
||||
|
||||
class TimerMetronome : public WorkspaceWindow
|
||||
{
|
||||
std::array< std::string, 2 > timer_menu;
|
||||
// clock times
|
||||
guint64 start_time_;
|
||||
guint64 start_time_hand_;
|
||||
guint64 duration_hand_;
|
||||
|
||||
public:
|
||||
TimerMetronome();
|
||||
|
||||
void Render();
|
||||
void setVisible(bool on);
|
||||
|
||||
// from WorkspaceWindow
|
||||
bool Visible() const override;
|
||||
};
|
||||
|
||||
class InputMappingInterface : public WorkspaceWindow
|
||||
{
|
||||
std::array< std::string, 4 > input_mode;
|
||||
std::array< uint, 4 > current_input_for_mode;
|
||||
uint current_input_;
|
||||
|
||||
Source *ComboSelectSource(Source *current = nullptr);
|
||||
uint ComboSelectCallback(uint current);
|
||||
void SliderParametersCallback(SourceCallback *callback, Source *source);
|
||||
|
||||
public:
|
||||
InputMappingInterface();
|
||||
|
||||
void Render();
|
||||
void setVisible(bool on);
|
||||
|
||||
// from WorkspaceWindow
|
||||
bool Visible() const override;
|
||||
};
|
||||
|
||||
class ShaderEditor : public WorkspaceWindow
|
||||
{
|
||||
CloneSource *current_;
|
||||
bool current_changed_;
|
||||
bool show_shader_inputs_;
|
||||
std::map<CloneSource *, FilteringProgram> filters_;
|
||||
std::promise<std::string> *compilation_;
|
||||
std::future<std::string> compilation_return_;
|
||||
|
||||
std::string status_;
|
||||
|
||||
public:
|
||||
ShaderEditor();
|
||||
|
||||
void Render();
|
||||
void setVisible(bool on);
|
||||
|
||||
void setVisible(CloneSource *cs);
|
||||
|
||||
// from WorkspaceWindow
|
||||
bool Visible() const override;
|
||||
};
|
||||
|
||||
|
||||
class UserInterface
|
||||
{
|
||||
friend class Navigator;
|
||||
friend class OutputPreview;
|
||||
|
||||
// Private Constructor
|
||||
UserInterface();
|
||||
UserInterface(UserInterface const& copy) = delete;
|
||||
UserInterface& operator=(UserInterface const& copy) = delete;
|
||||
|
||||
public:
|
||||
|
||||
static UserInterface& manager()
|
||||
{
|
||||
// The only instance
|
||||
static UserInterface _instance;
|
||||
return _instance;
|
||||
}
|
||||
|
||||
// pre-loop initialization
|
||||
bool Init();
|
||||
// loop update start new frame
|
||||
void NewFrame();
|
||||
// loop update rendering
|
||||
void Render();
|
||||
// try to close, return false if cannot
|
||||
bool TryClose();
|
||||
// Post-loop termination
|
||||
void Terminate();
|
||||
// Runtime
|
||||
uint64_t Runtime() const;
|
||||
|
||||
// status querries
|
||||
inline bool keyboardAvailable() const { return keyboard_available; }
|
||||
inline bool ctrlModifier() const { return ctrl_modifier_active; }
|
||||
inline bool altModifier() const { return alt_modifier_active; }
|
||||
inline bool shiftModifier() const { return shift_modifier_active; }
|
||||
|
||||
void showPannel(int id = 0);
|
||||
void showSourceEditor(Source *s);
|
||||
|
||||
void StartScreenshot();
|
||||
|
||||
protected:
|
||||
|
||||
// internal
|
||||
uint64_t start_time;
|
||||
bool ctrl_modifier_active;
|
||||
bool alt_modifier_active;
|
||||
bool shift_modifier_active;
|
||||
bool keyboard_available;
|
||||
bool show_vimix_about;
|
||||
bool show_imgui_about;
|
||||
bool show_gst_about;
|
||||
bool show_opengl_about;
|
||||
int show_view_navigator;
|
||||
int target_view_navigator;
|
||||
unsigned int screenshot_step;
|
||||
bool pending_save_on_exit;
|
||||
|
||||
// Dialogs
|
||||
DialogToolkit::OpenSessionDialog *sessionopendialog;
|
||||
DialogToolkit::OpenSessionDialog *sessionimportdialog;
|
||||
DialogToolkit::SaveSessionDialog *sessionsavedialog;
|
||||
|
||||
// objects and windows
|
||||
Navigator navigator;
|
||||
ToolBox toolbox;
|
||||
SourceController sourcecontrol;
|
||||
OutputPreview outputcontrol;
|
||||
TimerMetronome timercontrol;
|
||||
InputMappingInterface inputscontrol;
|
||||
ShaderEditor shadercontrol;
|
||||
HelperToolbox helpwindow;
|
||||
|
||||
void showMenuFile();
|
||||
void showMenuEdit();
|
||||
bool saveOrSaveAs(bool force_versioning = false);
|
||||
void selectSaveFilename();
|
||||
void selectOpenFilename();
|
||||
|
||||
void RenderMetrics (bool* p_open, int* p_corner, int *p_mode);
|
||||
int RenderViewNavigator(int* shift);
|
||||
void RenderAbout(bool* p_open);
|
||||
void RenderNotes();
|
||||
|
||||
void handleKeyboard();
|
||||
void handleMouse();
|
||||
void handleScreenshot();
|
||||
};
|
||||
|
||||
#endif /* #define __UI_MANAGER_H_ */
|
||||
|
||||
@@ -1,197 +0,0 @@
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
|
||||
// gstreamer
|
||||
#include <gst/gstformat.h>
|
||||
#include <gst/video/video.h>
|
||||
#include <gst/app/gstappsrc.h>
|
||||
#include <gst/pbutils/pbutils.h>
|
||||
|
||||
#include "Log.h"
|
||||
#include "Settings.h"
|
||||
#include "GstToolkit.h"
|
||||
#include "NetworkToolkit.h"
|
||||
|
||||
#include "VideoBroadcast.h"
|
||||
|
||||
#ifndef NDEBUG
|
||||
#define BROADCAST_DEBUG
|
||||
#endif
|
||||
|
||||
std::string VideoBroadcast::srt_sink_;
|
||||
std::string VideoBroadcast::h264_encoder_;
|
||||
|
||||
std::vector< std::pair<std::string, std::string> > pipeline_sink_ {
|
||||
{"srtsink", "srtsink uri=srt://:XXXX name=sink"},
|
||||
{"srtserversink", "srtserversink uri=srt://:XXXX name=sink"}
|
||||
};
|
||||
|
||||
std::vector< std::pair<std::string, std::string> > pipeline_encoder_ {
|
||||
{"nvh264enc", "nvh264enc zerolatency=true rc-mode=cbr-ld-hq bitrate=4000 ! video/x-h264, profile=(string)high ! h264parse config-interval=1 ! mpegtsmux ! queue ! "},
|
||||
{"vaapih264enc", "vaapih264enc rate-control=cqp init-qp=26 ! video/x-h264, profile=high ! h264parse config-interval=1 ! mpegtsmux ! queue ! "},
|
||||
{"x264enc", "x264enc tune=zerolatency ! video/x-h264, profile=high ! mpegtsmux ! "}
|
||||
};
|
||||
|
||||
bool VideoBroadcast::available()
|
||||
{
|
||||
// test for installation on first run
|
||||
static bool _tested = false;
|
||||
if (!_tested) {
|
||||
srt_sink_.clear();
|
||||
for (auto config = pipeline_sink_.cbegin();
|
||||
config != pipeline_sink_.cend() && srt_sink_.empty(); ++config) {
|
||||
if ( GstToolkit::has_feature(config->first) ) {
|
||||
srt_sink_ = config->second;
|
||||
}
|
||||
}
|
||||
|
||||
h264_encoder_.clear();
|
||||
for (auto config = pipeline_encoder_.cbegin();
|
||||
config != pipeline_encoder_.cend() && h264_encoder_.empty(); ++config) {
|
||||
if ( GstToolkit::has_feature(config->first) ) {
|
||||
h264_encoder_ = config->second;
|
||||
if (config->first != pipeline_encoder_.back().first)
|
||||
Log::Info("Video Broadcast uses hardware-accelerated encoder (%s)", config->first.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// perform test only once
|
||||
_tested = true;
|
||||
}
|
||||
|
||||
// video broadcast is installed if both srt and h264 are available
|
||||
return (!srt_sink_.empty() && !h264_encoder_.empty());
|
||||
}
|
||||
|
||||
VideoBroadcast::VideoBroadcast(int port): FrameGrabber(), port_(port), stopped_(false)
|
||||
{
|
||||
frame_duration_ = gst_util_uint64_scale_int (1, GST_SECOND, BROADCAST_FPS); // fixed 30 FPS
|
||||
}
|
||||
|
||||
std::string VideoBroadcast::init(GstCaps *caps)
|
||||
{
|
||||
// ignore
|
||||
if (caps == nullptr)
|
||||
return std::string("Video Broadcast : Invalid caps");
|
||||
|
||||
if (!VideoBroadcast::available())
|
||||
return std::string("Video Broadcast : Not available (missing SRT or H264)");
|
||||
|
||||
// create a gstreamer pipeline
|
||||
std::string description = "appsrc name=src ! videoconvert ! ";
|
||||
|
||||
// complement pipeline with encoder and sink
|
||||
description += VideoBroadcast::h264_encoder_;
|
||||
description += VideoBroadcast::srt_sink_;
|
||||
|
||||
// change the placeholder to include the broadcast port
|
||||
std::string::size_type xxxx = description.find("XXXX");
|
||||
if (xxxx != std::string::npos)
|
||||
description.replace(xxxx, 4, std::to_string(port_));
|
||||
else
|
||||
return std::string("Video Broadcast : Failed to configure broadcast port.");
|
||||
|
||||
// parse pipeline descriptor
|
||||
GError *error = NULL;
|
||||
pipeline_ = gst_parse_launch (description.c_str(), &error);
|
||||
if (error != NULL) {
|
||||
std::string msg = std::string("Video Broadcast : Could not construct pipeline ") + description + "\n" + std::string(error->message);
|
||||
g_clear_error (&error);
|
||||
return msg;
|
||||
}
|
||||
|
||||
// TODO Configure options
|
||||
// setup SRT streaming sink properties
|
||||
g_object_set (G_OBJECT (gst_bin_get_by_name (GST_BIN (pipeline_), "sink")),
|
||||
"latency", 1000,
|
||||
"wait-for-connection", false,
|
||||
NULL);
|
||||
|
||||
// setup custom app source
|
||||
src_ = GST_APP_SRC( gst_bin_get_by_name (GST_BIN (pipeline_), "src") );
|
||||
if (src_) {
|
||||
|
||||
g_object_set (G_OBJECT (src_),
|
||||
"is-live", TRUE,
|
||||
"format", GST_FORMAT_TIME,
|
||||
NULL);
|
||||
|
||||
// configure stream
|
||||
gst_app_src_set_stream_type( src_, GST_APP_STREAM_TYPE_STREAM);
|
||||
gst_app_src_set_latency( src_, -1, 0);
|
||||
|
||||
// Set buffer size
|
||||
gst_app_src_set_max_bytes( src_, buffering_size_ );
|
||||
|
||||
// specify streaming framerate in the given caps
|
||||
GstCaps *tmp = gst_caps_copy( caps );
|
||||
GValue v = { 0, };
|
||||
g_value_init (&v, GST_TYPE_FRACTION);
|
||||
gst_value_set_fraction (&v, BROADCAST_FPS, 1); // fixed 30 FPS
|
||||
gst_caps_set_value(tmp, "framerate", &v);
|
||||
g_value_unset (&v);
|
||||
|
||||
// instruct src to use the caps
|
||||
caps_ = gst_caps_copy( tmp );
|
||||
gst_app_src_set_caps (src_, caps_);
|
||||
gst_caps_unref (tmp);
|
||||
|
||||
// setup callbacks
|
||||
GstAppSrcCallbacks callbacks;
|
||||
callbacks.need_data = FrameGrabber::callback_need_data;
|
||||
callbacks.enough_data = FrameGrabber::callback_enough_data;
|
||||
callbacks.seek_data = NULL; // stream type is not seekable
|
||||
gst_app_src_set_callbacks (src_, &callbacks, this, NULL);
|
||||
|
||||
}
|
||||
else {
|
||||
return std::string("Video Broadcast : Failed to configure frame grabber.");
|
||||
}
|
||||
|
||||
// start
|
||||
GstStateChangeReturn ret = gst_element_set_state (pipeline_, GST_STATE_PLAYING);
|
||||
if (ret == GST_STATE_CHANGE_FAILURE) {
|
||||
return std::string("Video Broadcast : Failed to start frame grabber.");
|
||||
}
|
||||
|
||||
// all good
|
||||
initialized_ = true;
|
||||
|
||||
return std::string("Video Broadcast started SRT on port ") + std::to_string(port_);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void VideoBroadcast::terminate()
|
||||
{
|
||||
// send EOS
|
||||
gst_app_src_end_of_stream (src_);
|
||||
|
||||
Log::Notify("Video Broadcast terminated after %s s.",
|
||||
GstToolkit::time_to_string(duration_).c_str());
|
||||
}
|
||||
|
||||
void VideoBroadcast::stop ()
|
||||
{
|
||||
// stop recording
|
||||
FrameGrabber::stop ();
|
||||
|
||||
// force finished
|
||||
endofstream_ = true;
|
||||
active_ = false;
|
||||
}
|
||||
|
||||
std::string VideoBroadcast::info() const
|
||||
{
|
||||
std::ostringstream ret;
|
||||
|
||||
if (!initialized_)
|
||||
ret << "Starting SRT";
|
||||
else if (active_)
|
||||
ret << "SRT Broadcast on Port " << port_ << " (listener mode)";
|
||||
else
|
||||
ret << "SRT Terminated";
|
||||
|
||||
return ret.str();
|
||||
}
|
||||
|
||||
133
Visitor.h
@@ -1,133 +0,0 @@
|
||||
#ifndef VISITOR_H
|
||||
#define VISITOR_H
|
||||
|
||||
#include <string>
|
||||
|
||||
// Forward declare different kind of Node
|
||||
class Node;
|
||||
class Group;
|
||||
class Switch;
|
||||
class Primitive;
|
||||
class Scene;
|
||||
class Surface;
|
||||
class ImageSurface;
|
||||
class FrameBufferSurface;
|
||||
class LineStrip;
|
||||
class LineSquare;
|
||||
class LineCircle;
|
||||
class Mesh;
|
||||
class Frame;
|
||||
class Handles;
|
||||
class Symbol;
|
||||
class Disk;
|
||||
class Stream;
|
||||
class MediaPlayer;
|
||||
class Shader;
|
||||
class ImageShader;
|
||||
class MaskShader;
|
||||
class ImageProcessingShader;
|
||||
|
||||
class Source;
|
||||
class MediaSource;
|
||||
class PatternSource;
|
||||
class DeviceSource;
|
||||
class GenericStreamSource;
|
||||
class SrtReceiverSource;
|
||||
class SessionFileSource;
|
||||
class SessionGroupSource;
|
||||
class RenderSource;
|
||||
class CloneSource;
|
||||
class NetworkSource;
|
||||
class MixingGroup;
|
||||
class MultiFileSource;
|
||||
|
||||
class FrameBufferFilter;
|
||||
class PassthroughFilter;
|
||||
class DelayFilter;
|
||||
class ResampleFilter;
|
||||
class BlurFilter;
|
||||
class SharpenFilter;
|
||||
class SmoothFilter;
|
||||
class EdgeFilter;
|
||||
class AlphaFilter;
|
||||
class ImageFilter;
|
||||
|
||||
class SourceCallback;
|
||||
class SetAlpha;
|
||||
class SetDepth;
|
||||
class SetGeometry;
|
||||
class Loom;
|
||||
class Grab;
|
||||
class Resize;
|
||||
class Turn;
|
||||
class Play;
|
||||
|
||||
|
||||
// Declares the interface for the visitors
|
||||
class Visitor {
|
||||
|
||||
public:
|
||||
// Need to declare overloads for basic kind of Nodes to visit
|
||||
virtual void visit (Scene&) = 0;
|
||||
virtual void visit (Node&) = 0;
|
||||
virtual void visit (Primitive&) = 0;
|
||||
virtual void visit (Group&) = 0;
|
||||
virtual void visit (Switch&) = 0;
|
||||
|
||||
// not mandatory for all others
|
||||
virtual void visit (Surface&) {}
|
||||
virtual void visit (ImageSurface&) {}
|
||||
virtual void visit (FrameBufferSurface&) {}
|
||||
virtual void visit (LineStrip&) {}
|
||||
virtual void visit (LineSquare&) {}
|
||||
virtual void visit (Mesh&) {}
|
||||
virtual void visit (Frame&) {}
|
||||
virtual void visit (Handles&) {}
|
||||
virtual void visit (Symbol&) {}
|
||||
virtual void visit (Disk&) {}
|
||||
virtual void visit (Shader&) {}
|
||||
virtual void visit (ImageShader&) {}
|
||||
virtual void visit (MaskShader&) {}
|
||||
virtual void visit (ImageProcessingShader&) {}
|
||||
|
||||
// utility
|
||||
virtual void visit (Stream&) {}
|
||||
virtual void visit (MediaPlayer&) {}
|
||||
virtual void visit (MixingGroup&) {}
|
||||
virtual void visit (Source&) {}
|
||||
virtual void visit (MediaSource&) {}
|
||||
virtual void visit (NetworkSource&) {}
|
||||
virtual void visit (SrtReceiverSource&) {}
|
||||
virtual void visit (GenericStreamSource&) {}
|
||||
virtual void visit (DeviceSource&) {}
|
||||
virtual void visit (PatternSource&) {}
|
||||
virtual void visit (SessionFileSource&) {}
|
||||
virtual void visit (SessionGroupSource&) {}
|
||||
virtual void visit (RenderSource&) {}
|
||||
virtual void visit (CloneSource&) {}
|
||||
virtual void visit (MultiFileSource&) {}
|
||||
|
||||
virtual void visit (FrameBufferFilter&) {}
|
||||
virtual void visit (PassthroughFilter&) {}
|
||||
virtual void visit (DelayFilter&) {}
|
||||
virtual void visit (ResampleFilter&) {}
|
||||
virtual void visit (BlurFilter&) {}
|
||||
virtual void visit (SharpenFilter&) {}
|
||||
virtual void visit (SmoothFilter&) {}
|
||||
virtual void visit (EdgeFilter&) {}
|
||||
virtual void visit (AlphaFilter&) {}
|
||||
virtual void visit (ImageFilter&) {}
|
||||
|
||||
virtual void visit (SourceCallback&) {}
|
||||
virtual void visit (SetAlpha&) {}
|
||||
virtual void visit (SetDepth&) {}
|
||||
virtual void visit (SetGeometry&) {}
|
||||
virtual void visit (Loom&) {}
|
||||
virtual void visit (Grab&) {}
|
||||
virtual void visit (Resize&) {}
|
||||
virtual void visit (Turn&) {}
|
||||
virtual void visit (Play&) {}
|
||||
};
|
||||
|
||||
|
||||
#endif // VISITOR_H
|
||||
@@ -34,7 +34,7 @@ endif()
|
||||
|
||||
set(_version 2.0.0)
|
||||
|
||||
cmake_minimum_required(VERSION 3.3)
|
||||
cmake_minimum_required(VERSION 3.12)
|
||||
include(CMakeParseArguments)
|
||||
|
||||
if(COMMAND cmrc_add_resource_library)
|
||||
@@ -77,6 +77,10 @@ set(hpp_content [==[
|
||||
#include <system_error>
|
||||
#include <type_traits>
|
||||
|
||||
#if !(defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND) || defined(CMRC_NO_EXCEPTIONS))
|
||||
#define CMRC_NO_EXCEPTIONS 1
|
||||
#endif
|
||||
|
||||
namespace cmrc { namespace detail { struct dummy; } }
|
||||
|
||||
#define CMRC_DECLARE(libid) \
|
||||
@@ -101,7 +105,7 @@ public:
|
||||
iterator cbegin() const noexcept { return _begin; }
|
||||
iterator end() const noexcept { return _end; }
|
||||
iterator cend() const noexcept { return _end; }
|
||||
std::size_t size() const { return std::distance(begin(), end()); }
|
||||
std::size_t size() const { return static_cast<std::size_t>(std::distance(begin(), end())); }
|
||||
|
||||
file() = default;
|
||||
file(iterator beg, iterator end) noexcept : _begin(beg), _end(end) {}
|
||||
@@ -243,16 +247,16 @@ public:
|
||||
return !(*this == rhs);
|
||||
}
|
||||
|
||||
iterator operator++() noexcept {
|
||||
iterator& operator++() noexcept {
|
||||
++_base_iter;
|
||||
return *this;
|
||||
}
|
||||
|
||||
iterator operator++(int) noexcept {
|
||||
auto cp = *this;
|
||||
++_base_iter;
|
||||
return cp;
|
||||
}
|
||||
|
||||
iterator& operator++(int) noexcept {
|
||||
++_base_iter;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
using const_iterator = iterator;
|
||||
@@ -275,7 +279,7 @@ inline std::string normalize_path(std::string path) {
|
||||
}
|
||||
auto off = path.npos;
|
||||
while ((off = path.find("//")) != path.npos) {
|
||||
path.erase(path.begin() + off);
|
||||
path.erase(path.begin() + static_cast<std::string::difference_type>(off));
|
||||
}
|
||||
return path;
|
||||
}
|
||||
@@ -339,7 +343,12 @@ public:
|
||||
file open(const std::string& path) const {
|
||||
auto entry_ptr = _get(path);
|
||||
if (!entry_ptr || !entry_ptr->is_file()) {
|
||||
#ifdef CMRC_NO_EXCEPTIONS
|
||||
fprintf(stderr, "Error no such file or directory: %s\n", path.c_str());
|
||||
abort();
|
||||
#else
|
||||
throw std::system_error(make_error_code(std::errc::no_such_file_or_directory), path);
|
||||
#endif
|
||||
}
|
||||
auto& dat = entry_ptr->as_file();
|
||||
return file{dat.begin_ptr, dat.end_ptr};
|
||||
@@ -362,10 +371,20 @@ public:
|
||||
directory_iterator iterate_directory(const std::string& path) const {
|
||||
auto entry_ptr = _get(path);
|
||||
if (!entry_ptr) {
|
||||
#ifdef CMRC_NO_EXCEPTIONS
|
||||
fprintf(stderr, "Error no such file or directory: %s\n", path.c_str());
|
||||
abort();
|
||||
#else
|
||||
throw std::system_error(make_error_code(std::errc::no_such_file_or_directory), path);
|
||||
#endif
|
||||
}
|
||||
if (!entry_ptr->is_directory()) {
|
||||
#ifdef CMRC_NO_EXCEPTIONS
|
||||
fprintf(stderr, "Error not a directory: %s\n", path.c_str());
|
||||
abort();
|
||||
#else
|
||||
throw std::system_error(make_error_code(std::errc::not_a_directory), path);
|
||||
#endif
|
||||
}
|
||||
return entry_ptr->as_directory().begin();
|
||||
}
|
||||
@@ -387,14 +406,14 @@ endif()
|
||||
file(GENERATE OUTPUT "${cmrc_hpp}" CONTENT "${hpp_content}" CONDITION ${_generate})
|
||||
|
||||
add_library(cmrc-base INTERFACE)
|
||||
target_include_directories(cmrc-base INTERFACE "${CMRC_INCLUDE_DIR}")
|
||||
target_include_directories(cmrc-base INTERFACE $<BUILD_INTERFACE:${CMRC_INCLUDE_DIR}>)
|
||||
# Signal a basic C++11 feature to require C++11.
|
||||
target_compile_features(cmrc-base INTERFACE cxx_nullptr)
|
||||
set_property(TARGET cmrc-base PROPERTY INTERFACE_CXX_EXTENSIONS OFF)
|
||||
add_library(cmrc::base ALIAS cmrc-base)
|
||||
|
||||
function(cmrc_add_resource_library name)
|
||||
set(args ALIAS NAMESPACE)
|
||||
set(args ALIAS NAMESPACE TYPE)
|
||||
cmake_parse_arguments(ARG "" "${args}" "" "${ARGN}")
|
||||
# Generate the identifier for the resource library's namespace
|
||||
set(ns_re "[a-zA-Z_][a-zA-Z0-9_]*")
|
||||
@@ -410,6 +429,14 @@ function(cmrc_add_resource_library name)
|
||||
endif()
|
||||
endif()
|
||||
set(libname "${name}")
|
||||
# Check that type is either "STATIC" or "OBJECT", or default to "STATIC" if
|
||||
# not set
|
||||
if(NOT DEFINED ARG_TYPE)
|
||||
set(ARG_TYPE STATIC)
|
||||
elseif(NOT "${ARG_TYPE}" MATCHES "^(STATIC|OBJECT)$")
|
||||
message(SEND_ERROR "${ARG_TYPE} is not a valid TYPE (STATIC and OBJECT are acceptable)")
|
||||
set(ARG_TYPE STATIC)
|
||||
endif()
|
||||
# Generate a library with the compiled in character arrays.
|
||||
string(CONFIGURE [=[
|
||||
#include <cmrc/cmrc.hpp>
|
||||
@@ -468,7 +495,7 @@ function(cmrc_add_resource_library name)
|
||||
# Generate the actual static library. Each source file is just a single file
|
||||
# with a character array compiled in containing the contents of the
|
||||
# corresponding resource file.
|
||||
add_library(${name} STATIC ${libcpp})
|
||||
add_library(${name} ${ARG_TYPE} ${libcpp})
|
||||
set_property(TARGET ${name} PROPERTY CMRC_LIBDIR "${libdir}")
|
||||
set_property(TARGET ${name} PROPERTY CMRC_NAMESPACE "${ARG_NAMESPACE}")
|
||||
target_link_libraries(${name} PUBLIC cmrc::base)
|
||||
@@ -558,7 +585,7 @@ function(cmrc_add_resources name)
|
||||
endif()
|
||||
get_filename_component(dirpath "${ARG_PREFIX}${relpath}" DIRECTORY)
|
||||
_cmrc_register_dirs("${name}" "${dirpath}")
|
||||
get_filename_component(abs_out "${libdir}/intermediate/${relpath}.cpp" ABSOLUTE)
|
||||
get_filename_component(abs_out "${libdir}/intermediate/${ARG_PREFIX}${relpath}.cpp" ABSOLUTE)
|
||||
# Generate a symbol name relpath the file's character array
|
||||
_cm_encode_fpath(sym "${relpath}")
|
||||
# Get the symbol name for the parent directory
|
||||
|
||||
@@ -33,9 +33,10 @@ find_package(PkgConfig)
|
||||
|
||||
if (PKG_CONFIG_FOUND)
|
||||
pkg_check_modules(PKG_GSTREAMER gstreamer-${GSTREAMER_ABI_VERSION})
|
||||
exec_program(${PKG_CONFIG_EXECUTABLE}
|
||||
ARGS --variable pluginsdir gstreamer-${GSTREAMER_ABI_VERSION}
|
||||
OUTPUT_VARIABLE PKG_GSTREAMER_PLUGIN_DIR)
|
||||
execute_process(COMMAND ${PKG_CONFIG_EXECUTABLE}
|
||||
--variable pluginsdir gstreamer-${GSTREAMER_ABI_VERSION}
|
||||
OUTPUT_VARIABLE PKG_GSTREAMER_PLUGIN_DIR_TMP)
|
||||
string(STRIP ${PKG_GSTREAMER_PLUGIN_DIR_TMP} PKG_GSTREAMER_PLUGIN_DIR)
|
||||
endif()
|
||||
|
||||
find_library(GSTREAMER_LIBRARY
|
||||
|
||||
@@ -41,8 +41,8 @@ foreach(_component ${GStreamerPluginsBad_FIND_COMPONENTS})
|
||||
_find_gst_plugins_bad_component(PLAYER gstplayer.h)
|
||||
elseif (${_component} STREQUAL "webrtc")
|
||||
_find_gst_plugins_bad_component(WEBRTC webrtc.h)
|
||||
elseif (${_component} STREQUAL "mpegts")
|
||||
_find_gst_plugins_bad_component(MPEGTS mpegts.h)
|
||||
elseif (${_component} STREQUAL "srtsrc")
|
||||
_find_gst_plugins_bad_component(SRTSRC mpegts.h)
|
||||
else()
|
||||
message (AUTHOR_WARNING "FindGStreamerPluginBad.cmake: Invalid component \"${_component}\" was specified")
|
||||
endif()
|
||||
|
||||
112
defines.h
@@ -1,112 +0,0 @@
|
||||
#ifndef VMIX_DEFINES_H
|
||||
#define VMIX_DEFINES_H
|
||||
|
||||
#define APP_NAME "vimix"
|
||||
#define APP_TITLE "Video Live Mixer"
|
||||
#define APP_SETTINGS "vimix.xml"
|
||||
#define XML_VERSION_MAJOR 0
|
||||
#define XML_VERSION_MINOR 3
|
||||
#define MAX_RECENT_HISTORY 20
|
||||
#define MAX_SESSION_LEVEL 3
|
||||
|
||||
#define VIMIX_FILE_EXT "mix"
|
||||
#define VIMIX_FILE_PATTERN "*.mix"
|
||||
#define MEDIA_FILES_PATTERN "*.mix", "*.mp4", "*.mpg", "*.mpeg", "*.m2v", "*.m4v", "*.avi", "*.mov",\
|
||||
"*.mkv", "*.webm", "*.mod", "*.wmv", "*.mxf", "*.ogg",\
|
||||
"*.flv", "*.hevc", "*.asf", "*.jpg", "*.png", "*.gif",\
|
||||
"*.tif", "*.tiff", "*.webp", "*.bmp", "*.ppm", "*.svg,"
|
||||
#define IMAGES_FILES_PATTERN "*.jpg", "*.png", "*.bmp", "*.ppm", "*.gif"
|
||||
|
||||
#define MINI(a, b) (((a) < (b)) ? (a) : (b))
|
||||
#define MAXI(a, b) (((a) > (b)) ? (a) : (b))
|
||||
#define ABS(a) (((a) < 0) ? -(a) : (a))
|
||||
#define ABS_DIFF(a, b) ( (a) < (b) ? (b - a) : (a - b) )
|
||||
#define SIGN(a) (((a) < 0) ? -1.0 : 1.0)
|
||||
#define SQUARE(a) ((a) * (a))
|
||||
#define CLAMP(x, low, high) (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x)))
|
||||
#define EPSILON 0.00001
|
||||
#define LOG100(val) (50.0/log(10.0)*log((float)val + 1.0))
|
||||
#define EXP100(val) (exp(log(10.0)/50.0*(float)(val))-1.0)
|
||||
#define EUCLIDEAN(P1, P2) sqrt((P1.x() - P2.x()) * (P1.x() - P2.x()) + (P1.y() - P2.y()) * (P1.y() - P2.y()))
|
||||
#define ROUND(val, factor) float( int( val * factor ) ) / factor;
|
||||
|
||||
#define SCENE_UNIT 5.f
|
||||
#define MIN_SCALE 0.01f
|
||||
#define MAX_SCALE 10.f
|
||||
#define CLAMP_SCALE(x) SIGN(x) * CLAMP( ABS(x), MIN_SCALE, MAX_SCALE)
|
||||
#define SCENE_DEPTH 14.f
|
||||
#define MIN_DEPTH 0.f
|
||||
#define MAX_DEPTH 12.f
|
||||
#define DELTA_DEPTH 0.05f
|
||||
#define DELTA_ALPHA 0.0005f
|
||||
#define MIXING_DEFAULT_SCALE 2.4f
|
||||
#define MIXING_MIN_SCALE 0.8f
|
||||
#define MIXING_MAX_SCALE 7.0f
|
||||
#define MIXING_MIN_THRESHOLD 1.3f
|
||||
#define MIXING_MAX_THRESHOLD 1.9f
|
||||
#define MIXING_ICON_SCALE 0.15f, 0.15f, 1.f
|
||||
#define GEOMETRY_DEFAULT_SCALE 1.4f
|
||||
#define GEOMETRY_MIN_SCALE 0.4f
|
||||
#define GEOMETRY_MAX_SCALE 7.0f
|
||||
#define LAYER_DEFAULT_SCALE 0.6f
|
||||
#define LAYER_MIN_SCALE 0.25f
|
||||
#define LAYER_MAX_SCALE 1.7f
|
||||
#define LAYER_PERSPECTIVE 2.0f
|
||||
#define LAYER_BACKGROUND 2.f
|
||||
#define LAYER_FOREGROUND 10.f
|
||||
#define LAYER_STEP 0.25f
|
||||
#define APPEARANCE_DEFAULT_SCALE 2.f
|
||||
#define APPEARANCE_MIN_SCALE 0.4f
|
||||
#define APPEARANCE_MAX_SCALE 7.0f
|
||||
#define BRUSH_MIN_SIZE 0.05f
|
||||
#define BRUSH_MAX_SIZE 2.f
|
||||
#define BRUSH_MIN_PRESS 0.005f
|
||||
#define BRUSH_MAX_PRESS 1.f
|
||||
#define SHAPE_MIN_BLUR 0.f
|
||||
#define SHAPE_MAX_BLUR 1.f
|
||||
#define TRANSITION_DEFAULT_SCALE 5.0f
|
||||
#define TRANSITION_MIN_DURATION 0.2f
|
||||
#define TRANSITION_MAX_DURATION 10.f
|
||||
#define ARROWS_MOVEMENT_FACTOR 0.1f
|
||||
#define SESSION_THUMBNAIL_HEIGHT 120.f
|
||||
#define RECORD_MAX_TIMEOUT 1200000
|
||||
|
||||
#define IMGUI_TITLE_LOGS ICON_FA_LIST_UL " Logs"
|
||||
#define IMGUI_LABEL_RECENT_FILES " Recent files"
|
||||
#define IMGUI_LABEL_RECENT_RECORDS " Recent recordings"
|
||||
#define IMGUI_RIGHT_ALIGN -3.5f * ImGui::GetTextLineHeightWithSpacing()
|
||||
#define IMGUI_SAME_LINE 10
|
||||
#define IMGUI_TOP_ALIGN 10
|
||||
#define IMGUI_COLOR_OVERLAY IM_COL32(5, 5, 5, 150)
|
||||
#define IMGUI_COLOR_LIGHT_OVERLAY IM_COL32(5, 5, 5, 50)
|
||||
#define IMGUI_COLOR_CAPTURE 1.0, 0.55, 0.05
|
||||
#define IMGUI_COLOR_RECORD 1.0, 0.05, 0.05
|
||||
#define IMGUI_COLOR_STREAM 0.05, 0.8, 1.0
|
||||
#define IMGUI_COLOR_BROADCAST 0.1, 0.9, 0.1
|
||||
#define IMGUI_NOTIFICATION_DURATION 2.5f
|
||||
#define IMGUI_TOOLTIP_TIMEOUT 80
|
||||
|
||||
#define COLOR_BGROUND 0.2f, 0.2f, 0.2f
|
||||
#define COLOR_NAVIGATOR 0.1f, 0.1f, 0.1f
|
||||
#define COLOR_DEFAULT_SOURCE 0.7f, 0.7f, 0.7f
|
||||
#define COLOR_HIGHLIGHT_SOURCE 1.f, 1.f, 1.f
|
||||
#define COLOR_TRANSITION_SOURCE 1.f, 0.5f, 1.f
|
||||
#define COLOR_TRANSITION_LINES 0.9f, 0.9f, 0.9f
|
||||
#define COLOR_APPEARANCE_SOURCE 0.9f, 0.9f, 0.1f
|
||||
#define COLOR_APPEARANCE_LIGHT 1.0f, 1.0f, 0.9f
|
||||
#define COLOR_APPEARANCE_MASK 0.9f, 0.9f, 0.9f
|
||||
#define COLOR_APPEARANCE_MASK_DISABLE 0.6f, 0.6f, 0.6f
|
||||
#define COLOR_FRAME 0.75f, 0.2f, 0.75f
|
||||
#define COLOR_FRAME_LIGHT 0.9f, 0.6f, 0.9f
|
||||
#define COLOR_CIRCLE 0.75f, 0.2f, 0.75f
|
||||
#define COLOR_CIRCLE_OVER 0.8f, 0.8f, 0.8f
|
||||
#define COLOR_MIXING_GROUP 0.f, 0.95f, 0.2f
|
||||
#define COLOR_LIMBO_CIRCLE 0.173f, 0.173f, 0.173f
|
||||
#define COLOR_SLIDER_CIRCLE 0.11f, 0.11f, 0.11f
|
||||
#define COLOR_STASH_CIRCLE 0.06f, 0.06f, 0.06f
|
||||
|
||||
#define OSC_PORT_RECV_DEFAULT 7000
|
||||
#define OSC_PORT_SEND_DEFAULT 7001
|
||||
#define OSC_CONFIG_FILE "osc.xml"
|
||||
|
||||
#endif // VMIX_DEFINES_H
|
||||
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 377 KiB |
|
Before Width: | Height: | Size: 173 KiB After Width: | Height: | Size: 971 KiB |
|
Before Width: | Height: | Size: 110 KiB After Width: | Height: | Size: 130 KiB |
|
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 208 KiB |
|
Before Width: | Height: | Size: 119 KiB After Width: | Height: | Size: 381 KiB |
|
Before Width: | Height: | Size: 350 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 337 KiB |
|
Before Width: | Height: | Size: 356 KiB After Width: | Height: | Size: 429 KiB |
BIN
docs/images/manual_clonefilter_0.png
Normal file
|
After Width: | Height: | Size: 338 KiB |
BIN
docs/images/manual_clonefilter_1.png
Normal file
|
After Width: | Height: | Size: 498 KiB |
BIN
docs/images/manual_clonefilter_2.png
Normal file
|
After Width: | Height: | Size: 913 KiB |
BIN
docs/images/manual_filter_alpha_chromakey.png
Normal file
|
After Width: | Height: | Size: 113 KiB |
BIN
docs/images/manual_filter_alpha_fill.png
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
docs/images/manual_filter_alpha_lumakey.png
Normal file
|
After Width: | Height: | Size: 133 KiB |
BIN
docs/images/manual_filter_blur_closing.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
docs/images/manual_filter_blur_fast.png
Normal file
|
After Width: | Height: | Size: 676 KiB |
BIN
docs/images/manual_filter_blur_gaussian.png
Normal file
|
After Width: | Height: | Size: 592 KiB |
BIN
docs/images/manual_filter_blur_opening.png
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
docs/images/manual_filter_blur_scattered.png
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
docs/images/manual_filter_chain_0.png
Normal file
|
After Width: | Height: | Size: 426 KiB |
BIN
docs/images/manual_filter_chain_1.png
Normal file
|
After Width: | Height: | Size: 410 KiB |
BIN
docs/images/manual_filter_chain_2.png
Normal file
|
After Width: | Height: | Size: 526 KiB |
BIN
docs/images/manual_filter_chain_3.png
Normal file
|
After Width: | Height: | Size: 443 KiB |
BIN
docs/images/manual_filter_chain_4.png
Normal file
|
After Width: | Height: | Size: 441 KiB |
BIN
docs/images/manual_filter_chain_5.png
Normal file
|
After Width: | Height: | Size: 383 KiB |
BIN
docs/images/manual_filter_delay.png
Normal file
|
After Width: | Height: | Size: 863 KiB |
BIN
docs/images/manual_filter_downsample.png
Normal file
|
After Width: | Height: | Size: 742 KiB |
BIN
docs/images/manual_filter_edge_contour.png
Normal file
|
After Width: | Height: | Size: 330 KiB |
BIN
docs/images/manual_filter_edge_freichen.png
Normal file
|
After Width: | Height: | Size: 586 KiB |
BIN
docs/images/manual_filter_edge_sobel.png
Normal file
|
After Width: | Height: | Size: 565 KiB |
BIN
docs/images/manual_filter_edge_thresholding.png
Normal file
|
After Width: | Height: | Size: 330 KiB |
BIN
docs/images/manual_filter_sharpen_blackhat.png
Normal file
|
After Width: | Height: | Size: 990 KiB |
BIN
docs/images/manual_filter_sharpen_convolution.png
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
docs/images/manual_filter_sharpen_edge.png
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
BIN
docs/images/manual_filter_sharpen_unsharpmask.png
Normal file
|
After Width: | Height: | Size: 988 KiB |
BIN
docs/images/manual_filter_sharpen_whitehat.png
Normal file
|
After Width: | Height: | Size: 1006 KiB |
BIN
docs/images/manual_filter_smooth_add_grain.png
Normal file
|
After Width: | Height: | Size: 1.9 MiB |
BIN
docs/images/manual_filter_smooth_add_noise.png
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
docs/images/manual_filter_smooth_bilateral.png
Normal file
|
After Width: | Height: | Size: 710 KiB |
BIN
docs/images/manual_filter_smooth_closing.png
Normal file
|
After Width: | Height: | Size: 792 KiB |
BIN
docs/images/manual_filter_smooth_dilation.png
Normal file
|
After Width: | Height: | Size: 810 KiB |
BIN
docs/images/manual_filter_smooth_erosion.png
Normal file
|
After Width: | Height: | Size: 779 KiB |
BIN
docs/images/manual_filter_smooth_kuwahara.png
Normal file
|
After Width: | Height: | Size: 838 KiB |
BIN
docs/images/manual_filter_smooth_opening.png
Normal file
|
After Width: | Height: | Size: 735 KiB |
BIN
docs/images/manual_filter_smooth_removenoise.png
Normal file
|
After Width: | Height: | Size: 761 KiB |
|
Before Width: | Height: | Size: 291 KiB After Width: | Height: | Size: 986 KiB |
|
Before Width: | Height: | Size: 379 KiB After Width: | Height: | Size: 1.1 MiB |
BIN
docs/images/manual_group_sources_0.png
Normal file
|
After Width: | Height: | Size: 515 KiB |
BIN
docs/images/manual_group_sources_1.png
Normal file
|
After Width: | Height: | Size: 612 KiB |
BIN
docs/images/manual_group_sources_2.png
Normal file
|
After Width: | Height: | Size: 560 KiB |
BIN
docs/images/manual_group_sources_3.png
Normal file
|
After Width: | Height: | Size: 517 KiB |
BIN
docs/images/manual_group_sources_4.png
Normal file
|
After Width: | Height: | Size: 528 KiB |
BIN
docs/images/manual_group_sources_5.png
Normal file
|
After Width: | Height: | Size: 465 KiB |
BIN
docs/images/manual_laryxscreencast_receive.png
Normal file
|
After Width: | Height: | Size: 327 KiB |
|
Before Width: | Height: | Size: 389 KiB After Width: | Height: | Size: 392 KiB |
BIN
docs/images/manual_order_sources.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
docs/images/manual_recording_0.png
Normal file
|
After Width: | Height: | Size: 570 KiB |
BIN
docs/images/manual_recording_1.png
Normal file
|
After Width: | Height: | Size: 541 KiB |
BIN
docs/images/manual_recording_2.png
Normal file
|
After Width: | Height: | Size: 670 KiB |
BIN
docs/images/manual_sequence_0.png
Normal file
|
After Width: | Height: | Size: 270 KiB |
BIN
docs/images/manual_sequence_1.png
Normal file
|
After Width: | Height: | Size: 253 KiB |
BIN
docs/images/manual_shadertoy_0.png
Normal file
|
After Width: | Height: | Size: 866 KiB |
BIN
docs/images/manual_shadertoy_1.png
Normal file
|
After Width: | Height: | Size: 708 KiB |
BIN
docs/images/manual_shadertoy_2.png
Normal file
|
After Width: | Height: | Size: 303 KiB |
BIN
docs/images/manual_shadertoy_3.png
Normal file
|
After Width: | Height: | Size: 250 KiB |
BIN
docs/images/manual_shadertoy_4.png
Normal file
|
After Width: | Height: | Size: 1.3 MiB |