├── .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='