├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .gitmodules ├── LICENSE ├── README.org ├── install.sh ├── makepackage.sh ├── panon ├── backend │ ├── beat.py │ ├── client.py │ ├── convertor.py │ ├── decay.py │ ├── get_devices.py │ ├── get_pa_devices.py │ ├── server.py │ ├── source.py │ └── spectrum.py ├── effect │ ├── build_shader_source.py │ ├── get_effect_list.py │ ├── helper.py │ └── read_file.py └── logger.py ├── plasmoid ├── contents │ ├── config │ │ ├── config.qml │ │ ├── main.xml │ │ └── panon.knsrc │ ├── scripts │ │ ├── panon │ │ └── soundcard │ │ │ ├── LICENSE │ │ │ ├── __init__.py │ │ │ ├── pulseaudio.py │ │ │ └── pulseaudio.py.h │ ├── shaders │ │ ├── bar1ch │ │ │ ├── buffer.frag │ │ │ ├── image.frag │ │ │ └── meta.json │ │ ├── beam.frag │ │ ├── blur1ch.frag │ │ ├── chain │ │ │ ├── image.frag │ │ │ └── meta.json │ │ ├── default │ │ │ ├── hint.html │ │ │ └── image.frag │ │ ├── dune1ch.frag │ │ ├── gldft.fsh │ │ ├── hill1ch.frag │ │ ├── hsluv-glsl.fsh │ │ ├── oie1ch │ │ │ ├── hint.html │ │ │ └── image.frag │ │ ├── shadertoy-api-foot-buffer.fsh │ │ ├── shadertoy-api-foot.fsh │ │ ├── shadertoy-api-head.fsh │ │ ├── solid.frag │ │ ├── solid1ch.frag │ │ ├── spectrogram │ │ │ ├── buffer.frag │ │ │ ├── image.frag │ │ │ └── meta.json │ │ ├── utils.fsh │ │ ├── wave-buffer.fsh │ │ └── wave.frag │ └── ui │ │ ├── MessageQueue.qml │ │ ├── PreloadingTextures.qml │ │ ├── ShaderSource.qml │ │ ├── Spectrum.qml │ │ ├── WsConnection.qml │ │ ├── config │ │ ├── ConfigBackend.qml │ │ ├── ConfigColors.qml │ │ ├── ConfigEffect.qml │ │ ├── ConfigGeneral.qml │ │ ├── EffectArgument.qml │ │ ├── EffectArgumentBool.qml │ │ ├── EffectArgumentColor.qml │ │ ├── EffectArgumentDouble.qml │ │ ├── EffectArgumentInt.qml │ │ └── utils.js │ │ ├── main.qml │ │ └── utils.js └── metadata.desktop ├── test.sh └── translations ├── CMakeLists.txt ├── README ├── extract-messages.sh ├── install_translations.sh └── po ├── plasma_applet_panon.pot ├── plasma_applet_panon_de_DE.po ├── plasma_applet_panon_nl.po └── plasma_applet_panon_zh_CN.po /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG] {title}" 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Desktop (please complete the following information):** 11 | - OS: [e.g. ArchLinux or Ubuntu] 12 | - Version of Plasma Framework 13 | 14 | **Describe the bug** 15 | A clear and concise description of what the bug is. 16 | 17 | **Any error message shown in the console** 18 | Please execute the following commands in the console and upload the outputs. 19 | 20 | git clone https://github.com/rbn42/panon.git 21 | cd panon 22 | git submodule update --init 23 | # You need to install plasma-sdk to get plasmoidviewer. 24 | plasmoidviewer --applet ./plasmoid/ 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[Feature request] {title}" 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.plasmoid 2 | *.tgz 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *.swp 7 | *$py.class 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | env/ 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | .hypothesis/ 51 | 52 | # Translations 53 | *.mo 54 | #*.pot 55 | 56 | # Django stuff: 57 | *.log 58 | local_settings.py 59 | 60 | # Flask stuff: 61 | instance/ 62 | .webassets-cache 63 | 64 | # Scrapy stuff: 65 | .scrapy 66 | 67 | # Sphinx documentation 68 | docs/_build/ 69 | 70 | # PyBuilder 71 | target/ 72 | 73 | # Jupyter Notebook 74 | .ipynb_checkpoints 75 | 76 | # pyenv 77 | .python-version 78 | 79 | # celery beat schedule file 80 | celerybeat-schedule 81 | 82 | # SageMath parsed files 83 | *.sage.py 84 | 85 | # dotenv 86 | .env 87 | 88 | # virtualenv 89 | .venv 90 | venv/ 91 | ENV/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "SoundCard"] 2 | path = third_party/SoundCard 3 | url = https://github.com/bastibe/SoundCard 4 | [submodule "hsluv-glsl"] 5 | path = third_party/hsluv-glsl 6 | url = https://github.com/williammalo/hsluv-glsl 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | {one line to give the program's name and a brief idea of what it does.} 635 | Copyright (C) {year} {name of author} 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | {project} Copyright (C) {year} {fullname} 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | This is an audio spectrum analyzer for KDE panel. 2 | 3 | [[../../wiki/Previews][file:../../wiki/plasmoid/preview.png]] 4 | 5 | ** Requirements 6 | | | Version | 7 | |------------------+------------------| 8 | | OpenGL / GLSL | >= 3.0 / 1.30 | 9 | | org.kde.kirigami | >= 2.3 (kf 5.42) | 10 | | org.kde.newstuff | >= 1.1 (kf 5.63) | 11 | If your KDE Framework is older than 5.63, see [[../../wiki/Troubleshooting#cannot-load-the-visual-effects-page-in-the-configuration-dialog][here]]. 12 | ** Dependencies 13 | 14 | *** Arch Linux 15 | #+BEGIN_SRC sh 16 | sudo pacman -S qt5-websockets \ 17 | python-docopt python-numpy python-pyaudio python-cffi python-websockets 18 | #+END_SRC 19 | 20 | *** openSUSE 21 | #+BEGIN_SRC sh 22 | sudo zypper in libQt5WebSockets5 \ 23 | python3-docopt python3-numpy python3-PyAudio python3-cffi python3-websockets 24 | #+END_SRC 25 | 26 | *** Ubuntu 27 | #+BEGIN_SRC sh 28 | sudo apt-get install qml-module-qt-websockets \ 29 | python3-docopt python3-numpy python3-pyaudio python3-cffi python3-websockets 30 | #+END_SRC 31 | 32 | *** Solus 33 | #+BEGIN_SRC sh 34 | sudo eopkg install qt5-websockets \ 35 | python-docopt PyAudio numpy python-cffi python-websockets 36 | #+END_SRC 37 | 38 | ** Installation 39 | *** Via KDE Store 40 | 41 | 1. Open the "Add Widgets" dialog of your desktop 42 | 2. Go to "Get New Widgets" in the bottom 43 | 3. Click "Download New Plasma Widgets" 44 | 4. Search for "panon" 45 | 5. Click "Install" 46 | 47 | *** Via Command Line 48 | 49 | #+BEGIN_SRC sh 50 | git clone https://github.com/rbn42/panon.git 51 | cd panon 52 | 53 | # Download SoundCard and hsluv-glsl 54 | git submodule update --init 55 | 56 | # Build translations (optional) 57 | mkdir build 58 | cd build 59 | cmake ../translations 60 | make install DESTDIR=../plasmoid/contents/locale 61 | cd .. 62 | 63 | # To install 64 | kpackagetool5 -t Plasma/Applet --install plasmoid 65 | 66 | # To upgrade 67 | kpackagetool5 -t Plasma/Applet --upgrade plasmoid 68 | #+END_SRC 69 | 70 | *** Via AUR 71 | [[https://aur.archlinux.org/packages/plasma5-applets-panon/][plasma5-applets-panon]] is available for ArchLinux. 72 | 73 | ** [[../../wiki/VisualEffects][Visual Effects]] 74 | 75 | ** [[../../wiki/Troubleshooting][Troubleshooting]] 76 | ** Credits 77 | *** Main Contributors 78 | From old to new, 79 | | | Contributor | 80 | |----------------------------------------+----------------------------------------------------------------| 81 | | AUR package maintained by | [[https://aur.archlinux.org/packages/?K=mareex&SeB=m][mareex]], [[https://github.com/flying-sheep][flying-sheep (Philipp A.)]] | 82 | | German translation added by | [[https://github.com/NLDev][NullDev (Chris)]] | 83 | | "Download New Effects" dialog added by | [[https://github.com/flying-sheep][flying-sheep (Philipp A.)]] | 84 | | Dutch translation added by | [[https://github.com/Vistaus][Vistaus (Heimen Stoffels)]] | 85 | | "Monitor of Current Device" option added by | [[https://github.com/yuannan][Yuannan]] | 86 | And thanks for all the reported issues and suggestions, which I would not list here. 87 | *** Third Party Source 88 | | Files | Source | Licensed under | 89 | |-------------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------+----------------| 90 | | [[file:panon/backend/source.py][source.py]] and [[file:panon/backend/spectrum.py][spectrum.py]] | adapted from [[https://github.com/ajalt/PyVisualizer][PyVisualizer]] | | 91 | | =hsv2rgb= in [[file:plasmoid/contents/shaders/utils.fsh][utils.fsh]] | copied from [[https://gist.github.com/patriciogonzalezvivo/114c1653de9e3da6e1e3][GLSL-color.md]] | | 92 | 93 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -f "third_party/hsluv-glsl/hsluv-glsl.fsh" ];then 4 | kpackagetool5 -t Plasma/Applet --install plasmoid 5 | kpackagetool5 -t Plasma/Applet --upgrade plasmoid 6 | else 7 | echo "Cannot find third party files." 8 | fi 9 | -------------------------------------------------------------------------------- /makepackage.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #Verify the existence of third party files before packaging. 4 | if [ ! -f "third_party/hsluv-glsl/hsluv-glsl.fsh" ];then 5 | echo "Cannot find third party files." 6 | git submodule update --init 7 | 8 | fi 9 | 10 | # Remove caches 11 | rm ./plasmoid/contents/scripts/__pycache__/ -r 12 | rm ./plasmoid/contents/scripts/*/__pycache__/ -r 13 | rm ./plasmoid/contents/scripts/*/*/__pycache__/ -r 14 | rm ./plasmoid/contents/scripts/*/*/*/__pycache__/ -r 15 | rm ./panon.plasmoid 16 | 17 | # i18n 18 | mkdir build 19 | cd build 20 | cmake ../translations 21 | make install DESTDIR=../plasmoid/contents/locale 22 | cd .. 23 | 24 | zip -r panon.plasmoid ./plasmoid 25 | -------------------------------------------------------------------------------- /panon/backend/beat.py: -------------------------------------------------------------------------------- 1 | def canImportAubio(): 2 | import importlib 3 | return importlib.find_loader('aubio') is not None 4 | 5 | 6 | class BeatsDetector: 7 | def __init__(self, channels, samplerate, cfg_fps): 8 | 9 | hop_s = samplerate // cfg_fps * channels # hop size 10 | win_s = hop_s * 2 # fft size 11 | 12 | import aubio 13 | # create aubio tempo detection 14 | self.a_tempo = aubio.tempo("default", win_s, hop_s, samplerate) 15 | self.hop_s = hop_s 16 | 17 | def isBeat(self, samples): 18 | return float(self.a_tempo(samples.reshape((self.hop_s, )))[0]) 19 | 20 | 21 | if __name__ == '__main__': 22 | channels = 2 23 | samplerate = 44100 24 | fps = 60 25 | from . import source 26 | spectrum_source = source.SoundCardSource(channels, samplerate, 'default', fps) 27 | beatsDetector = BeatsDetector(channels, samplerate, fps) 28 | 29 | while True: 30 | data = spectrum_source.read() 31 | b = beatsDetector.isBeat(data) 32 | print(b) 33 | -------------------------------------------------------------------------------- /panon/backend/client.py: -------------------------------------------------------------------------------- 1 | """ 2 | panon client 3 | 4 | Usage: 5 | main [options] 6 | main -h | --help 7 | 8 | Options: 9 | -h --help Show this screen. 10 | --device-index=I Device index. 11 | --fps=F Fps [default: 30] 12 | --reduce-bass 13 | --gldft 14 | --bass-resolution-level=L [default: 1] 15 | --backend=B [default: pyaudio] 16 | --fifo-path=P 17 | --debug Debug 18 | --enable-wave-data 19 | --enable-spectrum-data 20 | """ 21 | import asyncio 22 | import numpy as np 23 | import json 24 | import websockets 25 | from . import spectrum 26 | from .decay import Decay 27 | from . import source 28 | 29 | import sys 30 | 31 | from docopt import docopt 32 | 33 | import sys 34 | from .. import logger 35 | logger.log('argv: %s', sys.argv[1:]) 36 | 37 | arguments = docopt(__doc__) 38 | cfg_fps = int(arguments['--fps']) 39 | bassResolutionLevel = int(arguments['--bass-resolution-level']) 40 | reduceBass = arguments['--reduce-bass'] 41 | use_glDFT = arguments['--gldft'] 42 | 43 | sample_rate = 44100 44 | beatsDetector = None 45 | 46 | if arguments['--backend'] == 'pyaudio': 47 | spectrum_source = source.PyaudioSource(spectrum.NUM_CHANNEL, sample_rate, arguments['--device-index'], cfg_fps) 48 | elif arguments['--backend'] == 'fifo': 49 | spectrum_source = source.FifoSource(spectrum.NUM_CHANNEL, sample_rate, arguments['--fifo-path'], cfg_fps) 50 | #elif arguments['--backend'] == 'sounddevice': 51 | # spectrum_source = source.SounddeviceSource(spectrum.NUM_CHANNEL, sample_rate, arguments['--device-index']) 52 | elif arguments['--backend'] == 'soundcard': 53 | spectrum_source = source.SoundCardSource(spectrum.NUM_CHANNEL, sample_rate, arguments['--device-index'], cfg_fps) 54 | from . import beat 55 | if beat.canImportAubio(): 56 | beatsDetector = beat.BeatsDetector(spectrum.NUM_CHANNEL, sample_rate, cfg_fps) 57 | else: 58 | assert False 59 | 60 | 61 | async def mainloop(): 62 | async with websockets.connect(arguments['']) as websocket: 63 | 64 | spec = spectrum.Spectrum() 65 | decay = Decay() 66 | 67 | from .convertor import Numpy2Str 68 | n2s = Numpy2Str() 69 | 70 | spectrum_data = True #None 71 | isBeat = False 72 | 73 | logger.log('loop') 74 | while True: 75 | smart_mode = type(spectrum_source) is source.SoundCardSource and spectrum_source.device_id == 'smart' 76 | 77 | if smart_mode and spectrum_source.smart_device_id == '': 78 | spectrum_source.update_smart_device() 79 | 80 | if not use_glDFT and spectrum_data is None: 81 | # Set fps to 2 to lower CPU usage, when audio is unavailable. 82 | latest_wave_data = spectrum_source.read(fps=2) 83 | if smart_mode: 84 | spectrum_source.update_smart_device() 85 | else: 86 | latest_wave_data = spectrum_source.read() 87 | isBeat = beatsDetector is not None and beatsDetector.isBeat(latest_wave_data) 88 | 89 | if latest_wave_data.dtype.type is np.float32: 90 | latest_wave_data = np.asarray(latest_wave_data * (2**16), dtype='int16') 91 | 92 | if use_glDFT: 93 | await websocket.send(n2s.convert_int16(latest_wave_data)) 94 | continue 95 | 96 | wave_hist = spec.updateHistory(latest_wave_data) 97 | data = spec.computeSpectrum(wave_hist, bassResolutionLevel=bassResolutionLevel, reduceBass=reduceBass) 98 | 99 | spectrum_data = decay.process(data) 100 | if spectrum_data is None: 101 | await websocket.send('') 102 | else: 103 | 104 | obj = { 105 | 'beat': isBeat, 106 | } 107 | if arguments['--enable-wave-data']: 108 | wave_data = latest_wave_data 109 | wave_max = np.max(np.abs(wave_data)) 110 | wave_data = (wave_data + wave_max) / wave_max / 2 * 256 111 | wave_data_m = n2s.convert(wave_data) 112 | obj['wave'] = wave_data_m 113 | if arguments['--enable-spectrum-data']: 114 | spectrum_data = np.clip(spectrum_data[1:] / 3.0, 0, 0.99) * 256 115 | spectrum_data_m = n2s.convert(spectrum_data) 116 | # When only spectrum data is enabled, send raw data to reduce cpu usage. 117 | if not arguments['--enable-wave-data']: 118 | await websocket.send(spectrum_data_m) 119 | continue 120 | obj['spectrum'] = spectrum_data_m 121 | 122 | await websocket.send(json.dumps(obj)) 123 | 124 | 125 | asyncio.get_event_loop().run_until_complete(mainloop()) 126 | -------------------------------------------------------------------------------- /panon/backend/convertor.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | #import io 3 | #from PIL import Image 4 | import base64 5 | 6 | 7 | class Numpy2Str: 8 | img_data_map = {} 9 | 10 | def convert(self, data): 11 | return self.new_convert(data) 12 | 13 | def new_convert(self, data): 14 | if data is None: 15 | return '' 16 | body = self.get_body(data) 17 | head = self.get_head(len(body), data.shape[0], 1) 18 | message = 'img/bmp;base64,' + base64.b64encode(head + body).decode() 19 | return message 20 | 21 | def convert_int16(self, data): 22 | body = self.get_body_int16(data) 23 | head = self.get_head(len(body), data.shape[0], 2) 24 | message = 'img/bmp;base64,' + base64.b64encode(head + body).decode() 25 | return message 26 | 27 | def get_head(self, body_size, width, height): 28 | return b'BM' + (54 + body_size).to_bytes(4, 'little') +\ 29 | b'\x00\x00\x00\x006\x00\x00\x00(\x00\x00\x00' + width.to_bytes(4, 'little') +\ 30 | height.to_bytes(4, 'little') +b'\x01\x00\x18\x00\x00\x00\x00\x00' + body_size.to_bytes(4, 'little') +\ 31 | b'\xc4\x0e\x00\x00\xc4\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' 32 | 33 | def get_body(self, data): 34 | data_length, _ = data.shape 35 | key = data_length 36 | if self.img_data_map.get(key) is None: 37 | self.img_data_map[key] = np.zeros((1, data_length, 3), dtype='uint8') 38 | img_data = self.img_data_map[key] 39 | img_data[0, :, :2] = data 40 | data = img_data[:, :, ::-1].tobytes() 41 | return data + b'\x00' * ((4 - len(data)) % 4) 42 | 43 | def get_body_int16(self, data): 44 | data_length, _ = data.shape 45 | key = data_length 46 | if self.img_data_map.get(key) is None: 47 | self.img_data_map[key] = np.zeros((2, data_length, 3), dtype='uint8') 48 | img_data = self.img_data_map[key] 49 | img_data[0, :, :2] = data % 256 50 | img_data[1, :, :2] = (data // 256) % 256 51 | data = img_data[:, :, ::-1].tobytes() 52 | return data + b'\x00' * ((4 - len(data)) % 4) 53 | 54 | def old_convert(self, data): 55 | if data is None: 56 | return '' 57 | data_length, _ = data.shape 58 | key = data_length 59 | if self.img_data_map.get(key) is None: 60 | self.img_data_map[key] = np.zeros((1, data_length, 3), dtype='uint8') 61 | img_data = self.img_data_map[key] 62 | img_data[0, :, :2] = data 63 | 64 | image = Image.fromarray(img_data) 65 | data = io.BytesIO() 66 | image.save(data, "bmp") 67 | message = 'img/bmp;base64,' + base64.b64encode(data.getvalue()).decode() 68 | return message 69 | 70 | 71 | if __name__ == '__main__': 72 | n2s = Numpy2Str() 73 | 74 | data = np.zeros((200, 2), dtype='int16') 75 | data[:, 0] = np.arange(200) 76 | data[:, 1] = np.arange(200)[::-1] 77 | print('image1:') 78 | print(n2s.convert(data)) 79 | 80 | data = np.zeros((200, 2), dtype='int16') 81 | data[:, 0] = np.arange(200) + np.arange(201 * 256, 1 * 256, -256) 82 | data[:, 1] = np.arange(200)[::-1] 83 | print('image2:') 84 | print(n2s.convert_int16(data)) 85 | -------------------------------------------------------------------------------- /panon/backend/decay.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | class Decay: 5 | def __init__(self): 6 | self.global_decay = 0.01 7 | 8 | self.min_sample = 10 9 | self.max_sample = self.min_sample 10 | 11 | self.local_max = None 12 | 13 | def process(self, fft): 14 | exp = 2 15 | retain = (1 - self.global_decay)**exp 16 | global_decay = 1 - retain 17 | global_max = self.max_sample**(1 / exp) 18 | 19 | if fft is None: 20 | return None 21 | 22 | vol = self.min_sample + np.mean(fft**exp) 23 | self.max_sample = self.max_sample * retain + vol * global_decay 24 | 25 | return fft / global_max 26 | -------------------------------------------------------------------------------- /panon/backend/get_devices.py: -------------------------------------------------------------------------------- 1 | import pyaudio 2 | import json 3 | p = pyaudio.PyAudio() 4 | l = [] 5 | for i in range(p.get_device_count()): 6 | obj=p.get_device_info_by_index(i) 7 | if obj['maxInputChannels']<1: 8 | # Remove devices with no input channel 9 | continue 10 | l.append(obj) 11 | s = json.dumps(l) 12 | print(s) 13 | -------------------------------------------------------------------------------- /panon/backend/get_pa_devices.py: -------------------------------------------------------------------------------- 1 | from soundcard import pulseaudio as sc 2 | import json 3 | 4 | l = [] 5 | for mic in sc.all_microphones(exclude_monitors=False): 6 | l.append({'id': mic.id, 'name': mic.name}) 7 | s = json.dumps(l) 8 | print(s) 9 | -------------------------------------------------------------------------------- /panon/backend/server.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import time 3 | import base64 4 | import io 5 | import numpy as np 6 | import json 7 | import websockets 8 | from PIL import Image 9 | from . import spectrum 10 | from .decay import Decay 11 | from .source import Source as Source 12 | 13 | import sys 14 | server_port, device_index = sys.argv[1:] 15 | server_port = int(server_port) 16 | device_index = int(device_index) 17 | if device_index < 0: 18 | device_index = None 19 | 20 | spectrum_map = {} 21 | sample_rate = 44100 22 | spectrum_source = Source(spectrum.NUM_CHANNEL, sample_rate, device_index) 23 | decay = Decay() 24 | 25 | spec = spectrum.Spectrum(spectrum_source, ) 26 | 27 | 28 | async def hello(websocket, path): 29 | 30 | config = await websocket.recv() 31 | config = json.loads(config) 32 | print('config', config) 33 | 34 | old_timestamp = time.time() 35 | img_data = None 36 | 37 | while True: 38 | fps = config['fps'] 39 | expect_buffer_size = sample_rate // fps 40 | hist = spec.updateHistory(expect_buffer_size) 41 | data = spec.getData(hist, bassResolutionLevel=2, **config) 42 | 43 | if data is None: 44 | data = '' 45 | else: 46 | data, local_max = decay.process(data) 47 | data = data / 3.0 48 | data = np.clip(data, 0, 0.99) 49 | 50 | #转换到datauri,这样可以直接作为texture被opengl处理 51 | #比起http直接传输png来说,这个应该相对还是有些优势,至少少了重复的http握手 52 | if img_data is None: 53 | img_data = np.zeros((8, data.shape[1], 3), dtype='uint8') 54 | img_data[:, :, 0] = data[0] * 256 55 | img_data[:, :, 1] = data[1] * 256 56 | 57 | #头部一些奇怪的数据去掉 58 | image = Image.fromarray(img_data[:, 1:, :]) 59 | #converts PIL image to datauri 60 | data = io.BytesIO() 61 | image.save(data, "png") 62 | data = 'data:img/png;base64,' + base64.b64encode(data.getvalue()).decode() 63 | 64 | await websocket.send(data) 65 | 66 | new_timestamp = time.time() 67 | time_sleep = max(0, 1 / fps - (new_timestamp - old_timestamp)) 68 | old_timestamp = new_timestamp 69 | try: 70 | config = await asyncio.wait_for(websocket.recv(), timeout=time_sleep) 71 | config = json.loads(config) 72 | print('new config', config) 73 | except asyncio.TimeoutError: 74 | pass 75 | 76 | 77 | start_server = websockets.serve(hello, "localhost", server_port) 78 | 79 | asyncio.get_event_loop().run_until_complete(start_server) 80 | asyncio.get_event_loop().run_forever() 81 | -------------------------------------------------------------------------------- /panon/backend/source.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from .. import logger 3 | try: 4 | import soundcard as sc 5 | p = sc.pulseaudio._PulseAudio() 6 | try: 7 | sc.set_name('Panon') 8 | except (AttributeError, NotImplementedError): 9 | pass 10 | except: 11 | pass 12 | 13 | 14 | def binary2numpy(data, num_channel): 15 | data = np.frombuffer(data, 'int16') 16 | len_data = len(data) // num_channel 17 | data = data.reshape((len_data, num_channel)) 18 | return data 19 | 20 | 21 | class PyaudioSource: 22 | def __init__(self, channel_count, sample_rate, device_index, fps): 23 | self.channel_count = channel_count 24 | self.sample_rate = sample_rate 25 | self.chunk = self.sample_rate // fps 26 | if device_index is not None: 27 | device_index = int(device_index) 28 | self.device_index = device_index 29 | 30 | self.start() 31 | 32 | def read(self, fps=None): 33 | # Ignores PyAudio exception on overflow. 34 | if fps is None: 35 | c = self.chunk 36 | else: 37 | c = self.sample_rate // fps 38 | result = self.stream.read(c, exception_on_overflow=False) 39 | return binary2numpy(result, self.channel_count) 40 | 41 | def start(self): 42 | import pyaudio 43 | p = pyaudio.PyAudio() 44 | self.stream = p.open( 45 | format=pyaudio.paInt16, 46 | channels=self.channel_count, 47 | rate=self.sample_rate, 48 | input=True, 49 | frames_per_buffer=self.chunk, 50 | input_device_index=self.device_index, 51 | ) 52 | 53 | 54 | class FifoSource: 55 | def __init__(self, channel_count, sample_rate, fifo_path, fps): 56 | self.channel_count = channel_count 57 | self.sample_rate = sample_rate 58 | self.fifo_path = fifo_path 59 | self.blocksize = sample_rate // fps * channel_count * 2 #int16 44100:16:2 60 | self.start() 61 | 62 | def read(self, fps=None): 63 | if fps is None: 64 | b = self.blocksize 65 | else: 66 | b = self.sample_rate // fps * self.channel_count * 2 #int16 44100:16:2 67 | data = self.stream.read(b) 68 | if data is None: 69 | return None 70 | return binary2numpy(data, self.channel_count) 71 | 72 | def start(self): 73 | import os 74 | self.stream = open(self.fifo_path, 'rb') 75 | 76 | 77 | class SounddeviceSource: 78 | def __init__(self, channel_count, sample_rate, device_index): 79 | self.channel_count = channel_count 80 | self.sample_rate = sample_rate 81 | if device_index is not None: 82 | device_index = int(device_index) 83 | self.device_index = device_index 84 | 85 | self.start() 86 | 87 | def readlatest(self, expect_size, max_size=1000000): 88 | size = self.stream.read_available 89 | data, _ = self.stream.read(size) 90 | return data 91 | 92 | def stop(self): 93 | self.stream.close() 94 | 95 | def start(self): 96 | import sounddevice as sd 97 | self.stream = sd.InputStream( 98 | latency='low', 99 | samplerate=self.sample_rate, 100 | device=self.device_index, 101 | channels=self.channel_count, 102 | dtype='int16', 103 | ) 104 | self.stream.start() 105 | 106 | 107 | class SoundCardSource: 108 | def __init__(self, channel_count, sample_rate, device_id, fps): 109 | self.channel_count = channel_count 110 | self.sample_rate = sample_rate 111 | self.device_id = device_id 112 | self.blocksize = self.sample_rate // fps 113 | self.start() 114 | 115 | def read(self, fps=None): 116 | if fps is None: 117 | b = self.blocksize 118 | else: 119 | b = self.sample_rate // fps 120 | data = [stream.record(b) for stream in self.streams] 121 | data = sum(data) / len(data) 122 | return data 123 | 124 | def update_smart_device(self, ): 125 | name = p.server_info['default sink id'] 126 | 127 | if name is not None: 128 | if not self.smart_device_id == name: 129 | logger.log('update smart device: %s', name) 130 | self.smart_device_id = name 131 | for stream in self.streams: 132 | stream.__exit__(None, None, None) 133 | 134 | mic = sc.get_microphone( 135 | self.smart_device_id + '.monitor', 136 | include_loopback=False, 137 | exclude_monitors=False, 138 | ) 139 | stream = mic.recorder( 140 | self.sample_rate, 141 | self.channel_count, 142 | self.blocksize, 143 | ) 144 | stream.__enter__() 145 | self.streams = [stream] 146 | 147 | def start(self): 148 | if self.device_id == 'all': 149 | mics = sc.all_microphones(exclude_monitors=False) 150 | elif self.device_id == 'allspeakers': 151 | mics = [mic for mic in sc.all_microphones(exclude_monitors=False) if mic.id.endswith('.monitor')] 152 | elif self.device_id == 'allmicrophones': 153 | mics = [mic for mic in sc.all_microphones(exclude_monitors=True)] 154 | elif self.device_id == 'default': 155 | mics = [sc.default_microphone()] 156 | elif self.device_id == 'smart': 157 | self.smart_device_id = '' 158 | mics = [sc.default_microphone()] 159 | else: 160 | mics = [sc.get_microphone( 161 | self.device_id, 162 | include_loopback=False, 163 | exclude_monitors=False, 164 | )] 165 | self.streams = [] 166 | for mic in mics: 167 | stream = mic.recorder( 168 | self.sample_rate, 169 | self.channel_count, 170 | self.blocksize, 171 | ) 172 | stream.__enter__() 173 | self.streams.append(stream) 174 | 175 | 176 | if __name__ == '__main__': 177 | sample = PyaudioSource(2, 44100, None, 60) 178 | print('Make sure you are playing music when run this script') 179 | 180 | data = sample.read() 181 | 182 | _max = np.max(data) 183 | _min = np.min(data) 184 | _sum = np.sum(data) 185 | print(_max, _min, _sum) 186 | 187 | if _max > 0: 188 | print('succeeded to catch audio') 189 | else: 190 | print('failed to catch audio') 191 | -------------------------------------------------------------------------------- /panon/backend/spectrum.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | NUM_CHANNEL = 2 4 | HISTORY_LENGTH = 32 * 8 5 | 6 | 7 | class Spectrum: 8 | def __init__( 9 | self, 10 | fft_size=44100 // 60, 11 | ): 12 | self.fft_size = fft_size 13 | 14 | self.history = np.zeros((self.fft_size * HISTORY_LENGTH, NUM_CHANNEL), dtype='int16') 15 | self.history_index = 0 16 | 17 | def get_max_wave_size(self): 18 | len_history, num_channel = self.history.shape 19 | return len_history * num_channel 20 | 21 | def updateHistory(self, data): 22 | len_history, num_channel = self.history.shape 23 | 24 | if data is not None and data.shape[0] > 0: 25 | len_data, _ = data.shape 26 | 27 | index = self.history_index 28 | #assert len_data < len_history 29 | 30 | if index + len_data > len_history: 31 | self.history[index:] = data[:len_history - index] 32 | self.history[:index + len_data - len_history] = data[len_history - index:] 33 | self.history_index -= len_history 34 | else: 35 | self.history[index:index + len_data] = data 36 | self.history_index += len_data 37 | 38 | data_history = np.concatenate([ 39 | self.history[self.history_index:], 40 | self.history[:self.history_index], 41 | ], axis=0) 42 | 43 | return data_history 44 | 45 | def fun( 46 | self, 47 | data_history, 48 | freq_from, 49 | freq_to, 50 | latency, 51 | reduceBass=False, 52 | weight_from=None, 53 | weight_to=None, 54 | ): 55 | size = self.fft_size 56 | freq_from = int(freq_from * latency) 57 | freq_to = int(freq_to * latency) 58 | size = int(size * latency) 59 | 60 | fft = np.absolute(np.fft.rfft(data_history[-size:], axis=0)) 61 | result = fft[freq_from:freq_to] 62 | if reduceBass and weight_from: 63 | size_output, _ = result.shape 64 | result = result * np.arange(weight_from, weight_to, (weight_to - weight_from) / size_output)[:size_output, np.newaxis] 65 | debug = False 66 | if debug: 67 | #add splitters 68 | result = np.concatenate([result, np.zeros((8, 2))], axis=0) 69 | return result 70 | else: 71 | return result 72 | 73 | def computeSpectrum( 74 | self, 75 | data_history, 76 | bassResolutionLevel, 77 | reduceBass=False, 78 | ): 79 | if np.max(data_history) == 0: 80 | return None 81 | 82 | if bassResolutionLevel == 0: 83 | fft = np.absolute(np.fft.rfft(data_history[-self.fft_size:], axis=0)) # 0-22050Hz 84 | return fft 85 | elif bassResolutionLevel == 1: 86 | # higher resolution and latency for lower frequency 87 | fft_freq = [ 88 | #self.fun(data_history, 250, 4000, 0.25), # 25px 89 | #self.fun(data_history, 200, 250, 0.5), # 25px 90 | #self.fun(data_history, 150, 200, 1), # 50px 91 | self.fun(data_history, 110, 150, 2), # 6600-9000Hz 80px 92 | self.fun(data_history, 80, 110, 3), # 4800-6600Hz 90px 93 | self.fun(data_history, 50, 80, 4), # 3000-4800Hz 120px 94 | self.fun(data_history, 30, 50, 5, reduceBass, 1 / 1.2, 1), # 1800-3000Hz 100px 95 | self.fun(data_history, 10, 30, 6, reduceBass, 1 / 1.5, 1 / 1.2), # 600-1800Hz 120px 96 | self.fun(data_history, 0, 10, 8, reduceBass, 1 / 3, 1 / 1.5), # 0-600Hz 80px 97 | ] 98 | elif bassResolutionLevel == 2: 99 | fft_freq = [ 100 | self.fun(data_history, 30, 50, 4, reduceBass, 1 / 1.2, 1), # 1800-3000Hz 80px 101 | self.fun(data_history, 10, 30, 8, reduceBass, 1 / 1.5, 1 / 1.2), # 600-1800Hz 160px 102 | self.fun(data_history, 0, 10, 12, reduceBass, 1 / 3, 1 / 1.5), # 0-600Hz 120px 103 | ] 104 | elif bassResolutionLevel == 3: 105 | fft_freq = [ 106 | self.fun(data_history, 10, 30, 12, reduceBass, 1 / 1.5, 1 / 1.2), # 600-1800Hz 120px 107 | self.fun(data_history, 0, 10, 16, reduceBass, 1 / 3, 1 / 1.5), # 0-600Hz 80px 108 | ] 109 | elif bassResolutionLevel == 4: 110 | fft_freq = [ 111 | self.fun(data_history, 0, 30, 6, reduceBass, 1 / 3, 1 / 1.2), # 0-1800Hz 112 | ] 113 | elif bassResolutionLevel == 5: 114 | fft_freq = [ 115 | self.fun(data_history, 5, 30, 6, reduceBass, 1 / 3, 1 / 1.2), # 300-1800Hz 116 | ] 117 | elif bassResolutionLevel == 6: 118 | fft_freq = [ 119 | self.fun(data_history, 0, 10, 16, reduceBass, 1 / 3, 1 / 1.5), # 0-600Hz 80px 120 | ] 121 | 122 | fft_freq.reverse() 123 | fft = np.concatenate(fft_freq, axis=0) 124 | 125 | return fft 126 | -------------------------------------------------------------------------------- /panon/effect/build_shader_source.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import json 3 | from pathlib import Path 4 | 5 | from docopt import docopt 6 | 7 | from .helper import effect_dirs, read_file, read_file_lines 8 | from . import get_effect_list 9 | 10 | import json, base64 11 | 12 | from .. import logger 13 | 14 | import sys 15 | logger.log('argv: %s', sys.argv[1:]) 16 | 17 | effect_id, effect_arguments = json.loads(base64.b64decode(sys.argv[1])) 18 | logger.log('effect_id: %s', effect_id) 19 | logger.log('effect_arguments: %s', effect_arguments) 20 | 21 | effect_list = get_effect_list.get_list() 22 | effect = None 23 | 24 | # Set a default effect. 25 | for e in effect_list: 26 | if e.name == 'default': 27 | effect = e 28 | 29 | for e in effect_list: 30 | if e.id == effect_id: 31 | effect = e 32 | 33 | 34 | def hex2vec4(value): 35 | assert value.startswith('#') 36 | value = value[1:] 37 | nums = [int(value[i:i + 2], base=16) / 256 for i in range(0, len(value), 2)] 38 | if len(nums) < 4: 39 | nums.insert(0, 1) 40 | assert len(nums) == 4 41 | return "vec4(%f,%f,%f,%f)" % tuple(nums[1:] + nums[:1]) 42 | 43 | 44 | def format_value(type_, value): 45 | if type_ == 'int': 46 | return f'int({value})' 47 | if type_ == 'double': 48 | return f'float({value})' 49 | if type_ == 'float': 50 | return f'float({value})' 51 | if type_ == 'bool': 52 | return f'bool({value})' 53 | if type_ == 'color': 54 | return hex2vec4(value) 55 | return value 56 | 57 | 58 | def build_source(files, main_file: Path, meta_file: Path = None, effect_arguments=None): 59 | if not main_file.exists(): 60 | return '' 61 | arguments_map = {} 62 | # If meta_file exists, construct a key-value map to store arguments' names and values. 63 | if meta_file is not None and meta_file.exists(): 64 | meta = json.loads(meta_file.read_bytes()) 65 | arguments_map = {arg['name']: format_value(arg['type'], value) for arg, value in zip(meta['arguments'], effect_arguments)} 66 | 67 | # Extract glsl version 68 | version = next(read_file_lines(main_file)) 69 | source = version 70 | for path in files: 71 | if path == main_file: 72 | for line in list(read_file_lines(path))[1:]: 73 | lst = line.split() 74 | if len(lst) >= 3: 75 | # Search for used arguments(start with $) in macro definitions. 76 | if lst[0] == '#define' and lst[2].startswith('$'): 77 | key = lst[2][1:] 78 | # Raise an error when the value of an argument is not found. 79 | if key not in arguments_map: 80 | json.dump({"error_code": 1}, sys.stdout) 81 | sys.exit() 82 | lst[2] = arguments_map[key] 83 | line = ' '.join(lst) + '\n' 84 | source += line 85 | else: 86 | source += read_file(path) 87 | return source 88 | 89 | 90 | def texture_uri(path: Path): 91 | if path.exists(): 92 | return str(path.absolute()) 93 | return '' 94 | 95 | 96 | applet_effect_home = effect_dirs[-1] 97 | 98 | image_shader_path = Path(effect.path) 99 | if not effect.name.endswith('.frag'): 100 | image_shader_path /= 'image.frag' 101 | 102 | image_shader_files = [ 103 | applet_effect_home / 'hsluv-glsl.fsh', 104 | applet_effect_home / 'utils.fsh', 105 | applet_effect_home / 'shadertoy-api-head.fsh', 106 | image_shader_path, 107 | applet_effect_home / 'shadertoy-api-foot.fsh', 108 | ] 109 | 110 | if effect.name.endswith('.frag'): 111 | obj = {'image_shader': build_source(image_shader_files, image_shader_path)} 112 | else: 113 | obj = { 114 | 'image_shader': 115 | build_source( 116 | image_shader_files, 117 | image_shader_path, 118 | Path(effect.path) / 'meta.json', 119 | effect_arguments, 120 | ), 121 | 'buffer_shader': 122 | build_source( 123 | [ 124 | applet_effect_home / 'shadertoy-api-head.fsh', 125 | Path(effect.path) / 'buffer.frag', 126 | applet_effect_home / 'shadertoy-api-foot-buffer.fsh', 127 | ], 128 | Path(effect.path) / 'buffer.frag', 129 | Path(effect.path) / 'meta.json', 130 | effect_arguments, 131 | ), 132 | 'texture': 133 | texture_uri(Path(effect.path) / 'texture.png'), 134 | } 135 | 136 | obj['wave_buffer'] = read_file(applet_effect_home / 'wave-buffer.fsh') 137 | obj['gldft'] = read_file(applet_effect_home / 'gldft.fsh') 138 | obj['enable_iChannel0'] = 'iChannel0' in (read_file(image_shader_path) + (read_file(Path(effect.path) / 'buffer.frag') if (Path(effect.path) / 'buffer.frag').exists() else "")) 139 | obj['enable_iChannel1'] = 'iChannel1' in (read_file(image_shader_path) + (read_file(Path(effect.path) / 'buffer.frag') if (Path(effect.path) / 'buffer.frag').exists() else "")) 140 | 141 | json.dump(obj, sys.stdout) 142 | logger.log('obj : %s',json.dumps(obj)) 143 | -------------------------------------------------------------------------------- /panon/effect/get_effect_list.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module defines the data structure of an effect, 3 | and the way to generate unique effect identities. 4 | The data structure is used by ConfigEffect.qml and 5 | build_shader_source.py. 6 | """ 7 | import sys 8 | from pathlib import Path 9 | from .helper import effect_dirs 10 | import collections 11 | 12 | # The data structure of an effect 13 | Effect = collections.namedtuple('Effect', 'name id path') 14 | 15 | 16 | def _get_shaders(root: Path, root_id): 17 | if not root.is_dir(): 18 | return 19 | for _file in root.iterdir(): 20 | if _file.suffix == '.frag' or any(_file.glob('*.frag')): 21 | yield Effect( 22 | _file.name, 23 | # generate unique effect identities 24 | str(root_id) + '.' + _file.name.replace(' ', '_').replace('"', '__').replace("'", '___').replace("$", '____'), 25 | str(_file.absolute()), 26 | ) 27 | 28 | 29 | def get_list(): 30 | """ 31 | Returns an array of all available visual effects. 32 | """ 33 | return sorted([effect for effect_dir_id, effect_dir in enumerate(effect_dirs) for effect in _get_shaders(effect_dir, effect_dir_id)]) 34 | 35 | 36 | if __name__ == '__main__': 37 | import json 38 | json.dump([effect._asdict() for effect in get_list()], sys.stdout) 39 | -------------------------------------------------------------------------------- /panon/effect/helper.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | from pathlib import Path 4 | 5 | _data_home = os.environ.get('XDG_DATA_HOME', None) or Path.home() / '.local' / 'share' 6 | 7 | effect_dirs = [ 8 | Path(_data_home) / 'panon', 9 | Path.home() / '.config' / 'panon', # legacy 10 | Path(os.curdir).absolute().parent / 'shaders' 11 | ] 12 | 13 | 14 | def read_file(path: Path): 15 | return path.open('rb').read().decode(errors='ignore') 16 | 17 | 18 | def read_file_lines(path: Path): 19 | for line in path.open('rb'): 20 | yield line.decode(errors='ignore') 21 | -------------------------------------------------------------------------------- /panon/effect/read_file.py: -------------------------------------------------------------------------------- 1 | """ 2 | Read a file of a specified visual effect. 3 | 4 | Usage: 5 | main [options] 6 | main -h | --help 7 | 8 | Options: 9 | -h --help Show this screen. 10 | --debug Debug 11 | """ 12 | from pathlib import Path 13 | from docopt import docopt 14 | from .helper import effect_dirs, read_file 15 | from . import get_effect_list 16 | 17 | arguments = docopt(__doc__) 18 | effect_id = arguments[''] 19 | 20 | for effect in get_effect_list.get_list(): 21 | if effect.id == effect_id: 22 | print(read_file(Path(effect.path) / arguments[''])) 23 | break 24 | -------------------------------------------------------------------------------- /panon/logger.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | _logger=None 4 | try: 5 | from systemd.journal import JournalHandler 6 | 7 | _logger = logging.getLogger('demo') 8 | _logger.addHandler(JournalHandler()) 9 | _logger.setLevel(logging.DEBUG) 10 | except: 11 | pass 12 | 13 | def log(*a): 14 | if _logger is not None: 15 | _logger.debug(*a) 16 | -------------------------------------------------------------------------------- /plasmoid/contents/config/config.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import org.kde.plasma.configuration 2.0 3 | 4 | ConfigModel { 5 | ConfigCategory { 6 | name: i18nc("@title","General") 7 | icon: "applications-multimedia" 8 | source: "config/ConfigGeneral.qml" 9 | } 10 | ConfigCategory { 11 | name: i18nc("@title", "Visual Effects") 12 | icon: "applications-graphics" 13 | source: "config/ConfigEffect.qml" 14 | } 15 | ConfigCategory { 16 | name: i18nc("@title","Back-end") 17 | icon: 'preferences-desktop-sound' 18 | source: 'config/ConfigBackend.qml' 19 | } 20 | ConfigCategory { 21 | name: i18nc("@title","Colors") 22 | icon: 'preferences-desktop-color' 23 | source: 'config/ConfigColors.qml' 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /plasmoid/contents/config/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | false 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | true 29 | 30 | 31 | false 32 | 33 | 34 | 4 35 | 36 | 37 | -1 38 | 39 | 40 | default 41 | 42 | 47 | 48 | 0 49 | 50 | 51 | /tmp/mpd.fifo 52 | 53 | 54 | 55 | false 56 | 57 | 58 | 59 | 60 | 61 | 62 | 30 63 | 64 | 65 | false 66 | 67 | 68 | false 69 | 70 | 71 | true 72 | 73 | 74 | true 75 | 76 | 77 | 700 78 | 79 | 80 | 1 81 | 82 | 83 | false 84 | 85 | 86 | 87 | true 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | true 96 | 97 | 98 | false 99 | 100 | 101 | 102 | 180 103 | 104 | 105 | 720 106 | 107 | 108 | 270 109 | 110 | 111 | -270 112 | 113 | 114 | 60 115 | 116 | 117 | 100 118 | 119 | 120 | 100 121 | 122 | 123 | 50 124 | 125 | 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /plasmoid/contents/config/panon.knsrc: -------------------------------------------------------------------------------- 1 | [KNewStuff3] 2 | Name=Panon 3 | Categories=Panon Shaders 4 | XdgTargetDir=panon 5 | Uncompress=archive 6 | -------------------------------------------------------------------------------- /plasmoid/contents/scripts/panon: -------------------------------------------------------------------------------- 1 | ../../../panon/ -------------------------------------------------------------------------------- /plasmoid/contents/scripts/soundcard/LICENSE: -------------------------------------------------------------------------------- 1 | ../../../../third_party/SoundCard/LICENSE -------------------------------------------------------------------------------- /plasmoid/contents/scripts/soundcard/__init__.py: -------------------------------------------------------------------------------- 1 | ../../../../third_party/SoundCard/soundcard/__init__.py -------------------------------------------------------------------------------- /plasmoid/contents/scripts/soundcard/pulseaudio.py: -------------------------------------------------------------------------------- 1 | ../../../../third_party/SoundCard/soundcard/pulseaudio.py -------------------------------------------------------------------------------- /plasmoid/contents/scripts/soundcard/pulseaudio.py.h: -------------------------------------------------------------------------------- 1 | ../../../../third_party/SoundCard/soundcard/pulseaudio.py.h -------------------------------------------------------------------------------- /plasmoid/contents/shaders/bar1ch/buffer.frag: -------------------------------------------------------------------------------- 1 | #version 130 2 | 3 | #define decay $decay 4 | 5 | void mainImage( out vec4 fragColor, in vec2 fragCoord ) { 6 | float x=fragCoord.x/iResolution.x; 7 | vec4 newdata= texture(iChannel1, vec2(x,0)); 8 | vec4 olddata= texelFetch(iChannel2,ivec2(fragCoord.x,0) , 0); 9 | fragColor=max(newdata,olddata-decay); 10 | } 11 | -------------------------------------------------------------------------------- /plasmoid/contents/shaders/bar1ch/image.frag: -------------------------------------------------------------------------------- 1 | #version 130 2 | 3 | #define pixel_fill $bar_width 4 | #define pixel_empty $gap_width 5 | 6 | void mainImage( out vec4 fragColor, in vec2 fragCoord ) { 7 | int pixel_x= int( fragCoord.x); 8 | int pixel_y= int( fragCoord.y); 9 | 10 | float h=fragCoord.y/iResolution.y; 11 | 12 | 13 | fragColor=vec4(0,0,0,0); 14 | if(pixel_x%(pixel_fill+pixel_empty)h ) 15 | fragColor+=vec4(rgb*1.,1.); 16 | } 17 | fragColor=fragColor/10.0; 18 | } 19 | -------------------------------------------------------------------------------- /plasmoid/contents/shaders/chain/image.frag: -------------------------------------------------------------------------------- 1 | #version 130 2 | 3 | #define particle_opacity $particle_opacity 4 | #define height_ratio $height_ratio 5 | #define strength $strength 6 | #define unit_radius $unit_radius 7 | #define density $density 8 | 9 | 10 | 11 | float rand(vec2 co) { 12 | return fract(sin(dot(co.xy,vec2(12.9898,78.233))) * 43758.5453); 13 | } 14 | 15 | void mainImage( out vec4 fragColor, in vec2 fragCoord ) { 16 | 17 | float h=fragCoord.y/iResolution.y; 18 | bool gr=(h>.5); 19 | h=abs(2*h-1); 20 | h+=0.01; 21 | 22 | fragColor=vec4(0,0,0,0); 23 | for(int j=0; j 2 | range is 0 to 9,000Hz. 3 | -------------------------------------------------------------------------------- /plasmoid/contents/shaders/default/image.frag: -------------------------------------------------------------------------------- 1 | #version 130 2 | 3 | void mainImage( out vec4 fragColor, in vec2 fragCoord ) { 4 | fragCoord=fragCoord/iResolution.xy; 5 | vec4 sample1= texture(iChannel1, vec2(fragCoord.x,0)) ; 6 | float h=fragCoord.y; 7 | vec3 rgb=getRGB(fragCoord.x); 8 | 9 | float[] rels=float[5](4.,3.,2.,1.,.5); 10 | float[] alphas=float[5](.1,.2,.3,.5,1.); 11 | //float[] rels=float[1](1.0); 12 | //float[] alphas=float[1](1.0); 13 | fragColor=vec4(0.001,0.001,0.001,0.001); 14 | for (int i=0; i<5; i++) { 15 | float r=rels[i]; 16 | float a=alphas[i]; 17 | float max_=.5+sample1.r*r; 18 | float min_=.5-sample1.g*r; 19 | if(min_<=h && h <=max_) 20 | fragColor=vec4(rgb*a,a); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /plasmoid/contents/shaders/dune1ch.frag: -------------------------------------------------------------------------------- 1 | #version 130 2 | 3 | float height(float distanc,float raw_height) { 4 | return raw_height*exp(-distanc*distanc*4); 5 | } 6 | 7 | float rand(vec2 co) { 8 | return fract(sin(dot(co.xy,vec2(12.9898,78.233))) * 43758.5453); 9 | } 10 | 11 | void mainImage( out vec4 fragColor, in vec2 fragCoord ) { 12 | fragCoord=fragCoord/iResolution.xy; 13 | float px_step=0.0000315; 14 | int max_distance=1280; 15 | 16 | float h=fragCoord.y; 17 | 18 | fragColor=vec4(0,0,0,0); 19 | for(int j=0; j<60; j++) { 20 | int i=int((2*max_distance+1)*rand(fragCoord+vec2(iTime/60/60/24/10,j)))-max_distance; 21 | float distanc=abs(i)/1.0/max_distance; 22 | float x=int(fragCoord.x/px_step+i)*px_step; 23 | 24 | vec4 raw=texture(iChannel1, vec2(x,0)); 25 | float raw_max=(raw.g+raw.r)/2.; 26 | float h_target=height(distanc,raw_max); 27 | if(h_target-.02<=h && h<=h_target) { 28 | fragColor=vec4(getRGB(5*x),1.); 29 | break; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /plasmoid/contents/shaders/gldft.fsh: -------------------------------------------------------------------------------- 1 | #version 130 2 | 3 | out vec4 out_Color; 4 | in mediump vec2 qt_TexCoord0; 5 | 6 | uniform sampler2D waveBuffer; 7 | 8 | uniform int dftSize; 9 | uniform int bufferSize; 10 | 11 | #define PI 3.14159265359 12 | 13 | struct Data{float r;float g;}; 14 | 15 | Data fun(float k){ 16 | 17 | int N=bufferSize; 18 | float vrc=0.0,vrs=0.0,vgc=0.0,vgs=0.0; 19 | for(int m=0;mmax_nb) { 26 | max_nb=raw_max; 27 | fragColor=vec4(getRGB(5*x),1.); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /plasmoid/contents/shaders/hsluv-glsl.fsh: -------------------------------------------------------------------------------- 1 | ../../../third_party/hsluv-glsl/hsluv-glsl.fsh -------------------------------------------------------------------------------- /plasmoid/contents/shaders/oie1ch/hint.html: -------------------------------------------------------------------------------- 1 | Inspired by OieIcons 2 | -------------------------------------------------------------------------------- /plasmoid/contents/shaders/oie1ch/image.frag: -------------------------------------------------------------------------------- 1 | #version 130 2 | 3 | /* 4 | * Inspired by OieIcons 5 | * https://store.kde.org/p/1299058/ 6 | */ 7 | 8 | void mainImage( out vec4 fragColor, in vec2 fragCoord ) { 9 | 10 | vec4 sample_prev= texture(iChannel1, vec2((fragCoord.x-1)/iResolution.x,0)) ; 11 | vec4 sample_ = texture(iChannel1, vec2(fragCoord.x /iResolution.x,0)) ; 12 | vec4 sample_next= texture(iChannel1, vec2((fragCoord.x+1)/iResolution.x,0)) ; 13 | 14 | int p1=int(1/2.*(sample_.r+sample_prev.r) * iResolution.y); 15 | int p2=int(1/2.*(sample_.r+sample_next.r) * iResolution.y); 16 | 17 | fragColor=vec4(0,0,0,0); 18 | 19 | bool draw=false; 20 | if(p1+2>=fragCoord.y&&fragCoord.y>=p2-0)draw=true; 21 | if(p1-0<=fragCoord.y&&fragCoord.y<=p2+2)draw=true; 22 | 23 | 24 | if(draw) { 25 | fragColor.rgb=getRGB(fragCoord.x/iResolution.x); 26 | fragColor.a=1; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /plasmoid/contents/shaders/shadertoy-api-foot-buffer.fsh: -------------------------------------------------------------------------------- 1 | // vim: set ft=glsl: 2 | out vec4 out_Color; 3 | in mediump vec2 qt_TexCoord0; 4 | 5 | vec2 getCoord() { 6 | return qt_TexCoord0; 7 | } 8 | 9 | void main() { 10 | mainImage( out_Color,floor(getCoord()*iResolution.xy) ); 11 | } 12 | -------------------------------------------------------------------------------- /plasmoid/contents/shaders/shadertoy-api-foot.fsh: -------------------------------------------------------------------------------- 1 | // vim: set ft=glsl: 2 | out vec4 out_Color; 3 | in mediump vec2 qt_TexCoord0; 4 | // gravity property: North (1), West (4), East (3), South (2) 5 | uniform int coord_gravity; 6 | uniform float qt_Opacity; 7 | uniform bool coord_inversion; 8 | 9 | vec2 getCoord() { 10 | switch(coord_gravity) { 11 | case 1: 12 | return vec2(coord_inversion?(1-qt_TexCoord0.x): qt_TexCoord0.x,1-qt_TexCoord0.y); 13 | case 2: 14 | return vec2(coord_inversion?(1-qt_TexCoord0.x):qt_TexCoord0.x,qt_TexCoord0.y); 15 | case 3: 16 | return vec2(coord_inversion?qt_TexCoord0.y:(1-qt_TexCoord0.y),1-qt_TexCoord0.x); 17 | case 4: 18 | return vec2(coord_inversion?qt_TexCoord0.y:(1-qt_TexCoord0.y),qt_TexCoord0.x); 19 | } 20 | } 21 | 22 | void main() { 23 | mainImage( out_Color,floor(getCoord()*iResolution.xy) ); 24 | } 25 | -------------------------------------------------------------------------------- /plasmoid/contents/shaders/shadertoy-api-head.fsh: -------------------------------------------------------------------------------- 1 | // vim: set ft=glsl: 2 | uniform vec3 iResolution; // viewport resolution (in pixels) 3 | uniform float iTime; // shader playback time (in seconds) 4 | uniform float iTimeDelta; // render time (in seconds) 5 | uniform float iBeat; // Is this frame a beat? (provided by aubio) 6 | uniform int iFrame; // shader playback frame 7 | //uniform float iChannelTime[4]; // channel playback time (in seconds) 8 | #define iChannelTime (float[4](iTime,iTime,iTime,iTime)) 9 | uniform vec3 iChannelResolution0; // channel resolution (in pixels) 10 | uniform vec3 iChannelResolution1; // channel resolution (in pixels) 11 | uniform vec3 iChannelResolution2; // channel resolution (in pixels) 12 | uniform vec3 iChannelResolution3; // channel resolution (in pixels) 13 | #define iChannelResolution (vec3[4](iChannelResolution0,iChannelResolution1,iChannelResolution2,iChannelResolution3)) 14 | uniform vec4 iMouse; // mouse pixel coords. xy: current (if MLB down), zw: click 15 | uniform sampler2D iChannel0; // input channel. XX = 2D/Cube 16 | uniform sampler2D iChannel1; // input channel. XX = 2D/Cube 17 | uniform sampler2D iChannel2; // input channel. XX = 2D/Cube 18 | uniform sampler2D iChannel3; // input channel. XX = 2D/Cube 19 | //uniform vec4 iDate; // (year, month, day, time in seconds) 20 | #define iDate vec4(0,0,0,0) 21 | //uniform float iSampleRate; // sound sample rate (i.e., 44100) 22 | #define iSampleRate 44100 23 | 24 | 25 | -------------------------------------------------------------------------------- /plasmoid/contents/shaders/solid.frag: -------------------------------------------------------------------------------- 1 | #version 130 2 | 3 | void mainImage( out vec4 fragColor, in vec2 fragCoord ) { 4 | fragCoord=fragCoord/iResolution.xy; 5 | // A list of available data channels 6 | // Spectrum data 7 | vec4 sample1= texture(iChannel1, vec2(fragCoord.x,0)) ; 8 | // Wave data channel 9 | // vec4 sample3= texture(iChannel0, vec2(fragCoord.x,0)) ; 10 | 11 | // Color defined by user configuration 12 | vec3 rgb=getRGB(fragCoord.x); 13 | 14 | // Background color 15 | fragColor=vec4(0.001,0.001,0.001,0.001); 16 | // Right channel 17 | float max_=.5+sample1.g*.5; 18 | // Left channel 19 | float min_=.5-sample1.r*.5; 20 | 21 | float h=fragCoord.y; 22 | if(min_<=h && h <=max_) 23 | fragColor=vec4(rgb,1.); 24 | } 25 | -------------------------------------------------------------------------------- /plasmoid/contents/shaders/solid1ch.frag: -------------------------------------------------------------------------------- 1 | #version 130 2 | 3 | void mainImage( out vec4 fragColor, in vec2 fragCoord ) { 4 | fragCoord=fragCoord/iResolution.xy; 5 | vec4 sample1= texture(iChannel1, vec2(fragCoord.x,0)) ; 6 | float h=fragCoord.y; 7 | vec3 rgb=getRGB(fragCoord.x); 8 | 9 | fragColor=vec4(0.001,0.001,0.001,0.001); 10 | float max_=sample1.g*.5+sample1.r*.5; 11 | if(max_>h ) 12 | fragColor=vec4(rgb*1.,1.); 13 | } 14 | -------------------------------------------------------------------------------- /plasmoid/contents/shaders/spectrogram/buffer.frag: -------------------------------------------------------------------------------- 1 | #version 130 2 | 3 | #define shrink_step $shrink_step 4 | 5 | void mainImage( out vec4 fragColor, in vec2 fragCoord ) { 6 | float x=fragCoord.x/iResolution.x; 7 | if(fragCoord.y<1){ 8 | fragColor= texture(iChannel1, vec2(x,0)); 9 | return; 10 | } 11 | int step_=min(shrink_step ,int(iResolution.x /1.2 / iResolution.y)); 12 | float current_width=(iResolution.x)-fragCoord.y*step_; 13 | float prev_width=(iResolution.x)-(fragCoord.y-1)*step_; 14 | 15 | x=(fragCoord.x- fragCoord.y*step_/2) / current_width; 16 | x=x * prev_width + (fragCoord.y-1)*step_/2; 17 | x=x/iResolution.x; 18 | if(x<0 || x>1){ 19 | fragColor=vec4(0,0,0,0); 20 | return; 21 | } 22 | 23 | fragColor= texture(iChannel2, vec2(x,(fragCoord.y-1)/iResolution.y)); 24 | } 25 | -------------------------------------------------------------------------------- /plasmoid/contents/shaders/spectrogram/image.frag: -------------------------------------------------------------------------------- 1 | #version 130 2 | 3 | #define opacity $opacity 4 | 5 | void mainImage( out vec4 fragColor, in vec2 fragCoord ) { 6 | fragColor= texelFetch(iChannel2,ivec2(fragCoord) , 0); 7 | fragColor.a=opacity+ max(fragColor.r,fragColor.g); 8 | } 9 | -------------------------------------------------------------------------------- /plasmoid/contents/shaders/spectrogram/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "arguments": [{ 3 | "name": "shrink_step", 4 | "default": 1, 5 | "type": "int", 6 | "min":0 7 | }, { 8 | "name": "opacity", 9 | "default": 1, 10 | "type": "double", 11 | "max":1, 12 | "min":0 13 | }] 14 | } 15 | -------------------------------------------------------------------------------- /plasmoid/contents/shaders/utils.fsh: -------------------------------------------------------------------------------- 1 | // vim: set ft=glsl: 2 | uniform bool colorSpaceHSL; 3 | uniform bool colorSpaceHSLuv; 4 | uniform int hueFrom; 5 | uniform int hueTo; 6 | uniform int saturation; 7 | uniform int lightness; 8 | 9 | 10 | 11 | vec3 hsv2rgb(vec3 c) { 12 | vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); 13 | vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); 14 | return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); 15 | } 16 | 17 | vec3 getRGB(float x) { 18 | if(colorSpaceHSL) { 19 | return hsv2rgb(vec3(x*(hueTo-hueFrom)/360.0+hueFrom/360.0,saturation/100.0,lightness/100.0)); 20 | } else if(colorSpaceHSLuv) { 21 | return hsluvToRgb(vec3(x*(hueTo-hueFrom)+hueFrom,saturation,lightness)); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /plasmoid/contents/shaders/wave-buffer.fsh: -------------------------------------------------------------------------------- 1 | #version 130 2 | 3 | out vec4 out_Color; 4 | in mediump vec2 qt_TexCoord0; 5 | 6 | uniform sampler2D newWave; 7 | uniform sampler2D waveBuffer; 8 | 9 | uniform int bufferSize; 10 | uniform int newWaveSize; 11 | 12 | void main() { 13 | float x=qt_TexCoord0.x*bufferSize; 14 | float y=qt_TexCoord0.y*2; 15 | 16 | if(y<2){ 17 | if(x>= bufferSize-newWaveSize ){ 18 | out_Color=texelFetch(newWave,ivec2(x-bufferSize+newWaveSize,y),0); 19 | }else{ 20 | out_Color=texelFetch(waveBuffer,ivec2(x+newWaveSize,y),0); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /plasmoid/contents/shaders/wave.frag: -------------------------------------------------------------------------------- 1 | #version 130 2 | 3 | void mainImage( out vec4 fragColor, in vec2 fragCoord ) { 4 | 5 | vec4 sample1= texture(iChannel0, vec2(0.5*fragCoord.x/iResolution.x,0)) ; 6 | 7 | fragColor=vec4(0,0,0,0); 8 | 9 | int max_=int(sample1.r*iResolution.y)+1; 10 | int min_=int(sample1.r*iResolution.y)-1; 11 | 12 | if(min_<=fragCoord.y) 13 | if(fragCoord.y <=max_) { 14 | fragColor.rgb=getRGB(fragCoord.x/iResolution.x); 15 | fragColor.a=1; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /plasmoid/contents/ui/MessageQueue.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | 3 | /* 4 | * The length of this queue is 2. The queue rejects new 5 | * messages before the images of old messages are loaded. 6 | */ 7 | 8 | Item{ 9 | // When only spectrum data is enabled, receive raw data to reduce cpu usage. 10 | property bool only_spectrum:false 11 | 12 | readonly property var cfg:plasmoid.configuration 13 | 14 | property variant imgsReady:pt0 15 | 16 | property variant imgsLoading:pt0 17 | 18 | function push(message){ 19 | if(message.length<1){ 20 | imgsReady=nt 21 | return 22 | } 23 | if(!imgsLoading.used) 24 | return 25 | 26 | if(cfg.glDFT){ 27 | imgsLoading.used=false 28 | imgsLoading.w.source = 'data:' + message 29 | }else{ 30 | if(only_spectrum){ 31 | imgsLoading.used=false 32 | imgsLoading.s.source = 'data:' + message 33 | }else{ 34 | var obj = JSON.parse(message) 35 | imgsLoading.used=false 36 | imgsLoading.s.source = 'data:' + obj.spectrum 37 | imgsLoading.w.source = 'data:' + obj.wave 38 | imgsLoading.beat = obj.beat 39 | } 40 | } 41 | 42 | if(imgsLoading.ready){ 43 | var p 44 | if(imgsLoading==pt0)p=pt1 45 | if(imgsLoading==pt1)p=pt0 46 | imgsReady=imgsLoading 47 | p.used=true 48 | imgsLoading=p 49 | } 50 | 51 | } 52 | 53 | PreloadingTextures{id:nt;audioAvailable:false} 54 | 55 | PreloadingTextures{id:pt0;onReadyChanged:{ 56 | if(ready){ 57 | imgsReady=pt0 58 | pt1.used=true 59 | imgsLoading=pt1 60 | } 61 | }} 62 | 63 | PreloadingTextures{id:pt1;onReadyChanged:{ 64 | if(ready){ 65 | imgsReady=pt1 66 | pt0.used=true 67 | imgsLoading=pt0 68 | } 69 | }} 70 | 71 | } 72 | -------------------------------------------------------------------------------- /plasmoid/contents/ui/PreloadingTextures.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | 3 | Item{ 4 | 5 | property variant w:Image{visible:false} 6 | property variant s:Image{visible:false} 7 | property double beat 8 | 9 | readonly property bool ready: { 10 | if(cfg.glDFT) 11 | return (w.status!=Image.Loading) 12 | else 13 | return (w.status!=Image.Loading) && (s.status!=Image.Loading) 14 | } 15 | property bool used:true 16 | property bool audioAvailable:true 17 | } 18 | -------------------------------------------------------------------------------- /plasmoid/contents/ui/ShaderSource.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import org.kde.plasma.plasmoid 2.0 3 | import org.kde.plasma.core 2.0 as PlasmaCore 4 | import "utils.js" as Utils 5 | 6 | /* 7 | * This module executes a python script to build 8 | * the shader sources. 9 | */ 10 | 11 | PlasmaCore.DataSource { 12 | 13 | readonly property var cfg:plasmoid.configuration 14 | engine: 'executable' 15 | 16 | property string image_shader_source:'' 17 | property string buffer_shader_source:'' 18 | property string wave_buffer_source:'' 19 | property string gldft_source:'' 20 | 21 | property bool enable_iChannel0:false 22 | property bool enable_iChannel1:false 23 | 24 | property string texture_uri:'' 25 | property string error_message:'' 26 | 27 | readonly property bool enable_buffer:buffer_shader_source.length>0 28 | 29 | readonly property string cmd:Utils.chdir_scripts_root() 30 | + 'python3 -m panon.effect.build_shader_source' 31 | + ' '+Qt.btoa(JSON.stringify([cfg.visualEffect,cfg.effectArgValues])) 32 | 33 | connectedSources: [cmd] 34 | 35 | onNewData:{ 36 | if(cfg.debugBackend){ 37 | console.log(cmd) 38 | console.log(data.stderr) 39 | } 40 | var obj; 41 | try{ 42 | obj=JSON.parse(data.stdout); 43 | }catch(e){ 44 | console.log("JSON parse error") 45 | console.log("Executed command:") 46 | console.log(cmd) 47 | console.log("JSON content:") 48 | console.log(data.stdout) 49 | console.log(data.stderr) 50 | return 51 | } 52 | if('error_code' in obj){ 53 | error_message={ 54 | 1:i18n("Error: Find undeclared arguments.") 55 | }[obj.error_code] 56 | return 57 | } 58 | enable_iChannel0=obj.enable_iChannel0 59 | enable_iChannel1=obj.enable_iChannel1 60 | image_shader_source=obj.image_shader 61 | buffer_shader_source=obj.buffer_shader 62 | wave_buffer_source=obj.wave_buffer 63 | gldft_source=obj.gldft 64 | texture_uri=obj.texture 65 | error_message='' 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /plasmoid/contents/ui/Spectrum.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Layouts 1.1 3 | 4 | import org.kde.plasma.plasmoid 2.0 5 | import org.kde.plasma.core 2.0 as PlasmaCore 6 | 7 | import QtQuick.Controls 2.0 as QQC2 8 | 9 | import "utils.js" as Utils 10 | 11 | Item{ 12 | id:root 13 | readonly property var cfg:plasmoid.configuration 14 | 15 | property bool vertical: (plasmoid.formFactor == PlasmaCore.Types.Vertical) 16 | 17 | // Layout.minimumWidth: cfg.autoHide ? animatedMinimum: -1 18 | Layout.preferredWidth: vertical ?-1: animatedMinimum 19 | Layout.preferredHeight: vertical ? animatedMinimum:-1 20 | Layout.maximumWidth:cfg.autoHide?Layout.preferredWidth:-1 21 | Layout.maximumHeight:cfg.autoHide?Layout.preferredHeight:-1 22 | 23 | // Gravity property: Center(0), North (1), West (4), East (3), South (2) 24 | readonly property int gravity:{ 25 | if(cfg.gravity>0) 26 | return cfg.gravity 27 | switch(plasmoid.location){ 28 | case PlasmaCore.Types.TopEdge: 29 | return 2 30 | case PlasmaCore.Types.BottomEdge: 31 | return 1 32 | case PlasmaCore.Types.RightEdge: 33 | return 3 34 | case PlasmaCore.Types.LeftEdge: 35 | return 4 36 | } 37 | return 1 38 | } 39 | 40 | property int animatedMinimum:(!cfg.autoHide) || audioAvailable? cfg.preferredWidth:0 41 | 42 | Layout.fillWidth: vertical? false:cfg.autoExtend 43 | Layout.fillHeight: vertical? cfg.autoExtend :false 44 | 45 | 46 | ShaderEffect { 47 | id:mainSE 48 | readonly property bool colorSpaceHSL: cfg.colorSpaceHSL 49 | readonly property bool colorSpaceHSLuv:cfg.colorSpaceHSLuv 50 | 51 | Behavior on hueFrom{ NumberAnimation { duration: 1000} } 52 | Behavior on hueTo{ NumberAnimation { duration: 1000} } 53 | Behavior on saturation{ NumberAnimation { duration: 1000} } 54 | Behavior on lightness{ NumberAnimation { duration: 1000} } 55 | 56 | property int hueFrom :{ 57 | if(cfg.colorSpaceHSL) 58 | return cfg.hslHueFrom 59 | else if(cfg.colorSpaceHSLuv) 60 | return cfg.hsluvHueFrom 61 | } 62 | property int hueTo :{ 63 | if(cfg.colorSpaceHSL) 64 | return cfg.hslHueTo 65 | else if(cfg.colorSpaceHSLuv) 66 | return cfg.hsluvHueTo 67 | } 68 | property int saturation :{ 69 | if(cfg.colorSpaceHSL) 70 | return cfg.hslSaturation 71 | else if(cfg.colorSpaceHSLuv) 72 | return cfg.hsluvSaturation 73 | } 74 | property int lightness :{ 75 | if(cfg.colorSpaceHSL) 76 | return cfg.hslLightness 77 | else if(cfg.colorSpaceHSLuv) 78 | return cfg.hsluvLightness 79 | } 80 | 81 | readonly property variant iMouse:iMouseArea.i 82 | 83 | property double iTime 84 | property double iTimeDelta 85 | property double iBeat 86 | property variant iResolution:root.gravity<=2?Qt.vector3d(mainSE.width,mainSE.height,0):Qt.vector3d(mainSE.height,mainSE.width,0) 87 | property int iFrame:0 88 | property vector3d iChannelResolution0:iChannel0?Qt.vector3d(iChannel0.width,iChannel0.height,0):Qt.vector3d(0,0,0) 89 | property vector3d iChannelResolution1:iChannel1?Qt.vector3d(iChannel1.width,iChannel1.height,0):Qt.vector3d(0,0,0) 90 | property vector3d iChannelResolution2:iChannel2?Qt.vector3d(iChannel2.width,iChannel2.height,0):Qt.vector3d(0,0,0) 91 | property vector3d iChannelResolution3:iChannel3?Qt.vector3d(iChannel3.width,iChannel3.height,0):Qt.vector3d(0,0,0) 92 | property variant iChannel0 93 | property variant iChannel1 94 | readonly property variant iChannel2:bufferSES 95 | readonly property variant iChannel3:Image{source:'file://'+shaderSourceReader.texture_uri} 96 | 97 | 98 | property int coord_gravity:root.gravity 99 | property bool coord_inversion:cfg.inversion 100 | 101 | anchors.fill: parent 102 | blending: true 103 | fragmentShader:shaderSourceReader.image_shader_source 104 | } 105 | 106 | ShaderEffectSource { 107 | visible:false 108 | id:bufferSES 109 | width: mainSE.iResolution.x 110 | height: mainSE.iResolution.y 111 | recursive :true 112 | live:false 113 | sourceItem: ShaderEffect { 114 | width: mainSE.iResolution.x 115 | height: mainSE.iResolution.y 116 | 117 | readonly property double iTime:mainSE.iTime 118 | readonly property double iTimeDelta:mainSE.iTimeDelta 119 | readonly property double iBeat:mainSE.iBeat 120 | readonly property variant iResolution:mainSE.iResolution 121 | readonly property int iFrame:mainSE.iFrame 122 | readonly property vector3d iChannelResolution0:mainSE.iChannelResolution0 123 | readonly property vector3d iChannelResolution1:mainSE.iChannelResolution1 124 | readonly property vector3d iChannelResolution2:mainSE.iChannelResolution2 125 | readonly property vector3d iChannelResolution3:mainSE.iChannelResolution3 126 | readonly property variant iChannel0:mainSE.iChannel0 127 | readonly property variant iChannel1:mainSE.iChannel1 128 | readonly property variant iChannel2:mainSE.iChannel2 129 | readonly property variant iChannel3:mainSE.iChannel3 130 | readonly property variant iMouse:mainSE.iMouse 131 | fragmentShader:shaderSourceReader.buffer_shader_source 132 | } 133 | } 134 | 135 | /* 136 | ShaderEffectSource { 137 | id:glDFTSES 138 | width: glDFTSE.width 139 | height: glDFTSE.height 140 | visible:false 141 | live:false 142 | sourceItem: ShaderEffect { 143 | 144 | id:glDFTSE 145 | width: 200 146 | height: 1 147 | property int dftSize:glDFTSE.width 148 | property int bufferSize:waveBufferSE.width 149 | fragmentShader:shaderSourceReader.gldft_source 150 | 151 | readonly property variant waveBuffer:ShaderEffectSource { 152 | id:waveBufferSES 153 | width: waveBufferSE.width 154 | height: waveBufferSE.height 155 | live:false 156 | sourceItem: ShaderEffect { 157 | id:waveBufferSE 158 | width: 2000 159 | height: 2 160 | property variant newWave 161 | property int bufferSize:waveBufferSE.width 162 | property int newWaveSize:newWave?newWave.width:0 163 | readonly property variant waveBuffer:waveBufferSES 164 | fragmentShader:shaderSourceReader.wave_buffer_source 165 | } 166 | } 167 | } 168 | } 169 | */ 170 | 171 | readonly property bool loadImageShaderSource: shaderSourceReader.image_shader_source.trim().length>0 172 | readonly property bool loadBufferShaderSource: shaderSourceReader.buffer_shader_source.trim().length>0 173 | readonly property bool failCompileImageShader: loadImageShaderSource && false // (mainSE.status==ShaderEffect.Error) 174 | readonly property bool failCompileBufferShader: loadBufferShaderSource && false // (bufferSES.sourceItem.status==ShaderEffect.Error) 175 | property string fps_message:"" 176 | property string error_message: 177 | shaderSourceReader.error_message 178 | + (loadImageShaderSource ?"":i18n("Error: Failed to load the visual effect. Please choose another visual effect in the configuration dialog.")) 179 | + (failCompileImageShader?(i18n("Error: Failed to compile image shader.")+mainSE.log):"") 180 | + (failCompileBufferShader?(i18n("Error: Failed to compile bufffer shader.")+bufferSES.sourceItem.log):"") 181 | QQC2.Label { 182 | id:console_output 183 | anchors.fill: parent 184 | color: PlasmaCore.ColorScope.textColor 185 | text:error_message+(cfg.showFps?fps_message:"") 186 | } 187 | 188 | MouseArea { 189 | id:iMouseArea 190 | hoverEnabled :true 191 | anchors.fill: parent 192 | 193 | readonly property double current_x:root.gravity<3?(cfg.inversion?(mainSE.width- mouseX):mouseX):(cfg.inversion?mouseY:(mainSE.height-mouseY)) 194 | readonly property double current_y:[mainSE.height- mouseY,mouseY ,mainSE.width-mouseX ,mouseX ][root.gravity-1] 195 | property double lastdown_x 196 | property double lastdown_y 197 | property double lastclick_x 198 | property double lastclick_y 199 | 200 | property var i:Qt.vector4d(lastdown_x,lastdown_y ,pressed?lastclick_x:-lastclick_x,-lastclick_y) 201 | onPressed:{ 202 | lastclick_x=current_x 203 | lastclick_y=current_y 204 | 205 | lastdown_x=current_x 206 | lastdown_y=current_y 207 | } 208 | onPositionChanged:{ 209 | if(pressed){ 210 | lastdown_x=current_x 211 | lastdown_y=current_y 212 | } 213 | } 214 | } 215 | 216 | ShaderSource{id:shaderSourceReader} 217 | 218 | WsConnection{ 219 | shaderSourceReader:shaderSourceReader 220 | queue:MessageQueue{ 221 | only_spectrum:shaderSourceReader.enable_iChannel1 && !shaderSourceReader.enable_iChannel0 222 | onImgsReadyChanged:{ 223 | 224 | audioAvailable=imgsReady.audioAvailable 225 | var time_current_frame=Date.now() 226 | var deltatime=(time_current_frame-time_prev_frame)/1000.0 227 | mainSE.iTime=(time_current_frame-time_first_frame) /1000.0 228 | mainSE.iTimeDelta=deltatime 229 | mainSE.iFrame+=1 230 | if(cfg.showFps) 231 | if(mainSE.iFrame%30==1){ 232 | fps_message='fps:'+ Math.round(1000*30/(time_current_frame-time_fps_start)) 233 | time_fps_start=time_current_frame 234 | } 235 | 236 | 237 | if(cfg.glDFT){ 238 | /* 239 | waveBufferSE.newWave=imgsReady.w; 240 | waveBufferSES.scheduleUpdate(); 241 | glDFTSES.scheduleUpdate(); 242 | mainSE.iChannel1=glDFTSES; 243 | */ 244 | }else{ 245 | mainSE.iChannel0=imgsReady.w; 246 | mainSE.iChannel1=imgsReady.s; 247 | mainSE.iBeat=imgsReady.beat; 248 | } 249 | if(shaderSourceReader.enable_buffer) 250 | bufferSES.scheduleUpdate(); 251 | 252 | time_prev_frame=time_current_frame 253 | 254 | } 255 | } 256 | } 257 | 258 | property bool audioAvailable 259 | 260 | property double time_first_frame:Date.now() 261 | property double time_fps_start:Date.now() 262 | property double time_prev_frame:Date.now() 263 | Behavior on animatedMinimum{ 264 | enabled:cfg.animateAutoHiding 265 | NumberAnimation { 266 | duration: 250 267 | easing.type: Easing.InCubic 268 | } 269 | } 270 | } 271 | 272 | -------------------------------------------------------------------------------- /plasmoid/contents/ui/WsConnection.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtWebSockets 1.0 3 | import org.kde.plasma.core 2.0 as PlasmaCore 4 | import "utils.js" as Utils 5 | /* 6 | * This module starts a python back-end client, 7 | * and pushs messages from the client to a queue. 8 | * 9 | * A queue is required to store new data sent from the 10 | * audio back-end. Because if new audio data is used 11 | * directly as an image by the shaders, those images 12 | * may be used before they are loaded, which will cause 13 | * flikering problems. 14 | */ 15 | Item{ 16 | 17 | readonly property var cfg:plasmoid.configuration 18 | 19 | property variant queue 20 | 21 | property var shaderSourceReader 22 | 23 | WebSocketServer { 24 | id: server 25 | listen: true 26 | onClientConnected: { 27 | webSocket.onTextMessageReceived.connect(function(message) { 28 | queue.push(message) 29 | }); 30 | } 31 | } 32 | 33 | readonly property string startBackEnd:{ 34 | if(server.port==0) return ''; 35 | if(shaderSourceReader.image_shader_source=='') return '' 36 | var cmd=Utils.chdir_scripts_root()+'exec python3 -m panon.backend.client ' 37 | cmd+=server.url //+':'+server.port 38 | var be=['pyaudio','soundcard','fifo'][cfg.backendIndex] 39 | cmd+=' --backend='+be 40 | if(be=='soundcard') 41 | cmd+=' --device-index="'+cfg.pulseaudioDevice+'"' 42 | if(be=='fifo') 43 | cmd+=' --fifo-path='+cfg.fifoPath 44 | cmd+=' --fps='+cfg.fps 45 | if(cfg.reduceBass) 46 | cmd+=' --reduce-bass' 47 | if(cfg.glDFT) 48 | cmd+=' --gldft' 49 | if(cfg.debugBackend) 50 | cmd+=' --debug' 51 | cmd+=' --bass-resolution-level='+cfg.bassResolutionLevel 52 | if(cfg.debugBackend){ 53 | console.log('Executing: '+cmd) 54 | cmd='echo do nothing' 55 | } 56 | if(shaderSourceReader.enable_iChannel0) 57 | cmd+=' --enable-wave-data' 58 | if(shaderSourceReader.enable_iChannel1) 59 | cmd+=' --enable-spectrum-data' 60 | return cmd 61 | } 62 | 63 | PlasmaCore.DataSource { 64 | engine: 'executable' 65 | connectedSources: [startBackEnd] 66 | onNewData:{ 67 | // Show back-end errors. 68 | console.log(data.stdout) 69 | console.log(data.stderr) 70 | } 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /plasmoid/contents/ui/config/ConfigBackend.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Layouts 1.1 3 | import QtQuick.Controls 2.0 as QQC2 4 | 5 | import org.kde.kirigami 2.3 as Kirigami 6 | import org.kde.plasma.core 2.0 as PlasmaCore 7 | 8 | import "utils.js" as Utils 9 | 10 | Kirigami.FormLayout { 11 | 12 | anchors.right: parent.right 13 | anchors.left: parent.left 14 | 15 | 16 | property alias cfg_reduceBass: reduceBass.checked 17 | property alias cfg_glDFT: glDFT.checked 18 | property alias cfg_debugBackend: debugBackend.checked 19 | 20 | property alias cfg_bassResolutionLevel: bassResolutionLevel.currentIndex 21 | 22 | property alias cfg_backendIndex:backend.currentIndex 23 | 24 | property alias cfg_fifoPath: fifoPath.text 25 | 26 | property int cfg_deviceIndex 27 | property string cfg_pulseaudioDevice 28 | 29 | 30 | RowLayout { 31 | Kirigami.FormData.label: i18n("Back-end:") 32 | Layout.fillWidth: true 33 | 34 | QQC2.ComboBox { 35 | id:backend 36 | //model: ['pyaudio (requires python3 package pyaudio)','fifo','sounddevice (requires python3 package sounddevice'] 37 | model: ['PortAudio','PulseAudio','fifo'] 38 | } 39 | } 40 | 41 | RowLayout { 42 | visible:false // backend.currentText=='portaudio' 43 | Kirigami.FormData.label: i18n("Input device:") 44 | Layout.fillWidth: true 45 | 46 | QQC2.ComboBox { 47 | id:deviceIndex 48 | model: ListModel { 49 | id: cbItems 50 | } 51 | textRole:'name' 52 | onCurrentIndexChanged:cfg_deviceIndex= cbItems.get(currentIndex).d_index 53 | } 54 | } 55 | 56 | RowLayout { 57 | visible:backend.currentText=='PulseAudio' 58 | Kirigami.FormData.label: i18n("Input device:") 59 | Layout.fillWidth: true 60 | 61 | QQC2.ComboBox { 62 | id:pulseaudioDevice 63 | model: ListModel { 64 | id: pdItems 65 | } 66 | textRole:'name' 67 | onCurrentIndexChanged:{ 68 | if(currentText.length>0) 69 | cfg_pulseaudioDevice= pdItems.get(currentIndex).id 70 | } 71 | } 72 | } 73 | 74 | RowLayout { 75 | visible:backend.currentText=='fifo' 76 | Kirigami.FormData.label: i18n("Fifo path:") 77 | Layout.fillWidth: true 78 | 79 | QQC2.TextField { 80 | id:fifoPath 81 | } 82 | } 83 | 84 | QQC2.CheckBox { 85 | id: reduceBass 86 | text: i18nc("@option:check", "Reduce the weight of bass") 87 | } 88 | 89 | QQC2.CheckBox { 90 | id: glDFT 91 | visible:false 92 | text: i18nc("@option:check", "Use GLDFT (to lower CPU Usage) (experimental, not recommended)") 93 | } 94 | 95 | QQC2.CheckBox { 96 | id: debugBackend 97 | text: i18nc("@option:check", "Debug") 98 | } 99 | 100 | RowLayout { 101 | Kirigami.FormData.label: i18n("Audio frequency:") 102 | Layout.fillWidth: true 103 | 104 | QQC2.ComboBox { 105 | id:bassResolutionLevel 106 | model: ['0 to 22,050Hz','0 to 9,000Hz','0 to 3,000Hz (F7)', 107 | '0 to 1,800Hz (A6) with higher resolution', 108 | '0 to 1,800Hz (A6) with lower latency', 109 | '300 to 1,800Hz (A6) (filter out bass)', 110 | '0 to 600Hz (D5)'] 111 | } 112 | } 113 | 114 | QQC2.Label { 115 | onLinkActivated: Qt.openUrlExternally(link) 116 | text: ""+ i18n("Test your audio frequency.") + "" 117 | } 118 | 119 | RowLayout { 120 | Kirigami.FormData.label: i18n("Latency / resolution:") 121 | Layout.fillWidth: true 122 | QQC2.Label { 123 | textFormat: Text.RichText 124 | 125 | text:{ 126 | var l; 127 | switch(bassResolutionLevel.currentText){ 128 | 129 | case '0 to 22,050Hz': 130 | l=[[0,22050,16]] 131 | break; 132 | 133 | case '0 to 9,000Hz': 134 | l=[[0,600,133], 135 | [600,1800,100], 136 | [1800,3000,83], 137 | [3000,4800,66], 138 | [4800,6600,50], 139 | [6600,9000,33]]; 140 | break; 141 | 142 | case '0 to 3,000Hz (F7)': 143 | l=[[0,600,200], 144 | [600,1800,133], 145 | [1800,3000,66]]; 146 | break; 147 | 148 | case '0 to 1,800Hz (A6) with higher resolution': 149 | l=[[0,600,266], 150 | [600,1800,200]]; 151 | break; 152 | 153 | case '0 to 1,800Hz (A6) with lower latency': 154 | l=[[0,1800,100]]; 155 | break; 156 | 157 | case '300 to 1,800Hz (A6) (filter out bass)': 158 | l=[[300,1800,100]]; 159 | break; 160 | 161 | case '0 to 600Hz (D5)': 162 | l=[[0,600,266]] 163 | break; 164 | } 165 | var html='' 166 | for(var i in l){ 167 | html+=(''); 171 | } 172 | html+='
'+l[i][0].toLocaleString()+'~' 168 | +l[i][1].toLocaleString() 169 | +'Hz - '+l[i][2]+'ms / ' 170 | +Math.ceil((l[i][1]-l[i][0])*l[i][2]/1000)+'px
' 173 | return html 174 | } 175 | } 176 | } 177 | 178 | readonly property string sh_get_devices:Utils.chdir_scripts_root()+'python3 -m panon.backend.get_devices' 179 | readonly property string sh_get_pa_devices:Utils.chdir_scripts_root()+'python3 -m panon.backend.get_pa_devices' 180 | 181 | PlasmaCore.DataSource { 182 | //id: getOptionsDS 183 | engine: 'executable' 184 | connectedSources: [ 185 | sh_get_pa_devices 186 | ] 187 | onNewData: { 188 | 189 | if(sourceName==sh_get_pa_devices){ 190 | pdItems.append({name:'default',id:'default'}) 191 | var lst=JSON.parse(data.stdout) 192 | for(var i in lst) 193 | pdItems.append(lst[i]) 194 | if(lst.length>1){ 195 | pdItems.append({name:i18n("Monitor of Current Device"),id:'smart'}) 196 | pdItems.append({name:i18n("Mixing All Speakers"),id:'allspeakers'}) 197 | pdItems.append({name:i18n("Mixing All Microphones"),id:'allmicrophones'}) 198 | pdItems.append({name:i18n("Mixing All Microphones and Speakers"),id:'all'}) 199 | } 200 | 201 | for(var i=0;i100){ 36 | hsluvSaturation.value= 80+20*Utils.random(random_seed+3) 37 | hsluvLightness.value= 60+20*Utils.random(random_seed+5) 38 | }else{ 39 | hsluvSaturation.value= 80+20*Utils.random(random_seed+4) 40 | hsluvLightness.value= 100*Utils.random(random_seed+6) 41 | } 42 | 43 | } 44 | } 45 | QQC2.ButtonGroup { id: colorGroup } 46 | 47 | QQC2.RadioButton { 48 | id:colorSpaceHSL 49 | Kirigami.FormData.label: i18nc("@label", "Color space:") 50 | text: i18nc("@option:radio", "HSL") 51 | QQC2.ButtonGroup.group: colorGroup 52 | } 53 | 54 | QQC2.RadioButton { 55 | id:colorSpaceHSLuv 56 | text: i18nc("@option:radio", "HSLuv") 57 | QQC2.ButtonGroup.group: colorGroup 58 | } 59 | 60 | QQC2.SpinBox { 61 | id:hslHueFrom 62 | Kirigami.FormData.label:i18nc("@label:spinbox","Hue from") 63 | visible:colorSpaceHSL.checked 64 | editable:true 65 | stepSize:10 66 | from:-4000 67 | to:4000 68 | } 69 | 70 | QQC2.SpinBox { 71 | id:hslHueTo 72 | Kirigami.FormData.label:i18nc("@label:spinbox","Hue to") 73 | visible:colorSpaceHSL.checked 74 | editable:true 75 | stepSize:10 76 | from:-4000 77 | to:4000 78 | } 79 | 80 | QQC2.SpinBox { 81 | id:hsluvHueFrom 82 | Kirigami.FormData.label:i18nc("@label:spinbox","Hue from") 83 | visible:colorSpaceHSLuv.checked 84 | editable:true 85 | stepSize:10 86 | from:-4000 87 | to:4000 88 | } 89 | 90 | QQC2.SpinBox { 91 | id:hsluvHueTo 92 | Kirigami.FormData.label:i18nc("@label:spinbox","Hue to") 93 | visible:colorSpaceHSLuv.checked 94 | editable:true 95 | stepSize:10 96 | from:-4000 97 | to:4000 98 | } 99 | 100 | QQC2.SpinBox { 101 | id:hslSaturation 102 | Kirigami.FormData.label:i18nc("@label:spinbox","Saturation") 103 | visible:colorSpaceHSL.checked 104 | editable:true 105 | stepSize:2 106 | from:0 107 | to:100 108 | } 109 | 110 | QQC2.SpinBox { 111 | id:hslLightness 112 | Kirigami.FormData.label:i18nc("@label:spinbox","Lightness") 113 | visible:colorSpaceHSL.checked 114 | editable:true 115 | stepSize:2 116 | from:0 117 | to:100 118 | } 119 | 120 | QQC2.SpinBox { 121 | id:hsluvSaturation 122 | Kirigami.FormData.label:i18nc("@label:spinbox","Saturation") 123 | visible:colorSpaceHSLuv.checked 124 | editable:true 125 | stepSize:2 126 | from:0 127 | to:100 128 | } 129 | 130 | QQC2.SpinBox { 131 | id:hsluvLightness 132 | Kirigami.FormData.label:i18nc("@label:spinbox","Lightness") 133 | visible:colorSpaceHSLuv.checked 134 | editable:true 135 | stepSize:2 136 | from:0 137 | to:100 138 | } 139 | 140 | 141 | Item { 142 | Kirigami.FormData.isSection: true 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /plasmoid/contents/ui/config/ConfigEffect.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Layouts 1.1 3 | import QtQuick.Controls 2.0 as QQC2 4 | 5 | import org.kde.kirigami 2.3 as Kirigami 6 | import org.kde.plasma.core 2.0 as PlasmaCore 7 | import org.kde.newstuff 1.1 as NewStuff 8 | 9 | import "utils.js" as Utils 10 | 11 | Kirigami.FormLayout { 12 | id:root 13 | 14 | anchors.right: parent.right 15 | anchors.left: parent.left 16 | 17 | property string cfg_visualEffect 18 | 19 | property var cfg_effectArgValues:[] 20 | property bool cfg_effectArgTrigger:false 21 | 22 | NewStuff.Button { 23 | downloadNewWhat: i18n("Effects") 24 | configFile: Utils.get_root() + "/config/panon.knsrc" 25 | onChangedEntriesChanged:{ 26 | /* 27 | * Triggers the executable DataSource to execute this line again: 28 | * if(shaderOptions.count<1)return[sh_get_visual_effects] 29 | * So that the list model shaderOptions will be refreshed. 30 | */ 31 | shaderOptions.clear() 32 | } 33 | } 34 | 35 | RowLayout { 36 | Kirigami.FormData.label: i18n("Effect:") 37 | Layout.fillWidth: true 38 | 39 | QQC2.ComboBox { 40 | id:visualeffect 41 | model: ListModel { 42 | id: shaderOptions 43 | } 44 | textRole: 'name' 45 | onCurrentIndexChanged:cfg_visualEffect= shaderOptions.get(currentIndex).id 46 | } 47 | } 48 | 49 | RowLayout { 50 | Kirigami.FormData.label: i18n("Hint:") 51 | Layout.fillWidth: true 52 | visible:hint.text.length>0 53 | QQC2.Label { 54 | id:hint 55 | text:'' 56 | onLinkActivated: Qt.openUrlExternally(link) 57 | } 58 | } 59 | 60 | 61 | readonly property string sh_get_visual_effects:Utils.chdir_scripts_root()+'python3 -m panon.effect.get_effect_list' 62 | 63 | readonly property string sh_read_effect_hint:Utils.chdir_scripts_root()+'python3 -m panon.effect.read_file "'+cfg_visualEffect+'" hint.html' 64 | readonly property string sh_read_effect_args:Utils.chdir_scripts_root()+'python3 -m panon.effect.read_file "'+cfg_visualEffect+'" meta.json' 65 | 66 | onCfg_visualEffectChanged:{ 67 | hint.text='' 68 | effect_arguments=[] 69 | } 70 | property bool firstTimeLoadArgs:true 71 | property var effect_arguments:[] 72 | 73 | 74 | PlasmaCore.DataSource { 75 | engine: 'executable' 76 | connectedSources: { 77 | // Load visual effects when shaderOptions is empty. 78 | if(shaderOptions.count<1)return[sh_get_visual_effects] 79 | 80 | // Load hint.html and meta.json 81 | if(!cfg_visualEffect.endsWith('.frag')) 82 | return[sh_read_effect_hint,sh_read_effect_args] 83 | 84 | return [] 85 | } 86 | 87 | // Text field components used to represent the arguments of the visual effect. 88 | property var textfieldlst:[] 89 | 90 | onNewData: { 91 | if(sourceName==sh_read_effect_hint){ 92 | hint.text=(data.stdout) 93 | }else if(sourceName==sh_read_effect_args){ 94 | if(data.stdout.length>0){ 95 | effect_arguments=JSON.parse(data.stdout)['arguments'] 96 | //while(textfieldlst.length>0)textfieldlst.pop().destroy() 97 | textfieldlst.map(function(o){o.visible=false}) 98 | for(var index=0;index plasmoid.width) 16 | 17 | property alias cfg_fps: fps.value 18 | property alias cfg_showFps: showFps.checked 19 | property alias cfg_hideTooltip: hideTooltip.checked 20 | 21 | property alias cfg_preferredWidth: preferredWidth.value 22 | property alias cfg_autoExtend: autoExtend.checked 23 | property alias cfg_autoHide: autoHideBtn.checked 24 | property alias cfg_animateAutoHiding: animateAutoHiding.checked 25 | 26 | property alias cfg_gravity:gravity.currentIndex 27 | property alias cfg_inversion:inversion.checked 28 | 29 | QQC2.SpinBox { 30 | id:fps 31 | Kirigami.FormData.label:i18nc("@label:spinbox","FPS:") 32 | editable:true 33 | stepSize:1 34 | from:1 35 | to:300 36 | } 37 | 38 | QQC2.Label { 39 | text: i18n("Lower FPS saves CPU and battries.") 40 | } 41 | 42 | QQC2.CheckBox { 43 | id:showFps 44 | text: i18nc("@option:radio", "Show FPS") 45 | } 46 | 47 | Item { 48 | Kirigami.FormData.isSection: true 49 | } 50 | 51 | QQC2.CheckBox { 52 | id:autoHideBtn 53 | text: i18nc("@option:radio", "Auto-hide (when audio is gone)") 54 | onCheckedChanged:{ 55 | autoExtend.checked=autoHideBtn.checked?false:autoExtend.checked 56 | } 57 | } 58 | 59 | QQC2.CheckBox { 60 | id:animateAutoHiding 61 | visible:autoHideBtn.checked 62 | text: i18nc("@option:radio", "Animate auto-hiding") 63 | } 64 | 65 | QQC2.SpinBox { 66 | id: preferredWidth 67 | 68 | Kirigami.FormData.label: vertical ? i18nc("@label:spinbox", "Height:"):i18nc("@label:spinbox", "Width:") 69 | editable:true 70 | stepSize:10 71 | 72 | from: 1 73 | to:8000 74 | } 75 | 76 | QQC2.CheckBox { 77 | id: autoExtend 78 | enabled:!autoHideBtn.checked 79 | text: vertical?i18nc("@option:check", "Fill height (don't work with Auto-hiding)"):i18nc("@option:check", "Fill width (don't work with Auto-hiding)") 80 | } 81 | 82 | Item { 83 | Kirigami.FormData.isSection: true 84 | } 85 | 86 | RowLayout { 87 | Kirigami.FormData.label: i18n("Gravity:") 88 | Layout.fillWidth: true 89 | 90 | QQC2.ComboBox { 91 | id:gravity 92 | model: [i18n("Center"),i18n("North"),i18n("South"),i18n("East"),i18n("West")] 93 | } 94 | } 95 | 96 | QQC2.CheckBox { 97 | id:inversion 98 | text: i18nc("@option:check", "Flip") 99 | } 100 | 101 | QQC2.CheckBox { 102 | id:hideTooltip 103 | text: i18nc("@option:check", "Hide tooltip") 104 | } 105 | 106 | RowLayout { 107 | Kirigami.FormData.label: i18n("Version:") 108 | Layout.fillWidth: true 109 | 110 | QQC2.Label { 111 | text: "0.4.5" 112 | } 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /plasmoid/contents/ui/config/EffectArgument.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Layouts 1.1 3 | import QtQuick.Controls 2.0 as QQC2 4 | 5 | import org.kde.kirigami 2.3 as Kirigami 6 | 7 | RowLayout { 8 | 9 | property var root 10 | property int index 11 | property var effectArgValues 12 | 13 | property var vali:null 14 | 15 | Kirigami.FormData.label: visible?root.effect_arguments[index]["name"]+":":"" 16 | visible:root.effect_arguments.length>index 17 | QQC2.TextField { 18 | text:visible? effectArgValues[index]:"" 19 | onTextChanged:{ 20 | effectArgValues[index]=text 21 | root.cfg_effectArgTrigger=!root.cfg_effectArgTrigger 22 | } 23 | validator:vali 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /plasmoid/contents/ui/config/EffectArgumentBool.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Layouts 1.1 3 | import QtQuick.Controls 2.0 as QQC2 4 | 5 | QQC2.CheckBox { 6 | 7 | property var root 8 | property int index 9 | property var effectArgValues 10 | 11 | 12 | visible:root.effect_arguments.length>index 13 | text: visible?root.effect_arguments[index]["name"]:"" 14 | checked:visible?(effectArgValues[index]=="true"):false 15 | 16 | onCheckedChanged:{ 17 | effectArgValues[index]=checked 18 | root.cfg_effectArgTrigger=!root.cfg_effectArgTrigger 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /plasmoid/contents/ui/config/EffectArgumentColor.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import org.kde.kirigami 2.3 as Kirigami 3 | import org.kde.kquickcontrols 2.0 as KQuickControls 4 | 5 | KQuickControls.ColorButton { 6 | property var root 7 | property int index 8 | property var effectArgValues 9 | 10 | visible:root.effect_arguments.length>index 11 | Kirigami.FormData.label: visible?root.effect_arguments[index]["name"]+":":"" 12 | 13 | showAlphaChannel:true 14 | 15 | color:visible?effectArgValues[index]:"" 16 | 17 | id:btn 18 | 19 | onColorChanged:{ 20 | effectArgValues[index]=btn.color 21 | root.cfg_effectArgTrigger=!root.cfg_effectArgTrigger 22 | } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /plasmoid/contents/ui/config/EffectArgumentDouble.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | 3 | EffectArgument{ 4 | vali:DoubleValidator{ 5 | top:{ 6 | if(root.effect_arguments[index]) 7 | if('max' in root.effect_arguments[index]) 8 | return root.effect_arguments[index].max; 9 | return 1000000; 10 | } 11 | 12 | bottom:{ 13 | if(root.effect_arguments[index]) 14 | if('min' in root.effect_arguments[index]) 15 | return root.effect_arguments[index].min; 16 | return -1000000; 17 | } 18 | notation:DoubleValidator.StandardNotation 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /plasmoid/contents/ui/config/EffectArgumentInt.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | 3 | EffectArgument{ 4 | vali:IntValidator{ 5 | top:{ 6 | if(root.effect_arguments[index]) 7 | if('max' in root.effect_arguments[index]) 8 | return root.effect_arguments[index].max; 9 | return 1000000; 10 | } 11 | 12 | bottom:{ 13 | if(root.effect_arguments[index]) 14 | if('min' in root.effect_arguments[index]) 15 | return root.effect_arguments[index].min; 16 | return -1000000; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /plasmoid/contents/ui/config/utils.js: -------------------------------------------------------------------------------- 1 | ../utils.js -------------------------------------------------------------------------------- /plasmoid/contents/ui/main.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import org.kde.plasma.plasmoid 2.0 3 | import org.kde.plasma.core 2.0 as PlasmaCore 4 | 5 | Item { 6 | 7 | readonly property var cfg:plasmoid.configuration 8 | 9 | Plasmoid.preferredRepresentation: Plasmoid.compactRepresentation 10 | 11 | Plasmoid.compactRepresentation: Spectrum{} 12 | 13 | Plasmoid.toolTipItem: cfg.hideTooltip?tooltipitem:null 14 | 15 | Plasmoid.backgroundHints: PlasmaCore.Types.DefaultBackground | PlasmaCore.Types.ConfigurableBackground 16 | 17 | Item{id:tooltipitem} 18 | 19 | } 20 | -------------------------------------------------------------------------------- /plasmoid/contents/ui/utils.js: -------------------------------------------------------------------------------- 1 | function get_root() { 2 | var p_ui = plasmoid.file("ui") 3 | p_ui = p_ui.split('/') 4 | p_ui.pop(-1) 5 | return p_ui.join('/') 6 | } 7 | 8 | function get_scripts_root() { 9 | return get_root() + '/scripts' 10 | } 11 | 12 | function chdir_scripts_root() { 13 | return 'cd "'+get_scripts_root()+'";' 14 | } 15 | 16 | function random(seed) { 17 | var x = Math.sin(seed*1000) * 10000; 18 | return x - Math.floor(x); 19 | } 20 | -------------------------------------------------------------------------------- /plasmoid/metadata.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=Panon 3 | Comment=A Different Audio Spectrum Analyzer 4 | Comment[zh_CN]=一个音乐视觉特效插件 5 | Icon=applications-multimedia 6 | 7 | Type=Service 8 | X-KDE-ServiceTypes=Plasma/Applet 9 | X-KDE-PluginInfo-Author=rbn42 10 | X-KDE-PluginInfo-Email= 11 | X-KDE-PluginInfo-Name=panon 12 | X-KDE-PluginInfo-Version=0.4.5 13 | X-KDE-PluginInfo-Website=https://github.com/rbn42/panon 14 | X-KDE-PluginInfo-Category=Multimedia 15 | X-KDE-PluginInfo-Depends= 16 | X-KDE-PluginInfo-License=GPL-3.0+ 17 | X-KDE-PluginInfo-EnabledByDefault=true 18 | 19 | X-Plasma-Provides=panon 20 | X-Plasma-API=declarativeappletscript 21 | X-Plasma-MainScript=ui/main.qml 22 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -f "third_party/hsluv-glsl/hsluv-glsl.fsh" ];then 4 | plasmoidviewer --applet ./plasmoid/ 5 | else 6 | echo "Cannot find third party files. Please execute: git submodule update --init" 7 | fi 8 | -------------------------------------------------------------------------------- /translations/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | FIND_PROGRAM(GETTEXT_MSGFMT_EXECUTABLE msgfmt) 2 | 3 | IF(NOT GETTEXT_MSGFMT_EXECUTABLE) 4 | MESSAGE( 5 | "------ 6 | NOTE: msgfmt not found. Translations will *not* be installed 7 | ------") 8 | ELSE(NOT GETTEXT_MSGFMT_EXECUTABLE) 9 | 10 | SET(catalogname plasma_applet_panon) 11 | 12 | FILE(GLOB PO_FILES po/*.po) 13 | SET(GMO_FILES) 14 | 15 | ADD_CUSTOM_TARGET(translations ALL DEPENDS ${GMO_FILES}) 16 | 17 | FOREACH(_poFile ${PO_FILES}) 18 | GET_FILENAME_COMPONENT(_poFileName ${_poFile} NAME) 19 | STRING(REGEX REPLACE "^${catalogname}_?" "" _langCode ${_poFileName} ) 20 | STRING(REGEX REPLACE "\\.po$" "" _langCode ${_langCode} ) 21 | 22 | IF( _langCode ) 23 | STRING(REGEX REPLACE "\\.po$" "" _lang ${_poFileName} ) 24 | SET(_gmoFile ${CMAKE_CURRENT_BINARY_DIR}/${_lang}.gmo) 25 | 26 | ADD_CUSTOM_COMMAND(TARGET translations 27 | COMMAND ${GETTEXT_MSGFMT_EXECUTABLE} --check -o ${_gmoFile} ${_poFile} 28 | DEPENDS ${_poFile}) 29 | INSTALL(FILES ${_gmoFile} DESTINATION ${LOCALE_INSTALL_DIR}/${_langCode}/LC_MESSAGES/ RENAME ${catalogname}.mo) 30 | LIST(APPEND GMO_FILES ${_gmoFile}) 31 | ENDIF( _langCode ) 32 | 33 | ENDFOREACH(_poFile ${PO_FILES}) 34 | 35 | ENDIF(NOT GETTEXT_MSGFMT_EXECUTABLE) 36 | -------------------------------------------------------------------------------- /translations/README: -------------------------------------------------------------------------------- 1 | README FOR TRANSLATORS 2 | 3 | 1) 4 | Copy file po/plasma_applet_panon.pot 5 | 6 | 2) 7 | Rename it to plasma_applet_panon_$LANG.po 8 | - where $LANG is desired language, e.g. "pl" (or locale, e.g. "en_GB") 9 | 10 | 3) 11 | In the copied file: 12 | - change Language accordingly 13 | - be sure to write your name :-) (as Last-Translator and Language-Team) 14 | 15 | 4) 16 | To "debug" the translation without changing your system language: 17 | 1. Execute /translations/install_translations.sh 18 | 2. Prepend these lines to /test.sh and run it. 19 | 20 | LANGUAGE=en_US.UTF-8 21 | LC_ALL=en_US.UTF-8 22 | LANG=en_US.UTF-8 23 | # Replace en_US with your language. 24 | 25 | Thank You for your translations! 26 | -------------------------------------------------------------------------------- /translations/extract-messages.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | BASEDIR="../plasmoid/" # root of translatable sources 3 | PROJECT="plasma_applet_panon" # project name 4 | BUGADDR="https://github.com/rbn42/panon/issues" # MSGID-Bugs 5 | WDIR=`pwd` # working dir 6 | 7 | 8 | 9 | echo "Extracting messages" 10 | cd ${BASEDIR} 11 | # see above on sorting 12 | find . -name '*.qml' | sort > ${WDIR}/infiles.list 13 | cd ${WDIR} 14 | xgettext --from-code=UTF-8 -C -kde -ci18n -ki18n:1 -ki18nc:1c,2 -ki18np:1,2 -ki18ncp:1c,2,3 -ktr2i18n:1 \ 15 | -kI18N_NOOP:1 -kI18N_NOOP2:1c,2 -kaliasLocale -kki18n:1 -kki18nc:1c,2 -kki18np:1,2 -kki18ncp:1c,2,3 \ 16 | --msgid-bugs-address="${BUGADDR}" \ 17 | --files-from=infiles.list -D ${BASEDIR} -D ${WDIR} -o po/${PROJECT}.pot || { echo "error while calling xgettext. aborting."; exit 1; } 18 | echo "Done extracting messages" 19 | 20 | 21 | echo "Merging translations" 22 | catalogs=`find ./po -name '*.po'` 23 | for cat in $catalogs; do 24 | echo $cat 25 | msgmerge -o $cat.new $cat po/${PROJECT}.pot 26 | mv $cat.new $cat 27 | done 28 | echo "Done merging translations" 29 | 30 | 31 | echo "Cleaning up" 32 | cd ${WDIR} 33 | rm infiles.list 34 | echo "Done" 35 | -------------------------------------------------------------------------------- /translations/install_translations.sh: -------------------------------------------------------------------------------- 1 | mkdir build 2 | cd build 3 | cmake .. 4 | make install DESTDIR=~/.local/share/locale/ 5 | -------------------------------------------------------------------------------- /translations/po/plasma_applet_panon.pot: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: PACKAGE VERSION\n" 10 | "Report-Msgid-Bugs-To: https://github.com/rbn42/panon/issues\n" 11 | "POT-Creation-Date: 2021-02-20 22:39+0800\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=CHARSET\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | 20 | #: contents/config/config.qml:6 21 | msgctxt "@title" 22 | msgid "General" 23 | msgstr "" 24 | 25 | #: contents/config/config.qml:11 26 | msgctxt "@title" 27 | msgid "Visual Effects" 28 | msgstr "" 29 | 30 | #: contents/config/config.qml:16 31 | msgctxt "@title" 32 | msgid "Back-end" 33 | msgstr "" 34 | 35 | #: contents/config/config.qml:21 36 | msgctxt "@title" 37 | msgid "Colors" 38 | msgstr "" 39 | 40 | #: contents/ui/config/ConfigBackend.qml:31 41 | msgid "Back-end:" 42 | msgstr "" 43 | 44 | #: contents/ui/config/ConfigBackend.qml:43 45 | #: contents/ui/config/ConfigBackend.qml:58 46 | msgid "Input device:" 47 | msgstr "" 48 | 49 | #: contents/ui/config/ConfigBackend.qml:76 50 | msgid "Fifo path:" 51 | msgstr "" 52 | 53 | #: contents/ui/config/ConfigBackend.qml:86 54 | msgctxt "@option:check" 55 | msgid "Reduce the weight of bass" 56 | msgstr "" 57 | 58 | #: contents/ui/config/ConfigBackend.qml:92 59 | msgctxt "@option:check" 60 | msgid "Use GLDFT (to lower CPU Usage) (experimental, not recommended)" 61 | msgstr "" 62 | 63 | #: contents/ui/config/ConfigBackend.qml:97 64 | msgctxt "@option:check" 65 | msgid "Debug" 66 | msgstr "" 67 | 68 | #: contents/ui/config/ConfigBackend.qml:101 69 | msgid "Audio frequency:" 70 | msgstr "" 71 | 72 | #: contents/ui/config/ConfigBackend.qml:116 73 | msgid "Test your audio frequency." 74 | msgstr "" 75 | 76 | #: contents/ui/config/ConfigBackend.qml:120 77 | msgid "Latency / resolution:" 78 | msgstr "" 79 | 80 | #: contents/ui/config/ConfigBackend.qml:195 81 | msgid "Monitor of Current Device" 82 | msgstr "" 83 | 84 | #: contents/ui/config/ConfigBackend.qml:196 85 | msgid "Mixing All Speakers" 86 | msgstr "" 87 | 88 | #: contents/ui/config/ConfigBackend.qml:197 89 | msgid "Mixing All Microphones" 90 | msgstr "" 91 | 92 | #: contents/ui/config/ConfigBackend.qml:198 93 | msgid "Mixing All Microphones and Speakers" 94 | msgstr "" 95 | 96 | #: contents/ui/config/ConfigColors.qml:28 97 | msgid "Randomize colors" 98 | msgstr "" 99 | 100 | #: contents/ui/config/ConfigColors.qml:49 101 | msgctxt "@label" 102 | msgid "Color space:" 103 | msgstr "" 104 | 105 | #: contents/ui/config/ConfigColors.qml:50 106 | msgctxt "@option:radio" 107 | msgid "HSL" 108 | msgstr "" 109 | 110 | #: contents/ui/config/ConfigColors.qml:56 111 | msgctxt "@option:radio" 112 | msgid "HSLuv" 113 | msgstr "" 114 | 115 | #: contents/ui/config/ConfigColors.qml:62 116 | #: contents/ui/config/ConfigColors.qml:82 117 | msgctxt "@label:spinbox" 118 | msgid "Hue from" 119 | msgstr "" 120 | 121 | #: contents/ui/config/ConfigColors.qml:72 122 | #: contents/ui/config/ConfigColors.qml:92 123 | msgctxt "@label:spinbox" 124 | msgid "Hue to" 125 | msgstr "" 126 | 127 | #: contents/ui/config/ConfigColors.qml:102 128 | #: contents/ui/config/ConfigColors.qml:122 129 | msgctxt "@label:spinbox" 130 | msgid "Saturation" 131 | msgstr "" 132 | 133 | #: contents/ui/config/ConfigColors.qml:112 134 | #: contents/ui/config/ConfigColors.qml:132 135 | msgctxt "@label:spinbox" 136 | msgid "Lightness" 137 | msgstr "" 138 | 139 | #: contents/ui/config/ConfigEffect.qml:23 140 | msgid "Effects" 141 | msgstr "" 142 | 143 | #: contents/ui/config/ConfigEffect.qml:36 144 | msgid "Effect:" 145 | msgstr "" 146 | 147 | #: contents/ui/config/ConfigEffect.qml:50 148 | msgid "Hint:" 149 | msgstr "" 150 | 151 | #: contents/ui/config/ConfigGeneral.qml:31 152 | msgctxt "@label:spinbox" 153 | msgid "FPS:" 154 | msgstr "" 155 | 156 | #: contents/ui/config/ConfigGeneral.qml:39 157 | msgid "Lower FPS saves CPU and battries." 158 | msgstr "" 159 | 160 | #: contents/ui/config/ConfigGeneral.qml:44 161 | msgctxt "@option:radio" 162 | msgid "Show FPS" 163 | msgstr "" 164 | 165 | #: contents/ui/config/ConfigGeneral.qml:53 166 | msgctxt "@option:radio" 167 | msgid "Auto-hide (when audio is gone)" 168 | msgstr "" 169 | 170 | #: contents/ui/config/ConfigGeneral.qml:62 171 | msgctxt "@option:radio" 172 | msgid "Animate auto-hiding" 173 | msgstr "" 174 | 175 | #: contents/ui/config/ConfigGeneral.qml:68 176 | msgctxt "@label:spinbox" 177 | msgid "Height:" 178 | msgstr "" 179 | 180 | #: contents/ui/config/ConfigGeneral.qml:68 181 | msgctxt "@label:spinbox" 182 | msgid "Width:" 183 | msgstr "" 184 | 185 | #: contents/ui/config/ConfigGeneral.qml:79 186 | msgctxt "@option:check" 187 | msgid "Fill height (don't work with Auto-hiding)" 188 | msgstr "" 189 | 190 | #: contents/ui/config/ConfigGeneral.qml:79 191 | msgctxt "@option:check" 192 | msgid "Fill width (don't work with Auto-hiding)" 193 | msgstr "" 194 | 195 | #: contents/ui/config/ConfigGeneral.qml:87 196 | msgid "Gravity:" 197 | msgstr "" 198 | 199 | #: contents/ui/config/ConfigGeneral.qml:92 200 | msgid "Center" 201 | msgstr "" 202 | 203 | #: contents/ui/config/ConfigGeneral.qml:92 204 | msgid "North" 205 | msgstr "" 206 | 207 | #: contents/ui/config/ConfigGeneral.qml:92 208 | msgid "South" 209 | msgstr "" 210 | 211 | #: contents/ui/config/ConfigGeneral.qml:92 212 | msgid "East" 213 | msgstr "" 214 | 215 | #: contents/ui/config/ConfigGeneral.qml:92 216 | msgid "West" 217 | msgstr "" 218 | 219 | #: contents/ui/config/ConfigGeneral.qml:98 220 | msgctxt "@option:check" 221 | msgid "Flip" 222 | msgstr "" 223 | 224 | #: contents/ui/config/ConfigGeneral.qml:103 225 | msgctxt "@option:check" 226 | msgid "Hide tooltip" 227 | msgstr "" 228 | 229 | #: contents/ui/config/ConfigGeneral.qml:107 230 | msgid "Version:" 231 | msgstr "" 232 | 233 | #: contents/ui/ShaderSource.qml:54 234 | msgid "Error: Find undeclared arguments." 235 | msgstr "" 236 | 237 | #: contents/ui/Spectrum.qml:176 238 | msgid "" 239 | "Error: Failed to load the visual effect. Please choose another visual effect " 240 | "in the configuration dialog." 241 | msgstr "" 242 | 243 | #: contents/ui/Spectrum.qml:177 244 | msgid "Error: Failed to compile image shader." 245 | msgstr "" 246 | 247 | #: contents/ui/Spectrum.qml:178 248 | msgid "Error: Failed to compile bufffer shader." 249 | msgstr "" 250 | -------------------------------------------------------------------------------- /translations/po/plasma_applet_panon_de_DE.po: -------------------------------------------------------------------------------- 1 | # German Translation 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # Chris (NullDev) , 2020. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: PACKAGE VERSION\n" 9 | "Report-Msgid-Bugs-To: https://github.com/rbn42/panon/issues\n" 10 | "POT-Creation-Date: 2021-02-20 22:39+0800\n" 11 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 12 | "Last-Translator: Chris (NullDev) \n" 13 | "Language-Team: NullDev \n" 14 | "Language: German\n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | 19 | #: contents/config/config.qml:6 20 | msgctxt "@title" 21 | msgid "General" 22 | msgstr "Allgemein" 23 | 24 | #: contents/config/config.qml:11 25 | msgctxt "@title" 26 | msgid "Visual Effects" 27 | msgstr "Visuelle Effekte" 28 | 29 | #: contents/config/config.qml:16 30 | msgctxt "@title" 31 | msgid "Back-end" 32 | msgstr "" 33 | 34 | #: contents/config/config.qml:21 35 | msgctxt "@title" 36 | msgid "Colors" 37 | msgstr "Farben" 38 | 39 | #: contents/ui/config/ConfigBackend.qml:31 40 | msgid "Back-end:" 41 | msgstr "" 42 | 43 | #: contents/ui/config/ConfigBackend.qml:43 44 | #: contents/ui/config/ConfigBackend.qml:58 45 | msgid "Input device:" 46 | msgstr "Eingabegeräte" 47 | 48 | #: contents/ui/config/ConfigBackend.qml:76 49 | msgid "Fifo path:" 50 | msgstr "Fifo Pfad" 51 | 52 | #: contents/ui/config/ConfigBackend.qml:86 53 | msgctxt "@option:check" 54 | msgid "Reduce the weight of bass" 55 | msgstr "Bass-Stärke reduzieren" 56 | 57 | #: contents/ui/config/ConfigBackend.qml:92 58 | msgctxt "@option:check" 59 | msgid "Use GLDFT (to lower CPU Usage) (experimental, not recommended)" 60 | msgstr "" 61 | 62 | #: contents/ui/config/ConfigBackend.qml:97 63 | msgctxt "@option:check" 64 | msgid "Debug" 65 | msgstr "" 66 | 67 | #: contents/ui/config/ConfigBackend.qml:101 68 | msgid "Audio frequency:" 69 | msgstr "Audio Frequenz" 70 | 71 | #: contents/ui/config/ConfigBackend.qml:116 72 | msgid "Test your audio frequency." 73 | msgstr "" 74 | 75 | #: contents/ui/config/ConfigBackend.qml:120 76 | msgid "Latency / resolution:" 77 | msgstr "" 78 | 79 | #: contents/ui/config/ConfigBackend.qml:195 80 | msgid "Monitor of Current Device" 81 | msgstr "" 82 | 83 | #: contents/ui/config/ConfigBackend.qml:196 84 | msgid "Mixing All Speakers" 85 | msgstr "" 86 | 87 | #: contents/ui/config/ConfigBackend.qml:197 88 | msgid "Mixing All Microphones" 89 | msgstr "" 90 | 91 | #: contents/ui/config/ConfigBackend.qml:198 92 | msgid "Mixing All Microphones and Speakers" 93 | msgstr "Alle Mikrofone und Lautsprecher mischen" 94 | 95 | #: contents/ui/config/ConfigColors.qml:28 96 | msgid "Randomize colors" 97 | msgstr "Zufällige Farben" 98 | 99 | #: contents/ui/config/ConfigColors.qml:49 100 | msgctxt "@label" 101 | msgid "Color space:" 102 | msgstr "Farbraum" 103 | 104 | #: contents/ui/config/ConfigColors.qml:50 105 | msgctxt "@option:radio" 106 | msgid "HSL" 107 | msgstr "" 108 | 109 | #: contents/ui/config/ConfigColors.qml:56 110 | msgctxt "@option:radio" 111 | msgid "HSLuv" 112 | msgstr "" 113 | 114 | #: contents/ui/config/ConfigColors.qml:62 115 | #: contents/ui/config/ConfigColors.qml:82 116 | msgctxt "@label:spinbox" 117 | msgid "Hue from" 118 | msgstr "Farbverlauf von" 119 | 120 | #: contents/ui/config/ConfigColors.qml:72 121 | #: contents/ui/config/ConfigColors.qml:92 122 | msgctxt "@label:spinbox" 123 | msgid "Hue to" 124 | msgstr "Farbverlauf bis" 125 | 126 | #: contents/ui/config/ConfigColors.qml:102 127 | #: contents/ui/config/ConfigColors.qml:122 128 | msgctxt "@label:spinbox" 129 | msgid "Saturation" 130 | msgstr "Sättigung" 131 | 132 | #: contents/ui/config/ConfigColors.qml:112 133 | #: contents/ui/config/ConfigColors.qml:132 134 | msgctxt "@label:spinbox" 135 | msgid "Lightness" 136 | msgstr "Helligkeit" 137 | 138 | #: contents/ui/config/ConfigEffect.qml:23 139 | msgid "Effects" 140 | msgstr "Effekte" 141 | 142 | #: contents/ui/config/ConfigEffect.qml:36 143 | msgid "Effect:" 144 | msgstr "Effekt:" 145 | 146 | #: contents/ui/config/ConfigEffect.qml:50 147 | msgid "Hint:" 148 | msgstr "Hinweis:" 149 | 150 | #: contents/ui/config/ConfigGeneral.qml:31 151 | msgctxt "@label:spinbox" 152 | msgid "FPS:" 153 | msgstr "" 154 | 155 | #: contents/ui/config/ConfigGeneral.qml:39 156 | msgid "Lower FPS saves CPU and battries." 157 | msgstr "Niedrigere FPS sparen CPU-Leistung und Batterielaufzeit" 158 | 159 | #: contents/ui/config/ConfigGeneral.qml:44 160 | msgctxt "@option:radio" 161 | msgid "Show FPS" 162 | msgstr "Zeige FPS" 163 | 164 | #: contents/ui/config/ConfigGeneral.qml:53 165 | msgctxt "@option:radio" 166 | msgid "Auto-hide (when audio is gone)" 167 | msgstr "Automatisch Verstecken (wenn keine Töne abgespielt werden)" 168 | 169 | #: contents/ui/config/ConfigGeneral.qml:62 170 | msgctxt "@option:radio" 171 | msgid "Animate auto-hiding" 172 | msgstr "Automatisches Verstecken mit Animation" 173 | 174 | #: contents/ui/config/ConfigGeneral.qml:68 175 | msgctxt "@label:spinbox" 176 | msgid "Height:" 177 | msgstr "Höhe:" 178 | 179 | #: contents/ui/config/ConfigGeneral.qml:68 180 | msgctxt "@label:spinbox" 181 | msgid "Width:" 182 | msgstr "Breite:" 183 | 184 | #: contents/ui/config/ConfigGeneral.qml:79 185 | msgctxt "@option:check" 186 | msgid "Fill height (don't work with Auto-hiding)" 187 | msgstr "Fülle Höhe (funktioniert nicht mit automatischem Verstecken)" 188 | 189 | #: contents/ui/config/ConfigGeneral.qml:79 190 | msgctxt "@option:check" 191 | msgid "Fill width (don't work with Auto-hiding)" 192 | msgstr "Fülle Breite (funktioniert nicht mit automatischem Verstecken)" 193 | 194 | #: contents/ui/config/ConfigGeneral.qml:87 195 | msgid "Gravity:" 196 | msgstr "Schwerkraft" 197 | 198 | #: contents/ui/config/ConfigGeneral.qml:92 199 | msgid "Center" 200 | msgstr "Mitte" 201 | 202 | #: contents/ui/config/ConfigGeneral.qml:92 203 | msgid "North" 204 | msgstr "Nord" 205 | 206 | #: contents/ui/config/ConfigGeneral.qml:92 207 | msgid "South" 208 | msgstr "Süd" 209 | 210 | #: contents/ui/config/ConfigGeneral.qml:92 211 | msgid "East" 212 | msgstr "Ost" 213 | 214 | #: contents/ui/config/ConfigGeneral.qml:92 215 | msgid "West" 216 | msgstr "West" 217 | 218 | #: contents/ui/config/ConfigGeneral.qml:98 219 | msgctxt "@option:check" 220 | msgid "Flip" 221 | msgstr "" 222 | 223 | #: contents/ui/config/ConfigGeneral.qml:103 224 | msgctxt "@option:check" 225 | msgid "Hide tooltip" 226 | msgstr "Verstecke Hinweis" 227 | 228 | #: contents/ui/config/ConfigGeneral.qml:107 229 | msgid "Version:" 230 | msgstr "" 231 | 232 | #: contents/ui/ShaderSource.qml:54 233 | msgid "Error: Find undeclared arguments." 234 | msgstr "Fehler: Undeklarierte Argumente gefunden." 235 | 236 | #: contents/ui/Spectrum.qml:176 237 | msgid "" 238 | "Error: Failed to load the visual effect. Please choose another visual effect " 239 | "in the configuration dialog." 240 | msgstr "" 241 | "Fehler: Laden des visuellen Effektes fehlgeschlagen. Bitte anderen Effekt im " 242 | "Konfigurationsdialog auswählen." 243 | 244 | #: contents/ui/Spectrum.qml:177 245 | msgid "Error: Failed to compile image shader." 246 | msgstr "Fehler: Kompilieren der Bilderschattierung fehlgeschlagen." 247 | 248 | #: contents/ui/Spectrum.qml:178 249 | msgid "Error: Failed to compile bufffer shader." 250 | msgstr "" 251 | "Fehler: Kompilieren der zwischengespeicherten Schattierungen fehlgeschlagen." 252 | 253 | #~ msgctxt "@option:check" 254 | #~ msgid "Random effect (on startup)" 255 | #~ msgstr "Zufälliger Effekt (bei Start)" 256 | 257 | #~ msgid "Unwanted effects can be removed
from here." 258 | #~ msgstr "" 259 | #~ "Unerwünschte Effekte können
hier entfernt " 260 | #~ "werden." 261 | 262 | #~ msgid "" 263 | #~ "Download more effects" 265 | #~ msgstr "" 266 | #~ "Mehr Effekte herunterladen" 268 | -------------------------------------------------------------------------------- /translations/po/plasma_applet_panon_nl.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: \n" 9 | "Report-Msgid-Bugs-To: https://github.com/rbn42/panon/issues\n" 10 | "POT-Creation-Date: 2021-02-20 22:39+0800\n" 11 | "PO-Revision-Date: 2021-01-25 14:46+0100\n" 12 | "Last-Translator: Heimen Stoffels \n" 13 | "Language-Team: Dutch \n" 14 | "Language: nl\n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "X-Generator: Poedit 2.4.2\n" 19 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 20 | 21 | #: contents/config/config.qml:6 22 | msgctxt "@title" 23 | msgid "General" 24 | msgstr "Algemeen" 25 | 26 | #: contents/config/config.qml:11 27 | msgctxt "@title" 28 | msgid "Visual Effects" 29 | msgstr "Visuele effecten" 30 | 31 | #: contents/config/config.qml:16 32 | msgctxt "@title" 33 | msgid "Back-end" 34 | msgstr "Backend" 35 | 36 | #: contents/config/config.qml:21 37 | msgctxt "@title" 38 | msgid "Colors" 39 | msgstr "Kleuren" 40 | 41 | #: contents/ui/config/ConfigBackend.qml:31 42 | msgid "Back-end:" 43 | msgstr "Backend:" 44 | 45 | #: contents/ui/config/ConfigBackend.qml:43 46 | #: contents/ui/config/ConfigBackend.qml:58 47 | msgid "Input device:" 48 | msgstr "Invoerapparaat:" 49 | 50 | #: contents/ui/config/ConfigBackend.qml:76 51 | msgid "Fifo path:" 52 | msgstr "Fifo-pad:" 53 | 54 | #: contents/ui/config/ConfigBackend.qml:86 55 | msgctxt "@option:check" 56 | msgid "Reduce the weight of bass" 57 | msgstr "Basniveau verlagen" 58 | 59 | #: contents/ui/config/ConfigBackend.qml:92 60 | msgctxt "@option:check" 61 | msgid "Use GLDFT (to lower CPU Usage) (experimental, not recommended)" 62 | msgstr "" 63 | "GLDFT gebruiken (om het cpu-verbruik te verlagen - experimenteel en niet " 64 | "aanbevolen)" 65 | 66 | #: contents/ui/config/ConfigBackend.qml:97 67 | msgctxt "@option:check" 68 | msgid "Debug" 69 | msgstr "Foutopsporing" 70 | 71 | #: contents/ui/config/ConfigBackend.qml:101 72 | msgid "Audio frequency:" 73 | msgstr "Audiofrequentie:" 74 | 75 | #: contents/ui/config/ConfigBackend.qml:116 76 | msgid "Test your audio frequency." 77 | msgstr "Test uw audiofrequentie." 78 | 79 | #: contents/ui/config/ConfigBackend.qml:120 80 | msgid "Latency / resolution:" 81 | msgstr "Vertraging/Resolutie:" 82 | 83 | #: contents/ui/config/ConfigBackend.qml:195 84 | msgid "Monitor of Current Device" 85 | msgstr "" 86 | 87 | #: contents/ui/config/ConfigBackend.qml:196 88 | msgid "Mixing All Speakers" 89 | msgstr "Alle luidsprekers worden gemengd" 90 | 91 | #: contents/ui/config/ConfigBackend.qml:197 92 | msgid "Mixing All Microphones" 93 | msgstr "Alle microfoons worden gemengd" 94 | 95 | #: contents/ui/config/ConfigBackend.qml:198 96 | msgid "Mixing All Microphones and Speakers" 97 | msgstr "Alle microfoons en luidsprekers worden gemengd" 98 | 99 | #: contents/ui/config/ConfigColors.qml:28 100 | msgid "Randomize colors" 101 | msgstr "Willekeurige kleuren" 102 | 103 | #: contents/ui/config/ConfigColors.qml:49 104 | msgctxt "@label" 105 | msgid "Color space:" 106 | msgstr "Kleurruimte:" 107 | 108 | #: contents/ui/config/ConfigColors.qml:50 109 | msgctxt "@option:radio" 110 | msgid "HSL" 111 | msgstr "HSL" 112 | 113 | #: contents/ui/config/ConfigColors.qml:56 114 | msgctxt "@option:radio" 115 | msgid "HSLuv" 116 | msgstr "HSLuv" 117 | 118 | #: contents/ui/config/ConfigColors.qml:62 119 | #: contents/ui/config/ConfigColors.qml:82 120 | msgctxt "@label:spinbox" 121 | msgid "Hue from" 122 | msgstr "Tint van" 123 | 124 | #: contents/ui/config/ConfigColors.qml:72 125 | #: contents/ui/config/ConfigColors.qml:92 126 | msgctxt "@label:spinbox" 127 | msgid "Hue to" 128 | msgstr "Tint tot" 129 | 130 | #: contents/ui/config/ConfigColors.qml:102 131 | #: contents/ui/config/ConfigColors.qml:122 132 | msgctxt "@label:spinbox" 133 | msgid "Saturation" 134 | msgstr "Verzadiging" 135 | 136 | #: contents/ui/config/ConfigColors.qml:112 137 | #: contents/ui/config/ConfigColors.qml:132 138 | msgctxt "@label:spinbox" 139 | msgid "Lightness" 140 | msgstr "Belichting" 141 | 142 | #: contents/ui/config/ConfigEffect.qml:23 143 | msgid "Effects" 144 | msgstr "Effecten" 145 | 146 | #: contents/ui/config/ConfigEffect.qml:36 147 | msgid "Effect:" 148 | msgstr "Effect:" 149 | 150 | #: contents/ui/config/ConfigEffect.qml:50 151 | msgid "Hint:" 152 | msgstr "Hint:" 153 | 154 | #: contents/ui/config/ConfigGeneral.qml:31 155 | msgctxt "@label:spinbox" 156 | msgid "FPS:" 157 | msgstr "FPS:" 158 | 159 | #: contents/ui/config/ConfigGeneral.qml:39 160 | msgid "Lower FPS saves CPU and battries." 161 | msgstr "Verlaag het aantal fps om het cpu- en accuverbruik te verlagen." 162 | 163 | #: contents/ui/config/ConfigGeneral.qml:44 164 | msgctxt "@option:radio" 165 | msgid "Show FPS" 166 | msgstr "FPS tonen" 167 | 168 | #: contents/ui/config/ConfigGeneral.qml:53 169 | msgctxt "@option:radio" 170 | msgid "Auto-hide (when audio is gone)" 171 | msgstr "Automatisch verbergen (als er geen audio wordt afgespeeld)" 172 | 173 | #: contents/ui/config/ConfigGeneral.qml:62 174 | msgctxt "@option:radio" 175 | msgid "Animate auto-hiding" 176 | msgstr "Automatisch verbergen voorzien van effect" 177 | 178 | #: contents/ui/config/ConfigGeneral.qml:68 179 | msgctxt "@label:spinbox" 180 | msgid "Height:" 181 | msgstr "Hoogte:" 182 | 183 | #: contents/ui/config/ConfigGeneral.qml:68 184 | msgctxt "@label:spinbox" 185 | msgid "Width:" 186 | msgstr "Breedte:" 187 | 188 | #: contents/ui/config/ConfigGeneral.qml:79 189 | msgctxt "@option:check" 190 | msgid "Fill height (don't work with Auto-hiding)" 191 | msgstr "Hoogte opvullen (werkt niet i.c.m. automatisch verbergen)" 192 | 193 | #: contents/ui/config/ConfigGeneral.qml:79 194 | msgctxt "@option:check" 195 | msgid "Fill width (don't work with Auto-hiding)" 196 | msgstr "Breedte opvullen (werkt niet i.c.m. automatisch verbergen)" 197 | 198 | #: contents/ui/config/ConfigGeneral.qml:87 199 | msgid "Gravity:" 200 | msgstr "Zwaarte:" 201 | 202 | #: contents/ui/config/ConfigGeneral.qml:92 203 | msgid "Center" 204 | msgstr "Midden" 205 | 206 | #: contents/ui/config/ConfigGeneral.qml:92 207 | msgid "North" 208 | msgstr "Noorden" 209 | 210 | #: contents/ui/config/ConfigGeneral.qml:92 211 | msgid "South" 212 | msgstr "Zuiden" 213 | 214 | #: contents/ui/config/ConfigGeneral.qml:92 215 | msgid "East" 216 | msgstr "Oosten" 217 | 218 | #: contents/ui/config/ConfigGeneral.qml:92 219 | msgid "West" 220 | msgstr "Westen" 221 | 222 | #: contents/ui/config/ConfigGeneral.qml:98 223 | msgctxt "@option:check" 224 | msgid "Flip" 225 | msgstr "" 226 | 227 | #: contents/ui/config/ConfigGeneral.qml:103 228 | msgctxt "@option:check" 229 | msgid "Hide tooltip" 230 | msgstr "Hulpballon verbergen" 231 | 232 | #: contents/ui/config/ConfigGeneral.qml:107 233 | msgid "Version:" 234 | msgstr "Versie:" 235 | 236 | #: contents/ui/ShaderSource.qml:54 237 | msgid "Error: Find undeclared arguments." 238 | msgstr "Fout: niet-aangevulde aanvullende opties." 239 | 240 | #: contents/ui/Spectrum.qml:176 241 | msgid "" 242 | "Error: Failed to load the visual effect. Please choose another visual effect " 243 | "in the configuration dialog." 244 | msgstr "" 245 | "Fout: het visuele effect kan niet worden geladen. Kies een ander effect op " 246 | "het instellingenvenster." 247 | 248 | #: contents/ui/Spectrum.qml:177 249 | msgid "Error: Failed to compile image shader." 250 | msgstr "Fout: de afbeeldingsschaduw kan niet worden gecompileerd." 251 | 252 | #: contents/ui/Spectrum.qml:178 253 | msgid "Error: Failed to compile bufffer shader." 254 | msgstr "Fout: de bufferschaduw kan niet worden gecompileerd." 255 | -------------------------------------------------------------------------------- /translations/po/plasma_applet_panon_zh_CN.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: PACKAGE VERSION\n" 9 | "Report-Msgid-Bugs-To: https://github.com/rbn42/panon/issues\n" 10 | "POT-Creation-Date: 2021-02-20 22:39+0800\n" 11 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 12 | "Last-Translator: FULL NAME \n" 13 | "Language-Team: LANGUAGE \n" 14 | "Language: \n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | 19 | #: contents/config/config.qml:6 20 | msgctxt "@title" 21 | msgid "General" 22 | msgstr "常规" 23 | 24 | #: contents/config/config.qml:11 25 | msgctxt "@title" 26 | msgid "Visual Effects" 27 | msgstr "视觉特效" 28 | 29 | #: contents/config/config.qml:16 30 | msgctxt "@title" 31 | msgid "Back-end" 32 | msgstr "音频数据源" 33 | 34 | #: contents/config/config.qml:21 35 | msgctxt "@title" 36 | msgid "Colors" 37 | msgstr "色彩" 38 | 39 | #: contents/ui/config/ConfigBackend.qml:31 40 | msgid "Back-end:" 41 | msgstr "音频数据源:" 42 | 43 | #: contents/ui/config/ConfigBackend.qml:43 44 | #: contents/ui/config/ConfigBackend.qml:58 45 | msgid "Input device:" 46 | msgstr "输入设备:" 47 | 48 | #: contents/ui/config/ConfigBackend.qml:76 49 | msgid "Fifo path:" 50 | msgstr "fifo文件路径:" 51 | 52 | #: contents/ui/config/ConfigBackend.qml:86 53 | msgctxt "@option:check" 54 | msgid "Reduce the weight of bass" 55 | msgstr "减小低音权重" 56 | 57 | #: contents/ui/config/ConfigBackend.qml:92 58 | msgctxt "@option:check" 59 | msgid "Use GLDFT (to lower CPU Usage) (experimental, not recommended)" 60 | msgstr "" 61 | 62 | #: contents/ui/config/ConfigBackend.qml:97 63 | msgctxt "@option:check" 64 | msgid "Debug" 65 | msgstr "" 66 | 67 | #: contents/ui/config/ConfigBackend.qml:101 68 | msgid "Audio frequency:" 69 | msgstr "音域:" 70 | 71 | #: contents/ui/config/ConfigBackend.qml:116 72 | msgid "Test your audio frequency." 73 | msgstr "测试你的音域" 74 | 75 | #: contents/ui/config/ConfigBackend.qml:120 76 | msgid "Latency / resolution:" 77 | msgstr "延迟/分辨率" 78 | 79 | #: contents/ui/config/ConfigBackend.qml:195 80 | msgid "Monitor of Current Device" 81 | msgstr "当前设备的监视器" 82 | 83 | #: contents/ui/config/ConfigBackend.qml:196 84 | msgid "Mixing All Speakers" 85 | msgstr "混合所有扬声器" 86 | 87 | #: contents/ui/config/ConfigBackend.qml:197 88 | msgid "Mixing All Microphones" 89 | msgstr "混合所有麦克风" 90 | 91 | #: contents/ui/config/ConfigBackend.qml:198 92 | msgid "Mixing All Microphones and Speakers" 93 | msgstr "混合所有麦克风和扬声器" 94 | 95 | #: contents/ui/config/ConfigColors.qml:28 96 | msgid "Randomize colors" 97 | msgstr "生成随机颜色" 98 | 99 | #: contents/ui/config/ConfigColors.qml:49 100 | msgctxt "@label" 101 | msgid "Color space:" 102 | msgstr "色彩空间" 103 | 104 | #: contents/ui/config/ConfigColors.qml:50 105 | msgctxt "@option:radio" 106 | msgid "HSL" 107 | msgstr "" 108 | 109 | #: contents/ui/config/ConfigColors.qml:56 110 | msgctxt "@option:radio" 111 | msgid "HSLuv" 112 | msgstr "" 113 | 114 | #: contents/ui/config/ConfigColors.qml:62 115 | #: contents/ui/config/ConfigColors.qml:82 116 | msgctxt "@label:spinbox" 117 | msgid "Hue from" 118 | msgstr "色相起点" 119 | 120 | #: contents/ui/config/ConfigColors.qml:72 121 | #: contents/ui/config/ConfigColors.qml:92 122 | msgctxt "@label:spinbox" 123 | msgid "Hue to" 124 | msgstr "色相终点" 125 | 126 | #: contents/ui/config/ConfigColors.qml:102 127 | #: contents/ui/config/ConfigColors.qml:122 128 | msgctxt "@label:spinbox" 129 | msgid "Saturation" 130 | msgstr "饱和度" 131 | 132 | #: contents/ui/config/ConfigColors.qml:112 133 | #: contents/ui/config/ConfigColors.qml:132 134 | msgctxt "@label:spinbox" 135 | msgid "Lightness" 136 | msgstr "亮度" 137 | 138 | #: contents/ui/config/ConfigEffect.qml:23 139 | msgid "Effects" 140 | msgstr "特效" 141 | 142 | #: contents/ui/config/ConfigEffect.qml:36 143 | msgid "Effect:" 144 | msgstr "特效:" 145 | 146 | #: contents/ui/config/ConfigEffect.qml:50 147 | msgid "Hint:" 148 | msgstr "提示:" 149 | 150 | #: contents/ui/config/ConfigGeneral.qml:31 151 | msgctxt "@label:spinbox" 152 | msgid "FPS:" 153 | msgstr "" 154 | 155 | #: contents/ui/config/ConfigGeneral.qml:39 156 | msgid "Lower FPS saves CPU and battries." 157 | msgstr "更低的FPS能节省CPU和电池" 158 | 159 | #: contents/ui/config/ConfigGeneral.qml:44 160 | msgctxt "@option:radio" 161 | msgid "Show FPS" 162 | msgstr "显示FPS" 163 | 164 | #: contents/ui/config/ConfigGeneral.qml:53 165 | msgctxt "@option:radio" 166 | msgid "Auto-hide (when audio is gone)" 167 | msgstr "自动隐藏(当没有声音的时候)" 168 | 169 | #: contents/ui/config/ConfigGeneral.qml:62 170 | msgctxt "@option:radio" 171 | msgid "Animate auto-hiding" 172 | msgstr "自动隐藏动画" 173 | 174 | #: contents/ui/config/ConfigGeneral.qml:68 175 | msgctxt "@label:spinbox" 176 | msgid "Height:" 177 | msgstr "高度:" 178 | 179 | #: contents/ui/config/ConfigGeneral.qml:68 180 | msgctxt "@label:spinbox" 181 | msgid "Width:" 182 | msgstr "宽度:" 183 | 184 | #: contents/ui/config/ConfigGeneral.qml:79 185 | msgctxt "@option:check" 186 | msgid "Fill height (don't work with Auto-hiding)" 187 | msgstr "填充高度(不能和自动隐藏一起用)" 188 | 189 | #: contents/ui/config/ConfigGeneral.qml:79 190 | msgctxt "@option:check" 191 | msgid "Fill width (don't work with Auto-hiding)" 192 | msgstr "填充宽度(不能和自动隐藏一起用)" 193 | 194 | #: contents/ui/config/ConfigGeneral.qml:87 195 | msgid "Gravity:" 196 | msgstr "重力(反)方向:" 197 | 198 | #: contents/ui/config/ConfigGeneral.qml:92 199 | msgid "Center" 200 | msgstr "中心" 201 | 202 | #: contents/ui/config/ConfigGeneral.qml:92 203 | msgid "North" 204 | msgstr "北" 205 | 206 | #: contents/ui/config/ConfigGeneral.qml:92 207 | msgid "South" 208 | msgstr "南" 209 | 210 | #: contents/ui/config/ConfigGeneral.qml:92 211 | msgid "East" 212 | msgstr "东" 213 | 214 | #: contents/ui/config/ConfigGeneral.qml:92 215 | msgid "West" 216 | msgstr "西" 217 | 218 | #: contents/ui/config/ConfigGeneral.qml:98 219 | msgctxt "@option:check" 220 | msgid "Flip" 221 | msgstr "翻转" 222 | 223 | #: contents/ui/config/ConfigGeneral.qml:103 224 | msgctxt "@option:check" 225 | msgid "Hide tooltip" 226 | msgstr "隐藏提示窗口" 227 | 228 | #: contents/ui/config/ConfigGeneral.qml:107 229 | msgid "Version:" 230 | msgstr "版本:" 231 | 232 | #: contents/ui/ShaderSource.qml:54 233 | msgid "Error: Find undeclared arguments." 234 | msgstr "错误:发现未声明的参数" 235 | 236 | #: contents/ui/Spectrum.qml:176 237 | msgid "" 238 | "Error: Failed to load the visual effect. Please choose another visual effect " 239 | "in the configuration dialog." 240 | msgstr "错误:无法加载视觉特效,请从配置窗口中选择一个新的视觉特效." 241 | 242 | #: contents/ui/Spectrum.qml:177 243 | msgid "Error: Failed to compile image shader." 244 | msgstr "错误:无法编译image shader" 245 | 246 | #: contents/ui/Spectrum.qml:178 247 | msgid "Error: Failed to compile bufffer shader." 248 | msgstr "错误:无法编译buffer shader" 249 | 250 | #~ msgctxt "@option:check" 251 | #~ msgid "Invert" 252 | #~ msgstr "反转" 253 | 254 | #~ msgid "Any Active Speaker" 255 | #~ msgstr "任何一个活动的扬声器" 256 | 257 | #~ msgctxt "@option:check" 258 | #~ msgid "Random effect (on startup)" 259 | #~ msgstr "随机特效(启动的时候)" 260 | 261 | #~ msgid "Unwanted effects can be removed
from here." 262 | #~ msgstr "不想要的特效可以从这里删除" 263 | 264 | #~ msgid "" 265 | #~ "Download more effects" 267 | #~ msgstr "" 268 | #~ "下" 269 | #~ "载更多特效" 270 | --------------------------------------------------------------------------------