pax_global_header00006660000000000000000000000064141676436120014524gustar00rootroot0000000000000052 comment=e8977c124995a757b1a25eb93fb67e4949c5f80f elektroid-2.0/000077500000000000000000000000001416764361200133475ustar00rootroot00000000000000elektroid-2.0/.gitignore000066400000000000000000000011721416764361200153400ustar00rootroot00000000000000/autom4te.cache/ /config/ Makefile Makefile.in aclocal.m4 app.info compile config.h config.h.in config.log config.status configure configure.scan coverage_report depcomp install-sh libtool m4 missing stamp-h1 test-driver *~ *.html *.log *.o *.la *.so* *.a .deps *.tar* *.zip *.lo *.gcno *.gcda src/elektroid src/elektroid-cli src/*~ test/*.log test/*.trs config.guess config.sub ltmain.sh ABOUT-NLS config.rpath po/Makefile.in.in po/Makevars.template po/POTFILES po/Rules-quot po/boldquot.sed po/en@boldquot.header po/en@quot.header po/*.gmo po/insert-header.sin po/quot.sed po/remove-potcdate.sin po/stamp-po po/remove-potcdate.sed elektroid-2.0/AUTHORS000066400000000000000000000000501416764361200144120ustar00rootroot00000000000000David García Goñi elektroid-2.0/COPYING000066400000000000000000001045131416764361200144060ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . elektroid-2.0/ChangeLog000066400000000000000000000000001416764361200151070ustar00rootroot00000000000000elektroid-2.0/INSTALL000066400000000000000000000007751416764361200144110ustar00rootroot00000000000000As with other autotools project, you need to run the following commands. autoreconf --install ./configure make sudo make install The package dependencies for Debian based distributions are: automake libasound2-dev libgtk-3-dev libpulse-dev libsndfile1-dev libsamplerate0-dev libtool autopoint gettext You can easily install them by running sudo apt install automake libasound2-dev libgtk-3-dev libpulse-dev libsndfile1-dev libsamplerate0-dev libtool autopoint gettext. elektroid-2.0/Makefile.am000066400000000000000000000001371416764361200154040ustar00rootroot00000000000000ACLOCAL_AMFLAGS=-I m4 if CLI_ONLY SUBDIRS=src test man else SUBDIRS=src res test po man endif elektroid-2.0/NEWS000066400000000000000000000000001416764361200140340ustar00rootroot00000000000000elektroid-2.0/README000066400000000000000000000000001416764361200142150ustar00rootroot00000000000000elektroid-2.0/README.md000066400000000000000000000135741416764361200146400ustar00rootroot00000000000000# Elektroid Elektroid is a GNU/Linux transfer application for Elektron devices. It includes the `elektroid` GUI application and the `elektroid-cli` CLI application. Elektroid has been reported to work with Model:Samples, Model:Cycles, Digitakt, Digitone and Analog Rytm MKI and MKII. To use Elektroid, USB configuration must be set to `USB MIDI` or `USB AUDIO/MIDI` as it won't work in Overbridge mode. ## Installation As with other autotools project, you need to run the following commands. If you just want to compile `elektroid-cli`, pass `CLI_ONLY=yes` to `/configure`. ``` autoreconf --install ./configure make sudo make install ``` The package dependencies for Debian based distributions are: - automake - libtool - build-essential - libasound2-dev - libgtk-3-dev - libpulse-dev - libsndfile1-dev - libsamplerate0-dev - autopoint - gettext - zlib1g-dev - libjson-glib-dev - libzip-dev You can easily install them by running `sudo apt install automake libtool build-essential libasound2-dev libgtk-3-dev libpulse-dev libsndfile1-dev libsamplerate0-dev autopoint gettext zlib1g-dev libjson-glib-dev libzip-dev`. If you are only compiling the CLI, install the dependencies with `sudo apt install automake libtool build-essential libasound2-dev libglib2.0-dev libsndfile1-dev libsamplerate0-dev libtool zlib1g-dev libjson-glib-dev libzip-dev`. ## CLI `elektroid-cli` brings the same funcionality than `elektroid` to the command line. There are device commands and filesystem commands. The latter have the form `a-b` where `a` is a command and `b` is a filesystem, (e.g., `ls-project`, `download-sound`, `mkdir-sample`). Notice that the filesystem is always in the singular form and some older commands are deprecated but kept for compatibility reasons although there are not documented here. These are the available filesystems: * `sample` * `raw` * `preset` * `data` * `project` * `sound` Raw and data are intended to interface directly with the filesystems provided by the devices so the downloaded or uploaded files are **not** compatible with Elektron Transfer formats. Preset is a particular instance of raw and so are project and sound but regarding data. Thus, raw and data filesystems should be used only for testing and are **not** available in the GUI. These are the available commands: * `ls` or `list` * `mkdir` * `rmdir` or `rm` (both behave as `rm -rf`) * `mv` * `cp` * `cl`, clear item * `sw`, swap items * `ul` or `upload` * `dl` or `download` Keep in mind that not every filesystem implements all the commands. For instance, samples can not be swapped. Provided paths must always be prepended with the device id and a colon (':') e.g., `0:/incoming`. ### Device commands * `ld` or `ls-devices`, list compatible devices ``` $ elektroid-cli ld 0 Elektron Digitakt MIDI 1 ``` * `info` or `info-device`, show device info including device filesystems ``` $ elektroid-cli info 0 Digitakt 1.30 (Digitakt) filesystems=sample,data,project,sound ``` * `df` or `info-storage`, show size and use of +Drive and RAM ``` $ elektroid-cli df 0 Storage Size Used Available Use% +Drive 959.50MiB 198.20MiB 761.30MiB 20.66% RAM 64.00MiB 13.43MiB 50.57MiB 20.98% ``` * `upgrade`, upgrade firmware ``` $ elektroid-cli upgrade Digitakt_OS1.30.syx 0 ``` ### Sample, raw and preset commands * `ls-sample` It only works for directories. Notice that the first column is the file type, the second is the size, the third is an internal cksum and the last one is the sample name. ``` $ elektroid-cli ls-sample 0:/ D 0B 00000000 drum machines F 630.34KiB f8711cd9 saw F 1.29MiB 0bbc22bd square ``` * `mkdir-sample` ``` $ elektroid-cli mkdir-sample 0:/samples ``` * `rmdir-sample` ``` $ elektroid-cli rmdir-sample 0:/samples ``` * `ul-sample` ``` $ elektroid-cli ul-sample square.wav 0:/ ``` * `dl-sample` ``` $ elektroid-cli dl-sample 0:/square ``` * `mv-sample` ``` $ elektroid-cli mv-sample 0:/square 0:/sample ``` * `rm-sample` ``` $ elektroid-cli rm-sample 0:/sample ``` ### Data, sound and project commands There are a few things to clarify first. * All data commands are valid for both projects and sounds although the examples use just sounds. * All data commands that use paths to items and not directories use the item index instead the item name. Here are the commands. * `ls-data` It only works for directories. Notice that the first column is the file type, the second is the index, the third is the permissons in hexadecimal, the fourth indicates if the data in valid, the fifth indicates if it has metadatam, the sixth is the size and the last one is the item name. Permissions are 16 bits values but only 6 are used from bit 2 to bit 7 both included. From LSB to MSB, this permissions are read, write, clear, copy, swap, and move. ``` $ elektroid-cli ls-data 0:/ D -1 0000 0 0 0B projects D -1 0000 0 0 0B soundbanks ``` ``` $ elektroid-cli ls-data 0:/soundbanks/D F 1 0012 1 1 160B KICK F 2 0012 1 1 160B SNARE ``` * `cp-data` ``` $ elektroid-cli cp-data 0:/soundbanks/D/1 0:/soundbanks/D/3 $ elektroid-cli ls-data 0:/soundbanks/D F 1 0012 1 1 160B KICK F 2 0012 1 1 160B SNARE F 3 0012 1 1 160B KICK ``` * `sw-data` ``` $ elektroid-cli sw-data 0:/soundbanks/D/2 0:/soundbanks/D/3 $ elektroid-cli ls-data 0:/soundbanks/D F 1 0012 1 1 160B KICK F 2 0012 1 1 160B KICK F 3 0012 1 1 160B SNARE ``` * `mv-data` ``` $ elektroid-cli mv-data 0:/soundbanks/D/3 0:/soundbanks/D/1 $ elektroid-cli ls-data 0:/soundbanks/D F 1 0012 1 1 160B SNARE F 2 0012 1 1 160B KICK ``` * `cl-data` ``` $ elektroid-cli cl-data 0:/soundbanks/D/1 $ elektroid-cli ls-data 0:/soundbanks/D F 2 0012 1 1 160B KICK ``` * `dl-data` ``` $ elektroid-cli dl-data 0:/soundbanks/D/1 ``` * `ul-data` ``` $ elektroid-cli ul-data sound 0:/soundbanks/D ``` elektroid-2.0/THANKS000066400000000000000000000000441416764361200142600ustar00rootroot00000000000000Dennis Braun elektroid-2.0/configure.ac000066400000000000000000000032351416764361200156400ustar00rootroot00000000000000# -*- Autoconf -*- # Process this file with autoconf to produce a configure script. AC_PREREQ([2.69]) AC_INIT([elektroid],[2.0],[dagargo@gmail.com]) AC_CONFIG_SRCDIR([src]) AC_CONFIG_HEADERS([config.h]) AM_PROG_LIBTOOL AC_SEARCH_LIBS([sqrt], [m]) AC_CONFIG_MACRO_DIRS([m4]) AM_INIT_AUTOMAKE # Checks for programs. AC_PROG_CC # Define conditional prior to package checks AM_CONDITIONAL([CLI_ONLY], [test "${CLI_ONLY}" == yes]) AM_CONDITIONAL([GUI], [test "${CLI_ONLY}" != yes]) # Checks for libraries. PKG_CHECK_MODULES(ALSA, alsa >= 1.1.3) PKG_CHECK_MODULES(zlib, zlib >= 1.1.8) PKG_CHECK_MODULES(libzip, libzip >= 1.1.2) AM_COND_IF(GUI, [PKG_CHECK_MODULES([GTK], [gtk+-3.0])]) PKG_CHECK_MODULES(SNDFILE, sndfile >= 1.0.2, ac_cv_sndfile=1, ac_cv_sndfile=0) AC_DEFINE_UNQUOTED([HAVE_SNDFILE],${ac_cv_sndfile}, [Set to 1 if you have libsndfile.]) AC_SUBST(SNDFILE_CFLAGS) AC_SUBST(SNDFILE_LIBS) PKG_CHECK_MODULES(SAMPLERATE, samplerate >= 0.1.8, ac_cv_samplerate=1, ac_cv_samplerate=0) AC_DEFINE_UNQUOTED([HAVE_SAMPLERATE],${ac_cv_samplerate}, [Set to 1 if you have libsamplerate.]) AC_SUBST(SAMPLERATE_CFLAGS) AC_SUBST(SAMPLERATE_LIBS) AM_COND_IF(GUI, [PKG_CHECK_MODULES([PULSEAUDIO], [libpulse >= 5.0])]) AC_SUBST(PULSEAUDIO_CFLAGS) AC_SUBST(PULSEAUDIO_LIBS) AM_COND_IF(GUI, [ AM_GNU_GETTEXT([external]) AM_GNU_GETTEXT_VERSION([0.12.1]) ]) # Checks for header files. AC_CHECK_HEADERS([unistd.h limits.h]) # Checks for typedefs, structures, and compiler characteristics. # Checks for library functions. AC_FUNC_MALLOC AC_CONFIG_FILES([Makefile src/Makefile res/Makefile test/Makefile po/Makefile.in po/Makefile man/Makefile]) AC_OUTPUT elektroid-2.0/debian/000077500000000000000000000000001416764361200145715ustar00rootroot00000000000000elektroid-2.0/debian/changelog000066400000000000000000000007631416764361200164510ustar00rootroot00000000000000elektroid (1.2-1) unstable; urgency=medium * New upstream version 1.2 * Fix cross building * Bump dh-compat to 13 * Drop patches, applied by upstream * Drop obsolete --as-needed build-flag * d/upstream/metadata: Add archive and remove obsolete name field -- Dennis Braun Mon, 25 May 2020 17:09:24 +0200 elektroid (1.1-1) unstable; urgency=medium * Initial release (Closes: #953332) -- Dennis Braun Wed, 08 Apr 2020 21:46:46 +0200 elektroid-2.0/debian/control000066400000000000000000000023221416764361200161730ustar00rootroot00000000000000Source: elektroid Section: sound Priority: optional Maintainer: Debian Multimedia Maintainers Uploaders: Dennis Braun Build-Depends: debhelper-compat (= 13), libasound2-dev (>= 1.1.8), libgtk-3-dev, libpulse-dev, libsndfile1-dev (>= 1.0.2), libsamplerate0-dev (>= 0.1.9), zlib1g-dev, pkg-config Homepage: https://github.com/dagargo/elektroid Vcs-Browser: https://salsa.debian.org/multimedia-team/elektroid Vcs-Git: https://salsa.debian.org/multimedia-team/elektroid.git Standards-Version: 4.5.0 Rules-Requires-Root: no Package: elektroid Architecture: any Depends: ${misc:Depends}, ${shlibs:Depends} Description: Sample transfer application Elektroid is an sample transfer application for Elektron devices. . With elektroid you can easily load audio files like flac or wav into your elektron sampler. You can also transfer audio samples from your elektroid device to your local hard disk. . It has been reported to work with Model:Samples, Digitakt and Analog Rytm mk1 and mk2. . This package provides both the GUI and CLI application of elektroid. elektroid-2.0/debian/copyright000066400000000000000000000424461416764361200165360ustar00rootroot00000000000000Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: elektroid Upstream-Contact: https://github.com/dagargo/elektroid Source: https://github.com/dagargo/elektroid/releases Comment: This package was debianized by Dennis Braun on Sun, 08 Mar 2020 01:05:20 +0100. Files: * Copyright: 2019-2020 David García Goñi 2019 Olivier Humbert 2020 Dennis Braun License: GPL-3+ Files: debian/* Copyright: 2020 Dennis Braun License: GPL-3+ Files: res/elektroid.svg Copyright: 2020 David García Goñi License: CC-BY-SA-4.0 License: GPL-3+ This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. . This package is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. Comment: On Debian systems, the complete text of the GNU General Public License version 3 can be found in `/usr/share/common-licenses/GPL-3'. . You should have received a copy of the GNU General Public License along with this program. If not, see . License: CC-BY-SA-4.0 Creative Commons Attribution-ShareAlike 4.0 International Public License . By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution-ShareAlike 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions. . Section 1 -- Definitions. a. Adapted Material means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image. b. Adapter's License means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Material in accordance with the terms and conditions of this Public License. c. BY-SA Compatible License means a license listed at creativecommons.org/compatiblelicenses, approved by Creative Commons as essentially the equivalent of this Public License. d. Copyright and Similar Rights means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights. e. Effective Technological Measures means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements. f. Exceptions and Limitations means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material. g. License Elements means the license attributes listed in the name of a Creative Commons Public License. The License Elements of this Public License are Attribution and ShareAlike. h. Licensed Material means the artistic or literary work, database, or other material to which the Licensor applied this Public License. i. Licensed Rights means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license. j. Licensor means the individual(s) or entity(ies) granting rights under this Public License. k. Share means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them. l. Sui Generis Database Rights means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world. m. You means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning. . Section 2 -- Scope. a. License grant. 1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to: a. reproduce and Share the Licensed Material, in whole or in part; and b. produce, reproduce, and Share Adapted Material. 2. Exceptions and Limitations. For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions. 3. Term. The term of this Public License is specified in Section 6(a). 4. Media and formats; technical modifications allowed. The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a) (4) never produces Adapted Material. 5. Downstream recipients. a. Offer from the Licensor -- Licensed Material. Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License. b. Additional offer from the Licensor -- Adapted Material. Every recipient of Adapted Material from You automatically receives an offer from the Licensor to exercise the Licensed Rights in the Adapted Material under the conditions of the Adapter's License You apply. c. No downstream restrictions. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material. 6. No endorsement. Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i). b. Other rights. 1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise. 2. Patent and trademark rights are not licensed under this Public License. 3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties. . Section 3 -- License Conditions. Your exercise of the Licensed Rights is expressly made subject to the following conditions. a. Attribution. 1. If You Share the Licensed Material (including in modified form), You must: a. retain the following if it is supplied by the Licensor with the Licensed Material: i. identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated); ii. a copyright notice; iii. a notice that refers to this Public License; iv. a notice that refers to the disclaimer of warranties; v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable; b. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and c. indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License. 2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information. 3. If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable. b. ShareAlike. In addition to the conditions in Section 3(a), if You Share Adapted Material You produce, the following conditions also apply. 1. The Adapter's License You apply must be a Creative Commons license with the same License Elements, this version or later, or a BY-SA Compatible License. 2. You must include the text of, or the URI or hyperlink to, the Adapter's License You apply. You may satisfy this condition in any reasonable manner based on the medium, means, and context in which You Share Adapted Material. 3. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, Adapted Material that restrict exercise of the rights granted under the Adapter's License You apply. . Section 4 -- Sui Generis Database Rights. Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material: a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database; b. if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material, including for purposes of Section 3(b); and c. You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database. For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights. . Section 5 -- Disclaimer of Warranties and Limitation of Liability. a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. c. The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability. . Section 6 -- Term and Termination. a. This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically. b. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates: 1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or 2. upon express reinstatement by the Licensor. For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License. c. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License. d. Sections 1, 5, 6, 7, and 8 survive termination of this Public License. . Section 7 -- Other Terms and Conditions. a. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed. b. Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License. . Section 8 -- Interpretation. a. For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License. b. To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions. c. No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor. d. Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority. elektroid-2.0/debian/rules000077500000000000000000000005561416764361200156570ustar00rootroot00000000000000#!/usr/bin/make -f export DEB_BUILD_MAINT_OPTIONS = hardening=+all include /usr/share/dpkg/architecture.mk # Fix cross building ifneq ($(DEB_BUILD_GNU_TYPE),$(DEB_HOST_GNU_TYPE)) export ac_cv_func_malloc_0_nonnull=yes endif %: dh $@ --with autoreconf override_dh_auto_test: # Don't enable tests, if no elektron device is connected @echo "Tests disabled" elektroid-2.0/debian/source/000077500000000000000000000000001416764361200160715ustar00rootroot00000000000000elektroid-2.0/debian/source/format000066400000000000000000000000141416764361200172770ustar00rootroot000000000000003.0 (quilt) elektroid-2.0/debian/upstream/000077500000000000000000000000001416764361200164315ustar00rootroot00000000000000elektroid-2.0/debian/upstream/metadata000066400000000000000000000003631416764361200201360ustar00rootroot00000000000000Archive: github Bug-Database: https://github.com/dagargo/elektroid/issues Bug-Submit: https://github.com/dagargo/elektroid/issues/new Repository: https://github.com/dagargo/elektroid.git Repository-Browse: https://github.com/dagargo/elektroid elektroid-2.0/debian/watch000066400000000000000000000002321416764361200156170ustar00rootroot00000000000000version=4 opts=filenamemangle=s/.+\/v?(\d\S+)\.tar\.gz/elektroid-$1\.tar\.gz/ \ https://github.com/dagargo/elektroid/releases .*/v?(\d[\d\.]+)\.tar\.gz elektroid-2.0/man/000077500000000000000000000000001416764361200141225ustar00rootroot00000000000000elektroid-2.0/man/Makefile.am000066400000000000000000000001451416764361200161560ustar00rootroot00000000000000if CLI_ONLY dist_man1_MANS = elektroid-cli.1 else dist_man1_MANS = elektroid.1 elektroid-cli.1 endif elektroid-2.0/man/elektroid-cli.1000066400000000000000000000053411416764361200167360ustar00rootroot00000000000000.TH ELEKTROID-CLI "1" "Dec 2021" .SH NAME elektroid-cli \- transfer CLI application for Elektron devices .SH SYNOPSIS .B elektroid-cli .RI [ options ] .RI command .SH DESCRIPTION .B elektroid-cli provides the same funcionality than elektroid. See man elektroid for the GUI application. .PP Provided paths must always be prepended with the device id and a colon (':'), e.g. 0:/samples. Paths pointing to a data, sound or project file use the index of a file instead of its name. .SH DEVICE COMMANDS .TP [ \fBld\fR | \fBlist-devices\fR ] List compatible devices .TP [ \fBdf\fR | \fBinfo-storage\fR ] device_number Show size and use of +Drive and RAM where available .TP \fBinfo\fR device_number Show device info .TP \fBupgrade\fR firmware device_number Upgrade the device .SH FILESYSTEM COMMANDS .PP Filesystem commands must be followed by a hyphen ('-') and a filesystem name. The available filesystemas are sample, sound, project, data, preset and raw. Data is an unpackaged form of sound and project and so is raw with regards to preset. .TP [ \fBls\fR | \fBlist\fR ] device_number:path_to_directory List directory contents .TP \fBmkdir\fR device_number:path_to_directory Create a directory and its parent directories as needed .TP [ \fBrmdir\fR | \fBrm\fR ] device_number:path_to_directory Delete a directory recursively .TP [ \fBul\fR | \fBupload\fR ] file device_number:path_to_file_or_directory Upload a file. If the path does not exist it will be created. For the sample filesystem, the supported audio file formats are aiff, flac, ogg and wav. .TP [ \fBdl\fR | \fBdownload\fR ] device_number:path_to_file_or_directory Download a file into the current directory. For the sample filesystem, samples will be stored locally as 16-bit, 48kHz wav files. .TP \fBmv\fR device_number:path_to_file_or_directory device_number:path_to_file_or_directory Move a file. If the destination path does not exist, it will be created. .TP \fBrm\fR device_number:path_to_file Delete a file .TP \fBcl\fR device_number:path_to_file Clear file .TP \fBcp\fR device_number:path_to_file device_number:path_to_file Copy a file .TP \fBsw\fR device_number:path_to_file device_number:path_to_file Swap files .SH OPTIONS .TP \fB\-v\fR Show verbose output. Use it more than once for more verbosity. .PP .SH EXAMPLES .PP Upload a sample. .TP \tul-sample square.wav 0:/waveforms .PP Download a preset into the current directory. .TP \tdl-raw 0:/snare/snare.mc-snd .PP List projects. .TP \tls-project 0:/ .SH "SEE ALSO" The GitHub page provides some examples: .SH "AUTHOR" elektroid-cli was written by David García Goñi . .PP This manual page was written by Dennis Braun for the Debian project (but may be used by others). elektroid-2.0/man/elektroid.1000066400000000000000000000020371416764361200161700ustar00rootroot00000000000000.TH ELEKTROID "1" "Dec 2021" .SH NAME elektroid \- transfer application for Elektron devices .SH SYNOPSIS .B elektroid .RI [ options ] .SH DESCRIPTION .B Elektroid is a GNU/Linux transfer application for Elektron devices. See man elektroid-cli for the CLI application. .PP With Elektroid you can easily upload and download audio files, projects, sounds and presets to and from your Elektron device. It can also be used to send and receive MIDI SysEx files. .PP Elektroid has been reported to work with Model:Samples, Model:Cycles, Digitakt, Digitone and Analog Rytm MKI and MKII. .SH OPTIONS .TP \fB\-l\fR, \fB--local-directory\fR Open the provided local directory .TP \fB\-v\fR, \fB--verbose\fR Show verbose output. Use it more than once for more verbosity. .TP \fB\-h\fR, \fB--help\fR Show a little help about the options .PP .SH "AUTHOR" elektroid was written by David García Goñi . .PP This manual page was written by Dennis Braun for the Debian project (but may be used by others). elektroid-2.0/po/000077500000000000000000000000001416764361200137655ustar00rootroot00000000000000elektroid-2.0/po/LINGUAS000066400000000000000000000000251416764361200150070ustar00rootroot00000000000000ca de en es fr pt_BR elektroid-2.0/po/Makevars000066400000000000000000000065141416764361200154670ustar00rootroot00000000000000# Makefile variables for PO directory in any package using GNU gettext. # Usually the message domain is the same as the package name. DOMAIN = $(PACKAGE) # These two variables depend on the location of this directory. subdir = po top_builddir = .. # These options get passed to xgettext. XGETTEXT_OPTIONS = --keyword=_ --keyword=N_ --from-code=UTF-8 # This is the copyright holder that gets inserted into the header of the # $(DOMAIN).pot file. Set this to the copyright holder of the surrounding # package. (Note that the msgstr strings, extracted from the package's # sources, belong to the copyright holder of the package.) Translators are # expected to transfer the copyright for their translations to this person # or entity, or to disclaim their copyright. The empty string stands for # the public domain; in this case the translators are expected to disclaim # their copyright. COPYRIGHT_HOLDER = David García Goñi # This tells whether or not to prepend "GNU " prefix to the package # name that gets inserted into the header of the $(DOMAIN).pot file. # Possible values are "yes", "no", or empty. If it is empty, try to # detect it automatically by scanning the files in $(top_srcdir) for # "GNU packagename" string. PACKAGE_GNU = # This is the email address or URL to which the translators shall report # bugs in the untranslated strings: # - Strings which are not entire sentences, see the maintainer guidelines # in the GNU gettext documentation, section 'Preparing Strings'. # - Strings which use unclear terms or require additional context to be # understood. # - Strings which make invalid assumptions about notation of date, time or # money. # - Pluralisation problems. # - Incorrect English spelling. # - Incorrect formatting. # It can be your email address, or a mailing list address where translators # can write to without being subscribed, or the URL of a web page through # which the translators can contact you. MSGID_BUGS_ADDRESS = $(PACKAGE_BUGREPORT) # This is the list of locale categories, beyond LC_MESSAGES, for which the # message catalogs shall be used. It is usually empty. EXTRA_LOCALE_CATEGORIES = # This tells whether the $(DOMAIN).pot file contains messages with an 'msgctxt' # context. Possible values are "yes" and "no". Set this to yes if the # package uses functions taking also a message context, like pgettext(), or # if in $(XGETTEXT_OPTIONS) you define keywords with a context argument. USE_MSGCTXT = no # These options get passed to msgmerge. # Useful options are in particular: # --previous to keep previous msgids of translated messages, # --quiet to reduce the verbosity. MSGMERGE_OPTIONS = # These options get passed to msginit. # If you want to disable line wrapping when writing PO files, add # --no-wrap to MSGMERGE_OPTIONS, XGETTEXT_OPTIONS, and # MSGINIT_OPTIONS. MSGINIT_OPTIONS = # This tells whether or not to regenerate a PO file when $(DOMAIN).pot # has changed. Possible values are "yes" and "no". Set this to no if # the POT file is checked in the repository and the version control # program ignores timestamps. PO_DEPENDS_ON_POT = yes # This tells whether or not to forcibly update $(DOMAIN).pot and # regenerate PO files on "make dist". Possible values are "yes" and # "no". Set this to no if the POT file and PO files are maintained # externally. DIST_DEPENDS_ON_UPDATE_PO = yes elektroid-2.0/po/POTFILES.in000066400000000000000000000000361416764361200155410ustar00rootroot00000000000000src/elektroid.c res/gui.glade elektroid-2.0/po/ca.po000066400000000000000000000131441416764361200147130ustar00rootroot00000000000000# Catalan translations for Elektroid package. # Copyright (C) 2019 David García Goñi # This file is distributed under the same license as the Elektroid package. # David García Goñi , 2020. # msgid "" msgstr "" "Project-Id-Version: elektroid 1.4\n" "Report-Msgid-Bugs-To: dagargo@gmail.com\n" "POT-Creation-Date: 2021-12-01 18:58+0100\n" "PO-Revision-Date: 2020-04-26 13:00+0100\n" "Last-Translator: David García Goñi \n" "Language-Team: Catalan\n" "Language: ca\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: src/elektroid.c:249 msgid "Samples" msgstr "Mostres" #: src/elektroid.c:251 msgid "Presets" msgstr "Programes" #: src/elektroid.c:253 msgid "Projects" msgstr "Projectes" #: src/elektroid.c:255 msgid "Sounds" msgstr "Sos" #: src/elektroid.c:257 src/elektroid.c:1650 src/elektroid.c:1664 msgid "Undefined" msgstr "Indefinit" #: src/elektroid.c:398 #, c-format msgid "Connected to %s%s" msgstr "Connectat a %s%s" #: src/elektroid.c:406 src/elektroid.c:3215 msgid "Not connected" msgstr "No connectat" #: src/elektroid.c:549 msgid "Waiting..." msgstr "Esperant..." #: src/elektroid.c:552 msgid "Sending..." msgstr "Enviant..." #: src/elektroid.c:556 msgid "Receiving..." msgstr "Rebent..." #: src/elektroid.c:624 msgid "Receive SysEx" msgstr "Rep SysEx" #: src/elektroid.c:648 msgid "Save SysEx" msgstr "Alça SysEx" #: src/elektroid.c:651 src/elektroid.c:747 src/elektroid.c:915 msgid "_Cancel" msgstr "_Cancel·la" #: src/elektroid.c:653 msgid "_Save" msgstr "_Alça" #: src/elektroid.c:657 msgid "Received SysEx" msgstr "SysEx rebut" #: src/elektroid.c:662 src/elektroid.c:753 msgid "SysEx Files" msgstr "Fitxers SysEx" #: src/elektroid.c:696 #, c-format msgid "Error while saving “%s”: %s." msgstr "Error al guardar «%s»: %s." #: src/elektroid.c:744 msgid "Open SysEx" msgstr "Obri SysEx" #: src/elektroid.c:749 msgid "_Open" msgstr "_Obri" #: src/elektroid.c:770 #, c-format msgid "Error while loading “%s”: %s." msgstr "Error al carregar «%s»: %s." #: src/elektroid.c:780 msgid "Send SysEx" msgstr "Envia SysEx" #: src/elektroid.c:882 #, c-format msgid "Error while deleting “%s”: %s." msgstr "Error al eliminar «%s»: %s." #: src/elektroid.c:914 msgid "Are you sure you want to delete the selected items?" msgstr "Està segur de que vol eliminar els elements seleccionats?" #: src/elektroid.c:916 msgid "_Delete" msgstr "_Elimina" #: src/elektroid.c:969 res/gui.glade:130 res/gui.glade:193 msgid "Rename" msgstr "Canvia el nom" #: src/elektroid.c:987 #, c-format msgid "Error while renaming to “%s”: %s." msgstr "Error al canviar el nom a «%s»: %s." #: src/elektroid.c:1550 res/gui.glade:121 res/gui.glade:183 res/gui.glade:184 msgid "Add Directory" msgstr "Afig directori" #: src/elektroid.c:1568 #, c-format msgid "Error while creating dir “%s”: %s." msgstr "Error al crear el directori «%s»: %s." #: src/elektroid.c:1640 msgid "Queued" msgstr "A la cua" #: src/elektroid.c:1642 msgid "Running" msgstr "Executant" #: src/elektroid.c:1644 msgid "Completed" msgstr "Completada" #: src/elektroid.c:1646 msgid "Completed with errors" msgstr "Completada amb errors" #: src/elektroid.c:1648 msgid "Canceled" msgstr "Cancel·lada" #: src/elektroid.c:1660 msgid "Upload" msgstr "Càrrega" #: src/elektroid.c:1662 msgid "Download" msgstr "Descàrrega" #: src/elektroid.c:2518 src/elektroid.c:2559 #, c-format msgid "Error while moving from “%s” to “%s”: %s." msgstr "Error al moure de «%s» a «%s»: %s." #: res/gui.glade:70 msgid "Upload Selection" msgstr "Carrega la selecció" #: res/gui.glade:85 msgid "Play" msgstr "Reproduïx" #: res/gui.glade:99 msgid "Open With External Editor" msgstr "Obri amb l'editor extern" #: res/gui.glade:107 msgid "Show in File Manager" msgstr "Mostra al navegador de fitxers" #: res/gui.glade:139 res/gui.glade:202 msgid "Delete" msgstr "Elimina" #: res/gui.glade:168 msgid "Download Selection" msgstr "Descarrega la selecció" #: res/gui.glade:238 msgid "GNU/Linux transfer application for Elektron devices" msgstr "Aplicació GNU/Linux de transferència per a dispositius Elektron" #: res/gui.glade:241 msgid "translator-credits" msgstr "David García Goñi " #: res/gui.glade:289 msgid "_Receive SysEx" msgstr "_Rep SysEx" #: res/gui.glade:302 msgid "_Send SysEx" msgstr "_Envia SysEx" #: res/gui.glade:326 msgid "OS _Upgrade" msgstr "_Actualitza el SO" #: res/gui.glade:350 msgid "_About" msgstr "_Quant a" #: res/gui.glade:434 res/gui.glade:583 msgid "Go to Parent Directory" msgstr "Ves al directori pare" #: res/gui.glade:455 res/gui.glade:604 msgid "Refresh Directory" msgstr "Actualitza el directori" #: res/gui.glade:512 res/gui.glade:671 res/gui.glade:1362 msgid "Name" msgstr "Nom" #: res/gui.glade:526 res/gui.glade:685 msgid "Size" msgstr "Mida" #: res/gui.glade:738 msgid "Refresh Devices" msgstr "Actualitza els dispositius" #. It is recommended to split the text in two lines if it is too long #: res/gui.glade:856 msgid "Auto play" msgstr "" "Reproducció\n" "automàtica" #: res/gui.glade:1088 msgid "Status" msgstr "Estat" #: res/gui.glade:1099 msgid "Type" msgstr "Tipus" #: res/gui.glade:1120 msgid "Source" msgstr "Origen" #: res/gui.glade:1134 msgid "Destination" msgstr "Destinació" #: res/gui.glade:1149 msgid "Progress" msgstr "Progrés" #: res/gui.glade:1181 msgid "Cancel Tasks" msgstr "Cancel·la les tasques" #: res/gui.glade:1202 msgid "Remove Queued Tasks" msgstr "Elimina les tasques de la cua" #: res/gui.glade:1223 msgid "Clear Finished Tasks" msgstr "Neteja les tasques terminadas" elektroid-2.0/po/de.po000066400000000000000000000132431416764361200147200ustar00rootroot00000000000000# German translations for Elektroid package. # Copyright (C) 2020 Dennis Braun # This file is distributed under the same license as the Elektroid package. # Dennis Braun , 2020. # msgid "" msgstr "" "Project-Id-Version: elektroid 1.4\n" "Report-Msgid-Bugs-To: dagargo@gmail.com\n" "POT-Creation-Date: 2021-12-01 18:58+0100\n" "PO-Revision-Date: 2021-01-11 22:00+0100\n" "Last-Translator: Dennis Braun \n" "Language-Team: German\n" "Language: de\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" #: src/elektroid.c:249 msgid "Samples" msgstr "Samples" #: src/elektroid.c:251 msgid "Presets" msgstr "Presets" #: src/elektroid.c:253 msgid "Projects" msgstr "Projekte" #: src/elektroid.c:255 msgid "Sounds" msgstr "Sounds" #: src/elektroid.c:257 src/elektroid.c:1650 src/elektroid.c:1664 msgid "Undefined" msgstr "Undefiniert" #: src/elektroid.c:398 #, c-format msgid "Connected to %s%s" msgstr "Verbunden mit %s%s" #: src/elektroid.c:406 src/elektroid.c:3215 msgid "Not connected" msgstr "Nicht verbunden" #: src/elektroid.c:549 msgid "Waiting..." msgstr "Warte..." #: src/elektroid.c:552 msgid "Sending..." msgstr "Sende..." #: src/elektroid.c:556 msgid "Receiving..." msgstr "Empfange..." #: src/elektroid.c:624 msgid "Receive SysEx" msgstr "Empfange SysEx" #: src/elektroid.c:648 msgid "Save SysEx" msgstr "Speichere SysEx" #: src/elektroid.c:651 src/elektroid.c:747 src/elektroid.c:915 msgid "_Cancel" msgstr "_Abbrechen" #: src/elektroid.c:653 msgid "_Save" msgstr "_Speichern" #: src/elektroid.c:657 msgid "Received SysEx" msgstr "SysEx empfangen" #: src/elektroid.c:662 src/elektroid.c:753 msgid "SysEx Files" msgstr "SysEx Dateien" #: src/elektroid.c:696 #, fuzzy, c-format msgid "Error while saving “%s”: %s." msgstr "Fehler beim Löschen »%s«: %s." #: src/elektroid.c:744 msgid "Open SysEx" msgstr "Öffne SysEx" #: src/elektroid.c:749 msgid "_Open" msgstr "_Öffnen" #: src/elektroid.c:770 #, fuzzy, c-format msgid "Error while loading “%s”: %s." msgstr "Fehler beim Löschen »%s«: %s." #: src/elektroid.c:780 msgid "Send SysEx" msgstr "Sende SysEx" #: src/elektroid.c:882 #, c-format msgid "Error while deleting “%s”: %s." msgstr "Fehler beim Löschen »%s«: %s." #: src/elektroid.c:914 msgid "Are you sure you want to delete the selected items?" msgstr "Bist du sicher das du die ausgewählten Elemente löschen möchtest?" #: src/elektroid.c:916 msgid "_Delete" msgstr "_Löschen" #: src/elektroid.c:969 res/gui.glade:130 res/gui.glade:193 msgid "Rename" msgstr "Umbenennen" #: src/elektroid.c:987 #, c-format msgid "Error while renaming to “%s”: %s." msgstr "Fehler beim Umbenennen von »%s«: %s." #: src/elektroid.c:1550 res/gui.glade:121 res/gui.glade:183 res/gui.glade:184 msgid "Add Directory" msgstr "Verzeichnis hinzufügen" #: src/elektroid.c:1568 #, c-format msgid "Error while creating dir “%s”: %s." msgstr "Fehler beim Erstellen des Verzeichnisses »%s«: %s." #: src/elektroid.c:1640 msgid "Queued" msgstr "In der Warteschlange" #: src/elektroid.c:1642 msgid "Running" msgstr "Wird ausgeführt" #: src/elektroid.c:1644 msgid "Completed" msgstr "Fertig" #: src/elektroid.c:1646 msgid "Completed with errors" msgstr "Abgeschlossen mit Fehlern" #: src/elektroid.c:1648 msgid "Canceled" msgstr "Abgebrochen" #: src/elektroid.c:1660 msgid "Upload" msgstr "Hochladen" #: src/elektroid.c:1662 msgid "Download" msgstr "Herunterladen" #: src/elektroid.c:2518 src/elektroid.c:2559 #, c-format msgid "Error while moving from “%s” to “%s”: %s." msgstr "Fehler beim Verschieben von »%s« nach »%s«: %s." #: res/gui.glade:70 msgid "Upload Selection" msgstr "Upload Auswahl" #: res/gui.glade:85 msgid "Play" msgstr "Abspielen" #: res/gui.glade:99 msgid "Open With External Editor" msgstr "Öffnen mit externen Editor" #: res/gui.glade:107 msgid "Show in File Manager" msgstr "Im Dateimanager _Anzeigen" #: res/gui.glade:139 res/gui.glade:202 msgid "Delete" msgstr "Löschen" #: res/gui.glade:168 msgid "Download Selection" msgstr "Download Auswahl" #: res/gui.glade:238 #, fuzzy msgid "GNU/Linux transfer application for Elektron devices" msgstr "GNU/Linux Transfer Anwendung für Elektron Instrumente" #: res/gui.glade:241 msgid "translator-credits" msgstr "Dennis Braun " #: res/gui.glade:289 msgid "_Receive SysEx" msgstr "_Empfange SysEx" #: res/gui.glade:302 msgid "_Send SysEx" msgstr "_Sende SysEx" #: res/gui.glade:326 msgid "OS _Upgrade" msgstr "OS _Aktualisierung" #: res/gui.glade:350 msgid "_About" msgstr "_Info" #: res/gui.glade:434 res/gui.glade:583 msgid "Go to Parent Directory" msgstr "Gehe zum Überverzeichnis" #: res/gui.glade:455 res/gui.glade:604 msgid "Refresh Directory" msgstr "Aktualisiere Verzeichnis" #: res/gui.glade:512 res/gui.glade:671 res/gui.glade:1362 msgid "Name" msgstr "Name" #: res/gui.glade:526 res/gui.glade:685 msgid "Size" msgstr "Größe" #: res/gui.glade:738 msgid "Refresh Devices" msgstr "Aktualisiere Geräte" #. It is recommended to split the text in two lines if it is too long #: res/gui.glade:856 msgid "Auto play" msgstr "Auto Play" #: res/gui.glade:1088 msgid "Status" msgstr "Status" #: res/gui.glade:1099 msgid "Type" msgstr "Typ" #: res/gui.glade:1120 msgid "Source" msgstr "Quelle" #: res/gui.glade:1134 msgid "Destination" msgstr "Ziel" #: res/gui.glade:1149 msgid "Progress" msgstr "Fortschritt" #: res/gui.glade:1181 msgid "Cancel Tasks" msgstr "Aufgaben abbrechen" #: res/gui.glade:1202 msgid "Remove Queued Tasks" msgstr "Lösche Aufgaben in der Warteschlange" #: res/gui.glade:1223 msgid "Clear Finished Tasks" msgstr "Abgeschlossene Aufgaben aufräumen" elektroid-2.0/po/elektroid.pot000066400000000000000000000110171416764361200164730ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR David García Goñi # This file is distributed under the same license as the elektroid package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: elektroid 2.0-beta\n" "Report-Msgid-Bugs-To: dagargo@gmail.com\n" "POT-Creation-Date: 2021-12-01 18:58+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: src/elektroid.c:249 msgid "Samples" msgstr "" #: src/elektroid.c:251 msgid "Presets" msgstr "" #: src/elektroid.c:253 msgid "Projects" msgstr "" #: src/elektroid.c:255 msgid "Sounds" msgstr "" #: src/elektroid.c:257 src/elektroid.c:1650 src/elektroid.c:1664 msgid "Undefined" msgstr "" #: src/elektroid.c:398 #, c-format msgid "Connected to %s%s" msgstr "" #: src/elektroid.c:406 src/elektroid.c:3215 msgid "Not connected" msgstr "" #: src/elektroid.c:549 msgid "Waiting..." msgstr "" #: src/elektroid.c:552 msgid "Sending..." msgstr "" #: src/elektroid.c:556 msgid "Receiving..." msgstr "" #: src/elektroid.c:624 msgid "Receive SysEx" msgstr "" #: src/elektroid.c:648 msgid "Save SysEx" msgstr "" #: src/elektroid.c:651 src/elektroid.c:747 src/elektroid.c:915 msgid "_Cancel" msgstr "" #: src/elektroid.c:653 msgid "_Save" msgstr "" #: src/elektroid.c:657 msgid "Received SysEx" msgstr "" #: src/elektroid.c:662 src/elektroid.c:753 msgid "SysEx Files" msgstr "" #: src/elektroid.c:696 #, c-format msgid "Error while saving “%s”: %s." msgstr "" #: src/elektroid.c:744 msgid "Open SysEx" msgstr "" #: src/elektroid.c:749 msgid "_Open" msgstr "" #: src/elektroid.c:770 #, c-format msgid "Error while loading “%s”: %s." msgstr "" #: src/elektroid.c:780 msgid "Send SysEx" msgstr "" #: src/elektroid.c:882 #, c-format msgid "Error while deleting “%s”: %s." msgstr "" #: src/elektroid.c:914 msgid "Are you sure you want to delete the selected items?" msgstr "" #: src/elektroid.c:916 msgid "_Delete" msgstr "" #: src/elektroid.c:969 res/gui.glade:130 res/gui.glade:193 msgid "Rename" msgstr "" #: src/elektroid.c:987 #, c-format msgid "Error while renaming to “%s”: %s." msgstr "" #: src/elektroid.c:1550 res/gui.glade:121 res/gui.glade:183 res/gui.glade:184 msgid "Add Directory" msgstr "" #: src/elektroid.c:1568 #, c-format msgid "Error while creating dir “%s”: %s." msgstr "" #: src/elektroid.c:1640 msgid "Queued" msgstr "" #: src/elektroid.c:1642 msgid "Running" msgstr "" #: src/elektroid.c:1644 msgid "Completed" msgstr "" #: src/elektroid.c:1646 msgid "Completed with errors" msgstr "" #: src/elektroid.c:1648 msgid "Canceled" msgstr "" #: src/elektroid.c:1660 msgid "Upload" msgstr "" #: src/elektroid.c:1662 msgid "Download" msgstr "" #: src/elektroid.c:2518 src/elektroid.c:2559 #, c-format msgid "Error while moving from “%s” to “%s”: %s." msgstr "" #: res/gui.glade:70 msgid "Upload Selection" msgstr "" #: res/gui.glade:85 msgid "Play" msgstr "" #: res/gui.glade:99 msgid "Open With External Editor" msgstr "" #: res/gui.glade:107 msgid "Show in File Manager" msgstr "" #: res/gui.glade:139 res/gui.glade:202 msgid "Delete" msgstr "" #: res/gui.glade:168 msgid "Download Selection" msgstr "" #: res/gui.glade:238 msgid "GNU/Linux transfer application for Elektron devices" msgstr "" #: res/gui.glade:241 msgid "translator-credits" msgstr "" #: res/gui.glade:289 msgid "_Receive SysEx" msgstr "" #: res/gui.glade:302 msgid "_Send SysEx" msgstr "" #: res/gui.glade:326 msgid "OS _Upgrade" msgstr "" #: res/gui.glade:350 msgid "_About" msgstr "" #: res/gui.glade:434 res/gui.glade:583 msgid "Go to Parent Directory" msgstr "" #: res/gui.glade:455 res/gui.glade:604 msgid "Refresh Directory" msgstr "" #: res/gui.glade:512 res/gui.glade:671 res/gui.glade:1362 msgid "Name" msgstr "" #: res/gui.glade:526 res/gui.glade:685 msgid "Size" msgstr "" #: res/gui.glade:738 msgid "Refresh Devices" msgstr "" #. It is recommended to split the text in two lines if it is too long #: res/gui.glade:856 msgid "Auto play" msgstr "" #: res/gui.glade:1088 msgid "Status" msgstr "" #: res/gui.glade:1099 msgid "Type" msgstr "" #: res/gui.glade:1120 msgid "Source" msgstr "" #: res/gui.glade:1134 msgid "Destination" msgstr "" #: res/gui.glade:1149 msgid "Progress" msgstr "" #: res/gui.glade:1181 msgid "Cancel Tasks" msgstr "" #: res/gui.glade:1202 msgid "Remove Queued Tasks" msgstr "" #: res/gui.glade:1223 msgid "Clear Finished Tasks" msgstr "" elektroid-2.0/po/en.po000066400000000000000000000127611416764361200147360ustar00rootroot00000000000000# English translations for Elektroid package. # Copyright (C) 2019 David García Goñi # This file is distributed under the same license as the Elektroid package. # David García Goñi , 2019. # msgid "" msgstr "" "Project-Id-Version: elektroid 1.4\n" "Report-Msgid-Bugs-To: dagargo@gmail.com\n" "POT-Creation-Date: 2021-12-01 18:58+0100\n" "PO-Revision-Date: 2020-04-26 13:00+0100\n" "Last-Translator: David García Goñi \n" "Language-Team: English\n" "Language: en\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: src/elektroid.c:249 msgid "Samples" msgstr "Samples" #: src/elektroid.c:251 msgid "Presets" msgstr "Presets" #: src/elektroid.c:253 msgid "Projects" msgstr "Projects" #: src/elektroid.c:255 msgid "Sounds" msgstr "Sounds" #: src/elektroid.c:257 src/elektroid.c:1650 src/elektroid.c:1664 msgid "Undefined" msgstr "Undefined" #: src/elektroid.c:398 #, c-format msgid "Connected to %s%s" msgstr "Connected to %s%s" #: src/elektroid.c:406 src/elektroid.c:3215 msgid "Not connected" msgstr "Not connected" #: src/elektroid.c:549 msgid "Waiting..." msgstr "Waiting..." #: src/elektroid.c:552 msgid "Sending..." msgstr "Sending..." #: src/elektroid.c:556 msgid "Receiving..." msgstr "Receiving..." #: src/elektroid.c:624 msgid "Receive SysEx" msgstr "Receive SysEx" #: src/elektroid.c:648 msgid "Save SysEx" msgstr "Save SysEx" #: src/elektroid.c:651 src/elektroid.c:747 src/elektroid.c:915 msgid "_Cancel" msgstr "_Cancel" #: src/elektroid.c:653 msgid "_Save" msgstr "_Save" #: src/elektroid.c:657 msgid "Received SysEx" msgstr "Received SysEx" #: src/elektroid.c:662 src/elektroid.c:753 msgid "SysEx Files" msgstr "SysEx Files" #: src/elektroid.c:696 #, c-format msgid "Error while saving “%s”: %s." msgstr "Error while saving “%s”: %s." #: src/elektroid.c:744 msgid "Open SysEx" msgstr "Open SysEx" #: src/elektroid.c:749 msgid "_Open" msgstr "_Open" #: src/elektroid.c:770 #, c-format msgid "Error while loading “%s”: %s." msgstr "Error while loading “%s”: %s." #: src/elektroid.c:780 msgid "Send SysEx" msgstr "Send SysEx" #: src/elektroid.c:882 #, c-format msgid "Error while deleting “%s”: %s." msgstr "Error while deleting “%s”: %s." #: src/elektroid.c:914 msgid "Are you sure you want to delete the selected items?" msgstr "Are you sure you want to delete the selected items?" #: src/elektroid.c:916 msgid "_Delete" msgstr "_Delete" #: src/elektroid.c:969 res/gui.glade:130 res/gui.glade:193 msgid "Rename" msgstr "Rename" #: src/elektroid.c:987 #, c-format msgid "Error while renaming to “%s”: %s." msgstr "Error while renaming to “%s”: %s." #: src/elektroid.c:1550 res/gui.glade:121 res/gui.glade:183 res/gui.glade:184 msgid "Add Directory" msgstr "Add Directory" #: src/elektroid.c:1568 #, c-format msgid "Error while creating dir “%s”: %s." msgstr "Error while creating dir “%s”: %s." #: src/elektroid.c:1640 msgid "Queued" msgstr "Queued" #: src/elektroid.c:1642 msgid "Running" msgstr "Running" #: src/elektroid.c:1644 msgid "Completed" msgstr "Completed" #: src/elektroid.c:1646 msgid "Completed with errors" msgstr "Completed with errors" #: src/elektroid.c:1648 msgid "Canceled" msgstr "Canceled" #: src/elektroid.c:1660 msgid "Upload" msgstr "Upload" #: src/elektroid.c:1662 msgid "Download" msgstr "Download" #: src/elektroid.c:2518 src/elektroid.c:2559 #, c-format msgid "Error while moving from “%s” to “%s”: %s." msgstr "Error while moving from “%s” to “%s”: %s." #: res/gui.glade:70 msgid "Upload Selection" msgstr "Upload Selection" #: res/gui.glade:85 msgid "Play" msgstr "Play" #: res/gui.glade:99 msgid "Open With External Editor" msgstr "Open With External Editor" #: res/gui.glade:107 msgid "Show in File Manager" msgstr "Show in File Manager" #: res/gui.glade:139 res/gui.glade:202 msgid "Delete" msgstr "Delete" #: res/gui.glade:168 msgid "Download Selection" msgstr "Download selection" #: res/gui.glade:238 msgid "GNU/Linux transfer application for Elektron devices" msgstr "GNU/Linux transfer application for Elektron devices" #: res/gui.glade:241 msgid "translator-credits" msgstr "David García Goñi " #: res/gui.glade:289 msgid "_Receive SysEx" msgstr "_Receive SysEx" #: res/gui.glade:302 msgid "_Send SysEx" msgstr "_Send SysEx" #: res/gui.glade:326 msgid "OS _Upgrade" msgstr "OS _Upgrade" #: res/gui.glade:350 msgid "_About" msgstr "_About" #: res/gui.glade:434 res/gui.glade:583 msgid "Go to Parent Directory" msgstr "Go to Parent Directory" #: res/gui.glade:455 res/gui.glade:604 msgid "Refresh Directory" msgstr "Refresh Directory" #: res/gui.glade:512 res/gui.glade:671 res/gui.glade:1362 msgid "Name" msgstr "Name" #: res/gui.glade:526 res/gui.glade:685 msgid "Size" msgstr "Size" #: res/gui.glade:738 msgid "Refresh Devices" msgstr "Refresh Devices" #. It is recommended to split the text in two lines if it is too long #: res/gui.glade:856 msgid "Auto play" msgstr "Auto play" #: res/gui.glade:1088 msgid "Status" msgstr "Status" #: res/gui.glade:1099 msgid "Type" msgstr "Type" #: res/gui.glade:1120 msgid "Source" msgstr "Source" #: res/gui.glade:1134 msgid "Destination" msgstr "Destination" #: res/gui.glade:1149 msgid "Progress" msgstr "Progress" #: res/gui.glade:1181 msgid "Cancel Tasks" msgstr "Cancel Tasks" #: res/gui.glade:1202 msgid "Remove Queued Tasks" msgstr "Remove Queued Tasks" #: res/gui.glade:1223 msgid "Clear Finished Tasks" msgstr "Clear Finished Tasks" elektroid-2.0/po/es.po000066400000000000000000000131471416764361200147420ustar00rootroot00000000000000# Spanish translations for Elektroid package. # Copyright (C) 2019 David García Goñi # This file is distributed under the same license as the Elektroid package. # David García Goñi , 2019. # msgid "" msgstr "" "Project-Id-Version: elektroid 1.4\n" "Report-Msgid-Bugs-To: dagargo@gmail.com\n" "POT-Creation-Date: 2021-12-01 18:58+0100\n" "PO-Revision-Date: 2020-04-26 13:00+0100\n" "Last-Translator: David García Goñi \n" "Language-Team: Spanish\n" "Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: src/elektroid.c:249 msgid "Samples" msgstr "Muestras" #: src/elektroid.c:251 msgid "Presets" msgstr "Programas" #: src/elektroid.c:253 msgid "Projects" msgstr "Proyectos" #: src/elektroid.c:255 msgid "Sounds" msgstr "Sonidos" #: src/elektroid.c:257 src/elektroid.c:1650 src/elektroid.c:1664 msgid "Undefined" msgstr "Indefinido" #: src/elektroid.c:398 #, c-format msgid "Connected to %s%s" msgstr "Conectado a %s%s" #: src/elektroid.c:406 src/elektroid.c:3215 msgid "Not connected" msgstr "No conectado" #: src/elektroid.c:549 msgid "Waiting..." msgstr "Esperando..." #: src/elektroid.c:552 msgid "Sending..." msgstr "Enviando..." #: src/elektroid.c:556 msgid "Receiving..." msgstr "Recibiendo..." #: src/elektroid.c:624 msgid "Receive SysEx" msgstr "Recibir SysEx" #: src/elektroid.c:648 msgid "Save SysEx" msgstr "Guardar SysEx" #: src/elektroid.c:651 src/elektroid.c:747 src/elektroid.c:915 msgid "_Cancel" msgstr "_Cancelar" #: src/elektroid.c:653 msgid "_Save" msgstr "_Guardar" #: src/elektroid.c:657 msgid "Received SysEx" msgstr "SysEx recibido" #: src/elektroid.c:662 src/elektroid.c:753 msgid "SysEx Files" msgstr "Archivos SysEx" #: src/elektroid.c:696 #, c-format msgid "Error while saving “%s”: %s." msgstr "Error al guardar «%s»: %s." #: src/elektroid.c:744 msgid "Open SysEx" msgstr "Abrir SysEx" #: src/elektroid.c:749 msgid "_Open" msgstr "_Abrir" #: src/elektroid.c:770 #, c-format msgid "Error while loading “%s”: %s." msgstr "Error al cargar «%s»: %s." #: src/elektroid.c:780 msgid "Send SysEx" msgstr "Enviar SysEx" #: src/elektroid.c:882 #, c-format msgid "Error while deleting “%s”: %s." msgstr "Error al eliminar «%s»: %s." #: src/elektroid.c:914 msgid "Are you sure you want to delete the selected items?" msgstr "¿Está seguro de que quiere eliminar los elementos seleccionados?" #: src/elektroid.c:916 msgid "_Delete" msgstr "_Eliminar" #: src/elektroid.c:969 res/gui.glade:130 res/gui.glade:193 msgid "Rename" msgstr "Renombrar" #: src/elektroid.c:987 #, c-format msgid "Error while renaming to “%s”: %s." msgstr "Error al renombrar a «%s»: %s." #: src/elektroid.c:1550 res/gui.glade:121 res/gui.glade:183 res/gui.glade:184 msgid "Add Directory" msgstr "Añadir directorio" #: src/elektroid.c:1568 #, c-format msgid "Error while creating dir “%s”: %s." msgstr "Error al crear el directorio «%s»: %s." #: src/elektroid.c:1640 msgid "Queued" msgstr "Encolada" #: src/elektroid.c:1642 msgid "Running" msgstr "Ejecutando" #: src/elektroid.c:1644 msgid "Completed" msgstr "Completada" #: src/elektroid.c:1646 msgid "Completed with errors" msgstr "Completada con errores" #: src/elektroid.c:1648 msgid "Canceled" msgstr "Cancelada" #: src/elektroid.c:1660 msgid "Upload" msgstr "Carga" #: src/elektroid.c:1662 msgid "Download" msgstr "Descarga" #: src/elektroid.c:2518 src/elektroid.c:2559 #, c-format msgid "Error while moving from “%s” to “%s”: %s." msgstr "Error al mover de «%s» a «%s»: %s." #: res/gui.glade:70 msgid "Upload Selection" msgstr "Cargar selección" #: res/gui.glade:85 msgid "Play" msgstr "Reproducir" #: res/gui.glade:99 msgid "Open With External Editor" msgstr "Abrir con editor externo" #: res/gui.glade:107 msgid "Show in File Manager" msgstr "Mostrar en gestor de archivos" #: res/gui.glade:139 res/gui.glade:202 msgid "Delete" msgstr "Eliminar" #: res/gui.glade:168 msgid "Download Selection" msgstr "Descargar selección" #: res/gui.glade:238 msgid "GNU/Linux transfer application for Elektron devices" msgstr "Aplicación GNU/Linux de transferencia para dispositivos Elektron" #: res/gui.glade:241 msgid "translator-credits" msgstr "David García Goñi " #: res/gui.glade:289 msgid "_Receive SysEx" msgstr "_Recibir SysEx" #: res/gui.glade:302 msgid "_Send SysEx" msgstr "_Enviar SysEx" #: res/gui.glade:326 msgid "OS _Upgrade" msgstr "Actualizar _SO" #: res/gui.glade:350 msgid "_About" msgstr "_Acerca de" #: res/gui.glade:434 res/gui.glade:583 msgid "Go to Parent Directory" msgstr "Ir al directorio padre" #: res/gui.glade:455 res/gui.glade:604 msgid "Refresh Directory" msgstr "Actualizar directorio" #: res/gui.glade:512 res/gui.glade:671 res/gui.glade:1362 msgid "Name" msgstr "Nombre" #: res/gui.glade:526 res/gui.glade:685 msgid "Size" msgstr "Tamaño" #: res/gui.glade:738 msgid "Refresh Devices" msgstr "Actualizar dispositivos" #. It is recommended to split the text in two lines if it is too long #: res/gui.glade:856 msgid "Auto play" msgstr "" "Reproducción\n" "automática" #: res/gui.glade:1088 msgid "Status" msgstr "Estado" #: res/gui.glade:1099 msgid "Type" msgstr "Tipo" #: res/gui.glade:1120 msgid "Source" msgstr "Origen" #: res/gui.glade:1134 msgid "Destination" msgstr "Destino" #: res/gui.glade:1149 msgid "Progress" msgstr "Progreso" #: res/gui.glade:1181 msgid "Cancel Tasks" msgstr "Cancelar tareas" #: res/gui.glade:1202 msgid "Remove Queued Tasks" msgstr "Eliminar tareas encoladas" #: res/gui.glade:1223 msgid "Clear Finished Tasks" msgstr "Limpiar tareas terminadas" elektroid-2.0/po/fr.po000066400000000000000000000135511416764361200147410ustar00rootroot00000000000000# French GUI translation for Elektroid. # Copyright (C) 2019-2021 Olivier Humbert # This file is distributed under the same license as the Elektroid package. # Olivier Humbert , 2019-2021. # msgid "" msgstr "" "Project-Id-Version: elektroid 2.0\n" "Report-Msgid-Bugs-To: dagargo@gmail.com\n" "POT-Creation-Date: 2021-12-01 18:58+0100\n" "PO-Revision-Date: 2021-12-03 17:59+0100\n" "Last-Translator: Olivier Humbert \n" "Language-Team: French\n" "Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" #: src/elektroid.c:249 msgid "Samples" msgstr "Échantillons" #: src/elektroid.c:251 msgid "Presets" msgstr "Préréglages" #: src/elektroid.c:253 msgid "Projects" msgstr "Projets" #: src/elektroid.c:255 msgid "Sounds" msgstr "Sons" #: src/elektroid.c:257 src/elektroid.c:1650 src/elektroid.c:1664 msgid "Undefined" msgstr "Indéfini" #: src/elektroid.c:398 #, c-format msgid "Connected to %s%s" msgstr "Connecté à %s%s" #: src/elektroid.c:406 src/elektroid.c:3215 msgid "Not connected" msgstr "Non connecté" #: src/elektroid.c:549 msgid "Waiting..." msgstr "En attente..." #: src/elektroid.c:552 msgid "Sending..." msgstr "Envoi en cours..." #: src/elektroid.c:556 msgid "Receiving..." msgstr "Réception en cours..." #: src/elektroid.c:624 msgid "Receive SysEx" msgstr "Réception de SysEx" #: src/elektroid.c:648 msgid "Save SysEx" msgstr "Sauvegarde de SysEx" #: src/elektroid.c:651 src/elektroid.c:747 src/elektroid.c:915 msgid "_Cancel" msgstr "_Annuler" #: src/elektroid.c:653 msgid "_Save" msgstr "_Sauvegarder" #: src/elektroid.c:657 msgid "Received SysEx" msgstr "SysEx reçu" #: src/elektroid.c:662 src/elektroid.c:753 msgid "SysEx Files" msgstr "Fichiers SysEx" #: src/elektroid.c:696 #, c-format msgid "Error while saving “%s”: %s." msgstr "Erreur lors de la sauvegarde de « %s » : %s." #: src/elektroid.c:744 msgid "Open SysEx" msgstr "Ouvrir SysEx" #: src/elektroid.c:749 msgid "_Open" msgstr "_Ouvrir" #: src/elektroid.c:770 #, c-format msgid "Error while loading “%s”: %s." msgstr "Erreur lors du chargement de « %s » : %s." #: src/elektroid.c:780 msgid "Send SysEx" msgstr "Envoyer SysEx" #: src/elektroid.c:882 #, c-format msgid "Error while deleting “%s”: %s." msgstr "Erreur lors de la suppression de « %s » : %s." #: src/elektroid.c:914 msgid "Are you sure you want to delete the selected items?" msgstr "Souhaitez-vous vraiment supprimer les éléments sélectionnés ?" #: src/elektroid.c:916 msgid "_Delete" msgstr "_Supprimer" #: src/elektroid.c:969 res/gui.glade:130 res/gui.glade:193 msgid "Rename" msgstr "Renommer" #: src/elektroid.c:987 #, c-format msgid "Error while renaming to “%s”: %s." msgstr "Erreur lors du renommage de « %s » : %s." #: src/elektroid.c:1550 res/gui.glade:121 res/gui.glade:183 res/gui.glade:184 msgid "Add Directory" msgstr "Ajout d'un répertoire" #: src/elektroid.c:1568 #, c-format msgid "Error while creating dir “%s”: %s." msgstr "Erreur lors de la création du répertoire « %s » : %s." #: src/elektroid.c:1640 msgid "Queued" msgstr "Mise en file d'attente" #: src/elektroid.c:1642 msgid "Running" msgstr "En cours" #: src/elektroid.c:1644 msgid "Completed" msgstr "Terminé" #: src/elektroid.c:1646 msgid "Completed with errors" msgstr "Terminé avec des erreurs" #: src/elektroid.c:1648 msgid "Canceled" msgstr "Annulé" #: src/elektroid.c:1660 msgid "Upload" msgstr "Téléversement" #: src/elektroid.c:1662 msgid "Download" msgstr "Téléchargement" #: src/elektroid.c:2518 src/elektroid.c:2559 #, c-format msgid "Error while moving from “%s” to “%s”: %s." msgstr "Erreur lors du passage de « %s » à « %s » : %s." #: res/gui.glade:70 msgid "Upload Selection" msgstr "Téléversement de la sélection" #: res/gui.glade:85 msgid "Play" msgstr "Lecture" #: res/gui.glade:99 msgid "Open With External Editor" msgstr "Ouvrir avec un éditeur externe" #: res/gui.glade:107 msgid "Show in File Manager" msgstr "Afficher dans un ge_stionnaire de fichier" #: res/gui.glade:139 res/gui.glade:202 msgid "Delete" msgstr "Supprimer" #: res/gui.glade:168 msgid "Download Selection" msgstr "Téléchargement de la sélection" #: res/gui.glade:238 msgid "GNU/Linux transfer application for Elektron devices" msgstr "Application GNU/Linux de transfert pour les périphériques Elektron" #: res/gui.glade:241 msgid "translator-credits" msgstr "Olivier Humbert " #: res/gui.glade:289 msgid "_Receive SysEx" msgstr "_Réception de SysEx" #: res/gui.glade:302 msgid "_Send SysEx" msgstr "Envoi de _Sysex" #: res/gui.glade:326 msgid "OS _Upgrade" msgstr "Mise à jo_ur SE" #: res/gui.glade:350 msgid "_About" msgstr "À _propos" #: res/gui.glade:434 res/gui.glade:583 msgid "Go to Parent Directory" msgstr "Aller au répertoire parent" #: res/gui.glade:455 res/gui.glade:604 msgid "Refresh Directory" msgstr "Rafraîchir le répertoire" #: res/gui.glade:512 res/gui.glade:671 res/gui.glade:1362 msgid "Name" msgstr "Nom" #: res/gui.glade:526 res/gui.glade:685 msgid "Size" msgstr "Taille" #: res/gui.glade:738 msgid "Refresh Devices" msgstr "Rafraîchir les périphériques" #. It is recommended to split the text in two lines if it is too long #: res/gui.glade:856 msgid "Auto play" msgstr "" "Lecture\n" "automatique" #: res/gui.glade:1088 msgid "Status" msgstr "Statut" #: res/gui.glade:1099 msgid "Type" msgstr "Type" #: res/gui.glade:1120 msgid "Source" msgstr "Source" #: res/gui.glade:1134 msgid "Destination" msgstr "Destination" #: res/gui.glade:1149 msgid "Progress" msgstr "Progrès" #: res/gui.glade:1181 msgid "Cancel Tasks" msgstr "Annuler les tâches" #: res/gui.glade:1202 msgid "Remove Queued Tasks" msgstr "Supprimer les tâches dans la file d'attente" #: res/gui.glade:1223 msgid "Clear Finished Tasks" msgstr "Nettoyer les tâches terminées" elektroid-2.0/po/pt_BR.po000066400000000000000000000131151416764361200153340ustar00rootroot00000000000000# elektroid Brazilian Portuguese translation # Copyright (C) 2021 David García Goñi # This file is distributed under the same license as the elektroid package. # Gustavo Costa , 2021. # msgid "" msgstr "" "Project-Id-Version: elektroid 1.4\n" "Report-Msgid-Bugs-To: dagargo@gmail.com\n" "POT-Creation-Date: 2021-12-01 18:58+0100\n" "PO-Revision-Date: 2021-12-01 17:56-0300\n" "Last-Translator: Gustavo Costa \n" "Language-Team: \n" "Language: pt_BR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 3.0\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" #: src/elektroid.c:249 msgid "Samples" msgstr "Samples" #: src/elektroid.c:251 msgid "Presets" msgstr "Presets" #: src/elektroid.c:253 msgid "Projects" msgstr "Projects" #: src/elektroid.c:255 msgid "Sounds" msgstr "Sounds" #: src/elektroid.c:257 src/elektroid.c:1650 src/elektroid.c:1664 msgid "Undefined" msgstr "Indefinido" #: src/elektroid.c:398 #, c-format msgid "Connected to %s%s" msgstr "Conectado a %s%s" #: src/elektroid.c:406 src/elektroid.c:3215 msgid "Not connected" msgstr "Não conectado" #: src/elektroid.c:549 msgid "Waiting..." msgstr "Esperando..." #: src/elektroid.c:552 msgid "Sending..." msgstr "Enviando..." #: src/elektroid.c:556 msgid "Receiving..." msgstr "Recebendo..." #: src/elektroid.c:624 msgid "Receive SysEx" msgstr "Receber SysEx" #: src/elektroid.c:648 msgid "Save SysEx" msgstr "Salvar SysEx" #: src/elektroid.c:651 src/elektroid.c:747 src/elektroid.c:915 msgid "_Cancel" msgstr "_Cancelar" #: src/elektroid.c:653 msgid "_Save" msgstr "_Salvar" #: src/elektroid.c:657 msgid "Received SysEx" msgstr "SysEx recebido" #: src/elektroid.c:662 src/elektroid.c:753 msgid "SysEx Files" msgstr "Arquivos SysEx" #: src/elektroid.c:696 #, c-format msgid "Error while saving “%s”: %s." msgstr "Erro ao excluir \"%s\": %s." #: src/elektroid.c:744 msgid "Open SysEx" msgstr "Abrir SysEx" #: src/elektroid.c:749 msgid "_Open" msgstr "_Abrir" #: src/elektroid.c:770 #, c-format msgid "Error while loading “%s”: %s." msgstr "Erro ao excluir \"%s\": %s." #: src/elektroid.c:780 msgid "Send SysEx" msgstr "Enviar SysEx" #: src/elektroid.c:882 #, c-format msgid "Error while deleting “%s”: %s." msgstr "Erro ao excluir \"%s\": %s." #: src/elektroid.c:914 msgid "Are you sure you want to delete the selected items?" msgstr "Tem certeza de que deseja excluir os itens selecionados?" #: src/elektroid.c:916 msgid "_Delete" msgstr "_Excluir" #: src/elektroid.c:969 res/gui.glade:130 res/gui.glade:193 msgid "Rename" msgstr "Renomar" #: src/elektroid.c:987 #, c-format msgid "Error while renaming to “%s”: %s." msgstr "Erro ao renomear para \"%s\": %s." #: src/elektroid.c:1550 res/gui.glade:121 res/gui.glade:183 res/gui.glade:184 msgid "Add Directory" msgstr "Adicionar diretório" #: src/elektroid.c:1568 #, c-format msgid "Error while creating dir “%s”: %s." msgstr "Erro ao criar diretório \"%s\": %s." #: src/elektroid.c:1640 msgid "Queued" msgstr "Na fila" #: src/elektroid.c:1642 msgid "Running" msgstr "Executando" #: src/elektroid.c:1644 msgid "Completed" msgstr "Completado" #: src/elektroid.c:1646 msgid "Completed with errors" msgstr "Completado com erros" #: src/elektroid.c:1648 msgid "Canceled" msgstr "Cancelado" #: src/elektroid.c:1660 msgid "Upload" msgstr "Upload" #: src/elektroid.c:1662 msgid "Download" msgstr "Download" #: src/elektroid.c:2518 src/elektroid.c:2559 #, c-format msgid "Error while moving from “%s” to “%s”: %s." msgstr "Erro ao mover \"%s\" para \"%s\": %s." #: res/gui.glade:70 msgid "Upload Selection" msgstr "Seleção de upload" #: res/gui.glade:85 msgid "Play" msgstr "Reproduzir" #: res/gui.glade:99 msgid "Open With External Editor" msgstr "Abrir com um editor externo" #: res/gui.glade:107 msgid "Show in File Manager" msgstr "Mostrar no gerenciador de arquivos" #: res/gui.glade:139 res/gui.glade:202 msgid "Delete" msgstr "Excluir" #: res/gui.glade:168 msgid "Download Selection" msgstr "Seleção de download" #: res/gui.glade:238 msgid "GNU/Linux transfer application for Elektron devices" msgstr "Aplicativo GNU/Linux de transferência para dispositivos Elektron" #: res/gui.glade:241 msgid "translator-credits" msgstr "Gustavo Costa " #: res/gui.glade:289 msgid "_Receive SysEx" msgstr "_Receber SysEx" #: res/gui.glade:302 msgid "_Send SysEx" msgstr "_Enviar SysEx" #: res/gui.glade:326 msgid "OS _Upgrade" msgstr "Atualizar _OS" #: res/gui.glade:350 msgid "_About" msgstr "_Sobre" #: res/gui.glade:434 res/gui.glade:583 msgid "Go to Parent Directory" msgstr "Ir para o diretório pai" #: res/gui.glade:455 res/gui.glade:604 msgid "Refresh Directory" msgstr "Atualizar diretório" #: res/gui.glade:512 res/gui.glade:671 res/gui.glade:1362 msgid "Name" msgstr "Nome" #: res/gui.glade:526 res/gui.glade:685 msgid "Size" msgstr "Tamanho" #: res/gui.glade:738 msgid "Refresh Devices" msgstr "Atualizar dispositivos" #. It is recommended to split the text in two lines if it is too long #: res/gui.glade:856 msgid "Auto play" msgstr "Reprodução automática" #: res/gui.glade:1088 msgid "Status" msgstr "Status" #: res/gui.glade:1099 msgid "Type" msgstr "Tipo" #: res/gui.glade:1120 msgid "Source" msgstr "Origem" #: res/gui.glade:1134 msgid "Destination" msgstr "Destino" #: res/gui.glade:1149 msgid "Progress" msgstr "Progresso" #: res/gui.glade:1181 msgid "Cancel Tasks" msgstr "Cancelar tarefas" #: res/gui.glade:1202 msgid "Remove Queued Tasks" msgstr "Remover tarefas em fila" #: res/gui.glade:1223 msgid "Clear Finished Tasks" msgstr "Limpar tarefas concluídas" elektroid-2.0/res/000077500000000000000000000000001416764361200141405ustar00rootroot00000000000000elektroid-2.0/res/Makefile.am000066400000000000000000000016641416764361200162030ustar00rootroot00000000000000resdir = $(datadir)/elektroid/res res_DATA = gui.glade gui.css desktopdir = $(datadir)/applications desktop_DATA = elektroid.desktop metainfodir= $(datadir)/metainfo metainfo_DATA = elektroid.appdata.xml svgicondir = $(datarootdir)/icons/hicolor/scalable/apps svgicon_DATA = elektroid.svg elektroid-symbolic.svg elektroid-wave-symbolic.svg elektroid-data-symbolic.svg elektroid-project-symbolic.svg elektroid-sound-symbolic.svg gtk_update_icon_cache = gtk-update-icon-cache -f -t $(datarootdir)/icons/hicolor install-data-hook: update-icon-cache uninstall-hook: update-icon-cache update-icon-cache: @-if test -z "$(DESTDIR)"; then \ echo "Updating Gtk icon cache."; \ $(gtk_update_icon_cache); \ else \ echo "*** Icon cache not updated. After (un)install, run this:"; \ echo "*** $(gtk_update_icon_cache)"; \ fi EXTRA_DIST = \ $(res_DATA) \ $(desktop_DATA) \ $(metainfo_DATA) \ $(svgicon_DATA) elektroid-2.0/res/elektroid-data-symbolic.svg000066400000000000000000000205041416764361200213720ustar00rootroot00000000000000 elektroid pattern symbolic image/svg+xml elektroid pattern symbolic David García Goñi elektroid-2.0/res/elektroid-project-symbolic.svg000066400000000000000000000300311416764361200221230ustar00rootroot00000000000000 elektroid pattern symbolic image/svg+xml elektroid pattern symbolic David García Goñi elektroid-2.0/res/elektroid-sound-symbolic.svg000066400000000000000000000423671416764361200216240ustar00rootroot00000000000000 elektroid pattern symbolic image/svg+xml elektroid pattern symbolic David García Goñi elektroid-2.0/res/elektroid-symbolic.svg000066400000000000000000000100711416764361200204610ustar00rootroot00000000000000 elektroid wave symbolic image/svg+xml elektroid wave symbolic David García Goñi elektroid-2.0/res/elektroid-wave-symbolic.svg000066400000000000000000000302341416764361200214240ustar00rootroot00000000000000 elektroid wave symbolic image/svg+xml elektroid wave symbolic David García Goñi elektroid-2.0/res/elektroid.appdata.xml000066400000000000000000000024401416764361200202550ustar00rootroot00000000000000 elektroid.desktop FSFAP GPL-3.0+ elektroid Sample transfer application

Elektroid is an sample transfer application for Elektron devices.

With elektroid you can easily load audio files like flac or wav into your elektron sampler. You can also transfer audio samples from your elektroid device to your local hard disk.

elektroid.desktop elektroid https://screenshots.debian.net/screenshots/000/019/315/large.png The main window showing the application in action Audio MIDI ALSA https://github.com/dagargo/elektroid https://github.com/dagargo David García Goñi dagargo@gmail.com
elektroid-2.0/res/elektroid.desktop000066400000000000000000000011661416764361200175210ustar00rootroot00000000000000[Desktop Entry] Type=Application Name=Elektroid Comment=Sample transfer application for Elektron devices Comment[ca]=Aplicació de transferència de mostres per a dispositius Elektron Comment[de]=Sample Transfer Anwendung für Elektron Instrumente Comment[es]=Aplicación de transferencia de muestras para dispositivos Elektron Comment[fr]=Application de transfert d'échantillon pour les périphériques Elektron Comment[pt_BR]=Aplicativo de transferência de sample para dispositivos Elektron Categories=Music;AudioVideo;X-Sound;Audio;X-Alsa Keywords=music;alsa;realtime;standalone; Exec=elektroid Icon=elektroid Terminal=false elektroid-2.0/res/elektroid.svg000066400000000000000000000105321416764361200166440ustar00rootroot00000000000000 elektroid image/svg+xml elektroid David García Goñi elektroid-2.0/res/gui.css000066400000000000000000000013711416764361200154400ustar00rootroot00000000000000@define-color local_color #3584e4; @define-color local_color_dark #255c9f; @define-color remote_color #e01b24; @define-color disabled_color #7f7f7f; .local { color: @local_color; } #local_up_button, #local_add_dir_button, #local_refresh_button, #play_button, #stop_button, #loop_button, #upload_button { color: @local_color; } #remote_up_button, #remote_add_dir_button, #remote_refresh_button, #refresh_devices_button, #download_button { color: @remote_color } *:disabled { color: @disabled_color; } #autoplay_switch:checked, #autoplay_switch:checked slider { border-color: @local_color_dark; } #local_tree_view:selected, #autoplay_switch:checked { background-color: @local_color; } #remote_tree_view:selected { background-color: @remote_color; } elektroid-2.0/res/gui.glade000066400000000000000000002232241416764361200157270ustar00rootroot00000000000000 True False True False Upload Selection True True False True False Play True True False True False Open With External Editor True True False Show in File Manager True True False True False Rename True True False Delete True True False True False Download Selection True True False True False Rename True True False Delete True False False True dialog main_window Elektroid Copyright © 2019 David García Goñi GNU/Linux transfer application for Elektron devices https://github.com/dagargo/elektroid David García Goñi<dagargo@gmail.com> translator-credits elektroid gpl-3-0 False vertical 2 False end False False 0 False menu_button True False 9 9 9 9 vertical True True True _Receive SysEx False True 0 True True True _Send SysEx False True 1 True False False True 2 True True True OS _Upgrade False True 3 True False False True 4 True True True _About False True 5 main 1 False elektroid True False vertical True False 6 6 6 True True vertical True False 6 True True vertical 6 True False True True 6 6 True False True True vertical 6 True False True 6 True True True False False True 0 local_up_button True True True Go to Parent Directory True False go-up-symbolic False True 1 local_up_button True True True Add Directory True False folder-new-symbolic False True 2 local_refresh_button True True True Refresh Directory True False view-refresh-symbolic False True 3 False True 0 400 200 True True True True in local_tree_view True True True True local_list_store 1 multiple 0 Name True middle 1 Size 3 False True 1 0 1 True False True True vertical 6 True False True 6 True True True False False True 0 remote_up_button True True True Go to Parent Directory True False go-up-symbolic False True 1 remote_up_button True True True Add Directory True False folder-new-symbolic False True 2 remote_refresh_button True True True Refresh Directory True False view-refresh-symbolic False True 3 False True 0 400 200 True True True True in remote_tree_view True True True True remote_list_store 1 multiple 0 5 Name True middle 1 Size 3 False True 1 1 1 True False 6 True False devices_list_store 1 True True 0 refresh_devices_button True True True Refresh Devices True False view-refresh-symbolic False True 1 True False False True 2 True False fs_list_store 1 3 1 2 False True 3 1 0 True False start True 0 0 False True 0 True False True 6 bottom True False start center vertical 12 True False 6 autoplay_switch True True start center True False True 0 True False center Auto play True False True 1 False True 0 True False center 6 True False center play_button True True True center center True False media-playback-start-symbolic False True 0 stop_button True True True center center True False media-playback-stop-symbolic False True 1 loop_button True True True center True False media-playlist-repeat-symbolic False True 2 False True 0 True True False True none vertical audio-volume-muted-symbolic audio-volume-high-symbolic audio-volume-low-symbolic audio-volume-medium-symbolic True True center center none True True center center none False True 1 False True 1 False True 0 True False True 0 0 in True False True False True False False True 1 False True 1 True False True False 6 True 6 True True True True in True True task_list_store 0 none Status 5 Type 6 8 Source True middle 2 Destination True middle 3 120 Progress 4 False True 0 True False start vertical 6 True True False True True Cancel Tasks True False edit-delete-symbolic False True 0 True False True True Remove Queued Tasks True False list-remove-all-symbolic False True 1 True False True True Clear Finished Tasks True False edit-clear-all-symbolic False True 2 False True 1 True False False True 0 True False vertical False True 1 True False Elektroid True True True True none main_popover end False False True dialog main_window False 6 6 6 6 vertical 6 False end gtk-cancel True True True True False True 0 gtk-ok True True True True False True False True 1 False False 0 True False 6 6 True False Name False True 0 True True True True 32 True alpha False True 1 False True 1 False False True dialog main_window False 6 6 6 6 vertical 6 False end gtk-cancel True True True True True True 1 False False 0 True False 6 vertical 6 True False start False True 0 True False False True 1 True True 1 elektroid-2.0/src/000077500000000000000000000000001416764361200141365ustar00rootroot00000000000000elektroid-2.0/src/Makefile.am000066400000000000000000000023211416764361200161700ustar00rootroot00000000000000PKG_CONFIG ?= pkg-config GUI_LIBS = alsa gtk+-3.0 libpulse libpulse-mainloop-glib zlib json-glib-1.0 libzip CLI_LIBS = alsa glib-2.0 zlib json-glib-1.0 libzip elektroid_CFLAGS = -I$(top_srcdir)/src `$(PKG_CONFIG) --cflags $(GUI_LIBS)` $(SNDFILE_CFLAGS) $(SAMPLERATE_CFLAGS) elektroid_LDFLAGS = `$(PKG_CONFIG) --libs $(GUI_LIBS)` $(SNDFILE_LIBS) $(SAMPLERATE_LIBS) elektroid_cli_CFLAGS = -I$(top_srcdir)/src `$(PKG_CONFIG) --cflags $(CLI_LIBS)` $(SNDFILE_CFLAGS) $(SAMPLERATE_CFLAGS) elektroid_cli_LDFLAGS = `$(PKG_CONFIG) --libs $(CLI_LIBS)` $(SNDFILE_LIBS) $(SAMPLERATE_LIBS) if CLI_ONLY bin_PROGRAMS = elektroid-cli else bin_PROGRAMS = elektroid elektroid-cli endif elektroid_SOURCES = audio.c audio.h browser.c browser.h connector.c connector.h elektroid.c sample.c sample.h utils.c utils.h notifier.c notifier.h local.c local.h preferences.c preferences.h utils.h package.c package.h elektroid_cli_SOURCES = connector.c connector.h elektroid-cli.c sample.c sample.h utils.c utils.h package.c package.h SNDFILE_CFLAGS = @SNDFILE_CFLAGS@ SNDFILE_LIBS = @SNDFILE_LIBS@ SAMPLERATE_CFLAGS = @SAMPLERATE_CFLAGS@ SAMPLERATE_LIBS = @SAMPLERATE_LIBS@ AM_CPPFLAGS = -Wall -O3 -DDATADIR='"$(datadir)"' -DLOCALEDIR=\""$(localedir)"\" elektroid-2.0/src/audio.c000066400000000000000000000205001416764361200154000ustar00rootroot00000000000000/* * audio.c * Copyright (C) 2019 David García Goñi * * This file is part of Elektroid. * * Elektroid is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Elektroid is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Elektroid. If not, see . */ #include "../config.h" #include "audio.h" #define PA_BUFFER_LEN 4800 #define CHANNELS 1 static const pa_buffer_attr buffer_attributes = { .maxlength = -1, .tlength = PA_BUFFER_LEN * 2, .prebuf = 0, .minreq = -1 }; static const pa_sample_spec sample_spec = { .format = PA_SAMPLE_S16LE, .channels = CHANNELS, .rate = 48000 }; static void audio_write_callback (pa_stream * stream, size_t size, void *data) { struct audio *audio = data; guint req_frames; void *buffer; gshort *v; gint i; if (audio->release_frames > PA_BUFFER_LEN) { pa_stream_cork (audio->stream, 1, NULL, NULL); return; } req_frames = size >> 1; debug_print (2, "Writing %2d frames...\n", req_frames); pa_stream_begin_write (stream, &buffer, &size); g_mutex_lock (&audio->control.mutex); if (!audio->sample->len) { g_mutex_unlock (&audio->control.mutex); pa_stream_cancel_write (stream); debug_print (2, "Canceled\n"); return; } if (audio->pos == audio->sample->len >> 1 && !audio->loop) { g_mutex_unlock (&audio->control.mutex); memset (buffer, 0, size); pa_stream_write (stream, buffer, size, NULL, 0, PA_SEEK_RELATIVE); audio->release_frames += req_frames; return; } v = buffer; for (i = 0; i < req_frames; i++) { if (audio->pos < audio->sample->len >> 1) { *v = ((short *) audio->sample->data)[audio->pos]; audio->pos++; } else { if (audio->loop) { audio->pos = 0; *v = ((short *) audio->sample->data)[0]; } else { break; } } v++; } g_mutex_unlock (&audio->control.mutex); pa_stream_write (stream, buffer, i * 2, NULL, 0, PA_SEEK_RELATIVE); } void audio_stop (struct audio *audio, gboolean flush) { pa_operation *operation; if (!audio->stream) { return; } debug_print (1, "Stopping audio...\n"); pa_threaded_mainloop_lock (audio->mainloop); if (flush) { operation = pa_stream_flush (audio->stream, NULL, NULL); if (operation != NULL) { pa_operation_unref (operation); } } operation = pa_stream_cork (audio->stream, 1, NULL, NULL); if (operation != NULL) { pa_operation_unref (operation); } pa_threaded_mainloop_unlock (audio->mainloop); } void audio_play (struct audio *audio) { pa_operation *operation; if (!audio->stream) { return; } audio_stop (audio, TRUE); debug_print (1, "Playing audio...\n"); g_mutex_lock (&audio->control.mutex); audio->pos = 0; audio->release_frames = 0; g_mutex_unlock (&audio->control.mutex); pa_threaded_mainloop_lock (audio->mainloop); operation = pa_stream_cork (audio->stream, 0, NULL, NULL); if (operation != NULL) { pa_operation_unref (operation); } pa_threaded_mainloop_unlock (audio->mainloop); } static void audio_set_sink_volume (pa_context * context, const pa_sink_input_info * info, int eol, void *data) { struct audio *audio = data; if (info && pa_cvolume_valid (&info->volume)) { gdouble v = pa_sw_volume_to_linear (pa_cvolume_avg (&info->volume)); audio->volume_change_callback (v); } } static void audio_notify (pa_context * context, pa_subscription_event_type_t type, uint32_t index, void *data) { struct audio *audio = data; if (audio->context != context) { return; } if ((type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK_INPUT) { if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_CHANGE) { pa_context_get_sink_input_info (audio->context, audio->index, audio_set_sink_volume, audio); } } } static void audio_connect_callback (pa_stream * stream, void *data) { struct audio *audio = data; if (pa_stream_get_state (stream) == PA_STREAM_READY) { pa_stream_set_write_callback (stream, audio_write_callback, audio); audio->index = pa_stream_get_index (audio->stream); debug_print (2, "Sink index: %d\n", audio->index); pa_context_get_sink_input_info (audio->context, audio->index, audio_set_sink_volume, audio); } } static void audio_context_callback (pa_context * context, void *data) { struct audio *audio = data; pa_operation *operation; pa_proplist *props = pa_proplist_new (); pa_stream_flags_t stream_flags = PA_STREAM_START_CORKED | PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_NOT_MONOTONIC | PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_ADJUST_LATENCY; if (pa_context_get_state (context) == PA_CONTEXT_READY) { pa_proplist_set (props, PA_PROP_APPLICATION_ICON_NAME, PACKAGE, sizeof (PACKAGE)); audio->stream = pa_stream_new_with_proplist (context, PACKAGE, &sample_spec, NULL, props); pa_proplist_free (props); pa_stream_set_state_callback (audio->stream, audio_connect_callback, audio); pa_stream_connect_playback (audio->stream, NULL, &buffer_attributes, stream_flags, NULL, NULL); pa_context_set_subscribe_callback (audio->context, audio_notify, audio); operation = pa_context_subscribe (audio->context, PA_SUBSCRIPTION_MASK_SINK_INPUT, NULL, NULL); if (operation != NULL) { pa_operation_unref (operation); } } } gint audio_init (struct audio *audio, void (*volume_change_callback) (gdouble), void (*load_progress_callback) (gdouble)) { pa_mainloop_api *api; gint err = 0; debug_print (1, "Initializing audio...\n"); audio->sample = g_byte_array_new (); audio->frames = 0; audio->loop = FALSE; audio->mainloop = pa_threaded_mainloop_new (); api = pa_threaded_mainloop_get_api (audio->mainloop); audio->context = pa_context_new (api, PACKAGE); audio->stream = NULL; audio->index = PA_INVALID_INDEX; audio->volume_change_callback = volume_change_callback; audio->control.callback = load_progress_callback; audio->name = malloc (PATH_MAX); audio->name[0] = 0; if (pa_context_connect (audio->context, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0) { pa_context_unref (audio->context); pa_threaded_mainloop_free (audio->mainloop); audio->mainloop = NULL; err = -1; } else { pa_context_set_state_callback (audio->context, audio_context_callback, audio); pa_threaded_mainloop_start (audio->mainloop); } return err; } void audio_destroy (struct audio *audio) { debug_print (1, "Destroying audio...\n"); audio_stop (audio, TRUE); g_byte_array_free (audio->sample, TRUE); if (audio->stream) { pa_stream_unref (audio->stream); audio->stream = NULL; } if (audio->mainloop) { pa_context_unref (audio->context); pa_threaded_mainloop_stop (audio->mainloop); pa_threaded_mainloop_free (audio->mainloop); audio->mainloop = NULL; } g_free (audio->name); } gboolean audio_check (struct audio *audio) { return audio->mainloop ? TRUE : FALSE; } void audio_reset_sample (struct audio *audio) { g_mutex_lock (&audio->control.mutex); g_byte_array_set_size (audio->sample, 0); audio->frames = 0; audio->pos = 0; g_mutex_unlock (&audio->control.mutex); } void audio_set_volume (struct audio *audio, gdouble volume) { pa_operation *operation; pa_volume_t v; if (audio->index != PA_INVALID_INDEX) { debug_print (1, "Setting volume to %f...\n", volume); v = pa_sw_volume_from_linear (volume); pa_cvolume_set (&audio->volume, CHANNELS, v); operation = pa_context_set_sink_input_volume (audio->context, audio->index, &audio->volume, NULL, NULL); if (operation != NULL) { pa_operation_unref (operation); } } } elektroid-2.0/src/audio.h000066400000000000000000000027221416764361200154130ustar00rootroot00000000000000/* * audio.h * Copyright (C) 2019 David García Goñi * * This file is part of Elektroid. * * Elektroid is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Elektroid is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Elektroid. If not, see . */ #include #include #include "utils.h" struct audio { GByteArray *sample; guint frames; gboolean loop; pa_threaded_mainloop *mainloop; pa_context *context; pa_stream *stream; gint pos; pa_cvolume volume; uint32_t index; void (*volume_change_callback) (gdouble); gint release_frames; struct job_control control; gchar *name; }; void audio_play (struct audio *); void audio_stop (struct audio *, gboolean); gboolean audio_check (struct audio *); gint audio_init (struct audio *, void (*)(gdouble), void (*)(gdouble)); void audio_destroy (struct audio *); void audio_reset_sample (struct audio *); void audio_set_volume (struct audio *, gdouble); elektroid-2.0/src/browser.c000066400000000000000000000157341416764361200157770ustar00rootroot00000000000000/* * browser.c * Copyright (C) 2019 David García Goñi * * This file is part of Elektroid. * * Elektroid is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Elektroid is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Elektroid. If not, see . */ #include "browser.h" gint browser_sort_samples (GtkTreeModel * model, GtkTreeIter * a, GtkTreeIter * b, gpointer data) { struct item *itema; struct item *itemb; gint ret = 0; itema = browser_get_item (model, a); itemb = browser_get_item (model, b); if (itema->type == itemb->type) { ret = g_utf8_collate (itema->name, itemb->name); } else { ret = itema->type > itemb->type; } browser_free_item (itema); browser_free_item (itemb); return ret; } gint browser_sort_data (GtkTreeModel * model, GtkTreeIter * a, GtkTreeIter * b, gpointer data) { struct item *itema; struct item *itemb; gint ret = 0; itema = browser_get_item (model, a); itemb = browser_get_item (model, b); ret = itema->index > itemb->index; browser_free_item (itema); browser_free_item (itemb); return ret; } struct item * browser_get_item (GtkTreeModel * model, GtkTreeIter * iter) { struct item *item = malloc (sizeof (struct item)); gtk_tree_model_get (model, iter, BROWSER_LIST_STORE_TYPE_FIELD, &item->type, BROWSER_LIST_STORE_NAME_FIELD, &item->name, BROWSER_LIST_STORE_SIZE_FIELD, &item->size, BROWSER_LIST_STORE_INDEX_FIELD, &item->index, -1); return item; } void browser_set_selected_row_iter (struct browser *browser, GtkTreeIter * iter) { GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (browser->view)); GtkTreeModel *model = GTK_TREE_MODEL (gtk_tree_view_get_model (browser->view)); GList *paths = gtk_tree_selection_get_selected_rows (selection, &model); gtk_tree_model_get_iter (model, iter, g_list_nth_data (paths, 0)); g_list_free_full (paths, (GDestroyNotify) gtk_tree_path_free); } void browser_reset (gpointer data) { struct browser *browser = data; GtkListStore *list_store = GTK_LIST_STORE (gtk_tree_view_get_model (browser->view)); gtk_entry_set_text (browser->dir_entry, browser->dir); gtk_list_store_clear (list_store); browser->check_selection (NULL); } void browser_selection_changed (GtkTreeSelection * selection, gpointer data) { struct browser *browser = data; g_idle_add (browser->check_selection, NULL); } void browser_refresh (GtkWidget * object, gpointer data) { struct browser *browser = data; g_idle_add (browser_load_dir, browser); } void browser_go_up (GtkWidget * object, gpointer data) { char *dup; char *new_path; struct browser *browser = data; if (strcmp (browser->dir, "/") != 0) { dup = strdup (browser->dir); new_path = dirname (dup); strcpy (browser->dir, new_path); free (dup); if (browser->notify_dir_change) { browser->notify_dir_change (browser); } } g_idle_add (browser_load_dir, browser); } void browser_item_activated (GtkTreeView * view, GtkTreePath * path, GtkTreeViewColumn * column, gpointer data) { GtkTreeIter iter; struct item *item; struct browser *browser = data; GtkTreeModel *model = GTK_TREE_MODEL (gtk_tree_view_get_model (browser->view)); gtk_tree_model_get_iter (model, &iter, path); item = browser_get_item (model, &iter); if (item->type == ELEKTROID_DIR) { if (strcmp (browser->dir, "/") != 0) { strcat (browser->dir, "/"); } strcat (browser->dir, item->name); browser_load_dir (browser); if (browser->notify_dir_change) { browser->notify_dir_change (browser); } } browser_free_item (item); } gint browser_get_selected_items_count (struct browser *browser) { GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (browser->view)); return gtk_tree_selection_count_selected_rows (selection); } void browser_free_item (struct item *item) { g_free (item->name); g_free (item); } gchar * browser_get_item_path (struct browser *browser, struct item *item) { gchar *path = chain_path (browser->dir, item->name); debug_print (1, "Using %s path for item %s (%d)...\n", path, item->name, item->index); return path; } gchar * browser_get_item_id_path (struct browser *browser, struct item *item) { gchar *id = browser->fs_ops->getid (item); gchar *path = chain_path (browser->dir, id); debug_print (1, "Using %s path for item %s (%d)...\n", path, item->name, item->index); g_free (id); return path; } static void local_add_dentry_item (struct browser *browser, struct item_iterator *iter) { gchar *hsize; GtkListStore *list_store = GTK_LIST_STORE (gtk_tree_view_get_model (browser->view)); hsize = iter->item.size ? get_human_size (iter->item.size, TRUE) : ""; gtk_list_store_insert_with_values (list_store, NULL, -1, BROWSER_LIST_STORE_ICON_FIELD, iter->item.type == ELEKTROID_DIR ? DIR_ICON : browser->file_icon, BROWSER_LIST_STORE_NAME_FIELD, iter->item.name, BROWSER_LIST_STORE_SIZE_FIELD, iter->item.size, BROWSER_LIST_STORE_SIZE_STR_FIELD, hsize, BROWSER_LIST_STORE_TYPE_FIELD, iter->item.type, BROWSER_LIST_STORE_INDEX_FIELD, iter->item.index, -1); if (strlen (hsize)) { g_free (hsize); } } static gboolean browser_file_match_extensions (struct browser *browser, struct item_iterator *iter) { gboolean match; const gchar *entry_ext; gchar **ext = browser->extensions; if (iter->item.type == ELEKTROID_DIR) { return TRUE; } if (!ext) { return TRUE; } entry_ext = get_ext (iter->item.name); if (!entry_ext) { return FALSE; } match = FALSE; while (*ext != NULL && !match) { match = !strcasecmp (entry_ext, *ext); ext++; } return match; } gboolean browser_load_dir (gpointer data) { struct item_iterator iter; struct browser *browser = data; browser_reset (browser); if (browser->fs_ops->readdir (&iter, browser->dir, browser->data)) { error_print ("Error while opening %s dir\n", browser->dir); goto end; } while (!next_item_iterator (&iter)) { if (browser_file_match_extensions (browser, &iter)) { local_add_dentry_item (browser, &iter); } } free_item_iterator (&iter); end: if (browser->check_callback) { browser->check_callback (); } gtk_tree_view_columns_autosize (browser->view); return FALSE; } elektroid-2.0/src/browser.h000066400000000000000000000053111416764361200157720ustar00rootroot00000000000000/* * browser.h * Copyright (C) 2019 David García Goñi * * This file is part of Elektroid. * * Elektroid is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Elektroid is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Elektroid. If not, see . */ #include #include #include "utils.h" #ifndef BROWSER_H #define BROWSER_H #define SIZE_LABEL_LEN 16 #define DIR_ICON "folder-visiting-symbolic" #define FILE_ICON_WAVE "elektroid-wave-symbolic" #define FILE_ICON_DATA "elektroid-data-symbolic" #define FILE_ICON_PRJ "elektroid-project-symbolic" #define FILE_ICON_SND "elektroid-sound-symbolic" enum browser_list_field { BROWSER_LIST_STORE_ICON_FIELD = 0, BROWSER_LIST_STORE_NAME_FIELD, BROWSER_LIST_STORE_SIZE_FIELD, BROWSER_LIST_STORE_SIZE_STR_FIELD, BROWSER_LIST_STORE_TYPE_FIELD, BROWSER_LIST_STORE_INDEX_FIELD }; struct browser { GSourceFunc check_selection; GtkTreeView *view; GtkWidget *up_button; GtkWidget *add_dir_button; GtkWidget *refresh_button; GtkEntry *dir_entry; gchar *dir; GtkMenu *menu; gboolean dnd; GtkTreePath *dnd_motion_path; gint dnd_timeout_function_id; GString *dnd_data; void (*notify_dir_change) (struct browser *); const gchar *file_icon; gchar **extensions; const struct fs_operations *fs_ops; void *data; gboolean (*check_callback) (); }; gint browser_sort_samples (GtkTreeModel *, GtkTreeIter *, GtkTreeIter *, gpointer); gint browser_sort_data (GtkTreeModel *, GtkTreeIter *, GtkTreeIter *, gpointer); struct item *browser_get_item (GtkTreeModel *, GtkTreeIter *); void browser_free_item (struct item *); gint browser_get_selected_items_count (struct browser *); void browser_set_selected_row_iter (struct browser *, GtkTreeIter *); void browser_reset (gpointer); void browser_selection_changed (GtkTreeSelection *, gpointer); void browser_refresh (GtkWidget *, gpointer); void browser_go_up (GtkWidget *, gpointer); void browser_item_activated (GtkTreeView *, GtkTreePath *, GtkTreeViewColumn *, gpointer); gchar *browser_get_item_path (struct browser *, struct item *); gchar *browser_get_item_id_path (struct browser *, struct item *); gboolean browser_load_dir (gpointer); #endif elektroid-2.0/src/connector.c000066400000000000000000002702261416764361200163050ustar00rootroot00000000000000/* * connector.c * Copyright (C) 2019 David García Goñi * * This file is part of Elektroid. * * Elektroid is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Elektroid is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Elektroid. If not, see . */ #include #include #include #include #include #include #include #include "connector.h" #include "utils.h" #include "sample.h" #include "package.h" #define KB 1024 #define BUFF_SIZE (4 * KB) #define RING_BUFF_SIZE (256 * KB) #define DATA_TRANSF_BLOCK_BYTES 0x2000 #define OS_TRANSF_BLOCK_BYTES 0x800 #define POLL_TIMEOUT 20 #define MAX_ZIP_SIZE (128 * 1024 * 1024) #define AFMK1_ID 0x04 #define AKEYS_ID 0x06 #define ARMK1_ID 0x08 #define AHMK1_ID 0x0a #define DTAKT_ID 0x0c #define AFMK2_ID 0x0e #define ARMK2_ID 0x10 #define DTONE_ID 0x14 #define AHMK2_ID 0x16 #define DKEYS_ID 0x1c #define MOD_S_ID 0x19 #define MOD_C_ID 0x1b #define FS_DATA_PRJ_PREFIX "/projects" #define FS_DATA_SND_PREFIX "/soundbanks" #define FS_SAMPLES_START_POS 5 #define FS_DATA_START_POS 18 #define FS_SAMPLES_SIZE_POS_W 21 #define FS_SAMPLES_LAST_FRAME_POS_W 33 #define FS_SAMPLES_PAD_RES 22 #define ELEKTRON_SAMPLE_INFO_PAD_I32_LEN 10 struct elektron_sample_info { guint32 type; guint32 sample_len_bytes; guint32 sample_rate; guint32 loop_start; guint32 loop_end; guint32 loop_type; guint32 padding[ELEKTRON_SAMPLE_INFO_PAD_I32_LEN]; }; typedef GByteArray *(*connector_msg_id_func) (guint); typedef GByteArray *(*connector_msg_id_len_func) (guint, guint); typedef GByteArray *(*connector_msg_path_func) (const gchar *); typedef GByteArray *(*connector_msg_path_len_func) (const gchar *, guint); typedef GByteArray *(*connector_msg_read_blk_func) (guint, guint, guint); typedef GByteArray *(*connector_msg_write_blk_func) (guint, GByteArray *, guint *, guint, void *); typedef void (*connector_copy_array) (GByteArray *, GByteArray *); typedef gint (*connector_path_func) (struct connector *, const gchar *); typedef gint (*connector_src_dst_func) (struct connector *, const gchar *, const gchar *); static gint connector_delete_samples_dir (struct connector *, const gchar *); static gint connector_read_samples_dir (struct item_iterator *, const gchar *, void *); static gint connector_create_samples_dir (const gchar *, void *); static gint connector_delete_samples_item (const gchar *, void *); static gint connector_move_samples_item (const gchar *, const gchar *, void *); static gint connector_download_sample (const gchar *, GByteArray *, struct job_control *, void *); static gint connector_upload_sample (const gchar *, GByteArray *, struct job_control *, void *); static gint connector_delete_raw_dir (struct connector *, const gchar *); static gint connector_read_raw_dir (struct item_iterator *, const gchar *, void *); static gint connector_create_raw_dir (const gchar *, void *); static gint connector_delete_raw_item (const gchar *, void *); static gint connector_move_raw_item (const gchar *, const gchar *, void *); static gint connector_download_raw (const gchar *, GByteArray *, struct job_control *, void *); static gint connector_upload_raw (const gchar *, GByteArray *, struct job_control *, void *); static gint connector_read_data_dir_any (struct item_iterator *, const gchar *, void *); static gint connector_read_data_dir_prj (struct item_iterator *, const gchar *, void *); static gint connector_read_data_dir_snd (struct item_iterator *, const gchar *, void *); static gint connector_move_data_item_any (const gchar *, const gchar *, void *); static gint connector_move_data_item_prj (const gchar *, const gchar *, void *); static gint connector_move_data_item_snd (const gchar *, const gchar *, void *); static gint connector_copy_data_item_any (const gchar *, const gchar *, void *); static gint connector_copy_data_item_prj (const gchar *, const gchar *, void *); static gint connector_copy_data_item_snd (const gchar *, const gchar *, void *); static gint connector_clear_data_item_any (const gchar *, void *); static gint connector_clear_data_item_prj (const gchar *, void *); static gint connector_clear_data_item_snd (const gchar *, void *); static gint connector_swap_data_item_any (const gchar *, const gchar *, void *); static gint connector_swap_data_item_prj (const gchar *, const gchar *, void *); static gint connector_swap_data_item_snd (const gchar *, const gchar *, void *); static gint connector_download_data_any (const gchar *, GByteArray *, struct job_control *, void *); static gint connector_download_data_snd_pkg (const gchar *, GByteArray *, struct job_control *, void *); static gint connector_download_data_prj_pkg (const gchar *, GByteArray *, struct job_control *, void *); static gint connector_download_raw_pst_pkg (const gchar *, GByteArray *, struct job_control *, void *); static gint connector_upload_data_any (const gchar *, GByteArray *, struct job_control *, void *); static gint connector_upload_data_prj_pkg (const gchar *, GByteArray *, struct job_control *, void *); static gint connector_upload_data_snd_pkg (const gchar *, GByteArray *, struct job_control *, void *); static gint connector_upload_raw_pst_pkg (const gchar *, GByteArray *, struct job_control *, void *); static gint connector_copy_iterator (struct item_iterator *, struct item_iterator *, gboolean); static const guint8 MSG_HEADER[] = { 0xf0, 0, 0x20, 0x3c, 0x10, 0 }; static const guint8 PING_REQUEST[] = { 0x1 }; static const guint8 SOFTWARE_VERSION_REQUEST[] = { 0x2 }; static const guint8 DEVICEUID_REQUEST[] = { 0x3 }; static const guint8 STORAGEINFO_REQUEST[] = { 0x5 }; static const guint8 FS_SAMPLE_READ_DIR_REQUEST[] = { 0x10 }; static const guint8 FS_SAMPLE_CREATE_DIR_REQUEST[] = { 0x11 }; static const guint8 FS_SAMPLE_DELETE_DIR_REQUEST[] = { 0x12 }; static const guint8 FS_SAMPLE_DELETE_FILE_REQUEST[] = { 0x20 }; static const guint8 FS_SAMPLE_RENAME_FILE_REQUEST[] = { 0x21 }; static const guint8 FS_SAMPLE_GET_FILE_INFO_FROM_HASH_AND_SIZE_REQUEST[] = { 0x23, 0, 0, 0, 0, 0, 0, 0, 0 }; static const guint8 FS_SAMPLE_OPEN_FILE_READER_REQUEST[] = { 0x30 }; static const guint8 FS_SAMPLE_CLOSE_FILE_READER_REQUEST[] = { 0x31 }; static const guint8 FS_SAMPLE_READ_FILE_REQUEST[] = { 0x32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; static const guint8 FS_SAMPLE_OPEN_FILE_WRITER_REQUEST[] = { 0x40, 0, 0, 0, 0 }; static const guint8 FS_SAMPLE_CLOSE_FILE_WRITER_REQUEST[] = { 0x41, 0, 0, 0, 0, 0, 0, 0, 0 }; static const guint8 FS_SAMPLE_WRITE_FILE_REQUEST[] = { 0x42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; static const guint8 FS_RAW_READ_DIR_REQUEST[] = { 0x14 }; static const guint8 FS_RAW_CREATE_DIR_REQUEST[] = { 0x15 }; static const guint8 FS_RAW_DELETE_DIR_REQUEST[] = { 0x16 }; static const guint8 FS_RAW_DELETE_FILE_REQUEST[] = { 0x24 }; static const guint8 FS_RAW_RENAME_FILE_REQUEST[] = { 0x25 }; static const guint8 FS_RAW_OPEN_FILE_READER_REQUEST[] = { 0x33 }; static const guint8 FS_RAW_CLOSE_FILE_READER_REQUEST[] = { 0x34 }; static const guint8 FS_RAW_READ_FILE_REQUEST[] = { 0x35, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; static const guint8 FS_RAW_OPEN_FILE_WRITER_REQUEST[] = { 0x43, 0, 0, 0, 0 }; static const guint8 FS_RAW_CLOSE_FILE_WRITER_REQUEST[] = { 0x44, 0, 0, 0, 0, 0, 0, 0, 0 }; static const guint8 FS_RAW_WRITE_FILE_REQUEST[] = { 0x45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; static const guint8 DATA_LIST_REQUEST[] = { 0x53 }; static const guint8 DATA_READ_OPEN_REQUEST[] = { 0x54 }; static const guint8 DATA_READ_PARTIAL_REQUEST[] = { 0x55 }; static const guint8 DATA_READ_CLOSE_REQUEST[] = { 0x56 }; static const guint8 DATA_WRITE_OPEN_REQUEST[] = { 0x57 }; static const guint8 DATA_WRITE_PARTIAL_REQUEST[] = { 0x58 }; static const guint8 DATA_WRITE_CLOSE_REQUEST[] = { 0x59 }; static const guint8 DATA_MOVE_REQUEST[] = { 0x5a }; static const guint8 DATA_COPY_REQUEST[] = { 0x5b }; static const guint8 DATA_CLEAR_REQUEST[] = { 0x5c }; static const guint8 DATA_SWAP_REQUEST[] = { 0x5d }; static const guint8 OS_UPGRADE_START_REQUEST[] = { 0x50, 0, 0, 0, 0, 's', 'y', 's', 'e', 'x', '\0', 1 }; static const guint8 OS_UPGRADE_WRITE_RESPONSE[] = { 0x51, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; static const gchar *FS_TYPE_NAMES[] = { "+Drive", "RAM" }; static const struct connector_device_desc ANALOG_FOUR_DESC = { .name = "Analog Four", .alias = "af", .id = AFMK1_ID, .fss = FS_DATA_ALL | FS_DATA_PRJ | FS_DATA_SND, .storages = 0 }; static const struct connector_device_desc ANALOG_KEYS_DESC = { .name = "Analog Keys", .alias = "ak", .id = AKEYS_ID, .fss = FS_DATA_ALL | FS_DATA_PRJ | FS_DATA_SND, .storages = 0 }; static const struct connector_device_desc ANALOG_RYTM_DESC = { .name = "Analog Rytm", .alias = "ar", .id = ARMK1_ID, .fss = FS_SAMPLES | FS_DATA_ALL | FS_DATA_PRJ | FS_DATA_SND, .storages = STORAGE_PLUS_DRIVE | STORAGE_RAM }; static const struct connector_device_desc ANALOG_HEAT_DESC = { .name = "Analog Heat", .alias = "ah", .id = AHMK1_ID, .fss = FS_DATA_ALL | FS_DATA_SND, .storages = 0 }; static const struct connector_device_desc DIGITAKT_DESC = { .name = "Digitakt", .alias = "dt", .id = DTAKT_ID, .fss = FS_SAMPLES | FS_DATA_ALL | FS_DATA_PRJ | FS_DATA_SND, .storages = STORAGE_PLUS_DRIVE | STORAGE_RAM }; static const struct connector_device_desc ANALOG_FOUR_MKII_DESC = { .name = "Analog Four MKII", .alias = "af", .id = AFMK2_ID, .fss = FS_DATA_ALL | FS_DATA_PRJ | FS_DATA_SND, .storages = 0 }; static const struct connector_device_desc ANALOG_RYTM_MKII_DESC = { .name = "Analog Rytm MKII", .alias = "ar", .id = ARMK2_ID, .fss = FS_SAMPLES | FS_DATA_ALL | FS_DATA_PRJ | FS_DATA_SND, .storages = STORAGE_PLUS_DRIVE | STORAGE_RAM }; static const struct connector_device_desc DIGITONE_DESC = { .name = "Digitone", .alias = "dn", .id = DTONE_ID, .fss = FS_DATA_ALL | FS_DATA_PRJ | FS_DATA_SND, .storages = 0 }; static const struct connector_device_desc ANALOG_HEAT_MKII_DESC = { .name = "Analog Heat MKII", .alias = "ah", .id = AHMK2_ID, .fss = FS_DATA_ALL | FS_DATA_SND, .storages = 0 }; static const struct connector_device_desc DIGITONE_KEYS_DESC = { .name = "Digitone Keys", .alias = "dn", .id = DKEYS_ID, .fss = FS_DATA_ALL | FS_DATA_PRJ | FS_DATA_SND, .storages = 0 }; static const struct connector_device_desc MODEL_SAMPLES_DESC = { .name = "Model:Samples", .alias = "ms", .id = MOD_S_ID, .fss = FS_SAMPLES | FS_DATA_ALL | FS_DATA_PRJ | FS_DATA_SND, .storages = STORAGE_PLUS_DRIVE | STORAGE_RAM }; static const struct connector_device_desc MODEL_CYCLES_DESC = { .name = "Model:Cycles", .alias = "mc", .id = MOD_C_ID, .fss = FS_RAW_ALL | FS_RAW_PRESETS | FS_DATA_ALL | FS_DATA_PRJ, .storages = STORAGE_PLUS_DRIVE }; static const struct connector_device_desc *CONNECTOR_DEVICE_DESCS[] = { &ANALOG_FOUR_DESC, &ANALOG_KEYS_DESC, &ANALOG_RYTM_DESC, &ANALOG_HEAT_DESC, &DIGITAKT_DESC, &ANALOG_FOUR_MKII_DESC, &ANALOG_RYTM_MKII_DESC, &DIGITONE_DESC, &ANALOG_HEAT_MKII_DESC, &DIGITONE_KEYS_DESC, &MODEL_SAMPLES_DESC, &MODEL_CYCLES_DESC }; static const struct fs_operations FS_SAMPLES_OPERATIONS = { .fs = FS_SAMPLES, .readdir = connector_read_samples_dir, .mkdir = connector_create_samples_dir, .delete = connector_delete_samples_item, .rename = connector_move_samples_item, .move = connector_move_samples_item, .copy = NULL, .clear = NULL, .swap = NULL, .download = connector_download_sample, .upload = connector_upload_sample, .getid = get_item_name, .load = sample_load, .save = sample_save, .extension = "wav" }; static const struct fs_operations FS_RAW_ANY_OPERATIONS = { .fs = FS_RAW_ALL, .readdir = connector_read_raw_dir, .mkdir = connector_create_raw_dir, .delete = connector_delete_raw_item, .rename = connector_move_raw_item, .move = connector_move_raw_item, .copy = NULL, .clear = NULL, .swap = NULL, .download = connector_download_raw, .upload = connector_upload_raw, .getid = get_item_name, .load = load_file, .save = save_file, .extension = "raw" }; static const struct fs_operations FS_RAW_PRESETS_OPERATIONS = { .fs = FS_RAW_PRESETS, .readdir = connector_read_raw_dir, .mkdir = connector_create_raw_dir, .delete = connector_delete_raw_item, .rename = connector_move_raw_item, .move = connector_move_raw_item, .copy = NULL, .clear = NULL, .swap = NULL, .download = connector_download_raw_pst_pkg, .upload = connector_upload_raw_pst_pkg, .getid = get_item_name, .load = load_file, .save = save_file, .extension = "pst" }; static const struct fs_operations FS_DATA_ANY_OPERATIONS = { .fs = FS_DATA_ALL, .readdir = connector_read_data_dir_any, .mkdir = NULL, .delete = connector_clear_data_item_any, .rename = NULL, .move = connector_move_data_item_any, .copy = connector_copy_data_item_any, .clear = connector_clear_data_item_any, .swap = connector_swap_data_item_any, .download = connector_download_data_any, .upload = connector_upload_data_any, .getid = get_item_index, .load = load_file, .save = save_file, .extension = "data" }; static const struct fs_operations FS_DATA_PRJ_OPERATIONS = { .fs = FS_DATA_PRJ, .readdir = connector_read_data_dir_prj, .mkdir = NULL, .delete = connector_clear_data_item_prj, .rename = NULL, .move = connector_move_data_item_prj, .copy = connector_copy_data_item_prj, .clear = connector_clear_data_item_prj, .swap = connector_swap_data_item_prj, .download = connector_download_data_prj_pkg, .upload = connector_upload_data_prj_pkg, .getid = get_item_index, .load = load_file, .save = save_file, .extension = "prj" }; static const struct fs_operations FS_DATA_SND_OPERATIONS = { .fs = FS_DATA_SND, .readdir = connector_read_data_dir_snd, .mkdir = NULL, .delete = connector_clear_data_item_snd, .rename = NULL, .move = connector_move_data_item_snd, .copy = connector_copy_data_item_snd, .clear = connector_clear_data_item_snd, .swap = connector_swap_data_item_snd, .download = connector_download_data_snd_pkg, .upload = connector_upload_data_snd_pkg, .getid = get_item_index, .load = load_file, .save = save_file, .extension = "snd" }; static const struct fs_operations *FS_OPERATIONS[] = { &FS_SAMPLES_OPERATIONS, &FS_RAW_ANY_OPERATIONS, &FS_RAW_PRESETS_OPERATIONS, &FS_DATA_ANY_OPERATIONS, &FS_DATA_PRJ_OPERATIONS, &FS_DATA_SND_OPERATIONS }; static const int FS_OPERATIONS_N = sizeof (FS_OPERATIONS) / sizeof (struct fs_operations *); static enum item_type connector_get_path_type (struct connector *, const gchar *, fs_init_iter_func); static void connector_free_iterator_data (void *iter_data) { struct connector_iterator_data *data = iter_data; if (!data->cached) { free_msg (data->msg); } g_free (data); } const struct fs_operations * connector_get_fs_operations (enum connector_fs fs) { const struct fs_operations *fs_operations = NULL; for (int i = 0; i < FS_OPERATIONS_N; i++) { if (FS_OPERATIONS[i]->fs == fs) { fs_operations = FS_OPERATIONS[i]; break; } } return fs_operations; } static inline gchar * connector_get_utf8 (const gchar * s) { return g_convert (s, -1, "UTF8", "CP1252", NULL, NULL, NULL); } static inline gchar * connector_get_cp1252 (const gchar * s) { return g_convert (s, -1, "CP1252", "UTF8", NULL, NULL, NULL); } static inline guint8 connector_get_msg_status (const GByteArray * msg) { return msg->data[5]; } static inline gchar * connector_get_msg_string (const GByteArray * msg) { return (gchar *) & msg->data[6]; } static guint connector_next_smplrw_entry (struct item_iterator *iter) { guint32 *data32; gchar *name_cp1252; struct connector_iterator_data *data = iter->data; if (iter->item.name != NULL) { g_free (iter->item.name); } if (data->pos == data->msg->len) { iter->item.name = NULL; return -ENOENT; } else { data32 = (guint32 *) & data->msg->data[data->pos]; data->hash = be32toh (*data32); data->pos += sizeof (guint32); data32 = (guint32 *) & data->msg->data[data->pos]; iter->item.size = be32toh (*data32); data->pos += sizeof (guint32); data->pos++; //write_protected iter->item.type = data->msg->data[data->pos]; data->pos++; name_cp1252 = (gchar *) & data->msg->data[data->pos]; iter->item.name = connector_get_utf8 (name_cp1252); if (data->fs == FS_RAW_ALL && iter->item.type == ELEKTROID_FILE) { //This eliminates the extension ".mc-snd" that the device provides. iter->item.name[strlen (iter->item.name) - 7] = 0; } data->pos += strlen (name_cp1252) + 1; iter->item.index = -1; return 0; } } static gint connector_init_iterator (struct item_iterator *iter, GByteArray * msg, iterator_next next, enum connector_fs fs, gboolean cached) { struct connector_iterator_data *data = malloc (sizeof (struct connector_iterator_data)); data->msg = msg; data->pos = fs == FS_DATA_ALL ? FS_DATA_START_POS : FS_SAMPLES_START_POS; data->fs = fs; data->cached = cached; iter->data = data; iter->next = next; iter->free = connector_free_iterator_data; iter->copy = connector_copy_iterator; iter->item.name = NULL; iter->item.index = -1; return 0; } static gint connector_copy_iterator (struct item_iterator *dst, struct item_iterator *src, gboolean cached) { GByteArray *array; struct connector_iterator_data *data = src->data; if (cached) { array = data->msg; } else { array = g_byte_array_sized_new (data->msg->len); g_byte_array_append (array, data->msg->data, data->msg->len); } return connector_init_iterator (dst, array, src->next, data->fs, cached); } static GByteArray * connector_decode_payload (const GByteArray * src) { GByteArray *dst; int i, j, k, dst_len; unsigned int shift; dst_len = src->len - ceill (src->len / 8.0); dst = g_byte_array_new (); g_byte_array_set_size (dst, dst_len); for (i = 0, j = 0; i < src->len; i += 8, j += 7) { shift = 0x40; for (k = 0; k < 7 && i + k + 1 < src->len; k++) { dst->data[j + k] = src->data[i + k + 1] | (src->data[i] & shift ? 0x80 : 0); shift = shift >> 1; } } return dst; } static GByteArray * connector_encode_payload (const GByteArray * src) { GByteArray *dst; int i, j, k, dst_len; unsigned int accum; dst_len = src->len + ceill (src->len / 7.0); dst = g_byte_array_new (); g_byte_array_set_size (dst, dst_len); for (i = 0, j = 0; j < src->len; i += 8, j += 7) { accum = 0; for (k = 0; k < 7; k++) { accum = accum << 1; if (j + k < src->len) { if (src->data[j + k] & 0x80) { accum |= 1; } dst->data[i + k + 1] = src->data[j + k] & 0x7f; } } dst->data[i] = accum; } return dst; } static GByteArray * connector_msg_to_raw (const GByteArray * msg) { GByteArray *encoded; GByteArray *sysex = g_byte_array_new (); g_byte_array_append (sysex, MSG_HEADER, sizeof (MSG_HEADER)); encoded = connector_encode_payload (msg); g_byte_array_append (sysex, encoded->data, encoded->len); free_msg (encoded); g_byte_array_append (sysex, (guint8 *) "\xf7", 1); return sysex; } static gint connector_get_smplrw_info_from_msg (GByteArray * info_msg, guint32 * id, guint * size) { if (connector_get_msg_status (info_msg)) { if (id) { *id = be32toh (*((guint32 *) & info_msg->data[6])); } if (size) { *size = be32toh (*((guint32 *) & info_msg->data[10])); } } else { if (id) { return -EIO; } } return 0; } static GByteArray * connector_new_msg (const guint8 * data, guint len) { GByteArray *msg = g_byte_array_new (); g_byte_array_append (msg, (guchar *) "\0\0\0\0", 4); g_byte_array_append (msg, data, len); return msg; } static GByteArray * connector_new_msg_uint8 (const guint8 * data, guint len, guint8 type) { GByteArray *msg = connector_new_msg (data, len); g_byte_array_append (msg, &type, 1); return msg; } static GByteArray * connector_new_msg_path (const guint8 * data, guint len, const gchar * path) { GByteArray *msg; gchar *path_cp1252 = connector_get_cp1252 (path); if (!path_cp1252) { return NULL; } msg = connector_new_msg (data, len); g_byte_array_append (msg, (guchar *) path_cp1252, strlen (path_cp1252) + 1); g_free (path_cp1252); return msg; } static GByteArray * connector_new_msg_close_common_read (const guint8 * data, guint len, guint id) { guint32 aux32; GByteArray *msg = connector_new_msg (data, len); aux32 = htobe32 (id); g_byte_array_append (msg, (guchar *) & aux32, sizeof (guint32)); return msg; } static GByteArray * connector_new_msg_close_sample_read (guint id) { return connector_new_msg_close_common_read (FS_SAMPLE_CLOSE_FILE_READER_REQUEST, sizeof (FS_SAMPLE_CLOSE_FILE_READER_REQUEST), id); } static GByteArray * connector_new_msg_close_raw_read (guint id) { return connector_new_msg_close_common_read (FS_RAW_CLOSE_FILE_READER_REQUEST, sizeof (FS_RAW_CLOSE_FILE_READER_REQUEST), id); } static GByteArray * connector_new_msg_open_common_write (const guint8 * data, guint len, const gchar * path, guint bytes) { guint32 aux32; GByteArray *msg = connector_new_msg_path (data, len, path); aux32 = htobe32 (bytes); memcpy (&msg->data[5], &aux32, sizeof (guint32)); return msg; } static GByteArray * connector_new_msg_open_sample_write (const gchar * path, guint bytes) { return connector_new_msg_open_common_write (FS_SAMPLE_OPEN_FILE_WRITER_REQUEST, sizeof (FS_SAMPLE_OPEN_FILE_WRITER_REQUEST), path, bytes + sizeof (struct elektron_sample_info)); } static GByteArray * connector_new_msg_open_raw_write (const gchar * path, guint bytes) { return connector_new_msg_open_common_write (FS_RAW_OPEN_FILE_WRITER_REQUEST, sizeof (FS_RAW_OPEN_FILE_WRITER_REQUEST), path, bytes); } static GByteArray * connector_new_msg_list (const gchar * path, int32_t start_index, int32_t end_index, gboolean all) { guint32 aux32; guint8 aux8; GByteArray *msg = connector_new_msg_path (DATA_LIST_REQUEST, sizeof (DATA_LIST_REQUEST), path); aux32 = htobe32 (start_index); g_byte_array_append (msg, (guchar *) & aux32, sizeof (guint32)); aux32 = htobe32 (end_index); g_byte_array_append (msg, (guchar *) & aux32, sizeof (guint32)); aux8 = all; g_byte_array_append (msg, (guchar *) & aux8, sizeof (guint8)); return msg; } static GByteArray * connector_new_msg_write_sample_blk (guint id, GByteArray * sample, guint * total, guint seq, void *data) { guint32 aux32; guint16 aux16, *aux16p; int i, consumed, bytes_blk; struct sample_loop_data *sample_loop_data = data; struct elektron_sample_info elektron_sample_info; GByteArray *msg = connector_new_msg (FS_SAMPLE_WRITE_FILE_REQUEST, sizeof (FS_SAMPLE_WRITE_FILE_REQUEST)); aux32 = htobe32 (id); memcpy (&msg->data[5], &aux32, sizeof (guint32)); aux32 = htobe32 (DATA_TRANSF_BLOCK_BYTES * seq); memcpy (&msg->data[13], &aux32, sizeof (guint32)); bytes_blk = DATA_TRANSF_BLOCK_BYTES; consumed = 0; if (seq == 0) { elektron_sample_info.type = 0; elektron_sample_info.sample_len_bytes = htobe32 (sample->len); elektron_sample_info.sample_rate = htobe32 (ELEKTRON_SAMPLE_RATE); elektron_sample_info.loop_start = htobe32 (sample_loop_data->start); elektron_sample_info.loop_end = htobe32 (sample_loop_data->end); elektron_sample_info.loop_type = htobe32 (ELEKTRON_LOOP_TYPE); memset (&elektron_sample_info.padding, 0, sizeof (guint32) * ELEKTRON_SAMPLE_INFO_PAD_I32_LEN); g_byte_array_append (msg, (guchar *) & elektron_sample_info, sizeof (struct elektron_sample_info)); consumed = sizeof (struct elektron_sample_info); bytes_blk -= consumed; } i = 0; aux16p = (guint16 *) & sample->data[*total]; while (i < bytes_blk && *total < sample->len) { aux16 = htobe16 (*aux16p); g_byte_array_append (msg, (guint8 *) & aux16, sizeof (guint16)); aux16p++; (*total) += sizeof (guint16); consumed += sizeof (guint16); i += sizeof (guint16); } aux32 = htobe32 (consumed); memcpy (&msg->data[9], &aux32, sizeof (guint32)); return msg; } static GByteArray * connector_new_msg_write_raw_blk (guint id, GByteArray * raw, guint * total, guint seq, void *data) { gint len; guint32 aux32; GByteArray *msg = connector_new_msg (FS_RAW_WRITE_FILE_REQUEST, sizeof (FS_RAW_WRITE_FILE_REQUEST)); aux32 = htobe32 (id); memcpy (&msg->data[5], &aux32, sizeof (guint32)); aux32 = htobe32 (DATA_TRANSF_BLOCK_BYTES * seq); memcpy (&msg->data[13], &aux32, sizeof (guint32)); len = raw->len - *total; len = len > DATA_TRANSF_BLOCK_BYTES ? DATA_TRANSF_BLOCK_BYTES : len; g_byte_array_append (msg, &raw->data[*total], len); (*total) += len; aux32 = htobe32 (len); memcpy (&msg->data[9], &aux32, sizeof (guint32)); return msg; } static GByteArray * connector_new_msg_close_common_write (const guint8 * data, guint len, guint id, guint bytes) { guint32 aux32; GByteArray *msg = connector_new_msg (data, len); aux32 = htobe32 (id); memcpy (&msg->data[5], &aux32, sizeof (guint32)); aux32 = htobe32 (bytes); memcpy (&msg->data[9], &aux32, sizeof (guint32)); return msg; } static GByteArray * connector_new_msg_close_sample_write (guint id, guint bytes) { return connector_new_msg_close_common_write (FS_SAMPLE_CLOSE_FILE_WRITER_REQUEST, sizeof (FS_SAMPLE_CLOSE_FILE_WRITER_REQUEST), id, bytes + sizeof (struct elektron_sample_info)); } static GByteArray * connector_new_msg_close_raw_write (guint id, guint bytes) { return connector_new_msg_close_common_write (FS_RAW_CLOSE_FILE_WRITER_REQUEST, sizeof (FS_RAW_CLOSE_FILE_WRITER_REQUEST), id, bytes); } static GByteArray * connector_new_msg_read_common_blk (const guint8 * data, guint len, guint id, guint start, guint size) { guint32 aux; GByteArray *msg = connector_new_msg (data, len); aux = htobe32 (id); memcpy (&msg->data[5], &aux, sizeof (guint32)); aux = htobe32 (size); memcpy (&msg->data[9], &aux, sizeof (guint32)); aux = htobe32 (start); memcpy (&msg->data[13], &aux, sizeof (guint32)); return msg; } static GByteArray * connector_new_msg_read_sample_blk (guint id, guint start, guint size) { return connector_new_msg_read_common_blk (FS_SAMPLE_READ_FILE_REQUEST, sizeof (FS_SAMPLE_READ_FILE_REQUEST), id, start, size); } static GByteArray * connector_new_msg_read_raw_blk (guint id, guint start, guint size) { return connector_new_msg_read_common_blk (FS_RAW_READ_FILE_REQUEST, sizeof (FS_RAW_READ_FILE_REQUEST), id, start, size); } static GByteArray * connector_raw_to_msg (GByteArray * sysex) { GByteArray *msg; GByteArray *payload; gint len = sysex->len - sizeof (MSG_HEADER) - 1; if (len > 0) { payload = g_byte_array_new (); g_byte_array_append (payload, &sysex->data[sizeof (MSG_HEADER)], len); msg = connector_decode_payload (payload); free_msg (payload); } else { msg = NULL; } return msg; } static ssize_t connector_tx_raw (struct connector *connector, const guint8 * data, guint len) { ssize_t tx_len; if (!connector->outputp) { error_print ("Output port is NULL\n"); return -ENOTCONN; } snd_rawmidi_read (connector->inputp, NULL, 0); // trigger reading tx_len = snd_rawmidi_write (connector->outputp, data, len); if (tx_len < 0) { error_print ("Error while writing to device: %s\n", snd_strerror (tx_len)); connector_destroy (connector); } return tx_len; } gint connector_tx_sysex (struct connector *connector, struct connector_sysex_transfer *transfer) { ssize_t tx_len; guint total; guint len; guchar *b; gint res = 0; transfer->status = SENDING; b = transfer->raw->data; total = 0; while (total < transfer->raw->len && transfer->active) { len = transfer->raw->len - total; if (len > BUFF_SIZE) { len = BUFF_SIZE; } tx_len = connector_tx_raw (connector, b, len); if (tx_len < 0) { res = tx_len; break; } b += len; total += len; } transfer->active = FALSE; transfer->status = FINISHED; return res; } static gint connector_tx (struct connector *connector, const GByteArray * msg) { gint res; guint16 aux; gchar *text; struct connector_sysex_transfer transfer; aux = htobe16 (connector->seq); memcpy (msg->data, &aux, sizeof (guint16)); if (connector->seq == USHRT_MAX) { connector->seq = 0; } else { connector->seq++; } transfer.active = TRUE; transfer.raw = connector_msg_to_raw (msg); res = connector_tx_sysex (connector, &transfer); if (!res) { if (debug_level > 1) { text = debug_get_hex_msg (transfer.raw); debug_print (2, "Raw message sent (%d): %s\n", transfer.raw->len, text); free (text); } text = debug_get_hex_msg (msg); debug_print (1, "Message sent (%d): %s\n", msg->len, text); free (text); } free_msg (transfer.raw); return res; } void connector_rx_drain (struct connector *connector) { debug_print (2, "Draining buffer...\n"); connector->rx_len = 0; snd_rawmidi_drain (connector->inputp); } static gboolean connector_is_rt_msg (guint8 * data, guint len) { guint i; guint8 *b; for (i = 0, b = data; i < len; i++, b++) { if (*b < 0xf8) //Not System Real-Time Messages { return FALSE; } } return TRUE; } static ssize_t connector_rx_raw (struct connector *connector, guint8 * data, guint len, struct connector_sysex_transfer *transfer) { ssize_t rx_len; guint total_time; unsigned short revents; gint err; gchar *text; if (!connector->inputp) { error_print ("Input port is NULL\n"); return -ENOTCONN; } total_time = 0; while (1) { err = poll (connector->pfds, connector->npfds, POLL_TIMEOUT); if (!transfer->active) { return -ENODATA; } if (err == 0) { total_time += POLL_TIMEOUT; if (((transfer->batch && transfer->status == RECEIVING) || !transfer->batch) && transfer->timeout > -1 && total_time >= transfer->timeout) { debug_print (1, "Timeout!\n"); return -ENODATA; } continue; } if (err < 0) { error_print ("Error while polling. %s.\n", g_strerror (errno)); connector_destroy (connector); return err; } if ((err = snd_rawmidi_poll_descriptors_revents (connector->inputp, connector->pfds, connector->npfds, &revents)) < 0) { error_print ("Error while getting poll events. %s.\n", snd_strerror (err)); connector_destroy (connector); return err; } if (revents & (POLLERR | POLLHUP)) { return -ENODATA; } if (!(revents & POLLIN)) { continue; } rx_len = snd_rawmidi_read (connector->inputp, data, len); if (rx_len == -EAGAIN || rx_len == 0) { continue; } if (rx_len > 0) { if (connector_is_rt_msg (data, rx_len)) { continue; } break; } if (rx_len < 0) { error_print ("Error while reading from device: %s\n", snd_strerror (rx_len)); connector_destroy (connector); break; } } if (debug_level > 1) { text = debug_get_hex_data (3, data, rx_len); debug_print (2, "Buffer content (%zu): %s\n", rx_len, text); free (text); } return rx_len; } gint connector_rx_sysex (struct connector *connector, struct connector_sysex_transfer *transfer) { gint i; guint8 *b; gint res = 0; transfer->status = WAITING; transfer->raw = g_byte_array_new (); i = 0; if (connector->rx_len < 0) { connector->rx_len = 0; } b = connector->buffer; while (1) { if (i == connector->rx_len) { connector->rx_len = connector_rx_raw (connector, connector->buffer, BUFF_SIZE, transfer); if (connector->rx_len == -ENODATA) { res = -ENODATA; goto error; } if (connector->rx_len < 0) { res = -EIO; goto error; } b = connector->buffer; i = 0; } while (i < connector->rx_len && *b != 0xf0) { b++; i++; } if (i < connector->rx_len) { break; } } g_byte_array_append (transfer->raw, b, 1); b++; i++; transfer->status = RECEIVING; while (1) { if (i == connector->rx_len) { connector->rx_len = connector_rx_raw (connector, connector->buffer, BUFF_SIZE, transfer); if (connector->rx_len == -ENODATA && transfer->batch) { break; } if (connector->rx_len < 0) { res = -EIO; goto error; } b = connector->buffer; i = 0; } while (i < connector->rx_len && (*b != 0xf7 || transfer->batch)) { if (!connector_is_rt_msg (b, 1)) { g_byte_array_append (transfer->raw, b, 1); } b++; i++; } if (i < connector->rx_len) { g_byte_array_append (transfer->raw, b, 1); connector->rx_len = connector->rx_len - i - 1; if (connector->rx_len > 0) { memmove (connector->buffer, &connector->buffer[i + 1], connector->rx_len); } break; } } goto end; error: free_msg (transfer->raw); transfer->raw = NULL; end: transfer->active = FALSE; transfer->status = FINISHED; return res; } static GByteArray * connector_rx (struct connector *connector) { gchar *text; GByteArray *msg; struct connector_sysex_transfer transfer; transfer.active = TRUE; transfer.timeout = SYSEX_TIMEOUT; transfer.batch = FALSE; if (connector_rx_sysex (connector, &transfer)) { return NULL; } while (transfer.raw->len < 12 || (transfer.raw->len >= 12 && (transfer.raw->data[0] != MSG_HEADER[0] || transfer.raw->data[1] != MSG_HEADER[1] || transfer.raw->data[2] != MSG_HEADER[2] || transfer.raw->data[3] != MSG_HEADER[3] || transfer.raw->data[4] != MSG_HEADER[4] || transfer.raw->data[5] != MSG_HEADER[5]))) { if (debug_level > 1) { text = debug_get_hex_msg (transfer.raw); debug_print (2, "Message skipped (%d): %s\n", transfer.raw->len, text); free (text); } free_msg (transfer.raw); transfer.active = TRUE; if (connector_rx_sysex (connector, &transfer)) { return NULL; } } if (debug_level > 1) { text = debug_get_hex_msg (transfer.raw); debug_print (2, "Raw message received (%d): %s\n", transfer.raw->len, text); free (text); } msg = connector_raw_to_msg (transfer.raw); if (msg) { text = debug_get_hex_msg (msg); debug_print (1, "Message received (%d): %s\n", msg->len, text); free (text); } free_msg (transfer.raw); return msg; } static GByteArray * connector_tx_and_rx (struct connector *connector, GByteArray * tx_msg) { ssize_t len; GByteArray *rx_msg; g_mutex_lock (&connector->mutex); connector_rx_drain (connector); len = connector_tx (connector, tx_msg); if (len < 0) { rx_msg = NULL; goto cleanup; } rx_msg = connector_rx (connector); cleanup: free_msg (tx_msg); g_mutex_unlock (&connector->mutex); return rx_msg; } static gint connector_read_common_dir (struct item_iterator *iter, const gchar * dir, void *data, const guint8 msg[], int size, fs_init_iter_func init_iter, enum connector_fs fs) { gboolean cache; GByteArray *tx_msg; GByteArray *rx_msg; struct connector *connector = data; g_mutex_lock (&connector->mutex); cache = connector->dir_cache != NULL; rx_msg = cache ? g_hash_table_lookup (connector->dir_cache, dir) : NULL; g_mutex_unlock (&connector->mutex); if (!rx_msg) { tx_msg = connector_new_msg_path (msg, size, dir); if (!tx_msg) { return -EINVAL; } rx_msg = connector_tx_and_rx (connector, tx_msg); if (!rx_msg) { return -EIO; } g_mutex_lock (&connector->mutex); cache = connector->dir_cache != NULL; if (cache) { gchar *key = g_strdup (dir); g_hash_table_insert (connector->dir_cache, key, rx_msg); } g_mutex_unlock (&connector->mutex); if (rx_msg->len == 5 && connector_get_path_type (connector, dir, init_iter) != ELEKTROID_DIR) { if (!cache) { free_msg (rx_msg); } return -ENOTDIR; } } return connector_init_iterator (iter, rx_msg, connector_next_smplrw_entry, fs, cache); } static gint connector_read_samples_dir (struct item_iterator *iter, const gchar * dir, void *data) { return connector_read_common_dir (iter, dir, data, FS_SAMPLE_READ_DIR_REQUEST, sizeof (FS_SAMPLE_READ_DIR_REQUEST), connector_read_samples_dir, FS_SAMPLES); } static gint connector_read_raw_dir (struct item_iterator *iter, const gchar * dir, void *data) { return connector_read_common_dir (iter, dir, data, FS_RAW_READ_DIR_REQUEST, sizeof (FS_RAW_READ_DIR_REQUEST), connector_read_raw_dir, FS_RAW_ALL); } static enum item_type connector_get_path_type (struct connector *connector, const gchar * path, fs_init_iter_func init_iter) { gchar *name_copy; gchar *parent_copy; gchar *name; gchar *parent; enum item_type res; struct item_iterator iter; if (strcmp (path, "/") == 0) { return ELEKTROID_DIR; } name_copy = strdup (path); parent_copy = strdup (path); name = basename (name_copy); parent = dirname (parent_copy); res = ELEKTROID_NONE; if (!init_iter (&iter, parent, connector)) { while (!next_item_iterator (&iter)) { if (strcmp (name, iter.item.name) == 0) { res = iter.item.type; break; } } free_item_iterator (&iter); } g_free (name_copy); g_free (parent_copy); return res; } static gint connector_src_dst_common (struct connector *connector, const gchar * src, const gchar * dst, const guint8 * data, guint len) { gint res; GByteArray *rx_msg; GByteArray *tx_msg = connector_new_msg (data, len); gchar *dst_cp1252 = connector_get_cp1252 (dst); if (!dst_cp1252) { return -EINVAL; } gchar *src_cp1252 = connector_get_cp1252 (src); if (!src_cp1252) { g_free (dst_cp1252); return -EINVAL; } g_byte_array_append (tx_msg, (guchar *) src_cp1252, strlen (src_cp1252) + 1); g_byte_array_append (tx_msg, (guchar *) dst_cp1252, strlen (dst_cp1252) + 1); g_free (src_cp1252); g_free (dst_cp1252); rx_msg = connector_tx_and_rx (connector, tx_msg); if (!rx_msg) { return -EIO; } //Response: x, x, x, x, 0xa1, [0 (error), 1 (success)]... if (connector_get_msg_status (rx_msg)) { res = 0; } else { res = -EPERM; error_print ("%s (%s)\n", snd_strerror (res), connector_get_msg_string (rx_msg)); } free_msg (rx_msg); return res; } static gint connector_rename_sample_file (struct connector *connector, const gchar * src, const gchar * dst) { return connector_src_dst_common (connector, src, dst, FS_SAMPLE_RENAME_FILE_REQUEST, sizeof (FS_SAMPLE_RENAME_FILE_REQUEST)); } static gint connector_rename_raw_file (struct connector *connector, const gchar * src, const gchar * dst) { return connector_src_dst_common (connector, src, dst, FS_RAW_RENAME_FILE_REQUEST, sizeof (FS_RAW_RENAME_FILE_REQUEST)); } static gint connector_move_common_item (const gchar * src, const gchar * dst, void *data, fs_init_iter_func init_iter, connector_src_dst_func mv, fs_path_func mkdir, connector_path_func rmdir) { enum item_type type; gint res; gchar *src_plus; gchar *dst_plus; struct item_iterator iter; struct connector *connector = data; //Renaming is not implemented for directories so we need to implement it. debug_print (1, "Renaming remotely from %s to %s...\n", src, dst); type = connector_get_path_type (connector, src, init_iter); if (type == ELEKTROID_FILE) { return mv (connector, src, dst); } else if (type == ELEKTROID_DIR) { res = mkdir (dst, connector); if (res) { return res; } if (!init_iter (&iter, src, connector)) { while (!next_item_iterator (&iter) && !res) { src_plus = chain_path (src, iter.item.name); dst_plus = chain_path (dst, iter.item.name); res = connector_move_common_item (src_plus, dst_plus, connector, init_iter, mv, mkdir, rmdir); free (src_plus); free (dst_plus); } free_item_iterator (&iter); } if (!res) { res = rmdir (connector, src); } return res; } else { return -EBADF; } } static gint connector_path_common (struct connector *connector, const gchar * path, const guint8 * template, gint size) { gint res; GByteArray *rx_msg; GByteArray *tx_msg; tx_msg = connector_new_msg_path (template, size, path); if (!tx_msg) { return -EINVAL; } rx_msg = connector_tx_and_rx (connector, tx_msg); if (!rx_msg) { return -EIO; } //Response: x, x, x, x, 0xX0, [0 (error), 1 (success)]... if (connector_get_msg_status (rx_msg)) { res = 0; } else { res = -EPERM; error_print ("%s (%s)\n", snd_strerror (res), connector_get_msg_string (rx_msg)); } free_msg (rx_msg); return res; } static gint connector_delete_sample (struct connector *connector, const gchar * path) { return connector_path_common (connector, path, FS_SAMPLE_DELETE_FILE_REQUEST, sizeof (FS_SAMPLE_DELETE_FILE_REQUEST)); } static gint connector_delete_samples_dir (struct connector *connector, const gchar * path) { return connector_path_common (connector, path, FS_SAMPLE_DELETE_DIR_REQUEST, sizeof (FS_SAMPLE_DELETE_DIR_REQUEST)); } //This adds back the extension ".mc-snd" that the device provides. static gchar * connector_add_ext_to_mc_snd (const gchar * path) { gchar *path_with_ext = malloc (PATH_MAX); snprintf (path_with_ext, PATH_MAX, "%s%s", path, ".mc-snd"); return path_with_ext; } static gint connector_delete_raw (struct connector *connector, const gchar * path) { gint ret; gchar *path_with_ext = connector_add_ext_to_mc_snd (path); ret = connector_path_common (connector, path_with_ext, FS_RAW_DELETE_FILE_REQUEST, sizeof (FS_RAW_DELETE_FILE_REQUEST)); g_free (path_with_ext); return ret; } static gint connector_delete_raw_dir (struct connector *connector, const gchar * path) { return connector_path_common (connector, path, FS_RAW_DELETE_DIR_REQUEST, sizeof (FS_RAW_DELETE_DIR_REQUEST)); } static gint connector_move_samples_item (const gchar * src, const gchar * dst, void *data) { return connector_move_common_item (src, dst, data, connector_read_samples_dir, connector_rename_sample_file, connector_create_samples_dir, connector_delete_samples_dir); } static gint connector_move_raw_item (const gchar * src, const gchar * dst, void *data) { gint ret; gchar *src_with_ext = connector_add_ext_to_mc_snd (src); ret = connector_move_common_item (src_with_ext, dst, data, connector_read_raw_dir, connector_rename_raw_file, connector_create_raw_dir, connector_delete_raw_dir); g_free (src_with_ext); return ret; } static gint connector_delete_common_item (const gchar * path, void *data, fs_init_iter_func init_iter, connector_path_func rmdir, connector_path_func rm) { enum item_type type; gchar *new_path; struct item_iterator iter; struct connector *connector = data; gint res; type = connector_get_path_type (connector, path, init_iter); if (type == ELEKTROID_FILE) { return rm (connector, path); } else if (type == ELEKTROID_DIR) { debug_print (1, "Deleting %s samples dir...\n", path); if (init_iter (&iter, path, connector)) { error_print ("Error while opening samples dir %s dir\n", path); } else { res = 0; while (!res && !next_item_iterator (&iter)) { new_path = chain_path (path, iter.item.name); res = res || connector_delete_common_item (new_path, connector, init_iter, rmdir, rm); free (new_path); } free_item_iterator (&iter); } return res || rmdir (connector, path); } else { return -EBADF; } } static gint connector_delete_samples_item (const gchar * path, void *data) { return connector_delete_common_item (path, data, connector_read_samples_dir, connector_delete_samples_dir, connector_delete_sample); } static gint connector_delete_raw_item (const gchar * path, void *data) { return connector_delete_common_item (path, data, connector_read_raw_dir, connector_delete_raw_dir, connector_delete_raw); } static gint connector_upload_smplrw (const gchar * path, GByteArray * input, struct job_control *control, void *data, connector_msg_path_len_func new_msg_open_write, connector_msg_write_blk_func new_msg_write_blk, connector_msg_id_len_func new_msg_close_write) { struct connector *connector = data; GByteArray *tx_msg; GByteArray *rx_msg; guint transferred; guint32 id; int i; gboolean active; gint res = 0; //If the file already exists the device makes no difference between creating a new file and creating an already existent file. //Also, the new file would be discarded if an upload is not completed. tx_msg = new_msg_open_write (path, input->len); if (!tx_msg) { return -EINVAL; } rx_msg = connector_tx_and_rx (connector, tx_msg); if (!rx_msg) { return -EIO; } //Response: x, x, x, x, 0xc0, [0 (error), 1 (success)], id, frames res = connector_get_smplrw_info_from_msg (rx_msg, &id, NULL); if (res) { error_print ("%s (%s)\n", snd_strerror (res), connector_get_msg_string (rx_msg)); free_msg (rx_msg); return res; } free_msg (rx_msg); transferred = 0; i = 0; if (control) { g_mutex_lock (&control->mutex); active = control->active; g_mutex_unlock (&control->mutex); } else { active = TRUE; } while (transferred < input->len && active) { tx_msg = new_msg_write_blk (id, input, &transferred, i, control->data); rx_msg = connector_tx_and_rx (connector, tx_msg); if (!rx_msg) { return -EIO; } //Response: x, x, x, x, 0xc2, [0 (error), 1 (success)]... if (!connector_get_msg_status (rx_msg)) { error_print ("Unexpected status\n"); } free_msg (rx_msg); i++; if (control) { set_job_control_progress (control, transferred / (double) input->len); g_mutex_lock (&control->mutex); active = control->active; g_mutex_unlock (&control->mutex); } usleep (REST_TIME); } debug_print (2, "%d bytes sent\n", transferred); if (active) { tx_msg = new_msg_close_write (id, transferred); rx_msg = connector_tx_and_rx (connector, tx_msg); if (!rx_msg) { return -EIO; } //Response: x, x, x, x, 0xc1, [0 (error), 1 (success)]... if (!connector_get_msg_status (rx_msg)) { error_print ("Unexpected status\n"); } free_msg (rx_msg); } return res; } static gint connector_upload_sample_part (const gchar * path, GByteArray * sample, struct job_control *control, void *data) { return connector_upload_smplrw (path, sample, control, data, connector_new_msg_open_sample_write, connector_new_msg_write_sample_blk, connector_new_msg_close_sample_write); } static gint connector_upload_sample (const gchar * path, GByteArray * output, struct job_control *control, void *data) { control->parts = 1; control->part = 0; return connector_upload_sample_part (path, output, control, data); } static gint connector_upload_raw (const gchar * path, GByteArray * sample, struct job_control *control, void *data) { return connector_upload_smplrw (path, sample, control, data, connector_new_msg_open_raw_write, connector_new_msg_write_raw_blk, connector_new_msg_close_raw_write); } static GByteArray * connector_new_msg_open_sample_read (const gchar * path) { return connector_new_msg_path (FS_SAMPLE_OPEN_FILE_READER_REQUEST, sizeof (FS_SAMPLE_OPEN_FILE_READER_REQUEST), path); } static GByteArray * connector_new_msg_open_raw_read (const gchar * path) { return connector_new_msg_path (FS_RAW_OPEN_FILE_READER_REQUEST, sizeof (FS_RAW_OPEN_FILE_READER_REQUEST), path); } static void connector_copy_sample_data (GByteArray * input, GByteArray * output) { gint i; gint16 v; gint16 *frame = (gint16 *) input->data; for (i = 0; i < input->len; i += sizeof (gint16)) { v = be16toh (*frame); g_byte_array_append (output, (guint8 *) & v, sizeof (gint16)); frame++; } } static void connector_copy_raw_data (GByteArray * input, GByteArray * output) { g_byte_array_append (output, input->data, input->len); } static gint connector_download_smplrw (const gchar * path, GByteArray * output, struct job_control *control, void *data, connector_msg_path_func new_msg_open_read, guint read_offset, connector_msg_read_blk_func new_msg_read_blk, connector_msg_id_func new_msg_close_read, connector_copy_array copy_array) { struct connector *connector = data; struct sample_loop_data *sample_loop_data; struct elektron_sample_info *elektron_sample_info; GByteArray *tx_msg; GByteArray *rx_msg; GByteArray *array; guint32 id; guint frames; guint next_block_start; guint req_size; guint offset; gboolean active; gint res; tx_msg = new_msg_open_read (path); if (!tx_msg) { return -EINVAL; } rx_msg = connector_tx_and_rx (connector, tx_msg); if (!rx_msg) { return -EIO; } res = connector_get_smplrw_info_from_msg (rx_msg, &id, &frames); if (res) { error_print ("%s (%s)\n", snd_strerror (res), connector_get_msg_string (rx_msg)); free_msg (rx_msg); return res; } free_msg (rx_msg); debug_print (2, "%d frames to download\n", frames); if (control) { g_mutex_lock (&control->mutex); active = control->active; g_mutex_unlock (&control->mutex); } else { active = TRUE; } array = g_byte_array_new (); res = 0; next_block_start = 0; offset = read_offset; control->data = NULL; while (next_block_start < frames && active) { req_size = frames - next_block_start > DATA_TRANSF_BLOCK_BYTES ? DATA_TRANSF_BLOCK_BYTES : frames - next_block_start; tx_msg = new_msg_read_blk (id, next_block_start, req_size); rx_msg = connector_tx_and_rx (connector, tx_msg); if (!rx_msg) { res = -EIO; goto cleanup; } g_byte_array_append (array, &rx_msg->data[FS_SAMPLES_PAD_RES + offset], req_size - offset); next_block_start += req_size; //Only in the first iteration. It has no effect for the raw filesystem (M:C) as offset is 0. if (offset) { offset = 0; elektron_sample_info = (struct elektron_sample_info *) &rx_msg->data[FS_SAMPLES_PAD_RES]; sample_loop_data = malloc (sizeof (struct elektron_sample_info)); sample_loop_data->start = be32toh (elektron_sample_info->loop_start); sample_loop_data->end = be32toh (elektron_sample_info->loop_end); control->data = sample_loop_data; debug_print (2, "Loop start at %d, loop end at %d\n", sample_loop_data->start, sample_loop_data->end); } free_msg (rx_msg); if (control) { set_job_control_progress (control, next_block_start / (double) frames); g_mutex_lock (&control->mutex); active = control->active; g_mutex_unlock (&control->mutex); } usleep (REST_TIME); } debug_print (2, "%d bytes received\n", next_block_start); if (active) { copy_array (array, output); } else { res = -1; } tx_msg = new_msg_close_read (id); rx_msg = connector_tx_and_rx (connector, tx_msg); if (!rx_msg) { res = -EIO; goto cleanup; } //Response: x, x, x, x, 0xb1, 00 00 00 0a 00 01 65 de (sample id and received bytes) free_msg (rx_msg); cleanup: free_msg (array); if (res) { g_free (control->data); } return res; } static gint connector_download_sample_part (const gchar * path, GByteArray * output, struct job_control *control, void *data) { return connector_download_smplrw (path, output, control, data, connector_new_msg_open_sample_read, sizeof (struct elektron_sample_info), connector_new_msg_read_sample_blk, connector_new_msg_close_sample_read, connector_copy_sample_data); } static gint connector_download_sample (const gchar * path, GByteArray * output, struct job_control *control, void *data) { control->parts = 1; control->part = 0; return connector_download_sample_part (path, output, control, data); } static gint connector_download_raw (const gchar * path, GByteArray * output, struct job_control *control, void *data) { gint ret; gchar *path_with_ext = connector_add_ext_to_mc_snd (path); ret = connector_download_smplrw (path_with_ext, output, control, data, connector_new_msg_open_raw_read, 0, connector_new_msg_read_raw_blk, connector_new_msg_close_raw_read, connector_copy_raw_data); g_free (path_with_ext); return ret; } static gint connector_create_samples_dir (const gchar * path, void *data) { struct connector *connector = data; return connector_path_common (connector, path, FS_SAMPLE_CREATE_DIR_REQUEST, sizeof (FS_SAMPLE_CREATE_DIR_REQUEST)); } static gint connector_create_raw_dir (const gchar * path, void *data) { struct connector *connector = data; return connector_path_common (connector, path, FS_RAW_CREATE_DIR_REQUEST, sizeof (FS_RAW_CREATE_DIR_REQUEST)); } static GByteArray * connector_new_msg_upgrade_os_start (guint size) { GByteArray *msg = connector_new_msg (OS_UPGRADE_START_REQUEST, sizeof (OS_UPGRADE_START_REQUEST)); memcpy (&msg->data[5], &size, sizeof (guint32)); return msg; } static GByteArray * connector_new_msg_upgrade_os_write (GByteArray * os_data, gint * offset) { GByteArray *msg = connector_new_msg (OS_UPGRADE_WRITE_RESPONSE, sizeof (OS_UPGRADE_WRITE_RESPONSE)); guint len; guint32 crc; guint32 aux32; if (*offset + OS_TRANSF_BLOCK_BYTES < os_data->len) { len = OS_TRANSF_BLOCK_BYTES; } else { len = os_data->len - *offset; } crc = crc32 (0xffffffff, &os_data->data[*offset], len); debug_print (2, "CRC: %0x\n", crc); aux32 = htobe32 (crc); memcpy (&msg->data[5], &aux32, sizeof (guint32)); aux32 = htobe32 (len); memcpy (&msg->data[9], &aux32, sizeof (guint32)); aux32 = htobe32 (*offset); memcpy (&msg->data[13], &aux32, sizeof (guint32)); g_byte_array_append (msg, &os_data->data[*offset], len); *offset = *offset + len; return msg; } gint connector_upgrade_os (struct connector *connector, struct connector_sysex_transfer *transfer) { GByteArray *tx_msg; GByteArray *rx_msg; gint8 op; gint offset; gint res = 0; transfer->status = SENDING; tx_msg = connector_new_msg_upgrade_os_start (transfer->raw->len); rx_msg = connector_tx_and_rx (connector, tx_msg); if (!rx_msg) { res = -EIO; goto end; } //Response: x, x, x, x, 0xd1, [0 (ok), 1 (error)]... op = connector_get_msg_status (rx_msg); if (op) { res = -EIO; error_print ("%s (%s)\n", snd_strerror (res), connector_get_msg_string (rx_msg)); free_msg (rx_msg); goto end; } free_msg (rx_msg); offset = 0; while (offset < transfer->raw->len && transfer->active) { tx_msg = connector_new_msg_upgrade_os_write (transfer->raw, &offset); rx_msg = connector_tx_and_rx (connector, tx_msg); if (!rx_msg) { res = -EIO; break; } //Response: x, x, x, x, 0xd1, int32, [0..3]... op = rx_msg->data[9]; if (op == 1) { break; } else if (op > 1) { res = -EIO; error_print ("%s (%s)\n", snd_strerror (res), connector_get_msg_string (rx_msg)); free_msg (rx_msg); break; } free_msg (rx_msg); usleep (REST_TIME); } end: transfer->active = FALSE; transfer->status = FINISHED; return res; } void connector_destroy (struct connector *connector) { int err; debug_print (1, "Destroying connector...\n"); if (connector->inputp) { err = snd_rawmidi_close (connector->inputp); if (err) { error_print ("Error while closing MIDI port: %s\n", snd_strerror (err)); } connector->inputp = NULL; } if (connector->outputp) { err = snd_rawmidi_close (connector->outputp); if (err) { error_print ("Error while closing MIDI port: %s\n", snd_strerror (err)); } connector->outputp = NULL; } if (connector->device_name) { free (connector->device_name); free (connector->fw_version); free (connector->alias); connector->device_name = NULL; } if (connector->buffer) { free (connector->buffer); connector->buffer = NULL; } if (connector->pfds) { free (connector->pfds); connector->pfds = NULL; } if (connector->dir_cache) { g_hash_table_destroy (connector->dir_cache); connector->dir_cache = NULL; } } gint connector_get_storage_stats (struct connector *connector, enum connector_storage type, struct connector_storage_stats *statfs) { GByteArray *tx_msg; GByteArray *rx_msg; gint8 op; guint64 *data; int index; gint res = 0; tx_msg = connector_new_msg_uint8 (STORAGEINFO_REQUEST, sizeof (STORAGEINFO_REQUEST), type); rx_msg = connector_tx_and_rx (connector, tx_msg); if (!rx_msg) { return -EIO; } op = connector_get_msg_status (rx_msg); if (!op) { error_print ("%s (%s)\n", snd_strerror (-EIO), connector_get_msg_string (rx_msg)); free_msg (rx_msg); return -EIO; } index = 0; for (int i = 0, storage = STORAGE_PLUS_DRIVE; storage <= STORAGE_RAM; i++, storage <<= 1) { if (storage == type) { index = i; } } statfs->name = FS_TYPE_NAMES[index]; data = (guint64 *) & rx_msg->data[6]; statfs->bfree = be64toh (*data); data = (guint64 *) & rx_msg->data[14]; statfs->bsize = be64toh (*data); free_msg (rx_msg); return res; } gdouble connector_get_storage_stats_percent (struct connector_storage_stats *statfs) { return (statfs->bsize - statfs->bfree) * 100.0 / statfs->bsize; } static const struct connector_device_desc * connector_get_device_desc (guint8 id) { guint total = sizeof (CONNECTOR_DEVICE_DESCS) / sizeof (struct connector_device_desc *); guint i; for (i = 0; i < total; i++) { if (id == CONNECTOR_DEVICE_DESCS[i]->id) { return CONNECTOR_DEVICE_DESCS[i]; } } return NULL; } gint connector_init (struct connector *connector, gint card) { int err; GByteArray *tx_msg; GByteArray *rx_msg; snd_rawmidi_params_t *params; gchar name[32]; sprintf (name, "hw:%d", card); connector->inputp = NULL; connector->outputp = NULL; connector->device_name = NULL; connector->buffer = NULL; connector->rx_len = 0; connector->pfds = NULL; connector->dir_cache = NULL; if (card < 0) { debug_print (1, "Invalid card\n"); err = -EINVAL; goto cleanup; } debug_print (1, "Initializing connector to '%s'...\n", name); if ((err = snd_rawmidi_open (&connector->inputp, &connector->outputp, name, SND_RAWMIDI_NONBLOCK | SND_RAWMIDI_SYNC)) < 0) { error_print ("Error while opening MIDI port: %s\n", g_strerror (err)); goto cleanup; } debug_print (1, "Setting blocking mode...\n"); if ((err = snd_rawmidi_nonblock (connector->outputp, 0)) < 0) { error_print ("Error while setting blocking mode\n"); goto cleanup; } if ((err = snd_rawmidi_nonblock (connector->inputp, 1)) < 0) { error_print ("Error while setting blocking mode\n"); goto cleanup; } debug_print (1, "Stopping device...\n"); if (snd_rawmidi_write (connector->outputp, "\xfc", 1) < 0) { error_print ("Error while stopping device\n"); } connector->seq = 0; connector->device_name = malloc (LABEL_MAX); if (!connector->device_name) { goto cleanup; } connector->buffer = malloc (sizeof (guint8) * BUFF_SIZE); if (!connector->buffer) { goto cleanup; } connector->npfds = snd_rawmidi_poll_descriptors_count (connector->inputp); connector->pfds = malloc (connector->npfds * sizeof (struct pollfd)); if (!connector->buffer) { goto cleanup; } snd_rawmidi_poll_descriptors (connector->inputp, connector->pfds, connector->npfds); err = snd_rawmidi_params_malloc (¶ms); if (err) { goto cleanup; } err = snd_rawmidi_params_current (connector->inputp, params); if (err) { goto cleanup_params; } err = snd_rawmidi_params_set_buffer_size (connector->inputp, params, RING_BUFF_SIZE); if (err) { goto cleanup_params; } err = snd_rawmidi_params (connector->inputp, params); if (err) { goto cleanup_params; } tx_msg = connector_new_msg (PING_REQUEST, sizeof (PING_REQUEST)); rx_msg = connector_tx_and_rx (connector, tx_msg); if (!rx_msg) { err = -EIO; goto cleanup_params; } connector->alias = strdup ((gchar *) & rx_msg->data[7 + rx_msg->data[6]]); connector->device_desc = connector_get_device_desc (rx_msg->data[5]); free_msg (rx_msg); if (!connector->device_desc) { err = -ENODEV; goto cleanup_params; } tx_msg = connector_new_msg (SOFTWARE_VERSION_REQUEST, sizeof (SOFTWARE_VERSION_REQUEST)); rx_msg = connector_tx_and_rx (connector, tx_msg); if (!rx_msg) { err = -EIO; goto cleanup_params; } connector->fw_version = strdup ((gchar *) & rx_msg->data[10]); free_msg (rx_msg); if (debug_level > 1) { tx_msg = connector_new_msg (DEVICEUID_REQUEST, sizeof (DEVICEUID_REQUEST)); rx_msg = connector_tx_and_rx (connector, tx_msg); if (rx_msg) { debug_print (1, "UID: %x\n", *((guint32 *) & rx_msg->data[5])); free_msg (rx_msg); } } snprintf (connector->device_name, LABEL_MAX, "%s %s (%s)", connector->device_desc->name, connector->fw_version, connector->alias); debug_print (1, "Connected to %s\n", connector->device_name); err = 0; cleanup_params: snd_rawmidi_params_free (params); cleanup: if (err) { connector_destroy (connector); } return err; } gboolean connector_check (struct connector *connector) { return (connector->inputp && connector->outputp); } static struct connector_system_device * connector_get_system_device (snd_ctl_t * ctl, int card, int device) { snd_rawmidi_info_t *info; const gchar *name; const gchar *sub_name; int subs, subs_in, subs_out; int sub; int err; struct connector_system_device *connector_system_device; snd_rawmidi_info_alloca (&info); snd_rawmidi_info_set_device (info, device); snd_rawmidi_info_set_stream (info, SND_RAWMIDI_STREAM_INPUT); err = snd_ctl_rawmidi_info (ctl, info); if (err >= 0) { subs_in = snd_rawmidi_info_get_subdevices_count (info); } else { subs_in = 0; } snd_rawmidi_info_set_stream (info, SND_RAWMIDI_STREAM_OUTPUT); err = snd_ctl_rawmidi_info (ctl, info); if (err >= 0) { subs_out = snd_rawmidi_info_get_subdevices_count (info); } else { subs_out = 0; } subs = subs_in > subs_out ? subs_in : subs_out; if (!subs) { return NULL; } if (subs_in <= 0 || subs_out <= 0) { return NULL; } sub = 0; snd_rawmidi_info_set_stream (info, sub < subs_in ? SND_RAWMIDI_STREAM_INPUT : SND_RAWMIDI_STREAM_OUTPUT); snd_rawmidi_info_set_subdevice (info, sub); err = snd_ctl_rawmidi_info (ctl, info); if (err < 0) { error_print ("Cannot get rawmidi information %d:%d:%d: %s\n", card, device, sub, snd_strerror (err)); return NULL; } name = snd_rawmidi_info_get_name (info); sub_name = snd_rawmidi_info_get_subdevice_name (info); if (strncmp (sub_name, "Elektron", 8) == 0) { debug_print (1, "Adding hw:%d (%s) %s...\n", card, name, sub_name); connector_system_device = malloc (sizeof (struct connector_system_device)); connector_system_device->card = card; connector_system_device->name = strdup (sub_name); return connector_system_device; } else { return NULL; } } static void connector_fill_card_elektron_devices (gint card, GArray * devices) { snd_ctl_t *ctl; gchar name[32]; gint device; gint err; struct connector_system_device *connector_system_device; sprintf (name, "hw:%d", card); if ((err = snd_ctl_open (&ctl, name, 0)) < 0) { error_print ("Cannot open control for card %d: %s\n", card, snd_strerror (err)); return; } device = -1; while (!(err = snd_ctl_rawmidi_next_device (ctl, &device)) && device >= 0) { connector_system_device = connector_get_system_device (ctl, card, device); if (connector_system_device) { g_array_append_vals (devices, connector_system_device, 1); } } if (err < 0) { error_print ("Cannot determine device number: %s\n", snd_strerror (err)); } snd_ctl_close (ctl); } GArray * connector_get_system_devices () { gint card, err; GArray *devices = g_array_new (FALSE, FALSE, sizeof (struct connector_system_device)); card = -1; while (!(err = snd_card_next (&card)) && card >= 0) { connector_fill_card_elektron_devices (card, devices); } if (err < 0) { error_print ("Cannot determine card number: %s\n", snd_strerror (err)); } return devices; } static guint connector_next_data_entry (struct item_iterator *iter) { gchar *name_cp1252; guint32 *data32; guint16 *data16; guint8 type; guint8 has_children; struct connector_iterator_data *data = iter->data; if (iter->item.name != NULL) { g_free (iter->item.name); } if (data->pos == data->msg->len) { iter->item.name = NULL; return -ENOENT; } name_cp1252 = (gchar *) & data->msg->data[data->pos]; iter->item.name = connector_get_utf8 (name_cp1252); data->pos += strlen (name_cp1252) + 1; has_children = data->msg->data[data->pos]; data->pos++; type = data->msg->data[data->pos]; data->pos++; switch (type) { case 1: iter->item.type = ELEKTROID_DIR; data->pos += sizeof (guint32); // child entries iter->item.size = 0; iter->item.index = -1; data->operations = 0; data->has_valid_data = 0; data->has_metadata = 0; break; case 2: iter->item.type = has_children ? ELEKTROID_DIR : ELEKTROID_FILE; data32 = (guint32 *) & data->msg->data[data->pos]; iter->item.index = be32toh (*data32); //index data->pos += sizeof (gint32); data32 = (guint32 *) & data->msg->data[data->pos]; iter->item.size = be32toh (*data32); data->pos += sizeof (guint32); data16 = (guint16 *) & data->msg->data[data->pos]; data->operations = be16toh (*data16); data->pos += sizeof (guint16); data->has_valid_data = data->msg->data[data->pos]; data->pos++; data->has_metadata = data->msg->data[data->pos]; data->pos++; break; default: error_print ("Unrecognized data entry: %d\n", iter->item.type); break; } return 0; } static gchar * connector_add_prefix_to_path (const gchar * dir, const gchar * prefix) { gchar *full = malloc (PATH_MAX); if (prefix) { snprintf (full, PATH_MAX, "%s%s", prefix, dir); } else { strcpy (full, dir); } return full; } static gint connector_read_data_dir_prefix (struct item_iterator *iter, const gchar * dir, void *data, const char *prefix) { int res; GByteArray *tx_msg; GByteArray *rx_msg; struct connector *connector = data; gchar *dir_w_prefix = connector_add_prefix_to_path (dir, prefix); tx_msg = connector_new_msg_list (dir_w_prefix, 0, 0, 1); g_free (dir_w_prefix); if (!tx_msg) { return -EINVAL; } rx_msg = connector_tx_and_rx (connector, tx_msg); if (!rx_msg) { return -EIO; } res = connector_get_msg_status (rx_msg); if (!res) { free_msg (rx_msg); return -ENOTDIR; } return connector_init_iterator (iter, rx_msg, connector_next_data_entry, FS_DATA_ALL, FALSE); } static gint connector_read_data_dir_any (struct item_iterator *iter, const gchar * dir, void *data) { return connector_read_data_dir_prefix (iter, dir, data, NULL); } static gint connector_read_data_dir_prj (struct item_iterator *iter, const gchar * dir, void *data) { return connector_read_data_dir_prefix (iter, dir, data, FS_DATA_PRJ_PREFIX); } static gint connector_read_data_dir_snd (struct item_iterator *iter, const gchar * dir, void *data) { return connector_read_data_dir_prefix (iter, dir, data, FS_DATA_SND_PREFIX); } static gint connector_dst_src_data_prefix_common (const gchar * src, const gchar * dst, void *data, const char *prefix, const guint8 * op_data, guint len) { gint res; struct connector *connector = data; char *src_w_prefix = connector_add_prefix_to_path (src, prefix); char *dst_w_prefix = connector_add_prefix_to_path (dst, prefix); res = connector_src_dst_common (connector, src_w_prefix, dst_w_prefix, op_data, len); g_free (src_w_prefix); g_free (dst_w_prefix); return res; } static gint connector_move_data_item_prefix (const gchar * src, const gchar * dst, void *data, const char *prefix) { return connector_dst_src_data_prefix_common (src, dst, data, prefix, DATA_MOVE_REQUEST, sizeof (DATA_MOVE_REQUEST)); } static gint connector_move_data_item_any (const gchar * src, const gchar * dst, void *data) { return connector_move_data_item_prefix (src, dst, data, NULL); } static gint connector_move_data_item_prj (const gchar * src, const gchar * dst, void *data) { return connector_move_data_item_prefix (src, dst, data, FS_DATA_PRJ_PREFIX); } static gint connector_move_data_item_snd (const gchar * src, const gchar * dst, void *data) { return connector_move_data_item_prefix (src, dst, data, FS_DATA_SND_PREFIX); } static gint connector_copy_data_item_prefix (const gchar * src, const gchar * dst, void *data, const gchar * prefix) { return connector_dst_src_data_prefix_common (src, dst, data, prefix, DATA_COPY_REQUEST, sizeof (DATA_COPY_REQUEST)); } static gint connector_copy_data_item_any (const gchar * src, const gchar * dst, void *data) { return connector_copy_data_item_prefix (src, dst, data, NULL); } static gint connector_copy_data_item_prj (const gchar * src, const gchar * dst, void *data) { return connector_copy_data_item_prefix (src, dst, data, FS_DATA_PRJ_PREFIX); } static gint connector_copy_data_item_snd (const gchar * src, const gchar * dst, void *data) { return connector_copy_data_item_prefix (src, dst, data, FS_DATA_SND_PREFIX); } static gint connector_path_data_prefix_common (const gchar * path, void *data, const char *prefix, const guint8 * op_data, guint len) { gint res; struct connector *connector = data; char *path_w_prefix = connector_add_prefix_to_path (path, prefix); res = connector_path_common (connector, path_w_prefix, op_data, len); g_free (path_w_prefix); return res; } static gint connector_clear_data_item_prefix (const gchar * path, void *data, const gchar * prefix) { return connector_path_data_prefix_common (path, data, prefix, DATA_CLEAR_REQUEST, sizeof (DATA_CLEAR_REQUEST)); } static gint connector_clear_data_item_any (const gchar * path, void *data) { return connector_clear_data_item_prefix (path, data, NULL); } static gint connector_clear_data_item_prj (const gchar * path, void *data) { return connector_clear_data_item_prefix (path, data, FS_DATA_PRJ_PREFIX); } static gint connector_clear_data_item_snd (const gchar * path, void *data) { return connector_clear_data_item_prefix (path, data, FS_DATA_SND_PREFIX); } static gint connector_swap_data_item_prefix (const gchar * src, const gchar * dst, void *data, const gchar * prefix) { return connector_dst_src_data_prefix_common (src, dst, data, prefix, DATA_SWAP_REQUEST, sizeof (DATA_SWAP_REQUEST)); } static gint connector_swap_data_item_any (const gchar * src, const gchar * dst, void *data) { return connector_swap_data_item_prefix (src, dst, data, NULL); } static gint connector_swap_data_item_prj (const gchar * src, const gchar * dst, void *data) { return connector_swap_data_item_prefix (src, dst, data, FS_DATA_PRJ_PREFIX); } static gint connector_swap_data_item_snd (const gchar * src, const gchar * dst, void *data) { return connector_swap_data_item_prefix (src, dst, data, FS_DATA_SND_PREFIX); } static gint connector_open_datum (struct connector *connector, const gchar * path, guint32 * jid, gint mode, guint32 size) { guint32 *data32; guint32 sizebe; guint32 chunk_size; guint8 compression; GByteArray *rx_msg; GByteArray *tx_msg; const guint8 *data; guint len; gchar *path_cp1252; gint res = 0; if (mode == O_RDONLY) { data = DATA_READ_OPEN_REQUEST; len = sizeof (DATA_READ_OPEN_REQUEST); } else if (mode == O_WRONLY) { data = DATA_WRITE_OPEN_REQUEST; len = sizeof (DATA_WRITE_OPEN_REQUEST); } else { return -EINVAL; } tx_msg = connector_new_msg (data, len); if (!tx_msg) { return -ENOMEM; } path_cp1252 = connector_get_cp1252 (path); if (mode == O_RDONLY) { g_byte_array_append (tx_msg, (guint8 *) path_cp1252, strlen (path_cp1252) + 1); chunk_size = htobe32 (DATA_TRANSF_BLOCK_BYTES); g_byte_array_append (tx_msg, (guint8 *) & chunk_size, sizeof (guint32)); compression = 1; g_byte_array_append (tx_msg, &compression, sizeof (guint8)); } if (mode == O_WRONLY) { sizebe = htobe32 (size); g_byte_array_append (tx_msg, (guint8 *) & sizebe, sizeof (guint32)); g_byte_array_append (tx_msg, (guint8 *) path_cp1252, strlen (path_cp1252) + 1); } rx_msg = connector_tx_and_rx (connector, tx_msg); if (!rx_msg) { res = -EIO; goto cleanup; } if (!connector_get_msg_status (rx_msg)) { res = -EPERM; error_print ("%s (%s)\n", snd_strerror (res), connector_get_msg_string (rx_msg)); free_msg (rx_msg); goto cleanup; } data32 = (guint32 *) & rx_msg->data[6]; *jid = be32toh (*data32); if (mode == O_RDONLY) { data32 = (guint32 *) & rx_msg->data[10]; chunk_size = be32toh (*data32); compression = rx_msg->data[14]; debug_print (1, "Open datum info: job id: %d; chunk size: %d; compression: %d\n", *jid, chunk_size, compression); } if (mode == O_WRONLY) { debug_print (1, "Open datum info: job id: %d\n", *jid); } free_msg (rx_msg); cleanup: g_free (path_cp1252); return res; } static gint connector_close_datum (struct connector *connector, guint32 jid, gint mode, guint32 wsize) { guint32 jidbe; guint32 wsizebe; guint32 r_jid; guint32 asize; guint32 *data32; GByteArray *rx_msg; GByteArray *tx_msg; const guint8 *data; guint len; if (mode == O_RDONLY) { data = DATA_READ_CLOSE_REQUEST; len = sizeof (DATA_READ_CLOSE_REQUEST); } else if (mode == O_WRONLY) { data = DATA_WRITE_CLOSE_REQUEST; len = sizeof (DATA_WRITE_CLOSE_REQUEST); } else { return -EINVAL; } tx_msg = connector_new_msg (data, len); if (!tx_msg) { return -ENOMEM; } jidbe = htobe32 (jid); g_byte_array_append (tx_msg, (guchar *) & jidbe, sizeof (guint32)); if (mode == O_WRONLY) { wsizebe = htobe32 (wsize); g_byte_array_append (tx_msg, (guchar *) & wsizebe, sizeof (guint32)); } rx_msg = connector_tx_and_rx (connector, tx_msg); if (!rx_msg) { return -EIO; } if (!connector_get_msg_status (rx_msg)) { error_print ("%s (%s)\n", snd_strerror (-EPERM), connector_get_msg_string (rx_msg)); free_msg (rx_msg); return -EPERM; } data32 = (guint32 *) & rx_msg->data[6]; r_jid = be32toh (*data32); data32 = (guint32 *) & rx_msg->data[10]; asize = be32toh (*data32); debug_print (1, "Close datum info: job id: %d; size: %d\n", r_jid, asize); free_msg (rx_msg); if (mode == O_WRONLY && asize != wsize) { error_print ("Actual download bytes (%d) differs from expected ones (%d)\n", asize, wsize); return -EINVAL; } return 0; } static gint connector_download_data_prefix (const gchar * path, GByteArray * output, struct job_control *control, void *data, const gchar * prefix) { gint res; guint32 seq; guint32 seqbe; guint32 jid; guint32 r_jid; guint32 r_seq; guint32 status; guint8 last; guint32 hash; guint32 *data32; guint32 jidbe; guint32 data_size; gboolean active; GByteArray *rx_msg; GByteArray *tx_msg; gchar *path_w_prefix = connector_add_prefix_to_path (path, prefix); struct connector *connector = data; res = connector_open_datum (connector, path_w_prefix, &jid, O_RDONLY, 0); g_free (path_w_prefix); if (res) { return -EIO; } usleep (REST_TIME); jidbe = htobe32 (jid); res = 0; seq = 0; last = 0; control->data = NULL; if (control) { g_mutex_lock (&control->mutex); active = control->active; g_mutex_unlock (&control->mutex); } else { active = TRUE; } while (!last && active) { tx_msg = connector_new_msg (DATA_READ_PARTIAL_REQUEST, sizeof (DATA_READ_PARTIAL_REQUEST)); g_byte_array_append (tx_msg, (guint8 *) & jidbe, sizeof (guint32)); seqbe = htobe32 (seq); g_byte_array_append (tx_msg, (guint8 *) & seqbe, sizeof (guint32)); rx_msg = connector_tx_and_rx (connector, tx_msg); if (!rx_msg) { res = -EIO; break; } if (!connector_get_msg_status (rx_msg)) { res = -EPERM; error_print ("%s (%s)\n", snd_strerror (res), connector_get_msg_string (rx_msg)); free_msg (rx_msg); break; } data32 = (guint32 *) & rx_msg->data[6]; r_jid = be32toh (*data32); data32 = (guint32 *) & rx_msg->data[10]; r_seq = be32toh (*data32); data32 = (guint32 *) & rx_msg->data[14]; status = be32toh (*data32); last = rx_msg->data[18]; data32 = (guint32 *) & rx_msg->data[19]; hash = be32toh (*data32); data32 = (guint32 *) & rx_msg->data[23]; data_size = be32toh (*data32); if (data_size) { debug_print (1, "Read datum info: job id: %d; last: %d; seq: %d; status: %d; hash: 0x%08x\n", r_jid, last, r_seq, status, hash); g_byte_array_append (output, (guint8 *) & rx_msg->data[27], data_size); } else { // Sometimes, the first message returns 0 data size and the rest of the parameters are not initialized. debug_print (1, "Read datum info: job id: %d; last: %d, hash: 0x%08x\n", r_jid, last, hash); status = 0; } free_msg (rx_msg); seq++; if (control) { set_job_control_progress (control, status / 1000.0); g_mutex_lock (&control->mutex); active = control->active; g_mutex_unlock (&control->mutex); } usleep (REST_TIME); } return connector_close_datum (connector, jid, O_RDONLY, 0); } static gint connector_download_data_any (const gchar * path, GByteArray * output, struct job_control *control, void *data) { control->parts = 1; control->part = 0; return connector_download_data_prefix (path, output, control, data, NULL); } static gint connector_download_data_prj (const gchar * path, GByteArray * output, struct job_control *control, void *data) { return connector_download_data_prefix (path, output, control, data, FS_DATA_PRJ_PREFIX); } static gint connector_download_data_snd (const gchar * path, GByteArray * output, struct job_control *control, void *data) { return connector_download_data_prefix (path, output, control, data, FS_DATA_SND_PREFIX); } gchar * connector_get_sample_path_from_hash_size (struct connector *connector, guint32 hash, guint32 size) { guint32 aux32; gchar *path; GByteArray *rx_msg, *tx_msg = connector_new_msg (FS_SAMPLE_GET_FILE_INFO_FROM_HASH_AND_SIZE_REQUEST, sizeof (FS_SAMPLE_GET_FILE_INFO_FROM_HASH_AND_SIZE_REQUEST)); aux32 = htobe32 (hash); memcpy (&tx_msg->data[5], &aux32, sizeof (guint32)); aux32 = htobe32 (size); memcpy (&tx_msg->data[9], &aux32, sizeof (guint32)); rx_msg = connector_tx_and_rx (connector, tx_msg); if (!rx_msg) { return NULL; } if (connector_get_msg_status (rx_msg)) { path = strdup ((gchar *) & rx_msg->data[14]); } else { path = NULL; } g_byte_array_free (rx_msg, TRUE); return path; } static gint connector_download_pkg (const gchar * path, GByteArray * output, struct job_control *control, void *data, enum package_type type, const struct fs_operations *ops, fs_remote_file_op download) { gint ret; gchar *pkg_name; struct package pkg; struct connector *connector = data; pkg_name = connector_get_download_name (connector, NULL, ops, path); if (!pkg_name) { return -1; } if (package_begin (&pkg, pkg_name, connector->fw_version, connector->device_desc, type)) { g_free (pkg_name); return -1; } ret = package_receive_pkg_resources (&pkg, path, control, connector, download, connector_download_sample_part); ret = ret || package_end (&pkg, output); package_destroy (&pkg); return ret; } static gint connector_download_data_snd_pkg (const gchar * path, GByteArray * output, struct job_control *control, void *data) { return connector_download_pkg (path, output, control, data, PKG_FILE_TYPE_SOUND, &FS_DATA_SND_OPERATIONS, connector_download_data_snd); } static gint connector_download_data_prj_pkg (const gchar * path, GByteArray * output, struct job_control *control, void *data) { return connector_download_pkg (path, output, control, data, PKG_FILE_TYPE_PROJECT, &FS_DATA_PRJ_OPERATIONS, connector_download_data_prj); } static gint connector_download_raw_pst_pkg (const gchar * path, GByteArray * output, struct job_control *control, void *data) { return connector_download_pkg (path, output, control, data, PKG_FILE_TYPE_PRESET, &FS_RAW_ANY_OPERATIONS, connector_download_raw); } gchar * connector_get_upload_path (struct connector *connector, struct item_iterator *remote_iter, const struct fs_operations *ops, const gchar * dst_dir, const gchar * src_path, gint32 * next_index) { gchar *path, *indexs, *namec, *name, *aux; gboolean new; if (ops->fs == FS_SAMPLES || ops->fs == FS_RAW_ALL || ops->fs == FS_RAW_PRESETS) { namec = strdup (src_path); name = basename (namec); remove_ext (name); aux = chain_path (dst_dir, name); g_free (namec); if (ops->fs == FS_RAW_ALL || ops->fs == FS_RAW_PRESETS) { path = connector_add_ext_to_mc_snd (aux); g_free (aux); } else { path = aux; } return path; } new = FALSE; if (!remote_iter) { new = TRUE; remote_iter = malloc (sizeof (struct item_iterator)); if (ops->readdir (remote_iter, dst_dir, connector)) { return strdup (dst_dir); } } if (remote_iter->item.index == *next_index) { (*next_index)++; } else { while (!next_item_iterator (remote_iter)) { if (remote_iter->item.index > *next_index) { break; } (*next_index)++; } } if (new) { free_item_iterator (remote_iter); } indexs = malloc (PATH_MAX); snprintf (indexs, PATH_MAX, "%d", *next_index); path = chain_path (dst_dir, indexs); g_free (indexs); (*next_index)++; return path; } gchar * connector_get_download_name (struct connector *connector, struct item_iterator *remote_iter, const struct fs_operations *ops, const gchar * src_path) { gint32 id; const gchar *src_dir; gchar *namec, *name, *src_dirc; gint ret; gboolean new = FALSE; namec = strdup (src_path); name = basename (namec); if (ops->fs == FS_SAMPLES || ops->fs == FS_RAW_ALL || ops->fs == FS_RAW_PRESETS) { name = strdup (name); goto end; } if (!remote_iter) { new = TRUE; remote_iter = malloc (sizeof (struct item_iterator)); src_dirc = strdup (src_path); src_dir = dirname (src_dirc); ret = ops->readdir (remote_iter, src_dir, connector); g_free (src_dirc); if (ret) { name = NULL; goto cleanup; } } id = atoi (name); name = NULL; while (!next_item_iterator (remote_iter)) { if (remote_iter->item.index == id) { name = get_item_name (&remote_iter->item); break; } } cleanup: if (new) { free_item_iterator (remote_iter); } g_free (namec); end: return name; } gchar * connector_get_download_path (struct connector *connector, struct item_iterator *remote_iter, const struct fs_operations *ops, const gchar * dst_dir, const gchar * src_path) { gchar *path, *src_pathc, *name, *dl_ext, *filename; const gchar *src_fpath, *md_ext, *ext = get_ext (src_path); src_pathc = strdup (src_path); if (ext && strcmp (ext, "metadata") == 0 && ops->fs != FS_SAMPLES) { src_fpath = dirname (src_pathc); md_ext = ".metadata"; } else { src_fpath = src_pathc; md_ext = ""; } name = connector_get_download_name (connector, remote_iter, ops, src_fpath); if (name) { dl_ext = connector_get_full_ext (connector->device_desc, ops); filename = malloc (PATH_MAX); snprintf (filename, PATH_MAX, "%s.%s%s", name, dl_ext, md_ext); path = chain_path (dst_dir, filename); g_free (name); g_free (dl_ext); g_free (filename); } else { path = NULL; } g_free (src_pathc); return path; } static gint connector_upload_data_prefix (const gchar * path, GByteArray * array, struct job_control *control, void *data, const gchar * prefix) { gint res; guint32 seq; guint32 jid; guint32 crc; guint32 len; guint32 r_jid; guint32 r_seq; guint32 offset; guint32 *data32; guint32 jidbe; guint32 aux32; gboolean active; guint32 total; GByteArray *rx_msg; GByteArray *tx_msg; gchar *path_w_prefix = connector_add_prefix_to_path (path, prefix); struct connector *connector = data; res = connector_open_datum (connector, path_w_prefix, &jid, O_WRONLY, array->len); g_free (path_w_prefix); if (res) { goto end; } usleep (REST_TIME); jidbe = htobe32 (jid); seq = 0; offset = 0; control->data = NULL; if (control) { g_mutex_lock (&control->mutex); active = control->active; g_mutex_unlock (&control->mutex); } else { active = TRUE; } while (offset < array->len && active) { tx_msg = connector_new_msg (DATA_WRITE_PARTIAL_REQUEST, sizeof (DATA_WRITE_PARTIAL_REQUEST)); g_byte_array_append (tx_msg, (guint8 *) & jidbe, sizeof (guint32)); aux32 = htobe32 (seq); g_byte_array_append (tx_msg, (guint8 *) & aux32, sizeof (guint32)); if (offset + DATA_TRANSF_BLOCK_BYTES < array->len) { len = DATA_TRANSF_BLOCK_BYTES; } else { len = array->len - offset; } crc = crc32 (0xffffffff, &array->data[offset], len); aux32 = htobe32 (crc); g_byte_array_append (tx_msg, (guint8 *) & aux32, sizeof (guint32)); aux32 = htobe32 (len); g_byte_array_append (tx_msg, (guint8 *) & aux32, sizeof (guint32)); g_byte_array_append (tx_msg, &array->data[offset], len); rx_msg = connector_tx_and_rx (connector, tx_msg); if (!rx_msg) { res = -EIO; goto end; } usleep (REST_TIME); if (!connector_get_msg_status (rx_msg)) { res = -EPERM; error_print ("%s (%s)\n", snd_strerror (res), connector_get_msg_string (rx_msg)); free_msg (rx_msg); break; } data32 = (guint32 *) & rx_msg->data[6]; r_jid = be32toh (*data32); data32 = (guint32 *) & rx_msg->data[10]; r_seq = be32toh (*data32); data32 = (guint32 *) & rx_msg->data[14]; total = be32toh (*data32); free_msg (rx_msg); debug_print (1, "Write datum info: job id: %d; seq: %d; total: %d\n", r_jid, r_seq, total); seq++; offset += len; if (total != offset) { error_print ("Actual upload bytes (%d) differs from expected ones (%d)\n", total, offset); } if (control) { set_job_control_progress (control, offset / (gdouble) array->len); g_mutex_lock (&control->mutex); active = control->active; g_mutex_unlock (&control->mutex); } } debug_print (2, "%d bytes sent\n", offset); res = connector_close_datum (connector, jid, O_WRONLY, array->len); end: return res; } static gint connector_upload_data_any (const gchar * path, GByteArray * array, struct job_control *control, void *data) { control->parts = 1; control->part = 0; return connector_upload_data_prefix (path, array, control, data, NULL); } static gint connector_upload_data_prj (const gchar * path, GByteArray * array, struct job_control *control, void *data) { return connector_upload_data_prefix (path, array, control, data, FS_DATA_PRJ_PREFIX); } static gint connector_upload_data_snd (const gchar * path, GByteArray * array, struct job_control *control, void *data) { return connector_upload_data_prefix (path, array, control, data, FS_DATA_SND_PREFIX); } static gint connector_upload_pkg (const gchar * path, GByteArray * input, struct job_control *control, void *data, guint8 type, const struct fs_operations *ops, fs_remote_file_op upload) { gint ret; struct package pkg; struct connector *connector = data; ret = package_open (&pkg, input, connector->device_desc); if (!ret) { ret = package_send_pkg_resources (&pkg, path, control, connector, upload, connector_upload_sample_part); package_close (&pkg); } return ret; } static gint connector_upload_data_snd_pkg (const gchar * path, GByteArray * input, struct job_control *control, void *data) { return connector_upload_pkg (path, input, control, data, PKG_FILE_TYPE_SOUND, &FS_DATA_SND_OPERATIONS, connector_upload_data_snd); } static gint connector_upload_data_prj_pkg (const gchar * path, GByteArray * input, struct job_control *control, void *data) { return connector_upload_pkg (path, input, control, data, PKG_FILE_TYPE_PROJECT, &FS_DATA_PRJ_OPERATIONS, connector_upload_data_prj); } static gint connector_upload_raw_pst_pkg (const gchar * path, GByteArray * input, struct job_control *control, void *data) { return connector_upload_pkg (path, input, control, data, PKG_FILE_TYPE_PRESET, &FS_RAW_ANY_OPERATIONS, connector_upload_raw); } gchar * connector_get_full_ext (const struct connector_device_desc *desc, const struct fs_operations *ops) { gchar *ext = malloc (LABEL_MAX); if (ops->fs == FS_SAMPLES) { snprintf (ext, LABEL_MAX, "%s", ops->extension); } else { snprintf (ext, LABEL_MAX, "%s%s", desc->alias, ops->extension); } return ext; } void connector_enable_dir_cache (struct connector *connector) { g_mutex_lock (&connector->mutex); connector->dir_cache = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, free_msg); g_mutex_unlock (&connector->mutex); } void connector_disable_dir_cache (struct connector *connector) { g_mutex_lock (&connector->mutex); g_hash_table_destroy (connector->dir_cache); connector->dir_cache = NULL; g_mutex_unlock (&connector->mutex); } elektroid-2.0/src/connector.h000066400000000000000000000074301416764361200163050ustar00rootroot00000000000000/* * connector.h * Copyright (C) 2019 David García Goñi * * This file is part of Elektroid. * * Elektroid is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Elektroid is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Elektroid. If not, see . */ #include #include #include "utils.h" #ifndef CONNECTOR_H #define CONNECTOR_H #define SYSEX_TIMEOUT 5000 #define REST_TIME 50000 struct connector_device_desc { const gchar *name; const gchar *alias; guint8 id; guint8 fss; guint8 storages; }; struct connector { const struct connector_device_desc *device_desc; snd_rawmidi_t *inputp; snd_rawmidi_t *outputp; gchar *device_name; gchar *alias; gchar *fw_version; gushort seq; GMutex mutex; ssize_t rx_len; guint8 *buffer; gint npfds; struct pollfd *pfds; GHashTable *dir_cache; }; enum connector_fs { FS_SAMPLES = 0x1, FS_RAW_ALL = 0x2, FS_RAW_PRESETS = 0x4, FS_DATA_ALL = 0x8, FS_DATA_PRJ = 0x10, FS_DATA_SND = 0x20, }; struct connector_iterator_data { GByteArray *msg; guint32 pos; guint32 hash; guint16 operations; guint8 has_valid_data; guint8 has_metadata; enum connector_fs fs; gboolean cached; }; struct connector_system_device { gchar *name; guint card; }; enum connector_sysex_transfer_status { WAITING, SENDING, RECEIVING, FINISHED }; struct connector_sysex_transfer { gboolean active; enum connector_sysex_transfer_status status; gint timeout; //Measured in ms. -1 is infinite. gboolean batch; GMutex mutex; GByteArray *raw; }; enum connector_storage { STORAGE_PLUS_DRIVE = 0x1, STORAGE_RAM = 0x2 }; struct connector_storage_stats { const gchar *name; guint64 bsize; guint64 bfree; }; const struct fs_operations *connector_get_fs_operations (enum connector_fs); gint connector_init (struct connector *, gint); void connector_destroy (struct connector *); gboolean connector_check (struct connector *); GArray *connector_get_system_devices (); gint connector_tx_sysex (struct connector *, struct connector_sysex_transfer *); gint connector_rx_sysex (struct connector *, struct connector_sysex_transfer *); void connector_rx_drain (struct connector *); gint connector_upgrade_os (struct connector *, struct connector_sysex_transfer *); gint connector_get_storage_stats (struct connector *, enum connector_storage, struct connector_storage_stats *); gdouble connector_get_storage_stats_percent (struct connector_storage_stats *); gchar *connector_get_upload_path (struct connector *, struct item_iterator *, const struct fs_operations *, const gchar *, const gchar *, gint32 *); gchar *connector_get_download_name (struct connector *, struct item_iterator *, const struct fs_operations *, const gchar *); gchar *connector_get_download_path (struct connector *, struct item_iterator *, const struct fs_operations *, const gchar *, const gchar *); gchar *connector_get_full_ext (const struct connector_device_desc *, const struct fs_operations *); gchar *connector_get_sample_path_from_hash_size (struct connector *, guint32, guint32); void connector_enable_dir_cache (struct connector *); void connector_disable_dir_cache (struct connector *); #endif elektroid-2.0/src/elektroid-cli.c000066400000000000000000000404001416764361200170270ustar00rootroot00000000000000/* * elektroid-cli.c * Copyright (C) 2019 David García Goñi * * This file is part of Elektroid. * * Elektroid is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Elektroid is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Elektroid. If not, see . */ #include "../config.h" #include #include #include #include #include #include #include #include #include "connector.h" #include "utils.h" #define GET_FS_OPS_OFFSET(member) offsetof(struct fs_operations, member) #define GET_FS_OPS_FUNC(type,fs,offset) (*(((type *) (((gchar *) connector_get_fs_operations(fs)) + offset)))) #define CHECK_FS_OPS_FUNC(f) if (!(f)) {error_print ("Operation not implemented\n"); return EXIT_FAILURE;} static struct connector connector; static struct job_control control; typedef void (*print_item) (struct item_iterator *); static const gchar *CLI_FSS[] = { "sample", "raw", "preset", "data", "project", "sound" }; static void print_smplrw (struct item_iterator *iter) { gchar *hsize = get_human_size (iter->item.size, FALSE); struct connector_iterator_data *data = iter->data; printf ("%c %10s %08x %s\n", iter->item.type, hsize, data->hash, iter->item.name); g_free (hsize); } static void print_data (struct item_iterator *iter) { gchar *hsize = get_human_size (iter->item.size, FALSE); struct connector_iterator_data *data = iter->data; printf ("%c %3d %04x %d %d %10s %s\n", iter->item.type, iter->item.index, data->operations, data->has_valid_data, data->has_metadata, hsize, iter->item.name); g_free (hsize); } static void null_control_callback (gdouble foo) { } static const gchar * cli_get_path (gchar * device_path) { gint len = strlen (device_path); char *path = device_path; gint i = 0; while (path[0] != '/' && i < len) { path++; i++; } return path; } static gint cli_ld () { gint i; struct connector_system_device device; GArray *devices = connector_get_system_devices (); for (i = 0; i < devices->len; i++) { device = g_array_index (devices, struct connector_system_device, i); printf ("%d %s\n", device.card, device.name); } g_array_free (devices, TRUE); return EXIT_SUCCESS; } static gint cli_connect (const char *device_path, enum connector_fs fs) { gint card = atoi (device_path); gint ret = connector_init (&connector, card); if (!ret && fs && !(connector.device_desc->fss & fs)) { error_print ("Filesystem not supported for device '%s'\n", connector.device_desc->name); return 1; } return ret; } static int cli_list (int argc, char *argv[], int optind, enum connector_fs fs, print_item print) { const gchar *path; struct item_iterator iter; gchar *device_path; const struct fs_operations *fs_ops = connector_get_fs_operations (fs); if (optind == argc) { error_print ("Remote path missing\n"); return EXIT_FAILURE; } else { device_path = argv[optind]; } if (cli_connect (device_path, fs)) { return EXIT_FAILURE; } path = cli_get_path (device_path); if (fs_ops->readdir (&iter, path, &connector)) { return EXIT_FAILURE; } while (!next_item_iterator (&iter)) { print (&iter); } free_item_iterator (&iter); return EXIT_SUCCESS; } static int cli_command_path (int argc, char *argv[], int optind, enum connector_fs fs, ssize_t member_offset) { const gchar *path; gchar *device_path; gint ret; fs_path_func f; if (optind == argc) { error_print ("Remote path missing\n"); return EXIT_FAILURE; } else { device_path = argv[optind]; } if (cli_connect (device_path, fs)) { return EXIT_FAILURE; } path = cli_get_path (device_path); f = GET_FS_OPS_FUNC (fs_path_func, fs, member_offset); CHECK_FS_OPS_FUNC (f); ret = f (path, &connector); return ret ? EXIT_FAILURE : EXIT_SUCCESS; } static int cli_command_src_dst (int argc, char *argv[], int optind, enum connector_fs fs, ssize_t member_offset) { const gchar *src_path, *dst_path; gchar *device_src_path, *device_dst_path; gint src_card; gint dst_card; int ret; fs_src_dst_func f; if (optind == argc) { error_print ("Remote path source missing\n"); return EXIT_FAILURE; } else { device_src_path = argv[optind]; optind++; } if (optind == argc) { error_print ("Remote path destination missing\n"); return EXIT_FAILURE; } else { device_dst_path = argv[optind]; } src_card = atoi (device_src_path); dst_card = atoi (device_dst_path); if (src_card != dst_card) { error_print ("Source and destination device must be the same\n"); return EXIT_FAILURE; } if (cli_connect (device_src_path, 0)) { return EXIT_FAILURE; } f = GET_FS_OPS_FUNC (fs_src_dst_func, fs, member_offset); CHECK_FS_OPS_FUNC (f); src_path = cli_get_path (device_src_path); dst_path = cli_get_path (device_dst_path); ret = f (src_path, dst_path, &connector); return ret ? EXIT_FAILURE : EXIT_SUCCESS; } static int cli_info (int argc, char *argv[], int optind) { gchar *device_path; gchar *comma; if (optind == argc) { error_print ("Device missing\n"); return EXIT_FAILURE; } else { device_path = argv[optind]; } if (cli_connect (device_path, 0)) { return EXIT_FAILURE; } printf ("%s filesystems=", connector.device_name); comma = ""; for (int fs = FS_SAMPLES, i = 0; fs <= FS_DATA_SND; fs <<= 1, i++) { if (connector.device_desc->fss & fs) { printf ("%s%s", comma, CLI_FSS[i]); } comma = ","; } printf ("\n"); return EXIT_SUCCESS; } static int cli_df (int argc, char *argv[], int optind) { gchar *device_path; gchar *size; gchar *diff; gchar *free; gint res; struct connector_storage_stats statfs; enum connector_storage storage; if (optind == argc) { error_print ("Device missing\n"); return EXIT_FAILURE; } else { device_path = argv[optind]; } if (cli_connect (device_path, 0)) { return EXIT_FAILURE; } if (!connector.device_desc->storages) { return EXIT_FAILURE; } printf ("%-10.10s%16.16s%16.16s%16.16s%11.10s\n", "Storage", "Size", "Used", "Available", "Use%"); res = 0; for (storage = STORAGE_PLUS_DRIVE; storage <= STORAGE_RAM; storage <<= 1) { if (connector.device_desc->storages & storage) { res |= connector_get_storage_stats (&connector, storage, &statfs); if (res) { continue; } size = get_human_size (statfs.bsize, FALSE); diff = get_human_size (statfs.bsize - statfs.bfree, FALSE); free = get_human_size (statfs.bfree, FALSE); printf ("%-10.10s%16s%16s%16s%10.2f%%\n", statfs.name, size, diff, free, connector_get_storage_stats_percent (&statfs)); g_free (size); g_free (diff); g_free (free); } } return res ? EXIT_FAILURE : EXIT_SUCCESS; } static int cli_upgrade (int argc, char *argv[], int optind) { gint res; const gchar *src_path; const gchar *device_path; struct connector_sysex_transfer sysex_transfer; if (optind == argc) { error_print ("Local path missing\n"); return EXIT_FAILURE; } else { src_path = argv[optind]; optind++; } if (optind == argc) { error_print ("Remote path missing\n"); return EXIT_FAILURE; } else { device_path = argv[optind]; } if (cli_connect (device_path, 0)) { return EXIT_FAILURE; } sysex_transfer.raw = g_byte_array_new (); res = load_file (src_path, sysex_transfer.raw, NULL); if (res) { error_print ("Error while loading '%s'.\n", src_path); } else { sysex_transfer.active = TRUE; sysex_transfer.timeout = SYSEX_TIMEOUT; res = connector_upgrade_os (&connector, &sysex_transfer); } g_byte_array_free (sysex_transfer.raw, TRUE); return res ? EXIT_FAILURE : EXIT_SUCCESS; } static int cli_download (int argc, char *argv[], int optind, enum connector_fs fs) { const gchar *src_path; gchar *device_src_path, *download_path; gint res; GByteArray *array; const struct fs_operations *fs_ops; if (optind == argc) { error_print ("Remote path missing\n"); return EXIT_FAILURE; } else { device_src_path = argv[optind]; } if (cli_connect (device_src_path, fs)) { return EXIT_FAILURE; } fs_ops = connector_get_fs_operations (fs); src_path = cli_get_path (device_src_path); control.active = TRUE; control.callback = null_control_callback; array = g_byte_array_new (); res = fs_ops->download (src_path, array, &control, &connector); if (res) { goto end; } download_path = connector_get_download_path (&connector, NULL, fs_ops, ".", src_path); if (!download_path) { res = -1; goto end; } res = fs_ops->save (download_path, array, &control); g_free (download_path); g_free (control.data); end: g_byte_array_free (array, TRUE); return res ? EXIT_FAILURE : EXIT_SUCCESS; } static int cli_upload (int argc, char *argv[], int optind, enum connector_fs fs) { const gchar *dst_dir; gchar *src_path, *device_dst_path, *upload_path; gint res; GByteArray *array; gint32 index = 1; const struct fs_operations *fs_ops; if (optind == argc) { error_print ("Local path missing\n"); return EXIT_FAILURE; } else { src_path = argv[optind]; optind++; } if (optind == argc) { error_print ("Remote path missing\n"); return EXIT_FAILURE; } else { device_dst_path = argv[optind]; } if (cli_connect (device_dst_path, fs)) { return EXIT_FAILURE; } fs_ops = connector_get_fs_operations (fs); dst_dir = cli_get_path (device_dst_path); upload_path = connector_get_upload_path (&connector, NULL, fs_ops, dst_dir, src_path, &index); array = g_byte_array_new (); control.active = TRUE; control.callback = null_control_callback; res = fs_ops->load (src_path, array, &control); if (res) { goto cleanup; } res = fs_ops->upload (upload_path, array, &control, &connector); g_free (control.data); cleanup: g_free (upload_path); g_byte_array_free (array, TRUE); return res ? EXIT_FAILURE : EXIT_SUCCESS; } static gint get_fs_operations_from_type (const gchar * type) { if (!strlen (type)) { return 0; } for (gint fs = FS_SAMPLES, i = 0; fs <= FS_DATA_SND; fs <<= 1, i++) { if (strcmp (type, CLI_FSS[i]) == 0) { return fs; } } return -1; } static gint get_op_type_from_command (const gchar * cmd, char *op, char *type) { gint i; gchar *c; gint len; strncpy (op, cmd, LABEL_MAX); op[LABEL_MAX - 1] = 0; len = strlen (op); c = op; for (i = 0; i < LABEL_MAX && i < strlen (op); i++, c++) { if (*c == '-') { *c = 0; break; } } if (i < len - 1) { c++; strncpy (type, c, LABEL_MAX); type[LABEL_MAX - 1] = 0; } else { *type = 0; } return get_fs_operations_from_type (type); } static void cli_end (int sig) { control.active = FALSE; } int main (int argc, char *argv[]) { gint c; gint res; gchar *command; gint vflg = 0, errflg = 0; struct sigaction action, old_action; gint fs; char op[LABEL_MAX]; char type[LABEL_MAX]; action.sa_handler = cli_end; sigemptyset (&action.sa_mask); action.sa_flags = 0; sigaction (SIGTERM, NULL, &old_action); if (old_action.sa_handler != SIG_IGN) { sigaction (SIGTERM, &action, NULL); } sigaction (SIGQUIT, NULL, &old_action); if (old_action.sa_handler != SIG_IGN) { sigaction (SIGQUIT, &action, NULL); } sigaction (SIGINT, NULL, &old_action); if (old_action.sa_handler != SIG_IGN) { sigaction (SIGINT, &action, NULL); } sigaction (SIGHUP, NULL, &old_action); if (old_action.sa_handler != SIG_IGN) { sigaction (SIGHUP, &action, NULL); } while ((c = getopt (argc, argv, "v")) != -1) { switch (c) { case 'v': vflg++; break; case '?': errflg++; } } if (optind == argc) { errflg = 1; } else { command = argv[optind]; optind++; } if (vflg) { debug_level = vflg; } if (errflg > 0) { fprintf (stderr, "%s\n", PACKAGE_STRING); char *exec_name = basename (argv[0]); fprintf (stderr, "Usage: %s [options] command\n", exec_name); exit (EXIT_FAILURE); } fs = get_op_type_from_command (command, op, type); debug_print (1, "Operation: '%s'; filesystem: '%s' (%d)\n", op, type, fs); if (fs < 0) { error_print ("Filesystem '%s' not recognized\n", type); res = EXIT_FAILURE; goto end; } if (!fs) { if (strcmp (command, "ld") == 0 || strcmp (command, "list-devices") == 0) { res = cli_ld (); } else if (strcmp (op, "info") == 0 || strcmp (command, "info-devices") == 0) { res = cli_info (argc, argv, optind); } else if (strcmp (command, "df") == 0 || strcmp (command, "info-storage") == 0) { res = cli_df (argc, argv, optind); } else if (strcmp (command, "upgrade") == 0) { res = cli_upgrade (argc, argv, optind); } else if (strcmp (command, "ls") == 0) { res = cli_list (argc, argv, optind, FS_SAMPLES, print_smplrw); } else if (strcmp (command, "mkdir") == 0) { res = cli_command_path (argc, argv, optind, FS_SAMPLES, GET_FS_OPS_OFFSET (mkdir)); } else if (strcmp (command, "mv") == 0) { res = cli_command_src_dst (argc, argv, optind, FS_SAMPLES, GET_FS_OPS_OFFSET (move)); } else if (strcmp (command, "rm") == 0 || strcmp (command, "rmdir") == 0) { res = cli_command_path (argc, argv, optind, FS_SAMPLES, GET_FS_OPS_OFFSET (delete)); } else if (strcmp (command, "download") == 0 || strcmp (command, "dl") == 0) { res = cli_download (argc, argv, optind, FS_SAMPLES); } else if (strcmp (command, "upload") == 0 || strcmp (command, "ul") == 0) { res = cli_upload (argc, argv, optind, FS_SAMPLES); } else { error_print ("Command '%s' not recognized\n", command); res = EXIT_FAILURE; } goto end; } if (strcmp (op, "ls") == 0 || strcmp (op, "list") == 0) { print_item print = (fs == FS_SAMPLES || fs == FS_RAW_ALL || fs == FS_RAW_PRESETS) ? print_smplrw : print_data; res = cli_list (argc, argv, optind, fs, print); } else if (strcmp (op, "mkdir") == 0) { res = cli_command_path (argc, argv, optind, fs, GET_FS_OPS_OFFSET (mkdir)); } else if (strcmp (op, "rm") == 0 || strcmp (op, "rmdir") == 0) { res = cli_command_path (argc, argv, optind, fs, GET_FS_OPS_OFFSET (delete)); } else if (strcmp (op, "download") == 0 || strcmp (op, "dl") == 0) { res = cli_download (argc, argv, optind, fs); } else if (strcmp (op, "upload") == 0 || strcmp (op, "ul") == 0) { res = cli_upload (argc, argv, optind, fs); } else if (strcmp (op, "cl") == 0) { res = cli_command_path (argc, argv, optind, fs, GET_FS_OPS_OFFSET (clear)); } else if (strcmp (op, "cp") == 0) { res = cli_command_src_dst (argc, argv, optind, fs, GET_FS_OPS_OFFSET (copy)); } else if (strcmp (op, "sw") == 0) { res = cli_command_src_dst (argc, argv, optind, fs, GET_FS_OPS_OFFSET (swap)); } else if (strcmp (op, "mv") == 0) { res = cli_command_src_dst (argc, argv, optind, fs, GET_FS_OPS_OFFSET (move)); } else { error_print ("Command '%s' not recognized\n", command); res = EXIT_FAILURE; } end: if (connector_check (&connector)) { connector_destroy (&connector); } usleep (REST_TIME * 2); return res; } elektroid-2.0/src/elektroid.c000066400000000000000000002633041416764361200162740ustar00rootroot00000000000000/* * elektroid.c * Copyright (C) 2019 David García Goñi * * This file is part of Elektroid. * * Elektroid is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Elektroid is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Elektroid. If not, see . */ #include "../config.h" #include #include #include #include #include #include #define _GNU_SOURCE #include #include "connector.h" #include "browser.h" #include "audio.h" #include "sample.h" #include "utils.h" #include "notifier.h" #include "local.h" #include "preferences.h" #define MAX_DRAW_X 10000 #define DUMP_TIMEOUT 2000 #define DND_TIMEOUT 1000 #define TEXT_URI_LIST_STD "text/uri-list" #define TEXT_URI_LIST_ELEKTROID "text/uri-list-elektroid" #define GUI_FSS (FS_SAMPLES | FS_RAW_PRESETS | FS_DATA_PRJ | FS_DATA_SND) #define MSG_WARN_SAME_SRC_DST "Same source and destination path. Skipping...\n" enum device_list_store_columns { DEVICES_LIST_STORE_CARD_FIELD, DEVICES_LIST_STORE_NAME_FIELD }; enum task_list_store_columns { TASK_LIST_STORE_STATUS_FIELD, TASK_LIST_STORE_TYPE_FIELD, TASK_LIST_STORE_SRC_FIELD, TASK_LIST_STORE_DST_FIELD, TASK_LIST_STORE_PROGRESS_FIELD, TASK_LIST_STORE_STATUS_HUMAN_FIELD, TASK_LIST_STORE_TYPE_HUMAN_FIELD, TASK_LIST_STORE_REMOTE_FS_ID_FIELD, TASK_LIST_STORE_REMOTE_FS_ICON_FIELD, }; enum fs_list_store_columns { FS_LIST_STORE_ID_FIELD, FS_LIST_STORE_ICON_FIELD, FS_LIST_STORE_NAME_FIELD }; enum { TARGET_STRING, }; enum elektroid_task_type { UPLOAD, DOWNLOAD }; enum elektroid_task_status { QUEUED, RUNNING, COMPLETED_OK, COMPLETED_ERROR, CANCELED }; struct elektroid_transfer { struct job_control control; gchar *src; //Contains a path to a file gchar *dst; //Contains a path to a file enum elektroid_task_status status; //Contains the final status const struct fs_operations *fs_ops; //Contains the fs_operations to use in this transfer }; struct elektroid_add_upload_task_data { struct item_iterator iter; gint32 index; }; typedef int (*connector_send_raw) (struct connector *, struct connector_sysex_transfer *); static gpointer elektroid_upload_task (gpointer); static gpointer elektroid_download_task (gpointer); static void elektroid_update_progress (gdouble); static const struct option ELEKTROID_OPTIONS[] = { {"local-directory", 1, NULL, 'l'}, {"verbose", 0, NULL, 'v'}, {"help", 0, NULL, 'h'}, {NULL, 0, NULL, 0} }; static const gchar *ELEKTROID_FS_ICONS[] = { FILE_ICON_WAVE, FILE_ICON_DATA, FILE_ICON_SND, FILE_ICON_DATA, FILE_ICON_PRJ, FILE_ICON_SND }; static gchar *ELEKTROID_AUDIO_LOCAL_EXTS[] = { "wav", "ogg", "aiff", "flac", NULL }; static GtkTargetEntry TARGET_ENTRIES_LOCAL_DST[] = { {TEXT_URI_LIST_ELEKTROID, GTK_TARGET_SAME_APP | GTK_TARGET_OTHER_WIDGET, TARGET_STRING}, {TEXT_URI_LIST_STD, GTK_TARGET_SAME_APP | GTK_TARGET_SAME_WIDGET, TARGET_STRING}, {TEXT_URI_LIST_STD, GTK_TARGET_OTHER_APP, TARGET_STRING} }; static GtkTargetEntry TARGET_ENTRIES_LOCAL_SRC[] = { {TEXT_URI_LIST_STD, GTK_TARGET_SAME_APP | GTK_TARGET_OTHER_WIDGET, TARGET_STRING}, {TEXT_URI_LIST_STD, GTK_TARGET_SAME_APP | GTK_TARGET_SAME_WIDGET, TARGET_STRING}, {TEXT_URI_LIST_STD, GTK_TARGET_OTHER_APP, TARGET_STRING} }; static GtkTargetEntry TARGET_ENTRIES_REMOTE_DST[] = { {TEXT_URI_LIST_STD, GTK_TARGET_SAME_APP | GTK_TARGET_OTHER_WIDGET, TARGET_STRING}, {TEXT_URI_LIST_ELEKTROID, GTK_TARGET_SAME_APP | GTK_TARGET_SAME_WIDGET, TARGET_STRING}, {TEXT_URI_LIST_STD, GTK_TARGET_OTHER_APP, TARGET_STRING} }; static GtkTargetEntry TARGET_ENTRIES_REMOTE_SRC[] = { {TEXT_URI_LIST_ELEKTROID, GTK_TARGET_SAME_APP | GTK_TARGET_OTHER_WIDGET, TARGET_STRING}, {TEXT_URI_LIST_ELEKTROID, GTK_TARGET_SAME_APP | GTK_TARGET_SAME_WIDGET, TARGET_STRING} }; static GtkTargetEntry TARGET_ENTRIES_UP_BUTTON_DST[] = { {TEXT_URI_LIST_STD, GTK_TARGET_SAME_APP, TARGET_STRING}, {TEXT_URI_LIST_STD, GTK_TARGET_OTHER_APP, TARGET_STRING}, {TEXT_URI_LIST_ELEKTROID, GTK_TARGET_SAME_APP, TARGET_STRING} }; static guint TARGET_ENTRIES_LOCAL_DST_N = G_N_ELEMENTS (TARGET_ENTRIES_LOCAL_DST); static guint TARGET_ENTRIES_LOCAL_SRC_N = G_N_ELEMENTS (TARGET_ENTRIES_LOCAL_SRC); static guint TARGET_ENTRIES_REMOTE_DST_N = G_N_ELEMENTS (TARGET_ENTRIES_REMOTE_DST); static guint TARGET_ENTRIES_REMOTE_SRC_N = G_N_ELEMENTS (TARGET_ENTRIES_REMOTE_SRC); static guint TARGET_ENTRIES_UP_BUTTON_DST_N = G_N_ELEMENTS (TARGET_ENTRIES_UP_BUTTON_DST); static struct browser remote_browser; static struct browser local_browser; static struct audio audio; static struct connector connector; static struct preferences preferences; static GThread *load_thread = NULL; static GThread *task_thread = NULL; static GThread *sysex_thread = NULL; static struct elektroid_transfer transfer; static struct connector_sysex_transfer sysex_transfer; static GThread *notifier_thread = NULL; struct notifier notifier; static GtkWidget *main_window; static GtkAboutDialog *about_dialog; static GtkDialog *name_dialog; static GtkEntry *name_dialog_entry; static GtkWidget *name_dialog_accept_button; static GtkDialog *progress_dialog; static GtkWidget *progress_dialog_cancel_button; static GtkWidget *progress_bar; static GtkWidget *progress_label; static GtkWidget *rx_sysex_button; static GtkWidget *tx_sysex_button; static GtkWidget *os_upgrade_button; static GtkWidget *about_button; static GtkWidget *remote_box; static GtkWidget *waveform_draw_area; static GtkStatusbar *status_bar; static GtkListStore *devices_list_store; static GtkComboBox *devices_combo; static GtkWidget *upload_menuitem; static GtkWidget *local_audio_box; static GtkWidget *local_play_menuitem; static GtkWidget *local_open_menuitem; static GtkWidget *local_show_menuitem; static GtkWidget *local_rename_menuitem; static GtkWidget *local_delete_menuitem; static GtkWidget *download_menuitem; static GtkWidget *remote_rename_menuitem; static GtkWidget *remote_delete_menuitem; static GtkWidget *play_button; static GtkWidget *stop_button; static GtkWidget *volume_button; static GtkListStore *task_list_store; static GtkWidget *task_tree_view; static GtkWidget *cancel_task_button; static GtkWidget *remove_tasks_button; static GtkWidget *clear_tasks_button; static GtkListStore *fs_list_store; static GtkComboBox *fs_combo; static GtkTreeViewColumn *remote_tree_view_index_column; static const gchar * elektroid_get_fs_name (enum connector_fs fs) { switch (fs) { case FS_SAMPLES: return _("Samples"); case FS_RAW_PRESETS: return _("Presets"); case FS_DATA_PRJ: return _("Projects"); case FS_DATA_SND: return _("Sounds"); default: return _("Undefined"); } } static void elektroid_set_file_extensions_for_fs (gchar ** extensions[], enum connector_fs sel_fs) { const struct fs_operations *ops; if (*extensions != NULL && *extensions != ELEKTROID_AUDIO_LOCAL_EXTS) { g_free ((*extensions)[0]); g_free (*extensions); } if (sel_fs == FS_SAMPLES) { *extensions = ELEKTROID_AUDIO_LOCAL_EXTS; } else { ops = connector_get_fs_operations (sel_fs); *extensions = malloc (sizeof (gchar *) * 2); (*extensions)[0] = connector_get_full_ext (connector.device_desc, ops); (*extensions)[1] = NULL; } } static const gchar * elektroid_get_inventory_icon_for_fs (enum connector_fs sel_fs) { const gchar *icon = FILE_ICON_DATA; for (int fs = FS_SAMPLES, i = 0; fs <= FS_DATA_SND; fs <<= 1, i++) { if (GUI_FSS & fs && sel_fs == fs) { icon = ELEKTROID_FS_ICONS[i]; break; } } return icon; } static void show_error_msg (const char *format, ...) { GtkWidget *dialog; gchar *msg; va_list args; va_start (args, format); g_vasprintf (&msg, format, args); dialog = gtk_message_dialog_new (GTK_WINDOW (main_window), GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "%s", msg); gtk_dialog_run (GTK_DIALOG (dialog)); gtk_widget_destroy (dialog); g_free (msg); va_end (args); } static void elektroid_load_devices (gboolean auto_select) { gint i; gint device_index; GArray *devices = connector_get_system_devices (); struct connector_system_device device; debug_print (1, "Loading devices...\n"); gtk_list_store_clear (fs_list_store); gtk_list_store_clear (devices_list_store); for (i = 0; i < devices->len; i++) { device = g_array_index (devices, struct connector_system_device, i); gtk_list_store_insert_with_values (devices_list_store, NULL, -1, DEVICES_LIST_STORE_CARD_FIELD, device.card, DEVICES_LIST_STORE_NAME_FIELD, device.name, -1); } g_array_free (devices, TRUE); device_index = auto_select && i == 1 ? 0 : -1; debug_print (1, "Selecting device %d...\n", device_index); gtk_combo_box_set_active (devices_combo, device_index); if (device_index == -1) { local_browser.file_icon = elektroid_get_inventory_icon_for_fs (FS_SAMPLES); elektroid_set_file_extensions_for_fs (&local_browser.extensions, FS_SAMPLES); gtk_widget_set_visible (local_audio_box, TRUE); gtk_tree_view_column_set_visible (remote_tree_view_index_column, FALSE); browser_load_dir (&local_browser); } } static void elektroid_update_statusbar () { gchar *status; gchar *statfss_str; gint res; enum connector_storage storage; struct connector_storage_stats statfs; GString *statfss; gtk_statusbar_pop (status_bar, 0); if (connector_check (&connector)) { statfss = g_string_new (NULL); for (storage = STORAGE_PLUS_DRIVE; storage <= STORAGE_RAM; storage <<= 1) { if (connector.device_desc->storages & storage) { res = connector_get_storage_stats (&connector, storage, &statfs); if (!res) { g_string_append_printf (statfss, " %s %.2f%%", statfs.name, connector_get_storage_stats_percent (&statfs)); } } } statfss_str = g_string_free (statfss, FALSE); status = malloc (LABEL_MAX); snprintf (status, LABEL_MAX, _("Connected to %s%s"), connector.device_name, statfss_str); gtk_statusbar_push (status_bar, 0, status); free (status); g_free (statfss_str); } else { gtk_statusbar_push (status_bar, 0, _("Not connected")); } } static gboolean elektroid_get_next_queued_task (GtkTreeIter * iter, enum elektroid_task_type *type, gchar ** src, gchar ** dst, gint * fs) { enum elektroid_task_status status; gboolean found = FALSE; gboolean valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (task_list_store), iter); while (valid) { if (type) { gtk_tree_model_get (GTK_TREE_MODEL (task_list_store), iter, TASK_LIST_STORE_STATUS_FIELD, &status, TASK_LIST_STORE_TYPE_FIELD, type, TASK_LIST_STORE_SRC_FIELD, src, TASK_LIST_STORE_DST_FIELD, dst, TASK_LIST_STORE_REMOTE_FS_ID_FIELD, fs, -1); } else { gtk_tree_model_get (GTK_TREE_MODEL (task_list_store), iter, TASK_LIST_STORE_STATUS_FIELD, &status, -1); } if (status == QUEUED) { found = TRUE; break; } valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (task_list_store), iter); } return found; } static gboolean elektroid_check_connector () { GtkListStore *list_store; GtkTreeIter iter; gboolean connected = connector_check (&connector); gboolean queued = elektroid_get_next_queued_task (&iter, NULL, NULL, NULL, NULL); gtk_widget_set_sensitive (remote_box, connected); gtk_widget_set_sensitive (rx_sysex_button, connected && !queued); gtk_widget_set_sensitive (tx_sysex_button, connected && !queued); gtk_widget_set_sensitive (os_upgrade_button, connected && !queued); if (!connected) { list_store = GTK_LIST_STORE (gtk_tree_view_get_model (remote_browser.view)); gtk_entry_set_text (remote_browser.dir_entry, ""); gtk_list_store_clear (list_store); elektroid_load_devices (FALSE); } elektroid_update_statusbar (); return connected; } static gboolean elektroid_check_connector_bg (gpointer data) { elektroid_check_connector (); return FALSE; } static void browser_refresh_devices (GtkWidget * object, gpointer data) { if (connector_check (&connector)) { connector_destroy (&connector); elektroid_check_connector (); } elektroid_load_devices (FALSE); } static gpointer elektroid_join_sysex_thread () { gpointer output = NULL; debug_print (1, "Stopping SysEx thread...\n"); if (sysex_thread) { output = g_thread_join (sysex_thread); } sysex_thread = NULL; return output; } static void elektroid_cancel_running_sysex (GtkDialog * dialog, gint response_id, gpointer data) { g_mutex_lock (&sysex_transfer.mutex); sysex_transfer.active = FALSE; g_mutex_unlock (&sysex_transfer.mutex); } static void elektroid_stop_sysex_thread () { elektroid_cancel_running_sysex (NULL, 0, NULL); elektroid_join_sysex_thread (); } static void elektroid_progress_dialog_end (gpointer data) { elektroid_cancel_running_sysex (NULL, 0, NULL); gtk_dialog_response (GTK_DIALOG (progress_dialog), GTK_RESPONSE_CANCEL); } static gboolean elektroid_update_sysex_progress (gpointer data) { gchar *text; gboolean active; enum connector_sysex_transfer_status status; g_mutex_lock (&sysex_transfer.mutex); status = sysex_transfer.status; g_mutex_unlock (&sysex_transfer.mutex); switch (status) { case WAITING: text = _("Waiting..."); break; case SENDING: text = _("Sending..."); gtk_progress_bar_pulse (GTK_PROGRESS_BAR (progress_bar)); break; case RECEIVING: text = _("Receiving..."); gtk_progress_bar_pulse (GTK_PROGRESS_BAR (progress_bar)); break; default: text = ""; } gtk_label_set_text (GTK_LABEL (progress_label), text); g_mutex_lock (&sysex_transfer.mutex); active = sysex_transfer.active; g_mutex_unlock (&sysex_transfer.mutex); return active; } static gpointer elektroid_rx_sysex_thread (gpointer data) { gint *res = malloc (sizeof (gint)); gchar *text; sysex_transfer.status = WAITING; sysex_transfer.active = TRUE; sysex_transfer.timeout = DUMP_TIMEOUT; sysex_transfer.batch = TRUE; g_timeout_add (100, elektroid_update_sysex_progress, NULL); connector_rx_drain (&connector); *res = connector_rx_sysex (&connector, &sysex_transfer); if (!*res) { text = debug_get_hex_msg (sysex_transfer.raw); debug_print (1, "SysEx message received (%d): %s\n", sysex_transfer.raw->len, text); free (text); } gtk_dialog_response (GTK_DIALOG (progress_dialog), GTK_RESPONSE_ACCEPT); return res; } static gboolean elektroid_start_rx_thread (gpointer data) { debug_print (1, "Creating rx SysEx thread...\n"); sysex_thread = g_thread_new ("sysex_thread", elektroid_rx_sysex_thread, NULL); return FALSE; } static void elektroid_rx_sysex (GtkWidget * object, gpointer data) { GtkWidget *dialog; GtkFileChooser *chooser; GtkFileFilter *filter; gint dres; gchar *filename; gchar *filename_w_ext; const gchar *ext; gint *res; GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_SAVE; g_idle_add (elektroid_start_rx_thread, NULL); gtk_window_set_title (GTK_WINDOW (progress_dialog), _("Receive SysEx")); dres = gtk_dialog_run (GTK_DIALOG (progress_dialog)); sysex_transfer.active = FALSE; gtk_widget_hide (GTK_WIDGET (progress_dialog)); res = elektroid_join_sysex_thread (); if (dres != GTK_RESPONSE_ACCEPT) { if (!*res) { g_byte_array_free (sysex_transfer.raw, TRUE); } g_free (res); return; } if (*res) { elektroid_check_connector (); g_free (res); return; } dialog = gtk_file_chooser_dialog_new (_("Save SysEx"), GTK_WINDOW (main_window), action, _("_Cancel"), GTK_RESPONSE_CANCEL, _("_Save"), GTK_RESPONSE_ACCEPT, NULL); chooser = GTK_FILE_CHOOSER (dialog); gtk_file_chooser_set_do_overwrite_confirmation (chooser, TRUE); gtk_file_chooser_set_current_name (chooser, _("Received SysEx")); gtk_file_chooser_set_create_folders (chooser, TRUE); filter = gtk_file_filter_new (); gtk_file_filter_set_name (filter, _("SysEx Files")); gtk_file_filter_add_pattern (filter, "*.syx"); gtk_file_chooser_add_filter (chooser, filter); gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (dialog), filter); while (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) { filename = gtk_file_chooser_get_filename (chooser); ext = get_ext (filename); if (ext == NULL || strcmp (ext, "syx") != 0) { filename_w_ext = g_strconcat (filename, ".syx", NULL); g_free (filename); filename = filename_w_ext; if (g_file_test (filename, G_FILE_TEST_EXISTS)) { gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (chooser), filename); g_free (filename); filename = NULL; continue; } } break; } if (filename != NULL) { *res = save_file (filename, sysex_transfer.raw, NULL); if (*res) { show_error_msg (_("Error while saving “%s”: %s."), filename, g_strerror (*res)); } g_byte_array_free (sysex_transfer.raw, TRUE); g_free (res); g_free (filename); } gtk_widget_destroy (dialog); } static gpointer elektroid_tx_sysex_thread (gpointer data) { gchar *text; gint *res = malloc (sizeof (gint)); connector_send_raw f = data; sysex_transfer.active = TRUE; sysex_transfer.timeout = SYSEX_TIMEOUT; g_timeout_add (100, elektroid_update_sysex_progress, NULL); *res = f (&connector, &sysex_transfer); if (!*res) { text = debug_get_hex_msg (sysex_transfer.raw); debug_print (1, "SysEx message sent (%d): %s\n", sysex_transfer.raw->len, text); free (text); } gtk_dialog_response (GTK_DIALOG (progress_dialog), GTK_RESPONSE_CANCEL); return res; } static void elektroid_tx_sysex_common (connector_send_raw f) { GtkWidget *dialog; GtkFileChooser *chooser; GtkFileFilter *filter; gint res, lres; char *filename; gint *response; GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN; dialog = gtk_file_chooser_dialog_new (_("Open SysEx"), GTK_WINDOW (main_window), action, _("_Cancel"), GTK_RESPONSE_CANCEL, _("_Open"), GTK_RESPONSE_ACCEPT, NULL); chooser = GTK_FILE_CHOOSER (dialog); filter = gtk_file_filter_new (); gtk_file_filter_set_name (filter, _("SysEx Files")); gtk_file_filter_add_pattern (filter, "*.syx"); gtk_file_chooser_add_filter (chooser, filter); res = gtk_dialog_run (GTK_DIALOG (dialog)); if (res == GTK_RESPONSE_ACCEPT) { filename = gtk_file_chooser_get_filename (chooser); gtk_widget_destroy (dialog); debug_print (1, "Opening SysEx file...\n"); sysex_transfer.raw = g_byte_array_new (); lres = load_file (filename, sysex_transfer.raw, NULL); if (lres) { show_error_msg (_("Error while loading “%s”: %s."), filename, g_strerror (lres)); response = NULL; } else { sysex_thread = g_thread_new ("sysex_thread", elektroid_tx_sysex_thread, f); gtk_window_set_title (GTK_WINDOW (progress_dialog), _("Send SysEx")); res = gtk_dialog_run (GTK_DIALOG (progress_dialog)); g_mutex_lock (&sysex_transfer.mutex); sysex_transfer.active = FALSE; g_mutex_unlock (&sysex_transfer.mutex); gtk_widget_hide (GTK_WIDGET (progress_dialog)); response = elektroid_join_sysex_thread (); } g_byte_array_free (sysex_transfer.raw, TRUE); if (*response < 0) { elektroid_check_connector (); } free (response); } else { gtk_widget_destroy (dialog); } } static void elektroid_tx_sysex (GtkWidget * object, gpointer data) { elektroid_tx_sysex_common (connector_tx_sysex); } static void elektroid_upgrade_os (GtkWidget * object, gpointer data) { elektroid_tx_sysex_common (connector_upgrade_os); connector_destroy (&connector); elektroid_check_connector (); } static void elektroid_show_about (GtkWidget * object, gpointer data) { gtk_dialog_run (GTK_DIALOG (about_dialog)); gtk_widget_hide (GTK_WIDGET (about_dialog)); } static void elektroid_controls_set_sensitive (gboolean sensitive) { gtk_widget_set_sensitive (local_play_menuitem, sensitive); gtk_widget_set_sensitive (play_button, sensitive); gtk_widget_set_sensitive (stop_button, sensitive); } static gboolean elektroid_update_ui_on_load (gpointer data) { gboolean ready_to_play; g_mutex_lock (&audio.control.mutex); ready_to_play = audio.frames >= LOAD_BUFFER_LEN || (!audio.control.active && audio.frames > 0); g_mutex_unlock (&audio.control.mutex); if (ready_to_play) { if (audio_check (&audio)) { elektroid_controls_set_sensitive (TRUE); } if (preferences.autoplay) { audio_play (&audio); } return FALSE; } return TRUE; } static void elektroid_delete_file (GtkTreeModel * model, GtkTreePath * tree_path, struct browser *browser) { GtkTreeIter iter; gchar *path; gchar *id_path; gint err; struct item *item; gtk_tree_model_get_iter (model, &iter, tree_path); item = browser_get_item (model, &iter); path = browser_get_item_path (browser, item); id_path = browser_get_item_id_path (browser, item); debug_print (1, "Deleting %s...\n", id_path); err = browser->fs_ops->delete (id_path, browser->data); if (err) { show_error_msg (_("Error while deleting “%s”: %s."), path, g_strerror (err)); } else { gtk_list_store_remove (GTK_LIST_STORE (model), &iter); } g_free (path); g_free (id_path); browser_free_item (item); } static void elektroid_delete_files (GtkWidget * object, gpointer data) { GtkTreeRowReference *reference; GList *list; GtkTreePath *tree_path; GtkTreeSelection *selection; GtkTreeModel *model; GtkWidget *dialog; GList *tree_path_list; GList *ref_list; gint confirmation; struct browser *browser = data; dialog = gtk_message_dialog_new (GTK_WINDOW (main_window), GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_NONE, _ ("Are you sure you want to delete the selected items?")); gtk_dialog_add_buttons (GTK_DIALOG (dialog), _("_Cancel"), GTK_RESPONSE_CANCEL, _("_Delete"), GTK_RESPONSE_ACCEPT, NULL); gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT); confirmation = gtk_dialog_run (GTK_DIALOG (dialog)); gtk_widget_destroy (dialog); if (confirmation != GTK_RESPONSE_ACCEPT) { return; } selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (browser->view)); model = GTK_TREE_MODEL (gtk_tree_view_get_model (browser->view)); tree_path_list = gtk_tree_selection_get_selected_rows (selection, &model); ref_list = NULL; for (list = tree_path_list; list != NULL; list = g_list_next (list)) { reference = gtk_tree_row_reference_new (model, list->data); ref_list = g_list_append (ref_list, reference); } g_list_free_full (tree_path_list, (GDestroyNotify) gtk_tree_path_free); for (list = ref_list; list != NULL; list = g_list_next (list)) { tree_path = gtk_tree_row_reference_get_path (list->data); elektroid_delete_file (model, tree_path, browser); } g_list_free_full (ref_list, (GDestroyNotify) gtk_tree_row_reference_free); browser_load_dir (browser); } static void elektroid_rename_item (GtkWidget * object, gpointer data) { char *old_path; char *new_path; int result; gint err; GtkTreeIter iter; struct item *item; struct browser *browser = data; GtkTreeModel *model = GTK_TREE_MODEL (gtk_tree_view_get_model (browser->view)); browser_set_selected_row_iter (browser, &iter); item = browser_get_item (model, &iter); old_path = browser_get_item_id_path (browser, item); gtk_entry_set_text (name_dialog_entry, item->name); gtk_widget_grab_focus (GTK_WIDGET (name_dialog_entry)); gtk_widget_set_sensitive (name_dialog_accept_button, FALSE); gtk_window_set_title (GTK_WINDOW (name_dialog), _("Rename")); result = GTK_RESPONSE_ACCEPT; err = -1; while (err < 0 && result == GTK_RESPONSE_ACCEPT) { result = gtk_dialog_run (GTK_DIALOG (name_dialog)); if (result == GTK_RESPONSE_ACCEPT) { new_path = chain_path (browser->dir, gtk_entry_get_text (name_dialog_entry)); err = browser->fs_ops->move (old_path, new_path, &connector); if (err) { show_error_msg (_("Error while renaming to “%s”: %s."), new_path, g_strerror (err)); } else { browser_load_dir (browser); } free (new_path); } } browser_free_item (item); g_free (old_path); gtk_widget_hide (GTK_WIDGET (name_dialog)); } static gboolean elektroid_drag_begin (GtkWidget * widget, GdkDragContext * context, gpointer data) { GtkTreeIter iter; GtkTreeSelection *selection; GtkTreeModel *model; GList *tree_path_list; GList *list; gchar *uri; gchar *path; struct item *item; struct browser *browser = data; selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget)); model = GTK_TREE_MODEL (gtk_tree_view_get_model (GTK_TREE_VIEW (widget))); tree_path_list = gtk_tree_selection_get_selected_rows (selection, &model); browser->dnd_data = g_string_new (""); for (list = tree_path_list; list != NULL; list = g_list_next (list)) { gtk_tree_model_get_iter (model, &iter, list->data); item = browser_get_item (model, &iter); path = browser_get_item_id_path (browser, item); browser_free_item (item); if (widget == GTK_WIDGET (local_browser.view)) { uri = g_filename_to_uri (path, NULL, NULL); } else if (widget == GTK_WIDGET (remote_browser.view)) { uri = chain_path ("file://", &path[1]); } else { continue; } g_free (path); g_string_append (browser->dnd_data, uri); g_free (uri); g_string_append (browser->dnd_data, "\n"); } g_list_free_full (tree_path_list, (GDestroyNotify) gtk_tree_path_free); browser->dnd = TRUE; debug_print (1, "Drag begin data:\n%s\n", browser->dnd_data->str); return FALSE; } static gboolean elektroid_drag_end (GtkWidget * widget, GdkDragContext * context, gpointer data) { struct browser *browser = data; debug_print (1, "Drag end\n"); g_string_free (browser->dnd_data, TRUE); browser->dnd = FALSE; return FALSE; } static gboolean elektroid_selection_function_true (GtkTreeSelection * selection, GtkTreeModel * model, GtkTreePath * path, gboolean path_currently_selected, gpointer data) { return TRUE; } static gboolean elektroid_selection_function_false (GtkTreeSelection * selection, GtkTreeModel * model, GtkTreePath * path, gboolean path_currently_selected, gpointer data) { return FALSE; } static gboolean elektroid_button_press (GtkWidget * treeview, GdkEventButton * event, gpointer data) { GtkTreePath *path; GtkTreeSelection *selection; struct browser *browser = data; selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (browser->view)); gtk_tree_selection_set_select_function (selection, elektroid_selection_function_true, NULL, NULL); if (event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) { return FALSE; } if (event->button == GDK_BUTTON_PRIMARY) { gtk_tree_view_get_path_at_pos (browser->view, event->x, event->y, &path, NULL, NULL, NULL); if (!path) { gtk_tree_selection_unselect_all (selection); return FALSE; } if (!gtk_tree_selection_path_is_selected (selection, path)) { gtk_tree_selection_unselect_all (selection); gtk_tree_selection_select_path (selection, path); } gtk_tree_path_free (path); gtk_tree_selection_set_select_function (selection, elektroid_selection_function_false, NULL, NULL); } else if (event->button == GDK_BUTTON_SECONDARY) { selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (browser->view)); gtk_tree_view_get_path_at_pos (browser->view, event->x, event->y, &path, NULL, NULL, NULL); if (!path) { gtk_tree_selection_unselect_all (selection); gtk_menu_popup_at_pointer (browser->menu, (GdkEvent *) event); return FALSE; } if (!gtk_tree_selection_path_is_selected (selection, path)) { gtk_tree_selection_unselect_all (selection); gtk_tree_selection_select_path (selection, path); } gtk_tree_path_free (path); gtk_menu_popup_at_pointer (browser->menu, (GdkEvent *) event); return TRUE; } return FALSE; } static gboolean elektroid_button_release (GtkWidget * treeview, GdkEventButton * event, gpointer data) { GtkTreePath *path; GtkTreeSelection *selection; struct browser *browser = data; if (event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) { return FALSE; } if (event->button == GDK_BUTTON_PRIMARY) { selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (browser->view)); gtk_tree_selection_set_select_function (selection, elektroid_selection_function_true, NULL, NULL); if (!browser->dnd) { gtk_tree_view_get_path_at_pos (browser->view, event->x, event->y, &path, NULL, NULL, NULL); if (!path) { return FALSE; } if (browser_get_selected_items_count (browser) > 1) { gtk_tree_selection_unselect_all (selection); gtk_tree_selection_select_path (selection, path); } gtk_tree_path_free (path); } return FALSE; } return FALSE; } static gboolean elektroid_queue_draw_waveform () { gtk_widget_queue_draw (waveform_draw_area); return FALSE; } static void elektroid_redraw_sample (gdouble percent) { g_idle_add (elektroid_queue_draw_waveform, NULL); } static gpointer elektroid_load_sample (gpointer path) { struct sample_loop_data *sample_loop_data; g_mutex_lock (&audio.control.mutex); audio.control.active = TRUE; g_mutex_unlock (&audio.control.mutex); if (sample_load_with_frames (path, audio.sample, &audio.control, &audio.frames) >= 0) { sample_loop_data = (struct sample_loop_data *) audio.control.data; debug_print (1, "Loop start at %d, loop end at %d\n", sample_loop_data->start, sample_loop_data->end); g_free (audio.control.data); } g_mutex_lock (&audio.control.mutex); audio.control.active = FALSE; g_mutex_unlock (&audio.control.mutex); free (path); return NULL; } static void elektroid_start_load_thread (gchar * path) { debug_print (1, "Creating load thread...\n"); load_thread = g_thread_new ("load_sample", elektroid_load_sample, path); g_timeout_add (100, elektroid_update_ui_on_load, NULL); } static void elektroid_stop_load_thread () { debug_print (1, "Stopping load thread...\n"); g_mutex_lock (&audio.control.mutex); audio.control.active = FALSE; g_mutex_unlock (&audio.control.mutex); if (load_thread) { g_thread_join (load_thread); load_thread = NULL; } } static void elektroid_join_task_thread () { debug_print (2, "Joining task thread...\n"); if (task_thread) { g_thread_join (task_thread); task_thread = NULL; } } static void elektroid_stop_task_thread () { debug_print (1, "Stopping task thread...\n"); g_mutex_lock (&transfer.control.mutex); transfer.control.active = FALSE; g_mutex_unlock (&transfer.control.mutex); elektroid_join_task_thread (); } static gboolean elektroid_remote_check_selection (gpointer data) { gint count = browser_get_selected_items_count (&remote_browser); gboolean dl_impl = remote_browser.fs_ops->download ? TRUE : FALSE; gboolean move_impl = remote_browser.fs_ops->move ? TRUE : FALSE; gboolean del_impl = remote_browser.fs_ops->delete ? TRUE : FALSE; gtk_widget_set_sensitive (download_menuitem, count > 0 && dl_impl); gtk_widget_set_sensitive (remote_rename_menuitem, count == 1 && move_impl); gtk_widget_set_sensitive (remote_delete_menuitem, count > 0 && del_impl); return FALSE; } static gboolean elektroid_local_check_selection (gpointer data) { GtkTreeIter iter; gchar *sample_path; GtkTreeModel *model; struct item *item = NULL; gint count = browser_get_selected_items_count (&local_browser); if (count == 0) { audio.name[0] = 0; } else if (count == 1) { browser_set_selected_row_iter (&local_browser, &iter); model = GTK_TREE_MODEL (gtk_tree_view_get_model (local_browser.view)); item = browser_get_item (model, &iter); if (!strcmp (audio.name, item->name)) { goto end; } } if (!remote_browser.fs_ops || remote_browser.fs_ops->fs == FS_SAMPLES) { audio_stop (&audio, TRUE); elektroid_stop_load_thread (); audio_reset_sample (&audio); gtk_widget_queue_draw (waveform_draw_area); elektroid_controls_set_sensitive (FALSE); if (item && item->type == ELEKTROID_FILE && strcmp (item->name, audio.name)) { sample_path = chain_path (local_browser.dir, item->name); elektroid_start_load_thread (sample_path); strcpy (audio.name, item->name); } gtk_widget_set_sensitive (local_open_menuitem, item && item->type == ELEKTROID_FILE); } end: if (item) { browser_free_item (item); } gtk_widget_set_sensitive (local_show_menuitem, count <= 1); gtk_widget_set_sensitive (local_rename_menuitem, count == 1); gtk_widget_set_sensitive (local_delete_menuitem, count > 0); gtk_widget_set_sensitive (upload_menuitem, count > 0 && remote_browser.fs_ops && remote_browser.fs_ops->upload); return FALSE; } static gboolean elektroid_draw_waveform (GtkWidget * widget, cairo_t * cr, gpointer data) { guint width, height; GdkRGBA color; GtkStyleContext *context; int i, x_widget, x_sample; double x_ratio, mid_y, value; short *sample; g_mutex_lock (&audio.control.mutex); context = gtk_widget_get_style_context (widget); width = gtk_widget_get_allocated_width (widget); height = gtk_widget_get_allocated_height (widget); mid_y = height / 2.0; gtk_render_background (context, cr, 0, 0, width, height); gtk_style_context_get_color (context, gtk_style_context_get_state (context), &color); gdk_cairo_set_source_rgba (cr, &color); sample = (short *) audio.sample->data; x_ratio = audio.frames / (double) MAX_DRAW_X; for (i = 0; i < MAX_DRAW_X; i++) { x_sample = i * x_ratio; if (x_sample < audio.sample->len >> 1) { x_widget = i * ((double) width) / MAX_DRAW_X; value = mid_y - mid_y * (sample[x_sample] / (float) SHRT_MIN); cairo_move_to (cr, x_widget, mid_y); cairo_line_to (cr, x_widget, value); cairo_stroke (cr); } } g_mutex_unlock (&audio.control.mutex); return FALSE; } static void elektroid_show_clicked (GtkWidget * object, gpointer data) { GtkTreeIter iter; GtkTreeModel *model; gchar *uri; GVariant *params, *result; GVariantBuilder builder; GFile *file; GDBusProxy *proxy; struct item *item; gchar *path = NULL; gboolean done = FALSE; gint count = browser_get_selected_items_count (&local_browser); if (count == 0) { path = chain_path (local_browser.dir, NULL); } else if (count == 1) { browser_set_selected_row_iter (&local_browser, &iter); model = GTK_TREE_MODEL (gtk_tree_view_get_model (local_browser.view)); item = browser_get_item (model, &iter); path = chain_path (local_browser.dir, item->name); browser_free_item (item); } else { return; } file = g_file_new_for_path (path); g_free (path); uri = g_file_get_uri (file); g_object_unref (file); proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS | G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES, NULL, "org.freedesktop.FileManager1", "/org/freedesktop/FileManager1", "org.freedesktop.FileManager1", NULL, NULL); if (proxy) { g_variant_builder_init (&builder, G_VARIANT_TYPE ("as")); g_variant_builder_add (&builder, "s", uri); params = g_variant_new ("(ass)", &builder, ""); result = g_dbus_proxy_call_sync (proxy, "ShowItems", params, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL); if (result != NULL) { done = TRUE; g_variant_unref (result); } g_object_unref (proxy); } if (!done) { g_app_info_launch_default_for_uri (uri, NULL, NULL); } g_free (uri); } static void elektroid_open_clicked (GtkWidget * object, gpointer data) { GtkTreeIter iter; GtkTreeModel *model; gchar *path; gchar *uri; GFile *file; struct item *item; browser_set_selected_row_iter (&local_browser, &iter); model = GTK_TREE_MODEL (gtk_tree_view_get_model (local_browser.view)); item = browser_get_item (model, &iter); path = chain_path (local_browser.dir, item->name); browser_free_item (item); file = g_file_new_for_path (path); g_free (path); uri = g_file_get_uri (file); g_object_unref (file); g_app_info_launch_default_for_uri_async (uri, NULL, NULL, NULL, NULL); free (uri); } static void elektroid_play_clicked (GtkWidget * object, gpointer data) { audio_play (&audio); } static void elektroid_stop_clicked (GtkWidget * object, gpointer data) { audio_stop (&audio, TRUE); } static void elektroid_loop_clicked (GtkWidget * object, gpointer data) { audio.loop = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (object)); } static gboolean elektroid_autoplay_clicked (GtkWidget * object, gboolean state, gpointer data) { preferences.autoplay = state; return FALSE; } static void elektroid_set_volume (GtkScaleButton * button, gdouble value, gpointer data) { audio_set_volume (&audio, value); } static void elektroid_set_volume_callback (gdouble value) { gtk_scale_button_set_value (GTK_SCALE_BUTTON (volume_button), value); } static void elektroid_add_dir (GtkWidget * object, gpointer data) { char *pathname; int result; gint err; struct browser *browser = data; gtk_entry_set_text (name_dialog_entry, ""); gtk_widget_grab_focus (GTK_WIDGET (name_dialog_entry)); gtk_widget_set_sensitive (name_dialog_accept_button, FALSE); gtk_window_set_title (GTK_WINDOW (name_dialog), _("Add Directory")); result = GTK_RESPONSE_ACCEPT; err = -1; while (err < 0 && result == GTK_RESPONSE_ACCEPT) { result = gtk_dialog_run (GTK_DIALOG (name_dialog)); if (result == GTK_RESPONSE_ACCEPT) { pathname = chain_path (browser->dir, gtk_entry_get_text (name_dialog_entry)); err = browser->fs_ops->mkdir (pathname, &connector); if (err) { show_error_msg (_("Error while creating dir “%s”: %s."), pathname, g_strerror (err)); } else { browser_load_dir (browser); } free (pathname); } } gtk_widget_hide (GTK_WIDGET (name_dialog)); } static void elektroid_accept_name (GtkWidget * object, gpointer data) { gtk_dialog_response (name_dialog, GTK_RESPONSE_ACCEPT); } static void elektroid_cancel_name (GtkWidget * object, gpointer data) { gtk_dialog_response (name_dialog, GTK_RESPONSE_CANCEL); } static void elektroid_name_dialog_entry_changed (GtkWidget * object, gpointer data) { if (strlen (gtk_entry_get_text (name_dialog_entry)) > 0) { gtk_widget_set_sensitive (name_dialog_accept_button, TRUE); } else { gtk_widget_set_sensitive (name_dialog_accept_button, FALSE); } } static gboolean elektroid_get_running_task (GtkTreeIter * iter) { enum elektroid_task_status status; gboolean found = FALSE; gboolean valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (task_list_store), iter); while (valid) { gtk_tree_model_get (GTK_TREE_MODEL (task_list_store), iter, TASK_LIST_STORE_STATUS_FIELD, &status, -1); if (status == RUNNING) { found = TRUE; break; } valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (task_list_store), iter); } return found; } static const gchar * elektroid_get_human_task_status (enum elektroid_task_status status) { switch (status) { case QUEUED: return _("Queued"); case RUNNING: return _("Running"); case COMPLETED_OK: return _("Completed"); case COMPLETED_ERROR: return _("Completed with errors"); case CANCELED: return _("Canceled"); default: return _("Undefined"); } } static const gchar * elektroid_get_human_task_type (enum elektroid_task_type type) { switch (type) { case UPLOAD: return _("Upload"); case DOWNLOAD: return _("Download"); default: return _("Undefined"); } } static void elektroid_stop_running_task (GtkWidget * object, gpointer data) { g_mutex_lock (&transfer.control.mutex); transfer.control.active = FALSE; g_mutex_unlock (&transfer.control.mutex); } static gboolean elektroid_task_is_queued (enum elektroid_task_status status) { return (status == QUEUED); } static gboolean elektroid_task_is_finished (enum elektroid_task_status status) { return (status == COMPLETED_OK || status == COMPLETED_ERROR || status == CANCELED); } static gboolean elektroid_check_task_buttons (gpointer data) { enum elektroid_task_status status; gboolean queued = FALSE; gboolean finished = FALSE; GtkTreeIter iter; gboolean valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (task_list_store), &iter); while (valid) { gtk_tree_model_get (GTK_TREE_MODEL (task_list_store), &iter, TASK_LIST_STORE_STATUS_FIELD, &status, -1); if (elektroid_task_is_queued (status)) { queued = TRUE; } if (elektroid_task_is_finished (status)) { finished = TRUE; } valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (task_list_store), &iter); } gtk_widget_set_sensitive (remove_tasks_button, queued); gtk_widget_set_sensitive (clear_tasks_button, finished); return FALSE; } static void elektroid_cancel_all_tasks (GtkWidget * object, gpointer data) { enum elektroid_task_status status; GtkTreeIter iter; gboolean valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (task_list_store), &iter); const gchar *canceled = elektroid_get_human_task_status (CANCELED); while (valid) { gtk_tree_model_get (GTK_TREE_MODEL (task_list_store), &iter, TASK_LIST_STORE_STATUS_FIELD, &status, -1); if (status == QUEUED) { gtk_list_store_set (task_list_store, &iter, TASK_LIST_STORE_STATUS_FIELD, CANCELED, TASK_LIST_STORE_STATUS_HUMAN_FIELD, canceled, -1); valid = gtk_list_store_iter_is_valid (task_list_store, &iter); } else { valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (task_list_store), &iter); } } elektroid_stop_running_task (NULL, NULL); elektroid_check_task_buttons (NULL); } static void elektroid_remove_tasks_on_cond (gboolean (*selector) (enum elektroid_task_status)) { enum elektroid_task_status status; GtkTreeIter iter; gboolean valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (task_list_store), &iter); while (valid) { gtk_tree_model_get (GTK_TREE_MODEL (task_list_store), &iter, TASK_LIST_STORE_STATUS_FIELD, &status, -1); if (selector (status)) { gtk_list_store_remove (task_list_store, &iter); valid = gtk_list_store_iter_is_valid (task_list_store, &iter); } else { valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (task_list_store), &iter); } } elektroid_check_task_buttons (NULL); } static void elektroid_remove_queued_tasks (GtkWidget * object, gpointer data) { elektroid_remove_tasks_on_cond (elektroid_task_is_queued); } static void elektroid_clear_finished_tasks (GtkWidget * object, gpointer data) { elektroid_remove_tasks_on_cond (elektroid_task_is_finished); } static gboolean elektroid_complete_running_task (gpointer data) { GtkTreeIter iter; const gchar *status = elektroid_get_human_task_status (transfer.status); if (elektroid_get_running_task (&iter)) { gtk_list_store_set (task_list_store, &iter, TASK_LIST_STORE_STATUS_FIELD, transfer.status, TASK_LIST_STORE_STATUS_HUMAN_FIELD, status, -1); elektroid_stop_running_task (NULL, NULL); g_free (transfer.src); g_free (transfer.dst); gtk_widget_set_sensitive (cancel_task_button, FALSE); } else { debug_print (1, "No task running. Skipping...\n"); } return FALSE; } static gboolean elektroid_run_next_task (gpointer data) { GtkTreeIter iter; enum elektroid_task_type type; gchar *src; gchar *dst; gint fs; GtkTreePath *path; gboolean transfer_active; gboolean found = elektroid_get_next_queued_task (&iter, &type, &src, &dst, &fs); const gchar *status_human = elektroid_get_human_task_status (RUNNING); g_mutex_lock (&transfer.control.mutex); transfer_active = transfer.control.active; g_mutex_unlock (&transfer.control.mutex); if (!transfer_active && found) { gtk_list_store_set (task_list_store, &iter, TASK_LIST_STORE_STATUS_FIELD, RUNNING, TASK_LIST_STORE_STATUS_HUMAN_FIELD, status_human, -1); path = gtk_tree_model_get_path (GTK_TREE_MODEL (task_list_store), &iter); gtk_tree_view_set_cursor (GTK_TREE_VIEW (task_tree_view), path, NULL, FALSE); gtk_tree_path_free (path); transfer.control.active = TRUE; transfer.control.callback = elektroid_update_progress; transfer.src = src; transfer.dst = dst; transfer.fs_ops = connector_get_fs_operations (fs); debug_print (1, "Running task type %d from %s to %s (%s)...\n", type, transfer.src, transfer.dst, elektroid_get_fs_name (fs)); if (type == UPLOAD) { task_thread = g_thread_new ("upload_task", elektroid_upload_task, NULL); } else if (type == DOWNLOAD) { task_thread = g_thread_new ("download_task", elektroid_download_task, NULL); } gtk_widget_set_sensitive (cancel_task_button, TRUE); } else { gtk_widget_set_sensitive (rx_sysex_button, TRUE); gtk_widget_set_sensitive (tx_sysex_button, TRUE); gtk_widget_set_sensitive (os_upgrade_button, TRUE); } elektroid_check_task_buttons (NULL); return FALSE; } static gpointer elektroid_upload_task (gpointer data) { gchar *dst_path; gchar *dst_dir; gint res; GByteArray *array; debug_print (1, "Local path: %s\n", transfer.src); debug_print (1, "Remote path: %s\n", transfer.dst); array = g_byte_array_new (); res = transfer.fs_ops->load (transfer.src, array, &transfer.control); if (res) { error_print ("Error while loading file\n"); transfer.status = COMPLETED_ERROR; goto end_cleanup; } debug_print (1, "Writing from file %s (filesystem %s)...\n", transfer.src, elektroid_get_fs_name (transfer.fs_ops->fs)); res = transfer.fs_ops->upload (transfer.dst, array, &transfer.control, remote_browser.data); g_free (transfer.control.data); transfer.control.data = NULL; g_idle_add (elektroid_check_connector_bg, NULL); if (res && transfer.control.active) { error_print ("Error while uploading\n"); transfer.status = COMPLETED_ERROR; } else { g_mutex_lock (&transfer.control.mutex); if (transfer.control.active) { transfer.status = COMPLETED_OK; } else { transfer.status = CANCELED; } g_mutex_unlock (&transfer.control.mutex); } if (!res && transfer.fs_ops == remote_browser.fs_ops) //There is no need to refresh the local browser { dst_path = strdup (transfer.dst); dst_dir = dirname (dst_path); if (strcmp (dst_dir, remote_browser.dir) == 0) { g_idle_add (browser_load_dir, &remote_browser); } g_free (dst_path); } end_cleanup: g_byte_array_free (array, TRUE); g_idle_add (elektroid_complete_running_task, NULL); g_idle_add (elektroid_run_next_task, NULL); return NULL; } static void elektroid_add_task (enum elektroid_task_type type, const char *src, const char *dst, gint remote_fs_id) { const gchar *status_human = elektroid_get_human_task_status (QUEUED); const gchar *type_human = elektroid_get_human_task_type (type); const gchar *icon = elektroid_get_inventory_icon_for_fs (remote_fs_id); gtk_list_store_insert_with_values (task_list_store, NULL, -1, TASK_LIST_STORE_STATUS_FIELD, QUEUED, TASK_LIST_STORE_TYPE_FIELD, type, TASK_LIST_STORE_SRC_FIELD, src, TASK_LIST_STORE_DST_FIELD, dst, TASK_LIST_STORE_PROGRESS_FIELD, 0.0, TASK_LIST_STORE_STATUS_HUMAN_FIELD, status_human, TASK_LIST_STORE_TYPE_HUMAN_FIELD, type_human, TASK_LIST_STORE_REMOTE_FS_ID_FIELD, remote_fs_id, TASK_LIST_STORE_REMOTE_FS_ICON_FIELD, icon, -1); gtk_widget_set_sensitive (remove_tasks_button, TRUE); } static void elektroid_add_upload_task_path (const gchar * rel_path, const gchar * src_dir, const gchar * dst_dir, struct item_iterator *remote_dir_iter, gint32 * next_idx) { gint32 children_next_idx; gchar *path; gchar *dst_abs_dir; gchar *upload_path; struct item_iterator iter; struct item_iterator children_remote_item_iterator; gchar *dst_abs_path = chain_path (dst_dir, rel_path); gchar *src_abs_path = chain_path (src_dir, rel_path); if (local_browser.fs_ops->readdir (&iter, src_abs_path, local_browser.data)) { dst_abs_dir = dirname (dst_abs_path); upload_path = connector_get_upload_path (&connector, remote_dir_iter, remote_browser.fs_ops, dst_abs_dir, src_abs_path, next_idx); elektroid_add_task (UPLOAD, src_abs_path, upload_path, remote_browser.fs_ops->fs); goto cleanup_not_dir; } if (remote_browser.fs_ops->mkdir) { if (remote_browser.fs_ops->mkdir (dst_abs_path, remote_browser.data)) { error_print ("Error while creating remote %s dir\n", dst_abs_path); goto cleanup; } if (!strchr (rel_path, '/')) { browser_load_dir (&remote_browser); } } if (!remote_browser.fs_ops->readdir (&children_remote_item_iterator, dst_abs_path, remote_browser.data)) { while (!next_item_iterator (&iter)) { path = chain_path (rel_path, iter.item.name); elektroid_add_upload_task_path (path, src_dir, dst_dir, &children_remote_item_iterator, &children_next_idx); free (path); } free_item_iterator (&children_remote_item_iterator); } cleanup: free_item_iterator (&iter); cleanup_not_dir: g_free (dst_abs_path); g_free (src_abs_path); } static void elektroid_add_upload_task (GtkTreeModel * model, GtkTreePath * path, GtkTreeIter * iter, gpointer userdata) { struct item *item = browser_get_item (model, iter); struct elektroid_add_upload_task_data *data = userdata; elektroid_add_upload_task_path (item->name, local_browser.dir, remote_browser.dir, &data->iter, &data->index); browser_free_item (item); } static void elektroid_add_upload_tasks (GtkWidget * object, gpointer userdata) { gboolean queued; GtkTreeIter iter; struct elektroid_add_upload_task_data data; GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (local_browser.view)); if (!gtk_tree_selection_count_selected_rows (selection)) { return; } queued = elektroid_get_next_queued_task (&iter, NULL, NULL, NULL, NULL); data.index = 1; remote_browser.fs_ops->readdir (&data.iter, remote_browser.dir, remote_browser.data); gtk_tree_selection_selected_foreach (selection, elektroid_add_upload_task, &data); free_item_iterator (&data.iter); gtk_widget_set_sensitive (rx_sysex_button, FALSE); gtk_widget_set_sensitive (tx_sysex_button, FALSE); gtk_widget_set_sensitive (os_upgrade_button, FALSE); if (!queued) { elektroid_run_next_task (NULL); } } static gpointer elektroid_download_task (gpointer userdata) { gint res; GByteArray *array; array = g_byte_array_new (); debug_print (1, "Remote path: %s\n", transfer.src); debug_print (1, "Local path: %s\n", transfer.dst); res = transfer.fs_ops->download (transfer.src, array, &transfer.control, remote_browser.data); g_idle_add (elektroid_check_connector_bg, NULL); g_mutex_lock (&transfer.control.mutex); if (res && transfer.control.active) { error_print ("Error while downloading\n"); transfer.status = COMPLETED_ERROR; } else { if (transfer.control.active) { debug_print (1, "Writing %d bytes to file %s (filesystem %s)...\n", array->len, transfer.dst, elektroid_get_fs_name (transfer.fs_ops->fs)); res = transfer.fs_ops->save (transfer.dst, array, &transfer.control); if (!res) { transfer.status = COMPLETED_OK; } } else { transfer.status = CANCELED; } g_byte_array_free (array, TRUE); g_free (transfer.control.data); transfer.control.data = NULL; } g_mutex_unlock (&transfer.control.mutex); g_idle_add (elektroid_complete_running_task, NULL); g_idle_add (elektroid_run_next_task, NULL); return NULL; } static void elektroid_add_download_task_path (const gchar * rel_path, const gchar * src_dir, const gchar * dst_dir, struct item_iterator *remote_dir_iter) { struct item_iterator iter, iter_copy; gchar *path, *id, *download_path, *dst_abs_dirc, *dst_abs_dir; gchar *src_abs_path = chain_path (src_dir, rel_path); gchar *dst_abs_path = chain_path (dst_dir, rel_path); if (remote_browser. fs_ops->readdir (&iter, src_abs_path, remote_browser.data)) { dst_abs_dirc = strdup (dst_abs_path); dst_abs_dir = dirname (dst_abs_dirc); download_path = connector_get_download_path (&connector, remote_dir_iter, remote_browser.fs_ops, dst_abs_dir, src_abs_path); elektroid_add_task (DOWNLOAD, src_abs_path, download_path, remote_browser.fs_ops->fs); g_free (dst_abs_dirc); g_free (download_path); goto cleanup_not_dir; } if (local_browser.fs_ops->mkdir (dst_abs_path, NULL)) { error_print ("Error while creating local %s dir\n", dst_abs_path); goto cleanup; } copy_item_iterator (&iter_copy, &iter, TRUE); while (!next_item_iterator (&iter)) { id = remote_browser.fs_ops->getid (&iter.item); path = chain_path (rel_path, id); elektroid_add_download_task_path (path, src_dir, dst_dir, &iter_copy); g_free (path); g_free (id); } free_item_iterator (&iter_copy); cleanup: free_item_iterator (&iter); cleanup_not_dir: free (dst_abs_path); free (src_abs_path); } static void elektroid_add_download_task (GtkTreeModel * model, GtkTreePath * path, GtkTreeIter * iter, gpointer data) { struct item *item = browser_get_item (model, iter); char *id = remote_browser.fs_ops->getid (item); elektroid_add_download_task_path (id, remote_browser.dir, local_browser.dir, data); g_free (id); browser_free_item (item); } static void elektroid_add_download_tasks (GtkWidget * object, gpointer data) { gboolean queued; GtkTreeIter iter; struct item_iterator item_iterator; GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (remote_browser.view)); if (!gtk_tree_selection_count_selected_rows (selection)) { return; } queued = elektroid_get_next_queued_task (&iter, NULL, NULL, NULL, NULL); remote_browser.fs_ops->readdir (&item_iterator, remote_browser.dir, remote_browser.data); gtk_tree_selection_selected_foreach (selection, elektroid_add_download_task, &item_iterator); free_item_iterator (&item_iterator); gtk_widget_set_sensitive (rx_sysex_button, FALSE); gtk_widget_set_sensitive (tx_sysex_button, FALSE); gtk_widget_set_sensitive (os_upgrade_button, FALSE); if (!queued) { elektroid_run_next_task (NULL); } } static gboolean elektroid_set_progress_value (gpointer data) { GtkTreeIter iter; gdouble *value = data; if (elektroid_get_running_task (&iter)) { gtk_list_store_set (task_list_store, &iter, TASK_LIST_STORE_PROGRESS_FIELD, 100.0 * (*value), -1); } free (data); return FALSE; } static void elektroid_update_progress (gdouble progress) { gdouble *value = malloc (sizeof (gdouble)); *value = progress; g_idle_add (elektroid_set_progress_value, value); } static gboolean elektroid_common_key_press (GtkWidget * widget, GdkEventKey * event, gpointer data) { gint count; GtkAllocation allocation; GdkWindow *gdk_window; struct browser *browser = data; if (event->keyval == GDK_KEY_Menu) { count = browser_get_selected_items_count (browser); gtk_widget_get_allocation (GTK_WIDGET (browser->view), &allocation); gdk_window = gtk_widget_get_window (GTK_WIDGET (browser->view)); gtk_menu_popup_at_rect (browser->menu, gdk_window, &allocation, GDK_GRAVITY_CENTER, GDK_GRAVITY_NORTH_WEST, NULL); return TRUE; } else if (event->keyval == GDK_KEY_F2) { count = browser_get_selected_items_count (browser); if (count == 1 && browser->fs_ops->rename) { elektroid_rename_item (NULL, browser); } return TRUE; } else if (event->keyval == GDK_KEY_Delete) { if (browser_get_selected_items_count (browser) > 0 && browser->fs_ops->delete) { elektroid_delete_files (NULL, browser); } return TRUE; } else if (event->state & GDK_CONTROL_MASK && event->keyval == GDK_KEY_r) { browser_load_dir (browser); return TRUE; } else if (event->state & GDK_CONTROL_MASK && (event->keyval == GDK_KEY_U || event->keyval == GDK_KEY_u)) { browser_go_up (NULL, browser); return TRUE; } else if (event->state & GDK_CONTROL_MASK && event->state & GDK_SHIFT_MASK && (event->keyval == GDK_KEY_N || event->keyval == GDK_KEY_n)) { elektroid_add_dir (NULL, browser); return TRUE; } else { return FALSE; } } static gboolean elektroid_remote_key_press (GtkWidget * widget, GdkEventKey * event, gpointer data) { if (event->type == GDK_KEY_PRESS) { if (event->state & GDK_CONTROL_MASK && event->keyval == GDK_KEY_Left) { if (remote_browser.fs_ops->download) { struct connector *connector = remote_browser.data; connector_enable_dir_cache (connector); elektroid_add_download_tasks (NULL, NULL); connector_disable_dir_cache (connector); } return TRUE; } else { return elektroid_common_key_press (widget, event, data); } } return FALSE; } static gboolean elektroid_local_key_press (GtkWidget * widget, GdkEventKey * event, gpointer data) { if (event->type == GDK_KEY_PRESS) { if (event->state & GDK_CONTROL_MASK && event->keyval == GDK_KEY_Right) { if (remote_browser.fs_ops->upload) { elektroid_add_upload_tasks (NULL, NULL); } return TRUE; } else { return elektroid_common_key_press (widget, event, data); } } return FALSE; } static void elektroid_set_fs (GtkWidget * object, gpointer data) { GtkTreeIter iter; GtkTreeSortable *sortable; GValue fsv = G_VALUE_INIT; enum connector_fs fs; if (gtk_combo_box_get_active_iter (fs_combo, &iter) == TRUE) { gtk_tree_model_get_value (GTK_TREE_MODEL (fs_list_store), &iter, FS_LIST_STORE_ID_FIELD, &fsv); fs = g_value_get_uint (&fsv); remote_browser.fs_ops = connector_get_fs_operations (fs); remote_browser.file_icon = elektroid_get_inventory_icon_for_fs (fs); strcpy (remote_browser.dir, "/"); browser_load_dir (&remote_browser); local_browser.file_icon = remote_browser.file_icon; elektroid_set_file_extensions_for_fs (&local_browser.extensions, fs); browser_load_dir (&local_browser); gtk_widget_set_sensitive (remote_browser.up_button, remote_browser.fs_ops->readdir != NULL); gtk_widget_set_visible (remote_browser.add_dir_button, remote_browser.fs_ops->mkdir != NULL); gtk_widget_set_sensitive (remote_browser.refresh_button, remote_browser.fs_ops->readdir != NULL); gtk_widget_set_visible (remote_rename_menuitem, remote_browser.fs_ops->rename != NULL); gtk_widget_set_visible (remote_delete_menuitem, remote_browser.fs_ops->delete != NULL); gtk_widget_set_visible (local_audio_box, fs == FS_SAMPLES); gtk_tree_view_column_set_visible (remote_tree_view_index_column, fs == FS_DATA_PRJ || fs == FS_DATA_SND); if (fs != FS_SAMPLES) { audio_stop (&audio, TRUE); } sortable = GTK_TREE_SORTABLE (gtk_tree_view_get_model (remote_browser.view)); if (fs == FS_SAMPLES || fs == FS_RAW_PRESETS) { gtk_tree_sortable_set_sort_func (sortable, BROWSER_LIST_STORE_NAME_FIELD, browser_sort_samples, NULL, NULL); gtk_tree_sortable_set_sort_column_id (sortable, BROWSER_LIST_STORE_NAME_FIELD, GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID); } else if (fs == FS_DATA_PRJ || fs == FS_DATA_SND) { gtk_tree_sortable_set_sort_func (sortable, BROWSER_LIST_STORE_INDEX_FIELD, browser_sort_data, NULL, NULL); gtk_tree_sortable_set_sort_column_id (sortable, BROWSER_LIST_STORE_INDEX_FIELD, GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID); } } } static void elektroid_fill_fs_combo () { gtk_list_store_clear (fs_list_store); for (int fs = FS_SAMPLES, i = 0; fs <= FS_DATA_SND; fs = fs << 1, i++) { if (GUI_FSS & fs && connector.device_desc->fss & fs) { gtk_list_store_insert_with_values (fs_list_store, NULL, -1, FS_LIST_STORE_ID_FIELD, fs, FS_LIST_STORE_ICON_FIELD, ELEKTROID_FS_ICONS[i], FS_LIST_STORE_NAME_FIELD, elektroid_get_fs_name (fs), -1); } } debug_print (1, "Selecting first filesystem...\n"); gtk_combo_box_set_active (fs_combo, 0); } static void elektroid_set_device (GtkWidget * object, gpointer data) { GtkTreeIter iter; GValue cardv = G_VALUE_INIT; guint card; if (gtk_combo_box_get_active_iter (devices_combo, &iter) == TRUE) { if (connector_check (&connector)) { connector_destroy (&connector); } gtk_tree_model_get_value (GTK_TREE_MODEL (devices_list_store), &iter, DEVICES_LIST_STORE_CARD_FIELD, &cardv); card = g_value_get_uint (&cardv); if (connector_init (&connector, card) < 0) { error_print ("Error while connecting\n"); } if (elektroid_check_connector ()) { elektroid_fill_fs_combo (); } } } static void elektroid_dnd_received_local (const gchar * type_name, const gchar * dir, const gchar * name, const gchar * filename, struct item_iterator *remote_item_iterator) { gchar *dst_path; gint res; if (strcmp (type_name, TEXT_URI_LIST_STD) == 0) { if (strcmp (dir, local_browser.dir)) { dst_path = chain_path (local_browser.dir, name); res = local_browser.fs_ops->move (filename, dst_path, NULL); if (res) { show_error_msg (_ ("Error while moving from “%s” to “%s”: %s."), filename, dst_path, g_strerror (res)); } g_free (dst_path); } else { debug_print (1, MSG_WARN_SAME_SRC_DST); } } else if (strcmp (type_name, TEXT_URI_LIST_ELEKTROID) == 0) { elektroid_add_download_task_path (name, dir, local_browser.dir, remote_item_iterator); } } static void elektroid_dnd_received_remote (const gchar * type_name, const gchar * dir, const gchar * name, const gchar * filename, struct item_iterator *remote_item_iterator, gint32 * next_idx) { gchar *dst_path; gint res; if (strcmp (type_name, TEXT_URI_LIST_ELEKTROID) == 0) { if (strcmp (dir, remote_browser.dir)) { dst_path = connector_get_upload_path (&connector, remote_item_iterator, remote_browser.fs_ops, remote_browser.dir, name, next_idx); res = remote_browser.fs_ops->move (filename, dst_path, remote_browser.data); if (res) { show_error_msg (_ ("Error while moving from “%s” to “%s”: %s."), filename, dst_path, g_strerror (res)); } g_free (dst_path); browser_load_dir (&remote_browser); } else { debug_print (1, MSG_WARN_SAME_SRC_DST); } } else if (strcmp (type_name, TEXT_URI_LIST_STD) == 0) { elektroid_add_upload_task_path (name, dir, remote_browser.dir, remote_item_iterator, next_idx); } } static void elektroid_dnd_received (GtkWidget * widget, GdkDragContext * context, gint x, gint y, GtkSelectionData * selection_data, guint info, guint time, gpointer userdata) { gchar *data; gchar **uris; gchar *filename; gchar *path_basename; gchar *path_dirname; gchar *name; gchar *dir; GtkTreeIter iter; gboolean queued; GdkAtom type; gchar *type_name; gint32 next_idx = 1; struct item_iterator remote_item_iterator; struct connector *connector = remote_browser.data; if (selection_data == NULL || !gtk_selection_data_get_length (selection_data) || info != TARGET_STRING) { goto end; } type = gtk_selection_data_get_data_type (selection_data); type_name = gdk_atom_name (type); data = (gchar *) gtk_selection_data_get_data (selection_data); debug_print (1, "DND received data (%s):\n%s\n", type_name, data); uris = g_uri_list_extract_uris (data); queued = elektroid_get_next_queued_task (&iter, NULL, NULL, NULL, NULL); if (widget == GTK_WIDGET (local_browser.view)) { connector_enable_dir_cache (connector); } remote_browser.fs_ops->readdir (&remote_item_iterator, remote_browser.dir, connector); for (int i = 0; uris[i] != NULL; i++) { filename = g_filename_from_uri (uris[i], NULL, NULL); path_basename = strdup (filename); path_dirname = strdup (filename); name = basename (path_basename); dir = dirname (path_dirname); if (widget == GTK_WIDGET (local_browser.view)) { elektroid_dnd_received_local (type_name, dir, name, filename, &remote_item_iterator); } else if (widget == GTK_WIDGET (remote_browser.view)) { elektroid_dnd_received_remote (type_name, dir, name, filename, &remote_item_iterator, &next_idx); } g_free (path_basename); g_free (path_dirname); g_free (filename); } free_item_iterator (&remote_item_iterator); if (widget == GTK_WIDGET (local_browser.view)) { connector_disable_dir_cache (connector); } if (!queued) { elektroid_run_next_task (NULL); } g_strfreev (uris); end: gtk_drag_finish (context, TRUE, TRUE, time); } static void elektroid_dnd_get (GtkWidget * widget, GdkDragContext * context, GtkSelectionData * selection_data, guint info, guint time, gpointer user_data) { GdkAtom type; gchar *type_name; struct browser *browser = user_data; switch (info) { case TARGET_STRING: debug_print (1, "Creating DND data...\n"); gtk_selection_data_set (selection_data, gtk_selection_data_get_target (selection_data), 8, (guchar *) browser->dnd_data->str, browser->dnd_data->len); type = gtk_selection_data_get_data_type (selection_data); type_name = gdk_atom_name (type); debug_print (1, "DND sent data (%s):\n%s\n", type_name, browser->dnd_data->str); break; default: error_print ("DND type not supported\n"); } } static gboolean elektroid_drag_list_timeout (gpointer user_data) { struct browser *browser = user_data; gchar *spath; spath = gtk_tree_path_to_string (browser->dnd_motion_path); debug_print (2, "Getting into path: %s...\n", spath); g_free (spath); browser_item_activated (browser->view, browser->dnd_motion_path, NULL, browser); gtk_tree_path_free (browser->dnd_motion_path); browser->dnd_timeout_function_id = 0; browser->dnd_motion_path = NULL; return FALSE; } static gboolean elektroid_drag_motion_list (GtkWidget * widget, GdkDragContext * context, gint wx, gint wy, guint time, gpointer user_data) { GtkTreePath *path; GtkTreeModel *model; GtkTreeIter iter; gchar *spath; gint tx; gint ty; GtkTreeSelection *selection; struct item *item; struct browser *browser = user_data; gtk_tree_view_convert_widget_to_bin_window_coords (GTK_TREE_VIEW (widget), wx, wy, &tx, &ty); if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget), tx, ty, &path, NULL, NULL, NULL)) { spath = gtk_tree_path_to_string (path); debug_print (2, "Drag motion path: %s\n", spath); g_free (spath); selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (browser->view)); if (gtk_tree_selection_path_is_selected (selection, path)) { if (browser->dnd_timeout_function_id) { g_source_remove (browser->dnd_timeout_function_id); browser->dnd_timeout_function_id = 0; } return TRUE; } model = GTK_TREE_MODEL (gtk_tree_view_get_model (GTK_TREE_VIEW (widget))); gtk_tree_model_get_iter (model, &iter, path); item = browser_get_item (model, &iter); if (item->type == ELEKTROID_DIR && (!browser->dnd_motion_path || (browser->dnd_motion_path && gtk_tree_path_compare (browser->dnd_motion_path, path)))) { if (browser->dnd_timeout_function_id) { g_source_remove (browser->dnd_timeout_function_id); browser->dnd_timeout_function_id = 0; } browser->dnd_timeout_function_id = g_timeout_add (DND_TIMEOUT, elektroid_drag_list_timeout, browser); } browser_free_item (item); } else { if (browser->dnd_timeout_function_id) { g_source_remove (browser->dnd_timeout_function_id); browser->dnd_timeout_function_id = 0; } } if (browser->dnd_motion_path) { gtk_tree_path_free (browser->dnd_motion_path); browser->dnd_motion_path = NULL; } browser->dnd_motion_path = path; return TRUE; } static void elektroid_drag_leave_list (GtkWidget * widget, GdkDragContext * context, guint time, gpointer user_data) { struct browser *browser = user_data; if (browser->dnd_timeout_function_id) { g_source_remove (browser->dnd_timeout_function_id); browser->dnd_timeout_function_id = 0; } } static gboolean elektroid_drag_up_timeout (gpointer user_data) { struct browser *browser = user_data; browser_go_up (NULL, browser); return TRUE; } static gboolean elektroid_drag_motion_up (GtkWidget * widget, GdkDragContext * context, gint wx, gint wy, guint time, gpointer user_data) { struct browser *browser = user_data; if (browser->dnd_timeout_function_id) { g_source_remove (browser->dnd_timeout_function_id); browser->dnd_timeout_function_id = 0; } browser->dnd_timeout_function_id = g_timeout_add (DND_TIMEOUT, elektroid_drag_up_timeout, browser); return TRUE; } static void elektroid_drag_leave_up (GtkWidget * widget, GdkDragContext * context, guint time, gpointer user_data) { struct browser *browser = user_data; if (browser->dnd_timeout_function_id) { g_source_remove (browser->dnd_timeout_function_id); browser->dnd_timeout_function_id = 0; } } static void elektroid_notify_local_dir_change (struct browser *browser) { notifier_set_dir (¬ifier, browser->dir); } static void elektroid_quit () { elektroid_stop_sysex_thread (); elektroid_stop_task_thread (); elektroid_stop_load_thread (); notifier.running = 0; notifier_close (¬ifier); g_thread_join (notifier_thread); notifier_free (¬ifier); debug_print (1, "Quitting GTK+...\n"); gtk_main_quit (); } static gboolean elektroid_delete_window (GtkWidget * widget, GdkEvent * event, gpointer data) { elektroid_quit (); return FALSE; } static int elektroid_run (int argc, char *argv[]) { GtkBuilder *builder; GtkCssProvider *css_provider; GtkTreeSortable *sortable; GtkWidget *name_dialog_cancel_button; GtkWidget *refresh_devices_button; GtkWidget *hostname_label; GtkWidget *loop_button; GtkWidget *autoplay_switch; char *glade_file = malloc (PATH_MAX); char *css_file = malloc (PATH_MAX); char hostname[LABEL_MAX]; if (snprintf (glade_file, PATH_MAX, "%s/%s/res/gui.glade", DATADIR, PACKAGE) >= PATH_MAX) { error_print ("Path too long\n"); return -1; } if (snprintf (css_file, PATH_MAX, "%s/%s/res/gui.css", DATADIR, PACKAGE) >= PATH_MAX) { error_print ("Path too long\n"); return -1; } gtk_init (&argc, &argv); builder = gtk_builder_new (); gtk_builder_add_from_file (builder, glade_file, NULL); free (glade_file); css_provider = gtk_css_provider_new (); gtk_css_provider_load_from_path (css_provider, css_file, NULL); gtk_style_context_add_provider_for_screen (gdk_screen_get_default (), GTK_STYLE_PROVIDER (css_provider), GTK_STYLE_PROVIDER_PRIORITY_USER); free (css_file); main_window = GTK_WIDGET (gtk_builder_get_object (builder, "main_window")); gtk_window_resize (GTK_WINDOW (main_window), 1, 1); //Compact window about_dialog = GTK_ABOUT_DIALOG (gtk_builder_get_object (builder, "about_dialog")); gtk_about_dialog_set_version (about_dialog, PACKAGE_VERSION); name_dialog = GTK_DIALOG (gtk_builder_get_object (builder, "name_dialog")); name_dialog_accept_button = GTK_WIDGET (gtk_builder_get_object (builder, "name_dialog_accept_button")); name_dialog_cancel_button = GTK_WIDGET (gtk_builder_get_object (builder, "name_dialog_cancel_button")); name_dialog_entry = GTK_ENTRY (gtk_builder_get_object (builder, "name_dialog_entry")); progress_dialog = GTK_DIALOG (gtk_builder_get_object (builder, "progress_dialog")); progress_dialog_cancel_button = GTK_WIDGET (gtk_builder_get_object (builder, "progress_dialog_cancel_button")); progress_bar = GTK_WIDGET (gtk_builder_get_object (builder, "progress_bar")); progress_label = GTK_WIDGET (gtk_builder_get_object (builder, "progress_label")); rx_sysex_button = GTK_WIDGET (gtk_builder_get_object (builder, "rx_sysex_button")); tx_sysex_button = GTK_WIDGET (gtk_builder_get_object (builder, "tx_sysex_button")); os_upgrade_button = GTK_WIDGET (gtk_builder_get_object (builder, "os_upgrade_button")); about_button = GTK_WIDGET (gtk_builder_get_object (builder, "about_button")); hostname_label = GTK_WIDGET (gtk_builder_get_object (builder, "hostname_label")); remote_box = GTK_WIDGET (gtk_builder_get_object (builder, "remote_box")); local_audio_box = GTK_WIDGET (gtk_builder_get_object (builder, "local_audio_box")); waveform_draw_area = GTK_WIDGET (gtk_builder_get_object (builder, "waveform_draw_area")); play_button = GTK_WIDGET (gtk_builder_get_object (builder, "play_button")); stop_button = GTK_WIDGET (gtk_builder_get_object (builder, "stop_button")); loop_button = GTK_WIDGET (gtk_builder_get_object (builder, "loop_button")); autoplay_switch = GTK_WIDGET (gtk_builder_get_object (builder, "autoplay_switch")); volume_button = GTK_WIDGET (gtk_builder_get_object (builder, "volume_button")); status_bar = GTK_STATUSBAR (gtk_builder_get_object (builder, "status_bar")); g_signal_connect (main_window, "delete-event", G_CALLBACK (elektroid_delete_window), NULL); g_signal_connect (progress_dialog_cancel_button, "clicked", G_CALLBACK (elektroid_progress_dialog_end), NULL); g_signal_connect (progress_dialog, "response", G_CALLBACK (elektroid_cancel_running_sysex), NULL); g_signal_connect (rx_sysex_button, "clicked", G_CALLBACK (elektroid_rx_sysex), NULL); g_signal_connect (tx_sysex_button, "clicked", G_CALLBACK (elektroid_tx_sysex), NULL); g_signal_connect (os_upgrade_button, "clicked", G_CALLBACK (elektroid_upgrade_os), NULL); g_signal_connect (about_button, "clicked", G_CALLBACK (elektroid_show_about), NULL); g_signal_connect (name_dialog_accept_button, "clicked", G_CALLBACK (elektroid_accept_name), NULL); g_signal_connect (name_dialog_cancel_button, "clicked", G_CALLBACK (elektroid_cancel_name), NULL); g_signal_connect (name_dialog_entry, "changed", G_CALLBACK (elektroid_name_dialog_entry_changed), name_dialog_accept_button); g_signal_connect (waveform_draw_area, "draw", G_CALLBACK (elektroid_draw_waveform), NULL); g_signal_connect (play_button, "clicked", G_CALLBACK (elektroid_play_clicked), NULL); g_signal_connect (stop_button, "clicked", G_CALLBACK (elektroid_stop_clicked), NULL); g_signal_connect (loop_button, "clicked", G_CALLBACK (elektroid_loop_clicked), NULL); g_signal_connect (autoplay_switch, "state-set", G_CALLBACK (elektroid_autoplay_clicked), NULL); g_signal_connect (volume_button, "value_changed", G_CALLBACK (elektroid_set_volume), NULL); download_menuitem = GTK_WIDGET (gtk_builder_get_object (builder, "download_menuitem")); remote_rename_menuitem = GTK_WIDGET (gtk_builder_get_object (builder, "remote_rename_menuitem")); remote_delete_menuitem = GTK_WIDGET (gtk_builder_get_object (builder, "remote_delete_menuitem")); g_signal_connect (download_menuitem, "activate", G_CALLBACK (elektroid_add_download_tasks), NULL); g_signal_connect (remote_rename_menuitem, "activate", G_CALLBACK (elektroid_rename_item), &remote_browser); g_signal_connect (remote_delete_menuitem, "activate", G_CALLBACK (elektroid_delete_files), &remote_browser); upload_menuitem = GTK_WIDGET (gtk_builder_get_object (builder, "upload_menuitem")); local_play_menuitem = GTK_WIDGET (gtk_builder_get_object (builder, "local_play_menuitem")); local_open_menuitem = GTK_WIDGET (gtk_builder_get_object (builder, "local_open_menuitem")); local_show_menuitem = GTK_WIDGET (gtk_builder_get_object (builder, "local_show_menuitem")); local_rename_menuitem = GTK_WIDGET (gtk_builder_get_object (builder, "local_rename_menuitem")); local_delete_menuitem = GTK_WIDGET (gtk_builder_get_object (builder, "local_delete_menuitem")); g_signal_connect (upload_menuitem, "activate", G_CALLBACK (elektroid_add_upload_tasks), NULL); g_signal_connect (local_play_menuitem, "activate", G_CALLBACK (elektroid_play_clicked), NULL); g_signal_connect (local_open_menuitem, "activate", G_CALLBACK (elektroid_open_clicked), NULL); g_signal_connect (local_show_menuitem, "activate", G_CALLBACK (elektroid_show_clicked), NULL); g_signal_connect (local_rename_menuitem, "activate", G_CALLBACK (elektroid_rename_item), &local_browser); g_signal_connect (local_delete_menuitem, "activate", G_CALLBACK (elektroid_delete_files), &local_browser); remote_browser = (struct browser) { .view = GTK_TREE_VIEW (gtk_builder_get_object (builder, "remote_tree_view")), .up_button = GTK_WIDGET (gtk_builder_get_object (builder, "remote_up_button")), .add_dir_button = GTK_WIDGET (gtk_builder_get_object (builder, "remote_add_dir_button")), .refresh_button = GTK_WIDGET (gtk_builder_get_object (builder, "remote_refresh_button")), .dir_entry = GTK_ENTRY (gtk_builder_get_object (builder, "remote_dir_entry")), .menu = GTK_MENU (gtk_builder_get_object (builder, "remote_menu")), .dir = malloc (PATH_MAX), .check_selection = elektroid_remote_check_selection, .file_icon = NULL, .fs_ops = connector_get_fs_operations (-1), .data = &connector, .notify_dir_change = NULL, .check_callback = elektroid_check_connector }; remote_tree_view_index_column = GTK_TREE_VIEW_COLUMN (gtk_builder_get_object (builder, "remote_tree_view_index_column")); g_signal_connect (gtk_tree_view_get_selection (remote_browser.view), "changed", G_CALLBACK (browser_selection_changed), &remote_browser); g_signal_connect (remote_browser.view, "row-activated", G_CALLBACK (browser_item_activated), &remote_browser); g_signal_connect (remote_browser.up_button, "clicked", G_CALLBACK (browser_go_up), &remote_browser); g_signal_connect (remote_browser.add_dir_button, "clicked", G_CALLBACK (elektroid_add_dir), &remote_browser); g_signal_connect (remote_browser.refresh_button, "clicked", G_CALLBACK (browser_refresh), &remote_browser); g_signal_connect (remote_browser.view, "button-press-event", G_CALLBACK (elektroid_button_press), &remote_browser); g_signal_connect (remote_browser.view, "button-release-event", G_CALLBACK (elektroid_button_release), &remote_browser); g_signal_connect (remote_browser.view, "key-press-event", G_CALLBACK (elektroid_remote_key_press), &remote_browser); g_signal_connect (remote_browser.view, "drag-begin", G_CALLBACK (elektroid_drag_begin), &remote_browser); g_signal_connect (remote_browser.view, "drag-end", G_CALLBACK (elektroid_drag_end), &remote_browser); g_signal_connect (remote_browser.view, "drag-data-get", G_CALLBACK (elektroid_dnd_get), &remote_browser); g_signal_connect (remote_browser.view, "drag-data-received", G_CALLBACK (elektroid_dnd_received), &remote_browser); g_signal_connect (remote_browser.view, "drag-motion", G_CALLBACK (elektroid_drag_motion_list), &remote_browser); g_signal_connect (remote_browser.view, "drag-leave", G_CALLBACK (elektroid_drag_leave_list), &remote_browser); g_signal_connect (remote_browser.up_button, "drag-motion", G_CALLBACK (elektroid_drag_motion_up), &remote_browser); g_signal_connect (remote_browser.up_button, "drag-leave", G_CALLBACK (elektroid_drag_leave_up), &remote_browser); gtk_drag_source_set ((GtkWidget *) remote_browser.view, GDK_BUTTON1_MASK, TARGET_ENTRIES_REMOTE_SRC, TARGET_ENTRIES_REMOTE_SRC_N, GDK_ACTION_COPY); gtk_drag_dest_set ((GtkWidget *) remote_browser.view, GTK_DEST_DEFAULT_ALL, TARGET_ENTRIES_REMOTE_DST, TARGET_ENTRIES_REMOTE_DST_N, GDK_ACTION_COPY); gtk_drag_dest_set ((GtkWidget *) remote_browser.up_button, GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_HIGHLIGHT, TARGET_ENTRIES_UP_BUTTON_DST, TARGET_ENTRIES_UP_BUTTON_DST_N, GDK_ACTION_COPY); local_browser = (struct browser) { .view = GTK_TREE_VIEW (gtk_builder_get_object (builder, "local_tree_view")), .up_button = GTK_WIDGET (gtk_builder_get_object (builder, "local_up_button")), .add_dir_button = GTK_WIDGET (gtk_builder_get_object (builder, "local_add_dir_button")), .refresh_button = GTK_WIDGET (gtk_builder_get_object (builder, "local_refresh_button")), .dir_entry = GTK_ENTRY (gtk_builder_get_object (builder, "local_dir_entry")), .menu = GTK_MENU (gtk_builder_get_object (builder, "local_menu")), .dir = malloc (PATH_MAX), .check_selection = elektroid_local_check_selection, .file_icon = elektroid_get_inventory_icon_for_fs (FS_SAMPLES), .extensions = NULL, .fs_ops = &FS_LOCAL_OPERATIONS, .data = NULL, .notify_dir_change = elektroid_notify_local_dir_change, .check_callback = NULL }; g_signal_connect (gtk_tree_view_get_selection (local_browser.view), "changed", G_CALLBACK (browser_selection_changed), &local_browser); g_signal_connect (local_browser.view, "row-activated", G_CALLBACK (browser_item_activated), &local_browser); g_signal_connect (local_browser.up_button, "clicked", G_CALLBACK (browser_go_up), &local_browser); g_signal_connect (local_browser.add_dir_button, "clicked", G_CALLBACK (elektroid_add_dir), &local_browser); g_signal_connect (local_browser.refresh_button, "clicked", G_CALLBACK (browser_refresh), &local_browser); g_signal_connect (local_browser.view, "button-press-event", G_CALLBACK (elektroid_button_press), &local_browser); g_signal_connect (local_browser.view, "button-release-event", G_CALLBACK (elektroid_button_release), &local_browser); g_signal_connect (local_browser.view, "key-press-event", G_CALLBACK (elektroid_local_key_press), &local_browser); g_signal_connect (local_browser.view, "drag-begin", G_CALLBACK (elektroid_drag_begin), &local_browser); g_signal_connect (local_browser.view, "drag-end", G_CALLBACK (elektroid_drag_end), &local_browser); g_signal_connect (local_browser.view, "drag-data-get", G_CALLBACK (elektroid_dnd_get), &local_browser); g_signal_connect (local_browser.view, "drag-data-received", G_CALLBACK (elektroid_dnd_received), &local_browser); g_signal_connect (local_browser.view, "drag-motion", G_CALLBACK (elektroid_drag_motion_list), &local_browser); g_signal_connect (local_browser.view, "drag-leave", G_CALLBACK (elektroid_drag_leave_list), &local_browser); g_signal_connect (local_browser.up_button, "drag-motion", G_CALLBACK (elektroid_drag_motion_up), &local_browser); g_signal_connect (local_browser.up_button, "drag-leave", G_CALLBACK (elektroid_drag_leave_up), &local_browser); sortable = GTK_TREE_SORTABLE (gtk_tree_view_get_model (local_browser.view)); gtk_tree_sortable_set_sort_func (sortable, BROWSER_LIST_STORE_NAME_FIELD, browser_sort_samples, NULL, NULL); gtk_tree_sortable_set_sort_column_id (sortable, BROWSER_LIST_STORE_NAME_FIELD, GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID); gtk_drag_source_set ((GtkWidget *) local_browser.view, GDK_BUTTON1_MASK, TARGET_ENTRIES_LOCAL_SRC, TARGET_ENTRIES_LOCAL_SRC_N, GDK_ACTION_MOVE); gtk_drag_dest_set ((GtkWidget *) local_browser.view, GTK_DEST_DEFAULT_ALL, TARGET_ENTRIES_LOCAL_DST, TARGET_ENTRIES_LOCAL_DST_N, GDK_ACTION_COPY); gtk_drag_dest_set ((GtkWidget *) local_browser.up_button, GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_HIGHLIGHT, TARGET_ENTRIES_UP_BUTTON_DST, TARGET_ENTRIES_UP_BUTTON_DST_N, GDK_ACTION_COPY); audio_init (&audio, elektroid_set_volume_callback, elektroid_redraw_sample); devices_list_store = GTK_LIST_STORE (gtk_builder_get_object (builder, "devices_list_store")); devices_combo = GTK_COMBO_BOX (gtk_builder_get_object (builder, "devices_combo")); refresh_devices_button = GTK_WIDGET (gtk_builder_get_object (builder, "refresh_devices_button")); g_signal_connect (devices_combo, "changed", G_CALLBACK (elektroid_set_device), NULL); g_signal_connect (refresh_devices_button, "clicked", G_CALLBACK (browser_refresh_devices), NULL); task_list_store = GTK_LIST_STORE (gtk_builder_get_object (builder, "task_list_store")); task_tree_view = GTK_WIDGET (gtk_builder_get_object (builder, "task_tree_view")); cancel_task_button = GTK_WIDGET (gtk_builder_get_object (builder, "cancel_task_button")); remove_tasks_button = GTK_WIDGET (gtk_builder_get_object (builder, "remove_tasks_button")); clear_tasks_button = GTK_WIDGET (gtk_builder_get_object (builder, "clear_tasks_button")); g_signal_connect (cancel_task_button, "clicked", G_CALLBACK (elektroid_cancel_all_tasks), NULL); g_signal_connect (remove_tasks_button, "clicked", G_CALLBACK (elektroid_remove_queued_tasks), NULL); g_signal_connect (clear_tasks_button, "clicked", G_CALLBACK (elektroid_clear_finished_tasks), NULL); gtk_statusbar_push (status_bar, 0, _("Not connected")); elektroid_loop_clicked (loop_button, NULL); gtk_switch_set_active (GTK_SWITCH (autoplay_switch), preferences.autoplay); fs_list_store = GTK_LIST_STORE (gtk_builder_get_object (builder, "fs_list_store")); fs_combo = GTK_COMBO_BOX (gtk_builder_get_object (builder, "fs_combo")); g_signal_connect (fs_combo, "changed", G_CALLBACK (elektroid_set_fs), NULL); gtk_widget_set_sensitive (remote_box, FALSE); gtk_widget_set_sensitive (rx_sysex_button, FALSE); gtk_widget_set_sensitive (tx_sysex_button, FALSE); gtk_widget_set_sensitive (os_upgrade_button, FALSE); local_browser.dir = preferences.local_dir; elektroid_load_devices (TRUE); //This triggers a local browser reload due to the extensions and icons selected for the fs gethostname (hostname, LABEL_MAX); gtk_label_set_text (GTK_LABEL (hostname_label), hostname); debug_print (1, "Creating notifier thread...\n"); notifier_init (¬ifier, &local_browser); notifier_set_dir (¬ifier, preferences.local_dir); notifier_thread = g_thread_new ("notifier_thread", notifier_run, ¬ifier); gtk_widget_show (main_window); gtk_main (); free (remote_browser.dir); if (connector_check (&connector)) { connector_destroy (&connector); } audio_destroy (&audio); return EXIT_SUCCESS; } static gboolean elektroid_end (gpointer data) { elektroid_quit (); return FALSE; } static void elektroid_print_help (gchar * executable_path) { gchar *exec_name; const struct option *option; fprintf (stderr, "%s\n", PACKAGE_STRING); exec_name = basename (executable_path); fprintf (stderr, "Usage: %s [options]\n", exec_name); fprintf (stderr, "Options:\n"); option = ELEKTROID_OPTIONS; while (option->name) { fprintf (stderr, " --%s, -%c", option->name, option->val); if (option->has_arg) { fprintf (stderr, " value"); } fprintf (stderr, "\n"); option++; } } int main (int argc, char *argv[]) { gint opt, ret; gchar *local_dir = NULL; gint vflg = 0, dflg = 0, errflg = 0; int long_index = 0; g_unix_signal_add (SIGHUP, elektroid_end, NULL); g_unix_signal_add (SIGINT, elektroid_end, NULL); g_unix_signal_add (SIGTERM, elektroid_end, NULL); setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); while ((opt = getopt_long (argc, argv, "l:vh", ELEKTROID_OPTIONS, &long_index)) != -1) { switch (opt) { case 'l': local_dir = optarg; dflg++; break; case 'v': vflg++; break; case 'h': elektroid_print_help (argv[0]); exit (EXIT_SUCCESS); case '?': errflg++; } } if (dflg > 1) { errflg++; } if (vflg) { debug_level = vflg; } if (errflg > 0) { elektroid_print_help (argv[0]); exit (EXIT_FAILURE); } preferences_load (&preferences); if (local_dir) { g_free (preferences.local_dir); preferences.local_dir = get_local_startup_path (local_dir); } ret = elektroid_run (argc, argv); preferences_save (&preferences); preferences_free (&preferences); return ret; } elektroid-2.0/src/local.c000066400000000000000000000117331416764361200154010ustar00rootroot00000000000000/* * local.c * Copyright (C) 2021 David García Goñi * * This file is part of Elektroid. * * Elektroid is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Elektroid is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Elektroid. If not, see . */ #include #include #include #include #include #include #include "local.h" static gint local_mkdir (const gchar *, void *); static gint local_delete (const gchar *, void *); static gint local_rename (const gchar *, const gchar *, void *); static gint local_read_dir (struct item_iterator *, const gchar *, void *); static gint local_copy_iterator (struct item_iterator *, struct item_iterator *, gboolean); const struct fs_operations FS_LOCAL_OPERATIONS = { .fs = 0, .readdir = local_read_dir, .mkdir = local_mkdir, .delete = local_delete, .rename = local_rename, .move = local_rename, .copy = NULL, .clear = NULL, .swap = NULL, .download = NULL, .upload = NULL, .getid = get_item_name, .extension = NULL }; gint local_mkdir (const gchar * name, void *data) { DIR *dir; gint res = 0; gchar *dup; gchar *parent; dup = strdup (name); parent = dirname (dup); dir = opendir (parent); if (dir) { closedir (dir); } else { res = local_mkdir (parent, data); if (res) { goto cleanup; } } if (mkdir (name, 0755) == 0 || errno == EEXIST) { res = 0; } else { error_print ("Error while creating dir %s\n", name); res = -errno; } cleanup: g_free (dup); return res; } static gint local_delete (const gchar * path, void *data) { DIR *dir; gchar *new_path; struct dirent *dirent; if ((dir = opendir (path))) { debug_print (1, "Deleting local %s dir...\n", path); while ((dirent = readdir (dir)) != NULL) { if (strcmp (dirent->d_name, ".") == 0 || strcmp (dirent->d_name, "..") == 0) { continue; } new_path = chain_path (path, dirent->d_name); local_delete (new_path, data); free (new_path); } closedir (dir); return rmdir (path); } else { debug_print (1, "Deleting local %s file...\n", path); return unlink (path); } } static gint local_rename (const gchar * old, const gchar * new, void *data) { debug_print (1, "Renaming locally from %s to %s...\n", old, new); return rename (old, new); } static void local_free_iterator_data (void *iter_data) { struct local_iterator_data *data = iter_data; closedir (data->dir); g_free (data->path); g_free (data); } static guint local_next_dentry (struct item_iterator *iter) { gchar *full_path; struct dirent *dirent; gboolean found; struct stat st; mode_t mode; struct local_iterator_data *data = iter->data; if (iter->item.name != NULL) { g_free (iter->item.name); } while ((dirent = readdir (data->dir)) != NULL) { if (dirent->d_name[0] == '.') { continue; } full_path = chain_path (data->path, dirent->d_name); if (stat (full_path, &st)) { free (full_path); continue; } mode = st.st_mode & S_IFMT; switch (mode) { case S_IFREG: case S_IFDIR: iter->item.name = strdup (dirent->d_name); iter->item.type = mode == S_IFREG ? ELEKTROID_FILE : ELEKTROID_DIR; iter->item.size = st.st_size; found = TRUE; break; default: error_print ("stat mode neither file nor directory for %s\n", full_path); found = FALSE; } free (full_path); if (found) { return 0; } } return -ENOENT; } static gint local_init_iterator (struct item_iterator *iter, const gchar * path, gboolean cached) { DIR *dir; struct local_iterator_data *data; if (!(dir = opendir (path))) { return -errno; } data = malloc (sizeof (struct local_iterator_data)); if (!data) { closedir (dir); return -errno; } data->dir = dir; data->path = strdup (path); iter->data = data; iter->next = local_next_dentry; iter->free = local_free_iterator_data; iter->copy = local_copy_iterator; iter->item.name = NULL; return 0; } static gint local_read_dir (struct item_iterator *iter, const gchar * path, void *userdata) { return local_init_iterator (iter, path, FALSE); } static gint local_copy_iterator (struct item_iterator *dst, struct item_iterator *src, gboolean cached) { struct local_iterator_data *data = src->data; return local_init_iterator (dst, data->path, cached); } elektroid-2.0/src/local.h000066400000000000000000000016511416764361200154040ustar00rootroot00000000000000/* * local.h * Copyright (C) 2021 David García Goñi * * This file is part of Elektroid. * * Elektroid is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Elektroid is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Elektroid. If not, see . */ #include #include "utils.h" struct local_iterator_data { DIR *dir; gchar *path; }; extern const struct fs_operations FS_LOCAL_OPERATIONS; elektroid-2.0/src/notifier.c000066400000000000000000000061431416764361200161250ustar00rootroot00000000000000/* * notifier.c * Copyright (C) 2021 David García Goñi * * This file is part of Elektroid. * * Elektroid is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Elektroid is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Elektroid. If not, see . */ #include #include #include #include #include "notifier.h" #include "utils.h" void notifier_init (struct notifier *notifier, struct browser *browser) { notifier->fd = inotify_init (); notifier->wd = -1; notifier->event_size = sizeof (struct inotify_event) + PATH_MAX; notifier->event = malloc (notifier->event_size); notifier->running = 1; notifier->browser = browser; } void notifier_set_dir (struct notifier *notifier, gchar * path) { debug_print (1, "Changing notifier path to %s...\n", path); if (notifier->fd < 0) { return; } if (notifier->wd >= 0) { inotify_rm_watch (notifier->fd, notifier->wd); } notifier->wd = inotify_add_watch (notifier->fd, path, IN_CREATE | IN_DELETE | IN_MOVED_FROM | IN_DELETE_SELF | IN_MOVE_SELF | IN_MOVED_TO); } void notifier_close (struct notifier *notifier) { if (notifier->fd < 0) { return; } if (notifier->wd >= 0) { inotify_rm_watch (notifier->fd, notifier->wd); } close (notifier->fd); } void notifier_free (struct notifier *notifier) { free (notifier->event); } static gboolean notifier_go_up (gpointer data) { struct browser *browser = data; browser_go_up (NULL, browser); return FALSE; } gpointer notifier_run (gpointer data) { ssize_t size; struct notifier *notifier = data; while (notifier->running) { size = read (notifier->fd, notifier->event, notifier->event_size); if (size == 0 || size == EBADF) { break; } if (size < 0) { debug_print (2, "Error while reading notifier: %s\n", g_strerror (errno)); continue; } if (notifier->event->mask & IN_CREATE || notifier->event->mask & IN_DELETE || notifier->event->mask & IN_MOVED_FROM || notifier->event->mask & IN_MOVED_TO) { debug_print (1, "Reloading local dir...\n"); g_idle_add (browser_load_dir, notifier->browser); } else if (notifier->event->mask & IN_DELETE_SELF || notifier->event->mask & IN_MOVE_SELF || notifier->event->mask & IN_MOVED_TO) { debug_print (1, "Loading local parent dir...\n"); g_idle_add (notifier_go_up, notifier->browser); } else { if (!(notifier->event->mask & IN_IGNORED)) { error_print ("Unexpected event: %d\n", notifier->event->mask); } } } return NULL; } elektroid-2.0/src/notifier.h000066400000000000000000000023071416764361200161300ustar00rootroot00000000000000/* * notifier.h * Copyright (C) 2021 David García Goñi * * This file is part of Elektroid. * * Elektroid is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Elektroid is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Elektroid. If not, see . */ #include #include #include "browser.h" struct notifier { gint fd; gint wd; size_t event_size; struct inotify_event *event; gint running; struct browser *browser; }; void notifier_init (struct notifier *, struct browser *); void notifier_set_dir (struct notifier *, gchar *); void notifier_close (struct notifier *); void notifier_free (struct notifier *); gpointer notifier_run (gpointer); elektroid-2.0/src/package.c000066400000000000000000000523501416764361200157020ustar00rootroot00000000000000/* * package.c * Copyright (C) 2021 David García Goñi * * This file is part of Elektroid. * * Elektroid is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Elektroid is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Elektroid. If not, see . */ #include #include #include #include "package.h" #include "utils.h" #include "sample.h" #define PKG_TAG_FORMAT_VERSION "FormatVersion" #define PKG_TAG_PRODUCT_TYPE "ProductType" #define PKG_TAG_PAYLOAD "Payload" #define PKG_TAG_FILE_TYPE "FileType" #define PKG_TAG_FIRMWARE_VERSION "FirmwareVersion" #define PKG_TAG_SAMPLES "Samples" #define PKG_TAG_FILE_NAME "FileName" #define PKG_TAG_FILE_SIZE "FileSize" #define PKG_TAG_HASH "Hash" #define PKG_VAL_FILE_TYPE_PRJ "Project" #define PKG_VAL_FILE_TYPE_SND "Sound" #define PKG_VAL_FILE_TYPE_UNK "Unknown" #define MAN_TAG_SAMPLE_REFS "sample_references" #define MAN_TAG_HASH "hash" #define MAN_TAG_SIZE "size" #define MAX_PACKAGE_LEN (64 * 1024 * 1024) #define MAX_MANIFEST_LEN (128 * 1024) #define MANIFEST_FILENAME "manifest.json" static gint package_add_resource (struct package *pkg, struct package_resource *pkg_resource, gboolean new) { zip_source_t *sample_source; zip_int64_t index; zip_error_t zerror; debug_print (1, "Adding file %s to zip (%d B)...\n", pkg_resource->path, pkg_resource->data->len); sample_source = zip_source_buffer_create (pkg_resource->data->data, pkg_resource->data->len, 0, &zerror); if (!sample_source) { error_print ("Error while creating file source: %s\n", zip_error_strerror (&zerror)); zip_error_fini (&zerror); return -1; } index = zip_file_add (pkg->zip, pkg_resource->path, sample_source, ZIP_FL_OVERWRITE | ZIP_FL_ENC_UTF_8); if (index < 0) { error_print ("Error while adding file: %s\n", zip_error_strerror (zip_get_error (pkg->zip))); zip_source_free (sample_source); return -1; } if (new) { pkg->resources = g_list_append (pkg->resources, pkg_resource); } return 0; } gint package_begin (struct package *pkg, gchar * name, const gchar * fw_version, const struct connector_device_desc *device_desc, enum package_type type) { zip_error_t zerror; pkg->resources = NULL; pkg->buff = g_malloc (MAX_PACKAGE_LEN); pkg->name = name; pkg->fw_version = strdup (fw_version); pkg->device_desc = device_desc; pkg->type = type; debug_print (1, "Creating zip buffer...\n"); zip_error_init (&zerror); pkg->zip_source = zip_source_buffer_create (pkg->buff, MAX_PACKAGE_LEN, 0, &zerror); if (!pkg->zip_source) { error_print ("Error while creating zip source: %s\n", zip_error_strerror (&zerror)); zip_error_fini (&zerror); g_free (pkg->buff); return -1; } pkg->zip = zip_open_from_source (pkg->zip_source, ZIP_TRUNCATE, &zerror); if (!pkg->zip) { error_print ("Error while creating in memory zip: %s\n", zip_error_strerror (&zerror)); zip_error_fini (&zerror); zip_source_free (pkg->zip_source); g_free (pkg->buff); return -1; } zip_source_keep (pkg->zip_source); pkg->manifest = g_malloc (sizeof (struct package_resource)); pkg->manifest->type = PKG_RES_TYPE_MANIFEST; pkg->manifest->data = g_byte_array_sized_new (MAX_MANIFEST_LEN); //We need this because we can not resize later. pkg->manifest->path = strdup (MANIFEST_FILENAME); package_add_resource (pkg, pkg->manifest, TRUE); return 0; } static gint package_add_manifest (struct package *pkg) { JsonBuilder *builder; JsonGenerator *gen; JsonNode *root; gchar *json; gint len; gchar *val = g_malloc (LABEL_MAX); GList *resource; gboolean samples_found = FALSE; struct package_resource *pkg_resource; builder = json_builder_new (); json_builder_begin_object (builder); json_builder_set_member_name (builder, PKG_TAG_FORMAT_VERSION); json_builder_add_string_value (builder, "1.0"); json_builder_set_member_name (builder, PKG_TAG_PRODUCT_TYPE); json_builder_begin_array (builder); snprintf (val, LABEL_MAX, "%d", pkg->device_desc->id); json_builder_add_string_value (builder, val); json_builder_end_array (builder); json_builder_set_member_name (builder, PKG_TAG_PAYLOAD); json_builder_add_string_value (builder, pkg->name); json_builder_set_member_name (builder, PKG_TAG_FILE_TYPE); json_builder_add_string_value (builder, pkg->type & PKG_FILE_TYPE_SOUND ? PKG_VAL_FILE_TYPE_SND : pkg->type & PKG_FILE_TYPE_PROJECT ? PKG_VAL_FILE_TYPE_PRJ : PKG_VAL_FILE_TYPE_UNK); if (pkg->type != PKG_FILE_TYPE_PRESET) { json_builder_set_member_name (builder, PKG_TAG_FIRMWARE_VERSION); json_builder_add_string_value (builder, pkg->fw_version); } if (pkg->device_desc->fss & FS_SAMPLES) { for (resource = pkg->resources; resource; resource = resource->next) { pkg_resource = resource->data; if (pkg_resource->type == PKG_RES_TYPE_SAMPLE) { samples_found = TRUE; break; } } } if (samples_found) { json_builder_set_member_name (builder, PKG_TAG_SAMPLES); json_builder_begin_array (builder); for (resource = pkg->resources; resource; resource = resource->next) { pkg_resource = resource->data; if (pkg_resource->type == PKG_RES_TYPE_SAMPLE) { json_builder_begin_object (builder); json_builder_set_member_name (builder, PKG_TAG_FILE_NAME); json_builder_add_string_value (builder, pkg_resource->path); json_builder_set_member_name (builder, PKG_TAG_FILE_SIZE); json_builder_add_int_value (builder, pkg_resource->size); json_builder_set_member_name (builder, PKG_TAG_HASH); snprintf (val, LABEL_MAX, "%d", pkg_resource->hash); json_builder_add_string_value (builder, val); json_builder_end_object (builder); } } json_builder_end_array (builder); } json_builder_end_object (builder); gen = json_generator_new (); g_object_set (gen, "pretty", TRUE, NULL); root = json_builder_get_root (builder); json_generator_set_root (gen, root); json = json_generator_to_data (gen, NULL); len = strlen (json); memcpy (pkg->manifest->data->data, json, len); pkg->manifest->data->len = len; package_add_resource (pkg, pkg->manifest, FALSE); g_free (json); json_node_free (root); g_object_unref (gen); g_object_unref (builder); g_free (val); return 0; } gint package_end (struct package *pkg, GByteArray * out) { int ret = 0; zip_stat_t zstat; ret = package_add_manifest (pkg); if (ret) { error_print ("Error while formatting %s\n", MANIFEST_FILENAME); return ret; } debug_print (1, "Writing zip to buffer...\n"); if (zip_close (pkg->zip)) { error_print ("Error while creating in memory zip: %s\n", zip_error_strerror (zip_get_error (pkg->zip))); return -1; } zip_source_stat (pkg->zip_source, &zstat); debug_print (1, "%ld B written to package\n", zstat.comp_size); zip_source_open (pkg->zip_source); g_byte_array_set_size (out, zstat.comp_size); zip_source_read (pkg->zip_source, out->data, zstat.comp_size); zip_source_close (pkg->zip_source); return 0; } void package_free_package_resource (gpointer data) { struct package_resource *pkg_resource = data; g_byte_array_free (pkg_resource->data, TRUE); g_free (pkg_resource); } void package_destroy (struct package *pkg) { zip_source_free (pkg->zip_source); g_free (pkg->buff); g_free (pkg->name); g_free (pkg->fw_version); g_list_free_full (pkg->resources, package_free_package_resource); } gint package_open (struct package *pkg, GByteArray * data, const struct connector_device_desc *device_desc) { gint ret; zip_error_t zerror; zip_file_t *manifest_file; zip_stat_t zstat; debug_print (1, "Opening zip stream...\n"); zip_error_init (&zerror); pkg->zip_source = zip_source_buffer_create (data->data, data->len, 0, &zerror); if (!pkg->zip_source) { error_print ("Error while creating zip source: %s\n", zip_error_strerror (&zerror)); zip_error_fini (&zerror); return -1; } pkg->zip = zip_open_from_source (pkg->zip_source, ZIP_RDONLY, &zerror); if (!pkg->zip) { error_print ("Error while creating in memory zip: %s\n", zip_error_strerror (&zerror)); zip_error_fini (&zerror); zip_source_free (pkg->zip_source); return -1; } ret = zip_stat (pkg->zip, MANIFEST_FILENAME, ZIP_FL_ENC_STRICT, &zstat); if (ret) { error_print ("Error while loading '%s': %s\n", MANIFEST_FILENAME, zip_error_strerror (&zerror)); zip_error_fini (&zerror); zip_source_free (pkg->zip_source); zip_close (pkg->zip); return -1; } pkg->manifest = g_malloc (sizeof (struct package_resource)); pkg->manifest->type = PKG_RES_TYPE_MANIFEST; pkg->manifest->data = g_byte_array_sized_new (zstat.size); pkg->manifest->path = strdup (MANIFEST_FILENAME); manifest_file = zip_fopen (pkg->zip, MANIFEST_FILENAME, 0); zip_fread (manifest_file, pkg->manifest->data->data, zstat.size); pkg->manifest->data->len = zstat.size; zip_fclose (manifest_file); pkg->resources = NULL; pkg->resources = g_list_append (pkg->resources, pkg->manifest); pkg->buff = NULL; pkg->name = NULL; pkg->fw_version = NULL; pkg->device_desc = device_desc; return ret; } void package_close (struct package *pkg) { zip_source_close (pkg->zip_source); package_destroy (pkg); } gint package_receive_pkg_resources (struct package *pkg, const gchar * payload_path, struct job_control *control, struct connector *connector, fs_remote_file_op download_data, fs_remote_file_op download_sample) { gint ret, i, elements; JsonParser *parser; JsonReader *reader; gint64 hash, size; GError *error; gchar *sample_path, *metadata_path; struct package_resource *pkg_resource; GByteArray *wave, *payload, *metadata, *sample; metadata_path = chain_path (payload_path, ".metadata"); debug_print (1, "Getting metadata from %s...\n", metadata_path); metadata = g_byte_array_new (); control->parts = 130; // 128 sample slots, metadata and main. control->part = 0; set_job_control_progress (control, 0.0); ret = download_data (metadata_path, metadata, control, connector); if (ret) { debug_print (1, "Metadata file not available\n"); control->parts = 1; goto get_payload; } control->part++; parser = json_parser_new (); if (!json_parser_load_from_data (parser, (gchar *) metadata->data, metadata->len, &error)) { error_print ("Unable to parse stream: %s. Continuing...", error->message); g_clear_error (&error); control->parts = 2; goto get_payload; } reader = json_reader_new (json_parser_get_root (parser)); if (!reader) { error_print ("Unable to read from parser. Continuing..."); g_object_unref (reader); control->parts = 2; goto get_payload; } if (!json_reader_read_member (reader, MAN_TAG_SAMPLE_REFS)) { debug_print (1, "Member '%s' not found\n", MAN_TAG_SAMPLE_REFS); control->parts = 2; goto get_payload; } if (!json_reader_is_array (reader)) { error_print ("Member '%s' is not an array. Continuing...\n", MAN_TAG_SAMPLE_REFS); control->parts = 2; goto cleanup_reader; } elements = json_reader_count_elements (reader); if (!elements) { debug_print (1, "No samples found\n"); control->parts = 2; goto cleanup_reader; } sample = g_byte_array_new (); control->parts = 2 + elements; set_job_control_progress (control, 0.0); for (i = 0; i < elements; i++, control->part++) { if (!json_reader_read_element (reader, i)) { error_print ("Cannot read element %d. Continuing...\n", i); continue; } if (!json_reader_read_member (reader, MAN_TAG_HASH)) { error_print ("Cannot read member '%s'. Continuing...\n", MAN_TAG_HASH); continue; } hash = json_reader_get_int_value (reader); json_reader_end_element (reader); if (!json_reader_read_member (reader, MAN_TAG_SIZE)) { error_print ("Cannot read member '%s'. Continuing...\n", MAN_TAG_SIZE); continue; } size = json_reader_get_int_value (reader); json_reader_end_element (reader); json_reader_end_element (reader); sample_path = connector_get_sample_path_from_hash_size (connector, hash, size); if (!sample_path) { debug_print (1, "Sample not found. Skipping...\n"); continue; } debug_print (1, "Hash: %ld; size: %ld; path: %s\n", hash, size, sample_path); debug_print (1, "Getting sample %s...\n", sample_path); g_byte_array_set_size (sample, 0); if (download_sample (sample_path, sample, control, connector)) { g_free (sample_path); error_print ("Error while downloading sample. Continuing...\n"); continue; } wave = g_byte_array_new (); if (sample_wave (sample, wave, control)) { error_print ("Error while converting sample to wave file. Continuing...\n"); g_byte_array_free (wave, TRUE); g_free (sample_path); continue; } g_free (control->data); control->data = NULL; pkg_resource = g_malloc (sizeof (struct package_resource)); pkg_resource->type = PKG_RES_TYPE_SAMPLE; pkg_resource->data = wave; pkg_resource->hash = hash; pkg_resource->size = size; pkg_resource->path = g_malloc (PATH_MAX); snprintf (pkg_resource->path, PATH_MAX, "%s%s.wav", PKG_TAG_SAMPLES, sample_path); if (package_add_resource (pkg, pkg_resource, TRUE)) { package_free_package_resource (pkg_resource); error_print ("Error while packaging sample\n"); continue; } } g_byte_array_free (sample, TRUE); cleanup_reader: g_object_unref (reader); g_object_unref (parser); get_payload: g_byte_array_free (metadata, TRUE); debug_print (1, "Getting payload from %s...\n", payload_path); payload = g_byte_array_new (); ret = download_data (payload_path, payload, control, connector); if (ret) { error_print ("Error while downloading payload\n"); ret = -1; } else { pkg_resource = g_malloc (sizeof (struct package_resource)); pkg_resource->type = PKG_RES_TYPE_PAYLOAD; pkg_resource->data = payload; pkg_resource->path = strdup (pkg->name); if (package_add_resource (pkg, pkg_resource, TRUE)) { package_free_package_resource (pkg_resource); ret = -1; } } return ret; } gint package_send_pkg_resources (struct package *pkg, const gchar * payload_path, struct job_control *control, struct connector *connector, fs_remote_file_op upload_data, fs_remote_file_op upload_sample) { gint elements, i, ret = 0; const gchar *file_type, *sample_path; gint64 product_type; JsonParser *parser; JsonReader *reader; GError *error; zip_stat_t zstat; zip_error_t zerror; zip_file_t *zip_file; GByteArray *wave, *raw; struct package_resource *pkg_resource; zip_error_init (&zerror); parser = json_parser_new (); if (!json_parser_load_from_data (parser, (gchar *) pkg->manifest->data->data, pkg->manifest->data->len, &error)) { error_print ("Unable to parse stream: %s", error->message); g_clear_error (&error); ret = -1; goto cleanup_parser; } reader = json_reader_new (json_parser_get_root (parser)); if (!reader) { ret = -1; goto cleanup_parser; } if (!json_reader_read_member (reader, PKG_TAG_PAYLOAD)) { error_print ("No '%s' found\n", PKG_TAG_PAYLOAD); ret = -1; goto cleanup_reader; } pkg->name = strdup (json_reader_get_string_value (reader)); json_reader_end_element (reader); if (zip_stat (pkg->zip, pkg->name, ZIP_FL_ENC_STRICT, &zstat)) { error_print ("Error while loading '%s': %s\n", MANIFEST_FILENAME, zip_error_strerror (&zerror)); zip_error_fini (&zerror); ret = -1; goto cleanup_reader; } pkg_resource = g_malloc (sizeof (struct package_resource)); pkg_resource->type = PKG_RES_TYPE_PAYLOAD; pkg_resource->data = g_byte_array_sized_new (zstat.size); pkg_resource->path = strdup (pkg->name); zip_file = zip_fopen (pkg->zip, pkg->name, 0); zip_fread (zip_file, pkg_resource->data->data, zstat.size); pkg_resource->data->len = zstat.size; zip_fclose (zip_file); pkg->resources = g_list_append (pkg->resources, pkg_resource); control->parts = 129; // 128 sample slots and main. control->part = 0; ret = upload_data (payload_path, pkg_resource->data, control, connector); if (ret) { error_print ("Error while uploading payload to '%s'\n", payload_path); goto cleanup_reader; } control->part++; if (!json_reader_read_member (reader, PKG_TAG_FIRMWARE_VERSION)) { error_print ("No '%s' found\n", PKG_TAG_FIRMWARE_VERSION); ret = -1; goto cleanup_reader; } pkg->fw_version = strdup (json_reader_get_string_value (reader)); json_reader_end_element (reader); if (!json_reader_read_member (reader, PKG_TAG_FILE_TYPE)) { error_print ("No '%s' found\n", PKG_TAG_FILE_TYPE); ret = -1; goto cleanup_reader; } file_type = json_reader_get_string_value (reader); json_reader_end_element (reader); if (strcmp (file_type, PKG_VAL_FILE_TYPE_SND) == 0) { pkg->type = PKG_FILE_TYPE_SOUND; } else if (strcmp (file_type, PKG_VAL_FILE_TYPE_PRJ) == 0) { pkg->type = PKG_FILE_TYPE_PROJECT; } else { pkg->type = PKG_FILE_TYPE_NONE; debug_print (1, "Invalid '%s': %s\n", PKG_TAG_FILE_TYPE, file_type); } if (!json_reader_read_member (reader, PKG_TAG_PRODUCT_TYPE)) { error_print ("No '%s' found\n", PKG_TAG_PRODUCT_TYPE); ret = 0; goto cleanup_reader; } if (!json_reader_is_array (reader)) { error_print ("Member '%s' is not an array\n", PKG_TAG_PRODUCT_TYPE); ret = -1; goto cleanup_reader; } if (!json_reader_count_elements (reader)) { error_print ("No product types found\n"); ret = 0; goto cleanup_reader; } if (!json_reader_read_element (reader, 0)) { ret = -1; goto cleanup_reader; } product_type = atoi (json_reader_get_string_value (reader)); debug_print (1, "ProductType: %ld\n", product_type); if (pkg->device_desc->id != product_type) { debug_print (1, "Incompatible product type. Continuing...\n"); } json_reader_end_element (reader); json_reader_end_element (reader); if (!(pkg->device_desc->fss & FS_SAMPLES)) { ret = 0; goto cleanup_reader; } if (!json_reader_read_member (reader, PKG_TAG_SAMPLES) || !json_reader_is_array (reader)) { debug_print (1, "No samples found\n"); control->parts = 1; // Only payload and it's done. control->part = 0; set_job_control_progress (control, 1.0); goto cleanup_reader; } wave = g_byte_array_sized_new (zstat.size); raw = g_byte_array_sized_new (MAX_PACKAGE_LEN); elements = json_reader_count_elements (reader); control->parts = elements + 1; for (i = 0; i < elements; i++, control->parts++) { json_reader_read_element (reader, i); json_reader_read_member (reader, PKG_TAG_FILE_NAME); sample_path = json_reader_get_string_value (reader); json_reader_end_element (reader); json_reader_end_element (reader); if (zip_stat (pkg->zip, sample_path, ZIP_FL_ENC_STRICT, &zstat)) { error_print ("Error while loading '%s': %s\n", MANIFEST_FILENAME, zip_error_strerror (&zerror)); zip_error_fini (&zerror); ret = -1; continue; } g_byte_array_set_size (wave, zstat.size); zip_file = zip_fopen (pkg->zip, sample_path, 0); zip_fread (zip_file, wave->data, zstat.size); wave->len = zstat.size; zip_fclose (zip_file); raw->len = 0; if (sample_raw (wave, raw, control)) { continue; } pkg_resource = g_malloc (sizeof (struct package_resource)); pkg_resource->type = PKG_RES_TYPE_SAMPLE; pkg_resource->data = g_byte_array_sized_new (raw->len); pkg_resource->data->len = raw->len; memcpy (pkg_resource->data->data, raw->data, raw->len); pkg_resource->path = strdup (sample_path); pkg->resources = g_list_append (pkg->resources, pkg_resource); //We remove the "Samples" at the beggining of the full zip path. ret = upload_sample (&sample_path[7], pkg_resource->data, control, connector); g_free (control->data); control->data = NULL; if (ret) { error_print ("Error while uploading sample to '%s'\n", &sample_path[7]); continue; } } g_byte_array_free (wave, TRUE); g_byte_array_free (raw, TRUE); cleanup_reader: g_object_unref (reader); cleanup_parser: g_object_unref (parser); return ret; } elektroid-2.0/src/package.h000066400000000000000000000041571416764361200157110ustar00rootroot00000000000000/* * package.h * Copyright (C) 2021 David García Goñi * * This file is part of Elektroid. * * Elektroid is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Elektroid is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Elektroid. If not, see . */ #include #include #include "connector.h" enum package_resource_type { PKG_RES_TYPE_NONE, PKG_RES_TYPE_PAYLOAD, PKG_RES_TYPE_MANIFEST, PKG_RES_TYPE_SAMPLE }; struct package_resource { enum package_resource_type type; guint32 hash; guint32 size; gchar *path; GByteArray *data; }; enum package_type { PKG_FILE_TYPE_NONE, PKG_FILE_TYPE_SOUND, PKG_FILE_TYPE_PROJECT, PKG_FILE_TYPE_PRESET, }; struct package { gchar *name; enum package_type type; gchar *fw_version; const struct connector_device_desc *device_desc; gchar *buff; zip_source_t *zip_source; zip_t *zip; GList *resources; struct package_resource *manifest; }; gint package_begin (struct package *, gchar *, const gchar *, const struct connector_device_desc *, enum package_type); gint package_receive_pkg_resources (struct package *, const gchar *, struct job_control *, struct connector *, fs_remote_file_op, fs_remote_file_op); gint package_end (struct package *, GByteArray *); void package_destroy (struct package *); gint package_open (struct package *, GByteArray *, const struct connector_device_desc *); gint package_send_pkg_resources (struct package *, const gchar *, struct job_control *, struct connector *, fs_remote_file_op, fs_remote_file_op); void package_close (struct package *); elektroid-2.0/src/preferences.c000066400000000000000000000071401416764361200166050ustar00rootroot00000000000000/* * preferences.c * Copyright (C) 2019 David García Goñi * * This file is part of Elektroid. * * Elektroid is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Elektroid is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Elektroid. If not, see . */ #include #include #include #include #include "preferences.h" #include "utils.h" #define CONF_DIR "~/.config/elektroid" #define CONF_FILE "/preferences.json" #define MEMBER_AUTOPLAY "autoplay" #define MEMBER_LOCALDIR "localDir" gint preferences_save (struct preferences *preferences) { size_t n; gchar *preferences_path; JsonBuilder *builder; JsonGenerator *gen; JsonNode *root; gchar *json; preferences_path = get_expanded_dir (CONF_DIR); if (g_mkdir_with_parents (preferences_path, S_IFDIR | S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)) { return 1; } n = PATH_MAX - strlen (preferences_path) - 1; strncat (preferences_path, CONF_FILE, n); preferences_path[PATH_MAX - 1] = 0; debug_print (1, "Saving preferences to '%s'...\n", preferences_path); builder = json_builder_new (); json_builder_begin_object (builder); json_builder_set_member_name (builder, MEMBER_AUTOPLAY); json_builder_add_boolean_value (builder, preferences->autoplay); json_builder_set_member_name (builder, MEMBER_LOCALDIR); json_builder_add_string_value (builder, preferences->local_dir); json_builder_end_object (builder); gen = json_generator_new (); root = json_builder_get_root (builder); json_generator_set_root (gen, root); json = json_generator_to_data (gen, NULL); save_file_char (preferences_path, (guint8 *) json, strlen (json)); g_free (json); json_node_free (root); g_object_unref (gen); g_object_unref (builder); g_free (preferences_path); return 0; } gint preferences_load (struct preferences *preferences) { size_t n; GError *error; JsonReader *reader; JsonParser *parser = json_parser_new (); gchar *preferences_file = get_expanded_dir (CONF_DIR CONF_FILE); error = NULL; json_parser_load_from_file (parser, preferences_file, &error); if (error) { error_print ("Error wile loading preferences from `%s': %s\n", CONF_DIR CONF_FILE, error->message); g_error_free (error); g_object_unref (parser); preferences->autoplay = TRUE; preferences->local_dir = get_expanded_dir ("~"); return 0; } reader = json_reader_new (json_parser_get_root (parser)); json_reader_read_member (reader, MEMBER_AUTOPLAY); preferences->autoplay = json_reader_get_boolean_value (reader); json_reader_end_member (reader); json_reader_read_member (reader, MEMBER_LOCALDIR); preferences->local_dir = malloc (PATH_MAX); n = PATH_MAX - 1; strncpy (preferences->local_dir, json_reader_get_string_value (reader), n); preferences->local_dir[PATH_MAX - 1] = 0; json_reader_end_member (reader); g_object_unref (reader); g_object_unref (parser); g_free (preferences_file); return 0; } void preferences_free (struct preferences *preferences) { g_free (preferences->local_dir); } elektroid-2.0/src/preferences.h000066400000000000000000000017671416764361200166230ustar00rootroot00000000000000/* * preferences.h * Copyright (C) 2019 David García Goñi * * This file is part of Elektroid. * * Elektroid is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Elektroid is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Elektroid. If not, see . */ #include struct preferences { gboolean autoplay; gchar *local_dir; }; gint preferences_save (struct preferences *); gint preferences_load (struct preferences *); void preferences_free (struct preferences *); elektroid-2.0/src/sample.c000066400000000000000000000332351416764361200155710ustar00rootroot00000000000000/* * sample.c * Copyright (C) 2019 David García Goñi * * This file is part of Elektroid. * * Elektroid is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Elektroid is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Elektroid. If not, see . */ #include "../config.h" #include #include #include #include "sample.h" #define JUNK_CHUNK_ID "JUNK" #define SMPL_CHUNK_ID "smpl" struct smpl_chunk_data { guint32 manufacturer; guint32 product; guint32 sample_period; guint32 midi_unity_note; guint32 midi_pitch_fraction; guint32 smpte_format; guint32 smpte_offset; guint32 num_sampler_loops; guint32 sampler_data; struct sample_loop { guint32 cue_point_id; guint32 type; guint32 start; guint32 end; guint32 fraction; guint32 play_count; } sample_loop; }; struct g_byte_array_io_data { GByteArray *array; guint pos; }; static const guint8 JUNK_CHUNK_DATA[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; static sf_count_t get_filelen_byte_array_io (void *); static sf_count_t seek_byte_array_io (sf_count_t, int, void *); static sf_count_t read_byte_array_io (void *, sf_count_t, void *); static sf_count_t write_byte_array_io (const void *, sf_count_t, void *); static sf_count_t tell_byte_array_io (void *); static SF_VIRTUAL_IO G_BYTE_ARRAY_IO = { .get_filelen = get_filelen_byte_array_io, .seek = seek_byte_array_io, .read = read_byte_array_io, .write = write_byte_array_io, .tell = tell_byte_array_io }; static sf_count_t get_filelen_byte_array_io (void *user_data) { struct g_byte_array_io_data *data = user_data; return data->array->len; } static sf_count_t seek_byte_array_io (sf_count_t offset, int whence, void *user_data) { struct g_byte_array_io_data *data = user_data; switch (whence) { case SEEK_SET: data->pos = offset; break; case SEEK_CUR: data->pos = data->pos + offset; break; case SEEK_END: data->pos = data->array->len + offset; break; default: break; }; if (data->pos > data->array->len) { g_byte_array_set_size (data->array, data->pos); } return data->pos; } static sf_count_t read_byte_array_io (void *ptr, sf_count_t count, void *user_data) { struct g_byte_array_io_data *data = user_data; if (data->pos + count > data->array->len) { count = data->array->len - data->pos; } memcpy (ptr, data->array->data + data->pos, count); data->pos += count; return count; } static sf_count_t write_byte_array_io (const void *ptr, sf_count_t count, void *user_data) { struct g_byte_array_io_data *data = user_data; if (data->pos >= data->array->len) { g_byte_array_set_size (data->array, data->pos); } if (data->pos + count > data->array->len) { g_byte_array_set_size (data->array, data->pos + count); } memcpy (data->array->data + data->pos, (guint8 *) ptr, count); data->pos += count; return count; } static sf_count_t tell_byte_array_io (void *user_data) { struct g_byte_array_io_data *data = user_data; return data->pos; } static gint sample_wave_data (GByteArray * sample, struct job_control *control, struct g_byte_array_io_data *wave) { SF_INFO sf_info; SNDFILE *sndfile; sf_count_t frames, total; struct SF_CHUNK_INFO junk_chunk_info; struct SF_CHUNK_INFO smpl_chunk_info; struct smpl_chunk_data smpl_chunk_data; struct sample_loop_data *sample_loop_data = control->data; g_byte_array_set_size (wave->array, sample->len + 1024); //We need space for the headers. wave->array->len = 0; debug_print (1, "Loop start at %d; loop end at %d\n", sample_loop_data->start, sample_loop_data->end); memset (&sf_info, 0, sizeof (sf_info)); sf_info.samplerate = ELEKTRON_SAMPLE_RATE; sf_info.channels = 1; sf_info.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16; sndfile = sf_open_virtual (&G_BYTE_ARRAY_IO, SFM_WRITE, &sf_info, wave); if (!sndfile) { error_print ("%s\n", sf_strerror (sndfile)); return -1; } strcpy (junk_chunk_info.id, JUNK_CHUNK_ID); junk_chunk_info.id_size = strlen (JUNK_CHUNK_ID); junk_chunk_info.datalen = sizeof (JUNK_CHUNK_DATA); junk_chunk_info.data = (void *) JUNK_CHUNK_DATA; if (sf_set_chunk (sndfile, &junk_chunk_info) != SF_ERR_NO_ERROR) { error_print ("%s\n", sf_strerror (sndfile)); } smpl_chunk_data.manufacturer = 0; smpl_chunk_data.product = 0; smpl_chunk_data.sample_period = 1000000000 / ELEKTRON_SAMPLE_RATE; smpl_chunk_data.midi_unity_note = 60; smpl_chunk_data.midi_pitch_fraction = 0; smpl_chunk_data.smpte_format = 0; smpl_chunk_data.smpte_offset = 0; smpl_chunk_data.num_sampler_loops = 1; smpl_chunk_data.sampler_data = 0; smpl_chunk_data.sample_loop.cue_point_id = 0; smpl_chunk_data.sample_loop.type = ELEKTRON_LOOP_TYPE; smpl_chunk_data.sample_loop.start = sample_loop_data->start; smpl_chunk_data.sample_loop.end = sample_loop_data->end; smpl_chunk_data.sample_loop.fraction = 0; smpl_chunk_data.sample_loop.play_count = 0; strcpy (smpl_chunk_info.id, SMPL_CHUNK_ID); smpl_chunk_info.id_size = strlen (SMPL_CHUNK_ID); smpl_chunk_info.datalen = sizeof (struct smpl_chunk_data); smpl_chunk_info.data = &smpl_chunk_data; if (sf_set_chunk (sndfile, &smpl_chunk_info) != SF_ERR_NO_ERROR) { error_print ("%s\n", sf_strerror (sndfile)); } frames = sample->len >> 1; total = sf_write_short (sndfile, (gint16 *) sample->data, frames); sf_close (sndfile); if (total != frames) { error_print ("Unexpected frames while writing to sample (%ld != %ld)\n", total, frames); return -1; } return 0; } gint sample_wave (GByteArray * sample, GByteArray * wave, struct job_control *control) { struct g_byte_array_io_data data; data.pos = 0; data.array = wave; return sample_wave_data (sample, control, &data); } gint sample_save (const gchar * path, GByteArray * sample, struct job_control *control) { GByteArray *wave = g_byte_array_new (); gint ret = sample_wave (sample, wave, control); if (!ret) { ret = save_file (path, wave, control); } g_byte_array_free (wave, TRUE); return ret; } static void audio_multichannel_to_mono (gshort * input, gshort * output, gint size, gint channels) { int i, j, v; debug_print (2, "Converting to mono...\n"); for (i = 0; i < size; i++) { v = 0; for (j = 0; j < channels; j++) { v += input[i * channels + j]; } v /= channels; output[i] = v; } } static gint sample_raw_data (struct g_byte_array_io_data *wave, struct job_control *control, GByteArray * sample, guint * frames) { SF_INFO sf_info; SNDFILE *sndfile; SRC_DATA src_data; SRC_STATE *src_state; struct SF_CHUNK_INFO chunk_info; SF_CHUNK_ITERATOR *chunk_iter; gint16 *buffer_input; gint16 *buffer_input_multi; gint16 *buffer_input_mono; gint16 *buffer_s; gfloat *buffer_f; gint err; gint resampled_buffer_len; gint f, frames_read; gboolean active; struct sample_loop_data *sample_loop_data; struct smpl_chunk_data smpl_chunk_data; if (control) { g_mutex_lock (&control->mutex); } g_byte_array_set_size (sample, 0); if (control) { g_mutex_unlock (&control->mutex); } sf_info.format = 0; sndfile = sf_open_virtual (&G_BYTE_ARRAY_IO, SFM_READ, &sf_info, wave); if (!sndfile) { error_print ("%s\n", sf_strerror (sndfile)); return -1; } strcpy (chunk_info.id, SMPL_CHUNK_ID); chunk_info.id_size = strlen (SMPL_CHUNK_ID); chunk_iter = sf_get_chunk_iterator (sndfile, &chunk_info); sample_loop_data = malloc (sizeof (struct sample_loop_data)); if (chunk_iter) { chunk_info.datalen = sizeof (struct smpl_chunk_data); chunk_info.data = &smpl_chunk_data; sf_get_chunk_data (chunk_iter, &chunk_info); sample_loop_data->start = le32toh (smpl_chunk_data.sample_loop.start); sample_loop_data->end = le32toh (smpl_chunk_data.sample_loop.end); } else { sample_loop_data->start = 0; sample_loop_data->end = 0; } control->data = sample_loop_data; //Set scale factor. See http://www.mega-nerd.com/libsndfile/api.html#note2 if ((sf_info.format & SF_FORMAT_FLOAT) == SF_FORMAT_FLOAT || (sf_info.format & SF_FORMAT_DOUBLE) == SF_FORMAT_DOUBLE) { debug_print (2, "Setting scale factor to ensure correct integer readings...\n"); sf_command (sndfile, SFC_SET_SCALE_FLOAT_INT_READ, NULL, SF_TRUE); } buffer_input_multi = malloc (LOAD_BUFFER_LEN * sf_info.channels * sizeof (gint16)); buffer_input_mono = malloc (LOAD_BUFFER_LEN * sizeof (gint16)); buffer_f = malloc (LOAD_BUFFER_LEN * sizeof (gfloat)); src_data.data_in = buffer_f; src_data.src_ratio = ((double) ELEKTRON_SAMPLE_RATE) / sf_info.samplerate; resampled_buffer_len = LOAD_BUFFER_LEN * src_data.src_ratio; buffer_s = malloc (resampled_buffer_len * sizeof (gint16)); src_data.data_out = malloc (resampled_buffer_len * sizeof (gfloat)); src_state = src_new (SRC_SINC_BEST_QUALITY, 1, &err); if (err) { goto cleanup; } if (frames) { *frames = sf_info.frames * src_data.src_ratio; } if (ELEKTRON_SAMPLE_RATE != sf_info.samplerate) { debug_print (2, "Loop start at %d, loop end at %d before resampling\n", sample_loop_data->start, sample_loop_data->end); sample_loop_data->start *= src_data.src_ratio; sample_loop_data->end *= src_data.src_ratio; debug_print (2, "Loop start at %d, loop end at %d after resampling\n", sample_loop_data->start, sample_loop_data->end); } debug_print (2, "Loading sample (%" PRId64 " frames)...\n", sf_info.frames); f = 0; if (control) { g_mutex_lock (&control->mutex); active = control->active; g_mutex_unlock (&control->mutex); } else { active = TRUE; } while (f < sf_info.frames && active) { debug_print (2, "Loading buffer...\n"); frames_read = sf_readf_short (sndfile, buffer_input_multi, LOAD_BUFFER_LEN); if (frames_read < LOAD_BUFFER_LEN) { src_data.end_of_input = SF_TRUE; } else { src_data.end_of_input = 0; } src_data.input_frames = frames_read; f += frames_read; if (sf_info.channels == 1) { buffer_input = buffer_input_multi; } else { audio_multichannel_to_mono (buffer_input_multi, buffer_input_mono, frames_read, sf_info.channels); buffer_input = buffer_input_mono; } if (sf_info.samplerate == ELEKTRON_SAMPLE_RATE) { if (control) { g_mutex_lock (&control->mutex); } g_byte_array_append (sample, (guint8 *) buffer_input, frames_read << 1); if (control) { g_mutex_unlock (&control->mutex); } } else { src_short_to_float_array (buffer_input, buffer_f, frames_read); src_data.output_frames = src_data.input_frames * src_data.src_ratio; err = src_process (src_state, &src_data); debug_print (2, "Resampling...\n"); if (err) { debug_print (2, "Error %s\n", src_strerror (err)); break; } src_float_to_short_array (src_data.data_out, buffer_s, src_data.output_frames_gen); if (control) { g_mutex_lock (&control->mutex); } g_byte_array_append (sample, (guint8 *) buffer_s, src_data.output_frames_gen << 1); if (control) { g_mutex_unlock (&control->mutex); } } if (control) { control->callback (f * 1.0 / sf_info.frames); g_mutex_lock (&control->mutex); active = control->active; g_mutex_unlock (&control->mutex); } } src_delete (src_state); if (err) { error_print ("Error while preparing resampling: %s\n", src_strerror (err)); } if (control) { g_mutex_lock (&control->mutex); if (control->active) { control->callback (1.0); } else { g_byte_array_set_size (sample, 0); } g_mutex_unlock (&control->mutex); } cleanup: free (buffer_input_multi); free (buffer_input_mono); free (buffer_s); free (buffer_f); free (src_data.data_out); sf_close (sndfile); if (!sample->len) { g_free (control->data); } return sample->len > 0 ? 0 : -1; } static gint sample_raw_frames (GByteArray * wave, GByteArray * sample, struct job_control *control, guint * frames) { struct g_byte_array_io_data data; data.pos = 0; data.array = wave; return sample_raw_data (&data, control, sample, frames); } gint sample_raw (GByteArray * wave, GByteArray * sample, struct job_control *control) { return sample_raw_frames (wave, sample, control, NULL); } gint sample_load_with_frames (const gchar * path, GByteArray * sample, struct job_control *control, guint * frames) { GByteArray *wave = g_byte_array_new (); debug_print (1, "Loading file %s...\n", path); int ret = load_file (path, wave, control); if (!ret) { ret = sample_raw_frames (wave, sample, control, frames); } g_byte_array_free (wave, TRUE); return ret; } gint sample_load (const gchar * path, GByteArray * sample, struct job_control *control) { return sample_load_with_frames (path, sample, control, NULL); } elektroid-2.0/src/sample.h000066400000000000000000000025171416764361200155750ustar00rootroot00000000000000/* * sample.h * Copyright (C) 2019 David García Goñi * * This file is part of Elektroid. * * Elektroid is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Elektroid is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Elektroid. If not, see . */ #include #include #include #include "utils.h" #ifndef SAMPLE_H #define SAMPLE_H #define LOAD_BUFFER_LEN 4800 // In guint16 frames; 9600 B; 0.1 ms gint sample_wave (GByteArray *, GByteArray *, struct job_control *); gint sample_raw (GByteArray *, GByteArray *, struct job_control *); gint sample_save (const gchar *, GByteArray *, struct job_control *); gint sample_load (const gchar *, GByteArray *, struct job_control *); gint sample_load_with_frames (const gchar *, GByteArray *, struct job_control *, guint *); #endif elektroid-2.0/src/utils.c000066400000000000000000000151251416764361200154460ustar00rootroot00000000000000/* * utils.c * Copyright (C) 2019 David García Goñi * * This file is part of Elektroid. * * Elektroid is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Elektroid is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Elektroid. If not, see . */ #include #include #include #include "utils.h" #define DEBUG_SHORT_HEX_LEN 64 #define DEBUG_FULL_HEX_THRES 3 #define KIB 1024 gint debug_level; static guint get_max_message_length (guint msg_len) { guint len; if (debug_level >= DEBUG_FULL_HEX_THRES) { len = msg_len; } else { len = msg_len > DEBUG_SHORT_HEX_LEN ? DEBUG_SHORT_HEX_LEN : msg_len; } return len; } gchar * debug_get_hex_data (gint level, guint8 * data, guint len) { gint i; guint8 *b; guint size; guint bytes_shown; guint extra; gchar *str; gchar *next; if (level >= DEBUG_FULL_HEX_THRES) { bytes_shown = len; extra = 0; } else { if (len > DEBUG_SHORT_HEX_LEN) { bytes_shown = DEBUG_SHORT_HEX_LEN; extra = 3; } else { bytes_shown = len; extra = 0; } } size = bytes_shown * 3 + extra; str = malloc (sizeof (char) * size); b = data; next = str; sprintf (next, "%02x", *b); next += 2; b++; i = 1; while (i < get_max_message_length (len)) { sprintf (next, " %02x", *b); next += 3; b++; i++; } if (level < DEBUG_FULL_HEX_THRES && len > DEBUG_SHORT_HEX_LEN) { sprintf (next, "..."); next += 3; } return str; } gchar * debug_get_hex_msg (const GByteArray * msg) { return debug_get_hex_data (debug_level, msg->data, msg->len); } gchar * chain_path (const gchar * parent, const gchar * child) { gchar *pathname = malloc (PATH_MAX); if (strcmp (parent, "/") == 0) { snprintf (pathname, PATH_MAX, "/%s", child); } else { snprintf (pathname, PATH_MAX, "%s/%s", parent, child); } return pathname; } void remove_ext (char *name) { gint namelen = strlen (name); gchar *dot = &name[namelen]; gint i = namelen; while (*dot != '.' && i > 0) { dot--; i--; } *dot = 0; } const gchar * get_ext (const gchar * name) { int namelen = strlen (name) - 1; int i = namelen; const gchar *ext = &name[namelen]; while (i > 0 && *(ext - 1) != '.') { ext--; i--; } if (i == 0 && name[0] != '.') { return NULL; } else { return ext; } } gchar * get_expanded_dir (const char *exp) { wordexp_t exp_result; size_t n; gchar *exp_dir = malloc (PATH_MAX); wordexp (exp, &exp_result, 0); n = PATH_MAX - 1; strncpy (exp_dir, exp_result.we_wordv[0], n); exp_dir[PATH_MAX - 1] = 0; wordfree (&exp_result); return exp_dir; } char * get_local_startup_path (const gchar * local_dir) { DIR *dir; gchar *startup_path = NULL; if (local_dir) { dir = opendir (local_dir); if (dir) { startup_path = malloc (PATH_MAX); realpath (local_dir, startup_path); } else { error_print ("Unable to open dir '%s'\n", local_dir); } closedir (dir); } if (!startup_path) { startup_path = get_expanded_dir ("~"); } debug_print (1, "Using '%s' as local dir...\n", startup_path); return startup_path; } void free_msg (gpointer msg) { g_byte_array_free ((GByteArray *) msg, TRUE); } gchar * get_item_name (struct item *item) { return strdup (item->name); } gchar * get_item_index (struct item *item) { gchar *index; if (item->type == ELEKTROID_DIR) { index = get_item_name (item); } else { index = malloc (LABEL_MAX); snprintf (index, LABEL_MAX, "%d", item->index); } return index; } guint next_item_iterator (struct item_iterator *iter) { return iter->next (iter); } void free_item_iterator (struct item_iterator *iter) { if (iter->free) { iter->free (iter->data); } } gint copy_item_iterator (struct item_iterator *dst, struct item_iterator *src, gboolean cached) { return src->copy (dst, src, cached); } gint load_file (const char *path, GByteArray * array, struct job_control *control) { FILE *file; long size; gint res; file = fopen (path, "rb"); if (!file) { return -errno; } res = 0; if (fseek (file, 0, SEEK_END)) { error_print ("Unexpected value\n"); res = -errno; goto end; } size = ftell (file); rewind (file); g_byte_array_set_size (array, size); if (fread (array->data, 1, size, file) == size) { debug_print (1, "%zu bytes read\n", size); } else { error_print ("Error while reading from file %s\n", path); res = -errno; } end: fclose (file); return res; } gint save_file_char (const gchar * path, const guint8 * data, ssize_t len) { gint res; long bytes; FILE *file; file = fopen (path, "w"); if (!file) { return -errno; } debug_print (1, "Saving file %s...\n", path); res = 0; bytes = fwrite (data, 1, len, file); if (bytes == len) { debug_print (1, "%zu bytes written\n", bytes); } else { error_print ("Error while writing to file %s\n", path); res = -EIO; } fclose (file); return res; } gint save_file (const gchar * path, GByteArray * array, struct job_control *control) { return save_file_char (path, array->data, array->len); } gchar * get_human_size (guint size, gboolean with_space) { gchar *label = malloc (LABEL_MAX); gchar *space = with_space ? " " : ""; if (size < KIB) { snprintf (label, LABEL_MAX, "%d%sB", size, space); } else if (size < KIB * KIB) { snprintf (label, LABEL_MAX, "%.2f%sKiB", size / (double) KIB, space); } else if (size < KIB * KIB * KIB) { snprintf (label, LABEL_MAX, "%.2f%sMiB", size / (double) (KIB * KIB), space); } else { snprintf (label, LABEL_MAX, "%.2f%sGiB", size / (double) (KIB * KIB * KIB), space); } return label; } void set_job_control_progress (struct job_control *control, gdouble p) { gdouble v = (double) control->part / control->parts + p / control->parts; control->callback (v); } elektroid-2.0/src/utils.h000066400000000000000000000074421416764361200154560ustar00rootroot00000000000000/* * utils.h * Copyright (C) 2019 David García Goñi * * This file is part of Elektroid. * * Elektroid is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Elektroid is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Elektroid. If not, see . */ #include #include #ifndef UTILS_H #define UTILS_H #define LABEL_MAX 128 #define ELEKTRON_LOOP_TYPE 0x7f #define ELEKTRON_SAMPLE_RATE 48000 #define debug_print(level, format, ...) if (level <= debug_level) fprintf(stderr, "DEBUG:" __FILE__ ":%d:(%s): " format, __LINE__, __FUNCTION__, ## __VA_ARGS__) #define error_print(format, ...) fprintf(stderr, "\x1b[31mERROR:" __FILE__ ":%d:(%s): " format "\x1b[m", __LINE__, __FUNCTION__, ## __VA_ARGS__) enum item_type { ELEKTROID_NONE = 0, ELEKTROID_FILE = 'F', ELEKTROID_DIR = 'D' }; struct item_iterator; typedef guint (*iterator_next) (struct item_iterator *); typedef void (*iterator_free) (void *); typedef gint (*iterator_copy) (struct item_iterator *, struct item_iterator *, gboolean); struct item { gchar *name; guint32 size; gint32 index; enum item_type type; }; struct item_iterator { iterator_next next; iterator_free free; iterator_copy copy; void *data; struct item item; }; typedef void (*job_control_callback) (gdouble); struct job_control { gboolean active; GMutex mutex; job_control_callback callback; gint parts; gint part; void *data; }; struct sample_loop_data { gint32 start; gint32 end; }; typedef gint (*fs_init_iter_func) (struct item_iterator *, const gchar *, void *); typedef gint (*fs_path_func) (const gchar *, void *); typedef gint (*fs_src_dst_func) (const gchar *, const gchar *, void *); typedef gint (*fs_remote_file_op) (const gchar *, GByteArray *, struct job_control *, void *); typedef gchar *(*fs_get_item_id) (struct item *); typedef gint (*fs_local_file_op) (const gchar *, GByteArray *, struct job_control *); struct fs_operations { gint fs; fs_init_iter_func readdir; fs_path_func mkdir; fs_path_func delete; fs_src_dst_func rename; fs_src_dst_func move; fs_src_dst_func copy; fs_path_func clear; fs_src_dst_func swap; fs_remote_file_op download; fs_remote_file_op upload; fs_get_item_id getid; fs_local_file_op save; fs_local_file_op load; const gchar *extension; }; extern int debug_level; gchar *debug_get_hex_data (gint, guint8 *, guint); gchar *debug_get_hex_msg (const GByteArray *); gchar *chain_path (const gchar *, const gchar *); void remove_ext (gchar *); const gchar *get_ext (const gchar *); gchar get_type_from_inventory_icon (const gchar *); gchar *get_expanded_dir (const char *); gchar *get_local_startup_path (const gchar *); void free_msg (gpointer); gchar *get_item_name (struct item *); gchar *get_item_index (struct item *); guint next_item_iterator (struct item_iterator *); void free_item_iterator (struct item_iterator *); gint copy_item_iterator (struct item_iterator *, struct item_iterator *, gboolean); gint load_file (const char *, GByteArray *, struct job_control *); gint save_file (const char *, GByteArray *, struct job_control *); gint save_file_char (const gchar *, const guint8 *, ssize_t); gchar *get_human_size (guint, gboolean); void set_job_control_progress (struct job_control *, gdouble); #endif elektroid-2.0/test/000077500000000000000000000000001416764361200143265ustar00rootroot00000000000000elektroid-2.0/test/Makefile.am000066400000000000000000000002771416764361200163700ustar00rootroot00000000000000TESTS = sample_fs_tests.sh data_fs_tests.sh EXTRA_DIST = \ $(TESTS) \ res/square.wav AM_TESTS_ENVIRONMENT = \ ecli='$(abs_top_builddir)'/src/elektroid-cli; \ export ecli; elektroid-2.0/test/data_fs_tests.sh000077500000000000000000000055251416764361200175170ustar00rootroot00000000000000#!/usr/bin/env bash function get_sound_n_with_id () { s="sound$1" echo "${!s}" | sed "s/^F $1 0012/F $2 007e/" } echo "Getting devices..." DEVICE=$($ecli ld | head -n 1 | awk '{print $1}') [ -z "$DEVICE" ] && echo "No device found" && exit 0 echo "Using device $DEVICE..." sound1=$($ecli ls-data $DEVICE:/soundbanks/A | grep "^F 1") nsound1=$(get_sound_n_with_id 1 64) sound2=$($ecli ls-data $DEVICE:/soundbanks/A | grep "^F 2") echo "Testing data copy..." $ecli cp-data $DEVICE:/soundbanks/A/1 $DEVICE:/soundbanks/H/64 [ $? -ne 0 ] && exit 1 $ecli cp-data $DEVICE:/soundbanks/A/2 $DEVICE:/soundbanks/H/63 [ $? -ne 0 ] && exit 1 output=$($ecli ls-data $DEVICE:/soundbanks/H) actual=$(echo "$output" | grep "^F 64") expected=$(get_sound_n_with_id 1 64) [ "$actual" != "$expected" ] && exit 1 actual=$(echo "$output" | grep "^F 63") expected=$(get_sound_n_with_id 2 63) [ "$actual" != "$expected" ] && exit 1 echo "Testing data move..." $ecli mv-data $DEVICE:/soundbanks/H/64 $DEVICE:/soundbanks/H/62 [ $? -ne 0 ] && exit 1 output=$($ecli ls-data $DEVICE:/soundbanks/H) actual=$(echo "$output" | grep "^F 62") expected=$(get_sound_n_with_id 1 62) [ "$actual" != "$expected" ] && exit 1 actual=$(echo "$output" | grep "^F 64") [ -n "$actual" ] && exit 1 echo "Testing data swap..." $ecli sw-data $DEVICE:/soundbanks/H/62 $DEVICE:/soundbanks/H/63 [ $? -ne 0 ] && exit 1 output=$($ecli ls-data $DEVICE:/soundbanks/H) actual=$(echo "$output" | grep "^F 62") expected=$(get_sound_n_with_id 2 62) [ "$actual" != "$expected" ] && exit 1 actual=$(echo "$output" | grep "^F 63") expected=$(get_sound_n_with_id 1 63) [ "$actual" != "$expected" ] && exit 1 echo "Testing data clear..." $ecli cl-data $DEVICE:/soundbanks/H/63 [ $? -ne 0 ] && exit 1 $ecli cl-data $DEVICE:/soundbanks/H/62 [ $? -ne 0 ] && exit 1 output=$($ecli ls-data $DEVICE:/soundbanks/H) [ $(echo "$output" | grep "^F 62" | wc -l) -ne 0 ] && exit 1 [ $(echo "$output" | grep "^F 63" | wc -l) -ne 0 ] && exit 1 echo "Testing upload..." $ecli ul-data $srcdir/res/SOUND.dtdata $DEVICE:/soundbanks/H [ $? -ne 0 ] && exit 1 id=$($ecli ls-data $DEVICE:/soundbanks/H | grep 'SOUND$' | awk '{print $2}') echo "Testing download..." $ecli dl-data $DEVICE:/soundbanks/H/$id [ $? -ne 0 ] && exit 1 ls "SOUND.dtdata" cksum SOUND.dtdata cksum $srcdir/res/SOUND.dtdata actual_cksum="$(cksum SOUND.dtdata | awk '{print $1}')" [ "$actual_cksum" != "$(cksum $srcdir/res/SOUND.dtdata | awk '{print $1}')" ] && exit 1 rm SOUND.dtdata [ $? -ne 0 ] && exit 1 $ecli cl-data $DEVICE:/soundbanks/H/$id [ $? -ne 0 ] && exit 1 echo "Testing upload..." $ecli ul-data $srcdir/res/SOUND.dtdata $DEVICE:/soundbanks/H [ $? -ne 0 ] && exit 1 id=$($ecli ls-data $DEVICE:/soundbanks/H/256 | grep 'SOUND$' | awk '{print $2}') [ $id != 256 ] && exit 1 echo "Testing data clear..." $ecli cl-data $DEVICE:/soundbanks/H/256 [ $? -ne 0 ] && exit 1 exit 0 elektroid-2.0/test/res/000077500000000000000000000000001416764361200151175ustar00rootroot00000000000000elektroid-2.0/test/res/SOUND.dtdata000066400000000000000000000003001416764361200171630ustar00rootroot000000000000000053 @SOUNDXI@@) @D(?]dKH!L@/0@n@a@$S,;. :ݓڪelektroid-2.0/test/res/square.wav000066400000000000000000002736541416764361200171570ustar00rootroot00000000000000RIFFwWAVEfmt wJUNK4smpl<aQ<datawSgffhffuff{f|f}f{f|f}f|f|f|f{ffyffzf~f{f|f}f{f~fzf~f{f|f}fzffxffxffyffzf~f{f|f}f{ffyffuffd|4lӤ&{{^GNg ffgfftffxffwffyf}f}fzffzf~fzf~f{f}f|f{f~fzf~f{f}f{f~fzf~f|fzffxffyffyffxffxffvffrffnff;۱昴o}zcԙqbHqg.ffkffxffyffzf}f}f{f}f|f{f~fyffzf}f|f|fzffyffzf}fzffxffyf}f}f{f}f{f~fzffyffyffvffpffJffBCǙew{}xhęB~BfJffpffwffyffxffyf~f|f|f|f}f{f}f|f|f|f}f{f}f|f{f~f{f}f{f~fyffyf~f{f}f{f~fzf}f|f|f}f}fwffhff+frgH~epԙdz|}}~~s昳;fqffwff{f|f}f{f}f|f|f}fzf~f{f}f|f|f|f|f}f{f}f|f{ffyffzf|f~fzffyffyffzf}f|f|f|f}fyffqffdfffgNH^z{%iӀ4dfrffwffyf}f}f{f}f{f}f{ffyf~f{f|f~fyffxffzf~f|fzffxffyf~fzf~f{f}f|f|f|f|f~fxffuffrffgfffgSu7^{_˖,Zcfaffwf~f|f|f}f{f~fyff{f|f~fyf~f|f|f}f{f}f{f~fzf~f{f|f~fyffxffzf|f~fzf}f}fzffyffxffsffgfffgWL pHZt}=$NagNffrff{f{f~f{f|f~fyffxffzf}f|f{f~f{f}f{f}f{f~f{f|f}fzffxff{f|f~fyff{f|f~fxffuffrffjff)fg[ ҙcz~lH^Zg:ffnffxf~f|f|f|f|f|f}f|f|f|f{ffyffzf}f}f{f}f{f}f|f}f{f}f{f}f|f|f}f{f|f}f{ffxffxfftffiff5f\g^Hn|}y`ԙ[g'ffiffuffxffyffzf}f|f|f|f}f|f|f{f~fzffyf~f{f~fyffxffwffzf}f}f{f|f~fxffvffwffwffrffLfgMa:$!ᙑz~~y[KnN Wgffefftff|fzffzf}f|f|f|f}f{f}f|f|f|f|f~fyffwffxf~f|fzffyffyffzf~f|fzffwffwffxffxffdffZc,\}{x\7tSgffifftffxf~f|f}fzffyffyf~f{f}f|f}fzffyffxffxffyf~f{f}f|f{f~fzffzf}f|f|f|f~fyffzf}f}fvffd|4jӦ%zuYFNg!ffjffxf}f~fxffxff{f{ffyffyffzffzf|f~fzf~f|fzffxffyf~f{f}f|f|f|f|f}f{f~fzf~f{f}f{f~fxffrff;۳嘵n~{dԙpdHrg.ffmffyf~f{f}f{f~fzf~f{f}f{f}f{f~f{f|f|f|f}f|f|f|f|f}f{f~fzf~f{f|f~fzf~f{f|f}f{f}f}fzffxffsffLffB?™j||hƙBBfHffoffyf~fzffyffzf}f|f}fzf~fzffzf~fzf}f}f{f~fzf~fzf~fzffyffzf}f|f{f~f|f{f~fzf~fzffvffkff/fqgHcoי`z{l㘴;fofftffzf}f|f}fzf~f|f{ffxff{f|f~fyffwffyf~f|f{f}f|f|f|f}f{f}f|f{ffxffyff{f{f~fyfftffhff fgNH[v}z$l|4dfrffzf}f}fyff{f|f}f{f|f~fzf~f{f|f}f{f~fzf~fzffyffzf~f{f|f}f{f}f}fyffyf~f{f~fxfftffsffhfffgSt7[y]˗,[cfbffvffyf~fzffyff{f|f}f{f}f|f|f}f{f}f|f{f~fzffyf~f|f{f~fzf}f}fzffyf~f|f{f~fzffxfftffhfffgWM nLߙ^yz=$JagJffrffzf~fzffyffzf}f}fzf~f|f{f~fyff{f}f{f}f{f~fzf~f{f}f|f|f{f~fzffwffwffyffxfftffjff)fg[ әazzkI^Wg;ffoffxf}f}f|f|f|f{f~f{f}f{f}f{ffyf~f{f|f~f{f{f~f{f|f~fyf~f}fyffzf|f}f|f{ffxffyffwffmff9fYg^Im|~~zt]ՙ [g+ffjffsffyffzf~fyff{f}f{f}f{f~f{f}fzffzf~f{f|f}f|f|f}fzf~f|f{ffyf~f{f}f|f|f}fzffvffoffJfgKa=$㙒x}w]LlP Wgffgffvffzf~f{f|f~fzf}f|f|f|f~fyffzf}f}f{f|f}f{f~fzf~fzf~f|fzffxff{f|f}f{f~fzf}f}fzfftff^ffXc,_x[6tSgfffffrfftffxf}f}f|f{f~f{f|f|f~fyffyf}f}f{f~fzf}f|f|f}f{f~fyffwffwffzf|f}f|f|f}fzffyffrffd|4mӢ({}uZENgffeffrffvffzf|f~f{f|f}f{f}f|f|f|f}f{f}f{f~fzf}f}f{f}f{f|f~fzf~fzf~f|f|f|f}fzffzf~f{f|f}fzffsff;ۯ蘳n|{bՙpcHpg.ffiffsffyf~fzffzf}f}fyffzf}f|f{f}f}f{f}f{f}f|f}fzf~f|f{f~fyff{f}f{f|f}f|f|f|f|f|ffvffmffGffBBǙh~ygǙC}BfMffsffyffyffzf~f{f|f}f{f}f|f|f|f|f}f{f~fzf}f}f{f~fzf}f|f}f{f~fyffzf~f|fzffzf}f}fyfftffiff-ftgH`rՙ`x|p䘵;fqffuffxffzf~fzffyffzf~fzffzf|f}f{f~f{f|f|f}f{f}f{f~fzf~fzf~f{f}f{f}f|f|f|f|f|f~fyffufflff%fgNG^x}z%k}4dfqffwff{f{f~f{f|f}f|f{f~f{f|f}f{f}f|f|f|f}f{f}f|f|f|f}f{f}f|f|f{ffyffzf~fzffwffxffvffhfffgSu7]}^˗,Ycf_ffvffzf}f}fzffzf}f|f|f|f}f|f|f{f~fzffzf}f|f}f{f}f|f{ffxffyffyffwffvffvffwffuffgfffgWN mK\v~~~}@$JagLffqffxf~f|f{f}f|f|f}f{f}f{f~f{f}f{f}f|f}f{f|f}f|f{ffxffzf}f|f}fzffyffxffyffyfftffjff,fg[ әa|{jD^Zg:ffnffuffwffzf}f}fzf~f{f}f|f|f}f{f}f{f}f|f}f{f|f}f{f~f{f}fzffzf~f|fzf~f|f|f|f}fzffwffnff;fXg^Dm|zbљ [g(ffhffuffyf~f{f}f{f~fzffyffzf~fzffyffzf}f}fzf~f{f}f|f|f|f}f{f}f{f~f{f|f}fzffzffwffpffLfgNa;$䙎}y\JoN Wgffiffvffwffwffzf}f|f|f|f|f}f{f}f|f{f~f{f|f~fzf}f|f}f|f|f{f}f}f{f}f{f}f|f|f|f}f{f~fxffsff]ffXc,]~xZ8rSgffeffpfftffyf~f{f}f{f}f|f|f|f}f{f}f|f{f~f{f|f~fyffzf}f~fyf~f|f{f~f{f|f}f{f}f}fzffxffyf~fvffd|4kӥ&{~x^FNgffjffwffyf~f|f{f}f|f|f}f{f}f{f~fzf~f{f|f}f{f~fzf~f{f}f|f{f~fzffyffyffzffxffyff{f{ffwffoff;۲嘴n}|b֙nf}Hmg1ffkffsffxf}f}f{f}f|f}fzffzf}f}fzffzf}f|f|f|f~fyff{f|f}f{f~fzf~f{f|f}f{f}f|f|f|f|f}fzffrffKffB{Gəd{}gǙD~BfGffoffyf~f{f|f}fzffyf}f~fxffyf~f{f|f}f|f{f~fyffyf~f{f}f|f|f|f}f{f~fzf}f}f{f~fxffwffsffhff-fpgH~dpԙdz~~r瘱;fnfftffxffzf}f|f{f~f{f}f{f~fyffzf|f~fyff{f}f{f}f{f~f{f|f}f{f}f|f{ffxffyf~f|fzffwffrffefffgNIߙ^y~{(ru4dfvf}f}f{f}f|f|f{ffxffzf}f|f{f~f|f{f}f{f}f}fzffyffzf~fzffyffzf}f|f|f}f{f}f|f{ffxfftffiff fgSw4[z^˖,Ycfbffzf|f|f}f{f}f{f}f|f}fzffyf~f|f{ffyf~f{f}f|f}fzffzf~fzf~f{f}f|f{f~fzffyffwffvfftffgfffgWJ qI^y{<$NagNffqffvffzf~fzf~fzffzf}f|f|f}f{f}f{f~fzf~f{f|f}f{f}f|f|f|f}f{f}f|f|f|f|f|f}f{f~fxffsffjff,fg[ ϙe{mG^Xg;ffmffwffyffyffzf~fzf~f|f{f}f|f|f}f{f}f|f|f|f|f}f|f{f~fzf~f|f{f~fzf~f{f}f{f}f|f|f}fxffofffUg^Hq{bҙ}[g0ffoffvffwffyff{f|f}fzffzf~f{f|f}f{f~fzf~f{f}f{f}f|f{ffxffzf}f|f|f|f}f|f{f~fzffyfftffMfgKa>$晎|y^ޙNkP Wgffeffpffuffxff{f}f{f}f{f~f{f}f{f}f|f|f|f}f{f~fyffyf~f|fzffxffyf}f}f{f~fzf~fyffvffrff^ffYc,]}uX5vSgffifftffuffwffxffxffzf}f|f|f|f}f|f{f~fzf~f|fzffxff{f|f|f}f|f{f~fzf}f}f{f}f}fzf}f~fxffpffd}4jӦ$zy^FNgffffftffyffzf~fzf}f|f}f{f~fyffzffxffwffyf}f}fzffyffyffyf~f{f|f}f}fzffyffzf~fzffxffrff;۱蘰s{t]ٙmeHrg.fflffwffxffwffyf}f}fzffzf~f{f|f}f{f}f}fzffyffzf~fzffyffyffzf}f|f|f|f}fzffxffuffnffMffB{DǙg}|fșCBfLfftffyffzf~f{f|f}f|f{ffxffyffzf}f{f~f{f|f}f{f}f|f{ffyffzf}f}fzffzf}f|f{ffyf~f}fvffeff)ftgHdoי`y}~|q꘮;fsffxffzf~f{f|f~fyffyf}f}f{f}f|f{f~fzffzf}f{ffxffxff{f|f}f|f|f}fzffzf~f{f|f}f|fzffuffiff!fgN D\yv!l{4dfuff{f}f{f}f|f{f~f{f}f|f{f}f}f{f}f|fzffyf~f{f|f|f}f|f|f|f|f|f}f|f|f|f|f}f|f|f|f|f}f{ffvffhfffgSv4Wv~_ˑ,_cfcffxf~f{f}f|f{f~f{f}f{f}f{f}f|f|f|f}f{f|f}f|f|f}f{f|f~fzffyffzf}f}f{f|f~fxffuffvfftffifffgWP mK^{x<$MagMffrffxffyffzf}f|f|f}f{f}f|f{f~fzf~f{f}fzffwffwff|fzffxffyffzf}f|f}f{f}f|f{ffwfflff*fg[ ҙbz}mF^[g7ffjffuffwffxffyf~f|fzffxffyf~fzffwffyf}f~fyff{f{ffyffzf}f|f|f|f}fzffzf}f~fvffkff8fZg^Gm~{cљ [g(ffiffvff{f|f~fyff{f|f}f{f}f}fzf~f{f}f|f|f{f~f{f}f|f{f~fzf~f{f}f|f|f|f|f}f|f{f~fzffxffpffJfgMa<$㙒w~~v[MmM Wg"ffjffvffzf~fzf~fzffyffzf~fzf~f{f}f|f{f}f}fzf~f{f|f~fyffyf~f{f}f|f|f|f|f}f{f}f{f~fzffxffdff\c, b~}zZ5wSgffhffuffxffyffzf}f|f}fzffyffzf~fzffyffzf~fzffyffzf}f|f}f{f}f{f}f}fzffzf}f|f|f|f}f}ftffd4iӦ%z~~x\ENgffhffvffzf}f|f|f|f}f|f|f|f|f}f{f~fyffxffyffyffzf~f{f|f}f|f{f~fzffyffxffwffyf~f{f~fxffpff;۳昱s{b֙oe~Hng2fflffuffxffyffyffzf}f|f}f{f}f|f|f}fzf~f{f}f|f{f~fzf~f{f}f|f|f|f|f}f{f~fzf~f{f}f{f~fyffrffNffB~Břgz~}fʙIyBfJffpffxf~f|f{f~f{f}f|f{f}f|f}f{f|f~fyffxffyf~f{f~fyffxffzf}f|f{ffxffxffzf}f|f|ffuffkff1fmgH~fmٙ]v~s蘱;fpffvff|f|f|f}f{f|f}f|f|f}fzf~f{f}f|f|f|f|f|f}f{f}f|f{f}f}fzffyf~f|f{f~fzf~f{f}fzffwfftffifffgNF[xz$i4dfuf}f~fzf~fzf~f{f~fzf~fzf~f|f|f|f|f|f}f|f|f|f|f}f|f{f~fzf~f|fzffzf}f}fyffzf}f|f{f~fzffuffefffgSu5Zx]˓,]cfbffvff|f{f~fzffyffzf}f}f{f~fzf~fzffzf}f|f|f|f}fzffyffyff{f}f{f|f}f}f{f}fzffzf~fyfflff fgWO mL\w|~x<$MagLffqffvffyf}f~fyf~f|f{ffxffyf~f|f{f}f}fyffvffxf~f|f|f|f}fzffyffxffzffyffwffvffmff/fg[}ԙ_w~oE^Xg=ffpffxf~f|f}f{f}f{f~fzffyf~f}fzf~f{f|f}f|f|f|f}f{f}f|f|f|f~fyffyffyffyf~f{f~fzffxffoff;fYg^Ckz~xaҙ~[g-fflffwff{f|f}f{f}f|f|f|f|f|f}f|f|f|f|f}f{f~fzf~f|f{f}f|f|f}f{f}f{f~f{f|f}f|f{f~fzf~f{ffuffOfgNa;$㙑yw[KmN Wg ffgffsffyf}f}f{f}f|f|f|f}f{f}f|f|f}f{f}fzff{f|f|f}fzffwffzf|f}fzff{f|f|f|f}f|f|f{ffwffaffYc,_wX4uSgffgffrffwf~f}f{f|f~fyff{f{f~f{f|f~fyf~f|f|f|f}fzffzf~fzffyffzf~f|fzffyff{f|f}fzffyfftffd{4lӤ%xy^ߙINgffhffsffxf}f~fyffyffyf~f|f|f}f{f}fzffvffwffzf~fzf~fzffyffyf}f}fzffyf}f}fzffzf}f}fxffnf f;ۯ蘱s|fљu^Htg-ffkffuffxffzf}f}fzf~f{f}f|f|f|f}f{f}f|f}f{f~fyffxffzf|f~fyff{f}f{f|f}f|f|f|f|f|f}fyffoffGffB}Eədx~}{hƙE|BfIffpffyffyffxff{f}f{f~fzf~f{f}f{f}f{f}f}fzffyffzf}f|f}f{f}f{f}f|f|f}fzffyff{f|f~fwffmff0fngH~enי`yp阯;foffwf~f}f{f|f~fyffyf~f{f}f|f}fzffyff{f|f|f}f{f}f|f{f}f|f|f|f}f{f}f{f}f|f|f}f{f|ffvffqffffffgNJ\w}x$j~4dfuf~f|f{f~fzffyf~f{f}f}fzf~fzffzf}f|f|f|f|f}f{f~fzf}f}f{f~fzf}f|f|f}f{f}f{f}f|f{ffxfftffhfffgSs8]{^˖,Ycf^ffwffzf}f{f~f{f|f}f{f~fzf~f{f}f|f|f}f{f}f{f}f|f|f|f|f|f}f|f{f~f{f|f~fyffzf~fzffwffrffiff"fgWK qGZw}9$PagNffsff{f}f|f}fzf~f{f}f|f{f~fzf~f{f|f~fzf~f{f|f}f|f|f|f|f}f{f}f|f|f}f{f|f}f|f|f{ffwfftffkff+fg[ љby{iK^Vg$㙑xx\LlP Wgffeffrffvff{f{f~f{f|f}f{f|f~fzf}f}fzffyf~f|f|f|f|f|f}f{f}f|f|f|f}fzffzf}f|f|f|f}fzffuff]ffYc,^~{v[9rSgfffffvffxffwffwff{f|f}f|f{f}f|f|f}f{f}f{f~f{f|f~fxffyf}f}fzffzf}f|f|f}f|f|f|f|f}f{f~f|fuffd}4kӥ$vy]HNgffgfftffyffzf}f|f|f}f{f}f|f|f}fzffyffyf~f{f}f{f}f|f|f}fzffxffvffwffzf~fzf~f{f}f{f~fxffqff;۶☷m|zb֙ng|Hmg2ffnffvffwff{f|f}f{f}f|f|f|f}f{f~fzf}f|f}f{f}fzffzffxffyffzf~fyffwffxffzf~f{f}f{ffrffKffB@l{~yhřB~BfKffpffwffxffyf~f{f}f|f{ffyf~f|f{f~f{f|f|f~fzf~fzf~f|f{f~fyffyf~f{f}f|f|f|f|f}f{f}f}fxffiff*fugHcnؙ`}~q蘱;foffsffwffzf}f|f|f}f{f}f|f|f|f}fzffyff{f{f~fzffzf|f~fzf~fzf~f{f}f|f{f~f{f|f~fzf~fzffxfflff"fgNFYv~y%l}4dfsff|f{f}f{f~fzf~f|fzffzf}f~fxffzf}f|f|f|f|f~fyff{f{ffzf|f~fyffyf~f{f}f{f~fyffvfftffjfffgSs7Xs|[˔,]cffff{f}fzffxff{f|f}f|f|f|f|f|f}f}fzf~f{f}f{f}f|f}f{f|f|f}f|f}fzffyffzf~f{f}f{f}f{ffwffkff!fgWJ qI]y|;$NagNfftffzf}f{f~f{f|f}fzffzf}f|f|f}fzf~f{f}f|f|f{ffyffzf}f}f{f}f|f{f~fzffyffyffxffuffkff*fg[Ιez~~ziK^Wg;ffoffwffzf|f}f{f~fzffxffyf~f|f|f|f}fzffzf~f{f|f}f|f{f~fzffzf}f|f{ffyffyffyffuffmff;fWg^Jn}|eЙ}[g*ffkffxf~fzffxffyf~f{f~fzffyffyff{f|f}fzf~f|f{f~fzf~f{f}f{f~fzffxffyffzf}f{ffxffpffLfgMa<$晏zx\JmN Wgffdffqffwff{f}f{f~fzf~f{f}f|f|f|f}f{f~fzf~fzffzf~fzf~f{f}f|f{ffyffzf}f}f{f}f{f~fzffwffaffZc,]{]6wSgfffffuffwffyffzf~f{f|f}f{f~f{f|f|f|f~fyffwffyf~f{f}f|f|f|f}f{f~fzf~f{f|f}f|f|f|f|f}f{ffrffd~4kӣ({~x[HNgffgffuff{f|f}fzffzf}f|f{ffyffzf}f|f}f{f}f|f{f~f{f}fzffwffzf{ffxffzf}f|fzffwffwffvffoff;۵䘴o|}~y^ڙlf~Hpg.ffiffxf}f}fzf~f{f~f{f{f~f{f|f~fyffxffyf~f|f|f|f}fyffwffyf}f|f|f}f|f|f{f~f{f|f}fzffyffqffIffBAșdw}fəGzBfLffpffyf~f{f}fzffzf~f{f}fzffyff{f|f}f{f}f|f|f|f}f{f}f|f{f~fzf~f{f}f{f~fyffzf|f~fxffuffjff.frgHbpי_yq蘯;fqffwffzf}f|f|f|f}f{f}f|f|f|f}f{f}f|f|f|f}f{f~fzf}f|f}f{f}f{f}f}f{f}f{f~fzffzf}f|f|f|f~fxfflff fgNF\y~w#j}4dfvf~f{f}f|f{ffyffzf}f|f|f}f|f{f}f{f~f|f{f|f}f|f}f{f|f~fzffyffzf~f|f{f~fzf~f{f}f|f{ffvffhfffgSt6[{`˕,Zcf`ffwff{f|f}f{f}f|f}fzf~f{f}f}fzf~f{f}f{f~fzf~f{f}f|f|f|f|f}f|f{ffxffwffwffvfftffpffefffgWI sG[x}y<$MagLffqffyf~f|f|f{f~fzffyffzf~f{f|f~fyff{f{ffyf~f}fyffyf~f|f{f}f}fzffyf~f|f{ffwffsffhff(fg[Йcx}kI^Xg:ffmffxffzf~f{f|f~fyff{f|f~fyffzf}f}f{f|f}f{f~f{f{f~f{f}f|f|f|f|f}f{f}f|f|f|f}fzffvffnff$噏|}w^ߙLmO Wgfffffvffzf}f{f~f{f|f}f{f}f}f{f|f~fyffzf|f}f{f}f}fyffxff{f{f~f{f}f{f}f{f~f{f}f{f~fyffwffcff\c,\}wY7tSgffhffuffxffxffwffzf}f}fzf~f{f}f|f{f~fzf~f|f{f}f{f~f{f}f{f|f~fzf~fzf~f{f~fzf~fzf~f|f|f|f}fvffdy4oӢ'z|`ޙINgffjffwffyf~f}fyff{f}f{f~fyffyf~f{f}f|f|f|f|f|f~fzf~fzf}f}f{f~fyffzf~f{f|f}f{f~fzffxffvffpff;۴☸jy}{b֙pcHog1ffkffuffyf~f{f}f{f}f|f|f}fzf~f{f}f|f|f|f|f}f{f}f|f|f}fzffzf}f}fzffzf}f|f{ffyffwffwffpffKffB{Eƙi}~~~jęD|BfKffrff{f~fxffvffyf~f{f~fzf~fzf~f|f|f|f|f|f}f|f|f{ffxffzf}f|f|f|f|f}f{f}f|f|f|f}fzffvffjff.fqgHdp֙az~~q昲;foffvffzf~f{f}f|f{f~f{f|f|f}f|f|f|f{ffyffzf}f|f|f|f~fyffzf}f|f|f|f}f{f|f}f{f~fzffwffsffffffgNG[u{}~y'm{4dfuff{f}f{f~fyffyf~f|f{f}f|f|f|f}fzffzf~fzf~fzf~f|f{f~fzf}f|f}f{f~fyff{f|f}f{f}f}fyfftffffffgSr8Yv` ˒,^cfcffvffyf~f|fzffzf~f{f}fzffzf~fzf~fzf~f{f}f{f}f|f{ffxffxffzf~f{f}f|f{f~f{f}f{f|ffwffkff"fgWM nJ[v~{=$LagMfftff|f|f|f}fzffyffxffxffzf}f|f{f~f{f}f{f}f|f}f{f|f}f|f|f|f|f|f~fzf}f{f~f{f}f{f~fwffiff)fg[~ؙ]w}}oE^\g6ffkffxf~f{f~fzf}f}f{f}f|f|f}f{f}f{f~f{f|f}f{f}f|f{f~f{f}f{f}f{f~f{f}f{f}f|f|f|f}fzfftffjff:fVg^~Ll|~~}dҙ[g*ffjffsffwffzf}f|f{ffxffzf|f~fyffyffzf}f|f|f}f|f|f{f~fzf~f{f}f{f}f{f~f{f|f}fzffwffrffOfgOa;$噏z{^HsI Wgffiffuffvffwffyf}f|f|f|f}f{f}f{f~fzf~f{f}f{f~fzf~f{f}f{f~fyffyf~f|f{f}f|f|f}fzffyffuffaff\c, avY6tSgffeffrffvffzf}f}f{f}f{f~fzf~f{f}f|f|f|f|f~fyffxffzf}f{f}f|f|f}fzffyffxff|f{f}f|f{ffzfxffd{4mӣ'|wZDNg!ffiffuffyf}f}f{f}f{f}f|f{ffyffzf|f~fzf~f{f|f~fyffyffzffzf}f{f}f|f}f{f}f{f}f}fyfftfftffpff;۰昵m|zbԙraHqg0ffnffyf}f}fzf~f{f}f|f|f|f|f}f{f~fzf~f{f|f}f|f|f|f|f|f~fzf}f|f|f}f|f{f~fzf~f{f~fyffxffxffpffJffB~BǙgz}l=BfHffpffuffxffyf}f|f}f{f~fyffzf~f{f|f}fzffyf}f|f|f}f|f|f|f{f~f{f}f|f{f~fzf~f|f{ffxffvfflff0fogHcrҙe}s䘵;fnffrffwff{f|f|f}f{f~fzf}f|f}f{f~fyffzf~f{f}f{f}f|f|f|f|f|f}f|f{f~fyffyffyffzffxffsffgfffgNDZxx#l{4dfuff{f|f}f{f}f|f|f}f{f}f|f{f~f{f|f~fzf}f}fzffzf}f}fzf~f|fzffxff{f|f|f}f|f|f{f}f|f|ffuffhfffgSq:[v~_ ˓,]cfdff|fzf~f{f}f|f|f|f|f}f{f}f|f{f~f{f|f}f{f}f|f|f|f|f}f{f}f|f{ffyffyffxffyffyffyffuffhfffgWM pI\z{=$KagKffsff|f|f|f|f|f}f|f|f|f{ffyffyffzf~f{f|f}f{f~fzf~f{f|f~fzf~fzf~f{f}f|f{f~fzffxffsffeff(fg[ ҙbx~}kK^UgfUg^Ip|xcϙ[g+ffifftff{f{f~fzf~f{f}f|f|f|f|f|f~fyffyff{f|f}f{f}f{f~f{f~fxffuffuffxf~f{f~fzffwffsffOfgNa;$♑zzt\ߙMlO Wgffdffsffxffxffyffzf~fzf~f{f|f~fyffxff{f|f}f|f{f}f|f|f~fxffzf}f|f|f{ffyffyffyffxffeff_c,^y[9rSgffgffxf~f{f|f|f}f}fzf~f{f|f~fyffyf~f{f}f{f~fyffxffyf~f|f|f{ffxffyf}f|f|f|f}f{f~fzf~fzffuffdw4qӠ)z}v]HNgffiffuffyf~f{f~fyffxffzf}f{f}f|f}fzffxffxffzf~f{f|f}f{f~fzf~fzf~f{f}f|f{f~fzf~f|f{f~fxffsff;۵䘴o~{`ؙmg|Hmg2ffjfftffyf}f|f|f|f}f{f~fyffxffzf|f}f{f}f}fzf~f{f}f|f|f|f|f}f|f{f~fzf~f|f{f}f|f|f|f~fwffkffEffBCǙfz}gșDBfEfflffvffzf}f}f{f}f{f}f}f{f}f{f~fzf~f{f|f~fyffzf~fzffyffzf}f}fzffzf}f|f|f}f{f~fyffuffjff-frgH`sәcys昴;fpffxf|f}f|f|f~fxffwffyf~f{f}f{f}f|f}fzffyff{f{ffxffwffxffyf~f{f~fzf~f{f|f~fzf}f}fxffiff!fgNDZw}}x"j}4dftff|f{f~fzf~f{f}f|f{f~f{f|f}f{f}f|f|f|f|f}f{f~fzf~fzffyff{f|f|f|f}f|f{f~fyffwffxffuffhfffgSu7\x}b ˕,Zcf`ffwff{f}f{f}f|f}fzffzf}f}fzf~f|f{f~f{f{ffyffzf}f|f|f}f{f}f{f}f}f{f}f|f{ffxffwffuffhfffgWL oKߙ_|~}w:$OagPffsffyffyffxffzf}fzffxffwffyffyf~f{f|f~fzf~fzffyffzf}f}f{f}f{f~fzffyffyffvffjff,fg[~ љby|lK^Wg;ffnffxf~f{f}f|f|f}fzf~f|fzffxffyf~f{f|f~fzf~f{f|f}f{f}f|f|f|f}f{f}f{f}f|f|f}f{f}f|fzffpff;fXg^Hnzbҙ}[g,ffhffrffxffzf~fzf~f{f}f|f|f{f~f{f}f|f{f}f}fzffyffzf~fzffyffzf~fzffzf}f{f~fzffvffqffLfgLa>$晎{z]KnN Wgffefftff{f|f}f|f{f~f{f|f}f{f}f}fyffyf~f|f{f~f{f|f}f{f~fzf}f|f}f{f}f{f}f|f|f|f}fzffyfftff^ffXc,]~w\8tSgffhffuffxffzffzf}f|f{f~f|f{f~fzf}f}f|f{f~fzffyffyffzf|f}f{f~fzf~f{f}f|f|f{ffyf~f|f{fftffd{4mӣ&x~~w]ߙJNg!ffiffuff{f|f|f}fzffzf}f|f|f}f|f{f}f|f|f}f{f}f|f|f|f}f{f~fzf}f}fzffyffzf}f|f|f}f{f~fxffsffnff;۶㘴p~y_ؙmf~Hng0ffkffvffwffwffyf~fzf~f{f}f|f|f|f|f}f{f}f{f}f|f|f|f}fzf~f{f~fzf~fzffyffyffyffxffwffoffIffBAęh}{hřC}BfKffqffwffwffxff{f}f{f}f|f|f}f{f|f}f{f}f|f|f|f|f|f|f~fzf~fzffyffyffxffyf~f{f}f{ffwffkff-fqgH|hlؙa|~~s瘳;fofftffyf~f{f}f{f}f|f{ffwffvffzf}f|f}fzffxffvffxf}f~fzf}f}f{f}f|f{f~f{f}f|f{f~fyffsfffff fgNI]zw$l|4dfuf}f}f{f}f|f|f|f}f|f{f~fzf~f|f{f}f|f{ffxffyffyffzf}f}fzf~f|f{f~fyffxffzf|f}f{f|ffuffhfffgSt8[x`˖,[cfbffvffwffzf}f|f}fzffyffyf~fzf~f{f}f|f|f|f|f}f{f~f{f|f}f{f~fzf~fzf~f{f}f{f}f{ffwffpffefffgWO lMߙ^y}}<$MagNffsffyffyffzf~f{f|f|f}f|f|f|f}fzffyffyf~f{f}f{f~fzf~f{f}f{f~fzf~fzffyffxffxffuffiff)fg[ ϙd{{kF^Zg:ffoffuffwff}fyf~f}fyffxf~f}fzf~f{f|f~f{f|f|f}fzffxff{f|f|f}f{f~fzf~fzffyffwffvffpffffpffwffxffxffwffxff{f|f}f|f|f{f~f{f}f|f{f}f}fzffyff{f{ffxffxffzf~fzffyffwffqff>fUg^Ji{z_ՙ [g*ffjffwf~f{f~f{f}f{f}f{f~f{f|f~fyffzf}f~fyffzf}f|f|f}f{f}f{f}f}fyffxffzf}f|f{f}f|f{ffrffMfgQa7$#ޙxy\KnM Wg!ffjfftfftffvffzf~fzffyf~f}fzffyf~f|f|f|f}fzffzf}f{f}f|f~fyffzf}f~fyffzf~fzffwffuffaff[c,_vZ6uSgffjffwff{f}f{f~fyffyf~f|f{f}f}fzf~f{f|f}f|f|f{f}f|f}f|f{f|f~f{f|f~fyff{f{f~f{f}f|f{f~fyffsffd{4mӤ%yw[FNg"ffkffvffxffzffzf}f|f{f~f|fzffzf}f|f|f}f{f}f{f}f}f{f}f{f~fzf~f{f|f~fzf~fzf~fzffwffvffsffmf f;۱昳p~~y`ؙne~Hog1ffmffvffxff{f|f|f}fzffxff{f|f~fyffxffzf}f|f|f{ffyffyf~f|f|f|f|f|f}fzffxffxffxffpffJffB}Eșf{}gǙE|BfKffpffxffzf~fzf~f{f}f|f|f{ffyffxff{f}f|f{f}f|f|f}f{f}f{f}f}fzffyffzf~fzf~fzffxffrffhff,fsgHemٙ^zo☵;frff{f|f|f|f|f}f{f~fyffxffzf}f|f|f|f}f{f}f{f~fzf~fzf~f{f}f|fzffxffxf}f~fyffzf{ffwffuffiff fgNI]z{&k~4dfsffyffzf~f{f}fzffwffwffzf|f~fzffxffyffzf}f|f|f|f}fzffzf}f|f|f|f}f{f|f~fzffxffuffffffgSs8[v}b ˑ,^cfbffuffyf}f~fxffvffyf~f{f}f{f}f|f}fzffyff{f|f}f|f{f~fzf~f|f{f~fzf~f{f}f|f}fzffvffrffhff fgWK pJ\w9$OagNffuf}f}f{f}f|f|f}fzffxffxff{f|f|f}f{f~fzf~f{f}f{f}f{f~f{f|f|f}fzffwffwffwffxfftffhff'fg[ Йd}mI^Zg8ffmffvffuffwffzf}f|f|f|f}f{f~fzf~fzf~f{f}f{f~fyffyf~f{f}f|f|f}f{f|f~fzffyf~f{ffxffmff7f\g^Gj}}~|eϙ [g'ffeffqffwff{f}fzffwffyf}f}f{f|f}f|f{f~fyffzf|f}f{f}f|f|f|f}f{f}f{f~f{f}f{f}f{ffxffrffMfgOa9$ ᙓw}~x]KnM Wg ffhfftffxffyf~f{f|f~fzf~f{f|f}f{f~fzffxffzf}f|f|f{ffyffzf}f|f|f~fyffzf}f}f{f|f}f|fyff_ffYc,]~wZ6uSgffhffsffwffyffzf|f~f{f|f~fyff{f|f}f|f{f~fzf}f}fzf~f{f|f}f{f~fyffxffzf|f}f{f~fzf~f{f|ffrffd~4kӤ&z}v_ݙJNgffgffuff{f|f}f{f|ffxff|fzffxff{f}f{f}f|f|f|f}f{f}f|f{f~fzf~f|fzffyff{f|f|f|f|f~fyffvffpff;۲嘴o~}cՙoe~Hng1ffmffvffxffxffyffzf}f|f}f{f}f|f{ffyf~f|fzffzf~f{f|f|f}f{f~f{f|f}f{f}f|f}f{f~fyffwffmffFffB~DǙf|~~~|iƙF{BfIffqff|f{f}f|f|f|f}f{f|f}f{f}f|f|f|f|f}f{f}f}fzf~f|f{f~fzf~f{f|f}f{f~fzf~f{f|f~fyffxffuffhff,frgHcpי_w}yk䘲;fqffyffyffzf}f|f|f|f|f~fyffzf~f{f}f|f{f~f{f}f|f|f{f~f|f|f|f{f~f{f}f{f}f|f{f~f{f|f~fxffsffgff fgNG\w~~{$k~4dfsff|f|f|f|f|f}f{f}f|f{f~f{f|f}fzffzf~fzf~f{f}f{f~fzffyf~f|f|f|f|f|f|f}f{f}f|f{f~fzffvffjfffgSs7[y^˕,Zcf`ffvf~f}f{f~fzf~fzffzf}f}fyffwffxf~f|f|f|f}fzffzf}f|f|f|f~fzf~fzf}f}f{f~fzf~f{f{ffvffiff fgWL pI\z|<$NagNfftffzf~fyffyffyffzf}f}fzffyf~f|f|f|f}f{f}f|f|f|f~fyff{f{ffyffzf~fzffyffxffvffmff+fg[ әaz~~oG^Xg;ffpffxffzf}f}f{f|f}f{f}f}fyffwffzf~fzf~fzffzf}f|f{ffyf~f|f{f~f{f}f{f}f|f|f}f{f~fyffnff;fWg^Kk|~~~~xcЙ [g(ffjffuffvffwffxffzf~f{f|f}f{f}f|f|f|f}f{f~fzf}f}fzffwffvffzf}f{f~fyffzf|f}f|fzfftffPfgNa<$♒xz\JnO Wg ffifftffxff{f|f|f}f{f~fzf}f|f|f|f|f|f}f|f{f~fyffxff{f|f}f{f~fzffxffzf}f}fzf~f{f|ffwffbff]c,^vW5uSgffeffrffwffzf}f|f|f|f|f|f}f{f}f{f}f|f|f|f}f{f~fzf~f{f~fyffxffzf|f}f{f}f|f}f{f}f{f}f}fzffsffd}4lӤ%v{~x\HNgffhffuffxff{f}f{f}f{f~fzffyffzf}f}fzffzf}f|f|f}f{f|f~fyffwff{f}f|f|f|f|f}f{f~fzf~f|fyffqff;۴䘵n|za֙odHpg.ffiffxf|f~fzf~f{f}f{f}f}fyffwffzf~fzffxff{f{f~fzffyffyffyf~f{f}f|f}fzffyffyffuffnffJffB}Dřj|~zgƙB~BfJffrffzf}f{f}f}fzffyffzf}f|f|f}f|f{f~fzf~f|fzffxff{f|f}f|f{f~fzffyffyffxff{f|ffuffkff0fogHarԙb{t꘯;fqfftffwff{f|f}f{f}f{f~fzf~f{f|f}f|f|f}fzffzf~f{f|f}f{f~fzf~f{f}f|f|f|f}f{f~fzf~f{f|f~fxffjff fgNG[xx!i}4dfwf|f}f|f|f}fzffzf}f|f|f|f~fyffzf~f{f}f{f~fzf~f{f|f}f|f|f|f}fzff{f|f}fzffzf~fzffxfftffifffgSs7Zuy|b ˑ,^cfaffvff|fzffxff{f|f}f|f{f}f}fzffwffxff|fzffyffzffyffyffzffyffzf|ffxffwffuffffffgWM oK]y}~}y:$PagQffsffvffwffzf}f}fzf~f{f|f~f{f|f|f|f}f|f}fzf~f{f}f}f{f|f}f{f~f{f|f|f}f|f{f~fyffwfftffiff*fg[ Йd{}mF^Yg:ffmffvffzf|f}f}fzffxffzf~fzf~f{f}f{f~fzf~f{f|f~fyffxff|f{f}f|f|f|f~fyffxffwffuffnff8f[g^Gk{zfΙ [g+ffjffuffyffzf|f~fzf~f{f}f{f}f|f{ffyf~f{f|f}f|f|f|f|f|f}f{f}f|f|f|f|f|f}f|f{f~fyffvffqffKfgJa?$䙐z~v[LlP Wgffgffsffwffyffzf}f|f|f|f}f{f}f{f~fzffyf~f|f{f~f{f{ffyffzf|f}f|f}f{f|f|f}f|f}f{f|f~fxffbff]c,_xY5vSgfffffrffwffyffyffxffyffyffzf~f{f|f}f|f|f|f|f}f|f|f{f~f{f~fyff{f|f}fzffzf}f|f{f~fzffuffdz4nӣ%x}w\INg"ffiffuffyffyffzf~f{f}f{f~fzf}f}f{f~fzf}f|f|f}f|f{f~fyffyffzf|f}f|f}f{f}f{f}f|f|f~fxfftffpff;۱嘶ky{_ٙmeHsg,ffhffrffvffzf~fzf~f{f}f|f{f}f|f}f{f}f{f~fyffyf~f{f}f{ffxff|fzffxf~f}fzffxfftffrffoffLffB}DǙf|~xdəFzBfMffpffvffyf~f|f{f~fzf~f{f|f~fyff{f{ffxffzf}f|f|f|f|f}f{f}f|f|f|f|f|f}f|f{f~fzffxffvffiff+ftgHbqԙc{~o㘶;fsffzf}f{ffwffxf~f{f}f|f|f|f}f{f}f|f|f}f{f|f}f|f|f|f}fzf~f{f}f|f|f}f{f}f{f}f|f~fyffyfftffefffgNDYz~y'ox4dfwf}f{f~fzf~f{f}f{f~fzf~fzffyffxffzf~f{f}f{f}f|f{f~fzf~f{f}f{f}f{f}f|f|f~fwffuffvffrffefffgSs8]x~]˒,^cfcffyf}f|f|f|f}f{f}f|f{ffyffzf}f}fzffxffzf~f{f~fyffyffyf~f{f|f~fzf}f}fyffwffxffvffhfffgWM oJ\wz!9$NagLffsff{f}f{f}f{f}f|f|f|f}f{f|f}f{f~f{f|f|f}f|f|f}fzffzf~fzf~f|f{f~fzf}f}f{f|f~fzf}f~fvffkff-fg[|ՙ`x|mE^\g7fflffvffvffuffxffxffyf~f|f{f~fzffyffyff{f|f}f{f}f|f|f|f}f{f}f|f|f|f|f|f}f{f~fxffnff:fXg^Imz}}dЙ [g(ffhffsffxf~f|f{f}f}fzffyf~f|f{f~f{f|f|f}fzffyf}f}fzffzf}f|f}f{f}f{f~f{f|f}f{f}f}fzfftffOfgNa;$ᙔu~x[IqJ Wg#ffmffzf}f|f}fzf~f{f|f~f{f{f~fzf~f{f}f{f~fzf~fzffyffyf~f{f}f|f}fzffyffxff{f|f}f|fzffvffaffZc,`~x[:qSgffkffxf~fzffxff{f{ffyffyff{f{ffxffyffyffxff{f|f}f|f{f~fzf~f|f{f~fyffzffyffyff|fuffd}4kӥ%x~}x]HNg!ffiffuffzf}f|f|f|f}f{f~fzf~f{f}f{f~fzf~f{f|f~fyffxff|fzffyffzf~f{f|f~fyffyf~f{f}f{fftfflf f;۴㘶m}y`ؙndHug*ffgffrffwffxffyf}f~fyff{f{ffzf}f|f{f}f}f{f}f|f{ffyf~f{f}f|f|f}fzf~f{f}f|f|f{f~f{f{ffoffHffB}Dři{}}ziřE{BfLffpffvffwffzf}f|f|f}f{f}f{f}f}fzf~fzf~f{f}f|f{f~fzf~f|f{f~fzf}f~fxffvff|fzf~f|fzffuffkff.fqgHcqԙc{}p꘮;fqffwffzf|f}f{f~fzffxffwffyf}f}f{f}f}fzf}f~fyffxff{f}f|f{f~fzf~f|fzffxff|fyffuffsffgfffgNH[v~}&l}4dfqffyffyffwffvffvffyf|f~fzffyf~f{f|f~fzf}f|f|f|f}f{f~fzf}f|f}f{f~fyffyf~f|f|f{ffvffgfffgSx4Xw^˖,Zcfaffyf|f}f{f}f|f|f|f|f}f{f}f|f|f}f{f}f{f~f{f}f{f}f{f~f{f|f}f{f~fzf}f}fzffxff{f{ffxfftffhff fgWJ rGZx}}z:$PagQfftffxff{f|f|f}f|f{f~f{f|f}f{f}f|f|f|f}f{f|f~fyffyf}f~fxffzf|f~fzf}f}fzffzf}f|f}fyfflff+fg[ ϙcx||ymI^Ug$LagLffofftffxf~fzffyff{f|f|f}fzffzf}f}fzf~f{f}f|f|f|f|f}f{f}f|f|f}f{f|f~fzf~fzffxfftffkff+fg[ Ιf|~kJ^Yg9fflffwffzf}f|f|f{ffyffzf}f}fzffzf}f}f{f}f|f|f|f}f|f{f~fzf~f{f|f}f{f~fzf~fzf~f{ffvfflff9fYg^Hk}{cљ [g-ffnffwffzf}f{f}f{f~f{f|f|f|f}f{f}f|f|f}fzf~f{f~fzf}f}fzf~f{f}f|f|f|f|f}f{f}f{ffxffvffqffOfgPa;$晎{}w\JoM Wgffhffuffyf~f{f}f{f}f|f{f~f{f{ffvffwff|fyffxffxff{f}f{f|f}f|f}fzf~f{f}f|f|f|f|f}f|fzffcff]c, a~{u[9rSgffiffwff{f|f}f{f}f|f|f}f{f}f|f|f}f{f}f|f{f~f{f|f~fxffxff{f{ffyffyffzf~f{f|f}f|f{f~fzffsffd4hӨ"w~z]FNg!ffgfftffxffzf~fzf~f{f}f{f}f{f}f|f{ffyf~f{f}f|f}fzf~fzffzf}f|f{f~f|f{f}f{f~f{f}f{f~fyfftffmff;۲昳p}}x`יocHpg0ffnffwffxffyffzf}f{f}f}f{f}f{f}f|f|f}f{f~fzf~fzffzf~fzf~f{f}f|f{f~f{f}f{f}f{ffyffxffqffJffB|Eșgz{ięB~BfMfftff|f|f|f~fxffxff{f{f~f{f|f~fyffyf}f}f{f~fzf~fzf~f{f}f{f~fzf~f{f}f|f}fzf~f|f{f~f{fzffiff-frgHbrԙb{o嘳;fqffxffyffzf}f|f}f{f}f{f}f|f|f|f}f{f~fzf}f|f|f}f|f{f}f|f|f}f{f}f|f|f}fzffyffyf~f{f|f~fxffhfffgNLݙ_x~}}|%i4dfvfzffuffwffzffzf}f|f{f~f|f{f}f|f{f~f{f}f|f{f}f{f~fzffyf~f{f}f|f|f|f|f~fyffxffyffvffffffgSu8]x]˖,Zcfafftffvffyf}f|f}fzffyffyffyf~f|f{f}f}fyffwffzf}f|f}fzf~f{f}f|f}fzf~f{f}f|f}fyffsffffffgWL pJ^z~}x 8$QagPffsffyff{f|f|f}fzffzf~fzffxffzf}f}fzf~f{f}f|f|f|f|f}f{f}f|f|f|f|f|f}f{f~fzf~fzffwffiff'fg[ љbwz|lG^Zg9ffnffxffxffwffzf}f}fzf~f{f}f|f|f|f|f}f{f}f|f|f|f|f|f~fyffzf}f|f}fzffyffzf~fyffvffnff=fVg^Fn|zbҙ [g)ffjffwf~f|f|f}fzffyff{f|f|f|f}f{f~fyffyf~f|fzffxff{f}f{f}f{f}f}fzffyffzf~f{f}fzffpffIfgMa:$㙐{~|w[GsI Wgfffffuff|f|f{f~fzf~f{f|f}f|f{f}f|f|f}f|f{f~fzf~f{f}f{f~fzf~f{f|f}f|f|f}f{f}f{f~f{f|f|f~fwff]ffWc, azZ6uSgffdffsffvffwffwffzf|f~fzf~fzf~f{f}f|f{f~fzf~f{f|f~fzf~fzf~f{f~fzf}f|f|f}f|f{f~fzf~f{f}f|fxffd{4lӤ&y~x\FNg!ffiffvffzf~fzf~fzffyffzf~f{f|f}f|f|f}f{f|f~fyffyf~f{f|f}f|f|f|f|f|f}f|f|f|f|f}fzffwffvffsff;۴䘵n|~}y`ؙmg|Hpg-ffgffsffzf}f}fzf~f{f}f|f{f}f|f|f}f{f|f}fzffyf~f{f|f}f|f}fzffxffxffxffxffwffwffvffoffIffBAęh{}yiřE{BfJffoffwffyf~f|f|f}fzf~f{f}f|f}fzf~f|f{f~fzf}f~fyffzf}f|f}f{f}f|f{ffxffzf}f|f|f}fzffvfflff1fpgHarՙaw}}p䘳;fmffvff{f{f~fyff|f{f~fyffzf~f{f|f}f{f}f|f|f|f|f}f{f~fzf~fzffyffzf~fzf}f}f{f~fyffyffvffjff$fgNF]x}~{'m{4dfyfzffzf~f{f}f{f~fzf~f{f}f{f}f{f~fzf~f{f|f}f{f~f{f|f|f}f{f~fzf}f|f|f|f}fzffyffwffxffvffhfffgSt6Yu|~[˗,Ycfaffwffzf~f{f}f{f~fzf~f{f}f{f~fzffxffzf}f|f{f~f{f}fzffzf}f}fzffyffzf~f{f|f|f~fxffrffefffgWL nLߙ^y~z<$MagNffrffwffxf~f|f|f}f|f{f}f|f|f}f{f~fzf}f}fzffwffxffyf~f|f{f~f{f}f|f|f|f}f{f}f|f|f}fyffnff,fg[ ҙc}}{lL~^Ug=ffnffuffyf~f{f}f|f{f~f{f|f~fzf}f}fzf~f|f{f~fzf}f|f}f{f}f{f}f|f}fzffyff{f|f}f|fzffwffoff$晏z}xZJmP Wgffiffvffyf~f{f}f{f~fyffzf|f~fyffyf~f{f}f|f{f~f{f}f{f}f|f|f}f{f}f|f|f}fzffzf~f{f|f{fftff^ff[c,\z]8tSgffgffvffzf~fzf~f{f|f~fyf~f|f{ffyffyff{f|f~fyffxffzf|f~fzf~f{f}f{f~fzf~f{f}f{f}f|f|f{ffrffd4jӥ%y~~~y^GNg"fffffsffwffyf~f|f{f~fzf~f|f{f~fzffyffzf}f}fzf~f{f|f~fyffxffyffyffxffzf|f~fyffxffvffqff;۲瘱r}xa֙pcHqg.ffifftffxff{f{ffxffzf}f|f{f}f}fzffvffvffyf}f}fzffyffzf~fzf~f{f}f|f{f~fzffyffvffoffJffB|Dęj}~}ziÙ@BfIffnffvff{f|f|f|f}f{f~fyff{f|f~fxff{f|f}fzffzf~fzffyffxff{f}f|f{f~fzf~f|f{f~fzf~fzffpff3fmgHbsҙd}o㘵;fpffuffxffzffzf}f{f~fzffyffzf}f|f|f}fzffzf}f|f|f|f}f{f}f|f|f{ffyffzf}f|f|f}f|fzfftffefffgNIߙ_yz"hӀ4dfrff{f|f~fzf}f|f}f{f}f|f{f~f{f|f}f{f}f}f{f}fzff{f|f~fxffxffzf~f{f}f{f}f{f~f{f}f{f|ffuffbfffgSv4Yx}~a˕,\cfeffzf|f}f{f~fzf~f{f}f{f~fyffzf|f}f|f{ffxff|fzffzf}f}fzf~f{f}f|f|f|f|f}fzffyffwffrffefffgWL oJ]x{8$QagOfftffzf}f|f|f}f{f}f|f|f|f|f}f|f|f{f~f{f}f{f}f{f~fzf}f}fzffyffyffzf~f|fzffyffwffsffhff(fg[ЙczyhF^Yg:fflffuffzf~fzffzf}f{f}f}fzffyffzf~fzf~f|f{f~fzf~fzffzf}f|f|f|f}f{f}f|f|f|f|f|f~fxffmff8fZg^Ioyaә [g)ffiffsffuffxff{f{f~f{f}f|f|f|f}f{f}f|f}f{f|f|f}f|f|f|f|f|f|f}f|f|f{f}f|f}f{f}f{f}f{ffsffLfgNa:$ ᙒxz_ݙOjQ Wgffjffvffwffxffxffyf~f}fyffyf~f|f|f{ffxffuffyf}f}f{f|f~fzf}f|f|f}f{f}f{f}f|f|f{ffuffaffZc,^z\9qSgffhffvffyffxffxffzf~f{f{f~fzffyffzf~fzf~f{f}f|f{f~f{f}f|f{f~fzffyffzf}f}fzffyffzfftffd}4kӥ%zxZGNg ffkffvffyf}f}f{f}f{f~f{f}f{f}f{ffyf~f{f}f|f}fzf~f{f}f}fyffzf~f|fzffyffxffwffxffxfftffpff;۰蘱rz`ؙne~Hog1ffmffxffzf~fzffyff{f{ffxffwffxffzffyffzf}f}f{f~fyffyffzf|f~fzf~f{f}f{f}fzffwffnffGffBAřhz|hƙD}BfKffrffzf~fzf~fzf~f{f|f}f{f}f{f}f{f}f|f|f|f|f|f}f{f}f{f~fzf~fzffzf~fzf}f|f~fyffwffxffuffjff/fpgHdoؙ^t}s阯;fqffzf}fzffzf}f~fxffzf}f|f|f|f}f{f~fzf~fzf~f{f}f|f{f~fzf}f~fyffxff{f}f|f{f}f|f|f~fxfftffiff!fgNIߙ]w|}~w#j4dfrff{f|f{ffwffvffyf~f{f}f{f~fyffzf~f{f}f{f}f|f|f}f|f{f~fzf~f{f}f{f}f{f~fyffuffrffnffcfffgSs8\x~^ˑ,]cf^ffsff|f{f}f|f|f|f}f{f|f}f{f}f|f|f{f~fzffyffzf~fzffxffvffyf}f}f{f|f~fyff{f{ffzf{ffkff fgWM oI[xx<$NagPfftffyf~f{f}f{f~fzf~f{f}f{f~fzf~f|fzffxff|fzffyff{f}f{f|f}f|f}f|fzffzf~f{f}fzfftffjff+fg[ ҙc{{oD^[g8fflffvffxffzf~f{f}f{f~fzf~f{f}f{f}f|f{ffxffxf~f{f~fzffyf~f|f{f~fzf~f{f}f{f~fyffvffoff;fYg^Fpzf̙[g*ffkffuffuffwff{f|f}f{f}f|f|f|f}fzffzf~f{f}f{f~f{f|f~fyffyf~f{f|f}f|f|f|f|f|f}f{f}f~fuffPfgQa9$噎||v\JpK Wg!ffgffrffyf|f~fzffyf~f{f~fzf~fzf~f|f{f}f|f|f}f|f|f{ffxffxffxffyf~f{f}f|f|f|f|f}f{f~fxffbffYc,`~zY3vSgffeffuff{f|f}f{f}f{f~fzf~f{f|f~fyff{f|f}f|fzffvffxffzf}f}f{f}f|fzffyf~f{f}fzffzf~f{f~ftffd|4mӢ(y~{\ENg ffhffsffwffxffyffxffzf|f~fyff{f|f}f{f|f}f}fyffvffzf}f|f|f{f~f{f}f|f{f~fzf~f{f}f{ffvffpff;۱瘲pxbԙqcHog0fflffsffsfftffxf~f{f}f{f~fzf~f{f}f{f}f{f~fzffyffyffzf~f{f|f|f}f{f}f|f{ffxffyffxffpffHffB?řgz|ięABfGffnffyffzf}f|f{ffyf~f|f{f~fzf~f{f~fzf~fzffzf}f}fyffwffyffzf~f{f|f}f{f}f}fzffwfftffkff0fogH}fn֙bzs昳;fofftffxf~f}fzf~f{f|f~fyffzf~f{f|f}f{f~fzffyffxff{f}f|f{f~f{f|f}f{f}f|f|f|f|f|f}f{f~fxfflff"fgNDZxx%n{4dfuf~f{f~fyffyf~f|f{f~fzf~f{f}f|f{f~fzf~f{f}f|f|f|f}fzffxffyf}f~fyffzf}f}fzffxffvfftffifffgSv3Vu}` ˓,\cf`ffxf~f{f~fyffzf~fzf~f{f}f{f~fyffyf~f|f{f~fzf~f{f|f~fzf~fzf~f{f~fyffzf~f{f}f{ffwffpff`fffgWJ qI]z}>$LagMffoffuffyffyffzf}f}f{f}f{f~fzf~f{f|f~fyffxffxffxffzf}f|f}fzffzf}f|f|f}f{f}f|f{ffoff0fg[~ љd||lI^Yg9ffmffwff{f|f|f}f{f~fzf~fzffyffzf~f{f|f}f|f|f|f|f|f}f|f{f~fzf}f}f{f}f|f{f~fzffxffuffmff;fXg^Hl}x_ԙ [g*ffkffuffwffvffxffzf}f|f{ffxffwffzf~fzf~fzf~f|f|f|f|f|f}f{f~fzf~f{f|f~fzf~fzffxffpffKfgMa;$噎|{xt]ޙOjP Wgffhffvffzf}f|f{ffxffxff{f{ffyffzf~f{f}fzffzf~fzf~fzf~f{f}f|f|f{ffyffzf}f}f{f|f~fxffdff^c,_}}w[7tSgffhffvffyff{f|f}f{f}f{f~fzffyf~f{f|f~fzf~fzf}f}f{f~fyffzf~f|f{f~fyffyff{f|f|f}f{f~fzffuffd{4mӣ'z~x]HNgffgffrffwff|fzf~f|f|f|f|f|f}f|f|f|f|f|f~fzf~fyffyffzf}f{f~fzffzf}f|f}f{f}f|f|f|f~fxffuffpff;۱瘳o~xaԙs`Hpg0ffjffrffwff{f|f}f{f}f|f{ffxffwffyf~f{f|f}f|f|f}f{f|f~fyffzf|f}f{f}f}fzf~f{f}f{f~fxffoffIffB~CǙgz|iƙFzBfIffoffxffzf~f|fzf~f{f}f|f{f~fzffyffzf}f|f|f~fyffyffyf~f|f{f~fzf~f|f{f~fyffxffwffvffmff/fqgHarԙcz~p嘴;fofftffyff{f{f~f{f|f}f|f{f~fzf~f|f{f}f|f|f}f{f}f{f~fzffyffyffxffwffzf~fzffxffuffsffhff!fgNE]z{'nz4dfwf|f~fzf~fyffyffyffzf~f{f|f}f|f|f}fzffyff{f|f}fzf~f|f|f|f|f|f}f{f}f{ffyf~f{f}f{ffvffhfffgSq8]{\˖,\cfeffyf~fzf~f{f}f|f|f{ffyffzf}f|f}f{f}f{f~f{f|f}f{f}f|f|f|f|f}fzffxffyf~f{f}f}fzf~f{f{ffkff fgWK pJ\w~~y<$KagKffqffzf}f}f{f}f{f}f}f{f|f}fzffxffzf~fzffyffyffyf~f|fzff{f{ffwffyf}f}fzffwfftffgff&fg[ ЙcznK^Zg9ffnffyf|f~f|f|f|f{f}f}f|f{f~fyff{f|f}f{f}f{f~fzffyffzf}f}fzffyffzf~fzf~fzffwffuffoff:fZg^Ify}wcϙ [g(ffiffvffyffyff{f|f|f}f{f~fzf}f}f{f}f|f{f~fzf~f{f}f{f}f|f{f~f{f|f~fyffyffxffyffxffqffOfgQa8$!ᙒx{aܙPjO Wg ffgffrffwffzf~fzffyf~f|f{f~fzf~f|fzffyffyf~f{f|f}f{f~fzf~fzf~f|f{f~fyffyf~f{f}f|f|f{fffff]c,a}tW5uSgffgffvf~f}f{f}f|f|f|f}f|f{ffwffuffvffxffwffyf}f}f{f}f}fzffzf}f|f|f}f{f~fzf}f}fzffzf}fxffdz4mӤ'|~xYB"Ngffeffsffyf~f{f}fzffzf~f{f|f|f}f{f}f}fzf~f{f|f~fzf~fzffyffzf}f}f{f|f}f{f}f|f|f|f|f}fzfftffnff;ۯ阱q}v]ٙncHrg.fflffwffzf}f|f}fzf~f|f{f~fzf}f~fxffvffvffzf|f}f{f}f|f|f|f|f}f{f}f}fyffyf~f{f~fyffuffnffIffB{FǙh{~kÙB}BfMfftff|f{f}f|f|f}fzffyffzf}f}fzffyff{f{f~f{f}f|f{f}f|f}f{f|f}f{f~fzf~fzffzf}f|f{ffxfflff/fpgHenי`x~~~n;fqffuffxf~f}fzf~f|fzff{f{ffxffzf}f|f|f|f}f{f}f|f|f}f{f}f{f~fzffyf~f{f}f}fyffxffwfftffffffgNG]v~x"j~4dfsffyffzf~f{f{f~f{f~fzf}f|f{ffxffxff|f{f~fzf~f{f}f{f}f|f|f}f{f}f{f~f{f}fzffxffxffwffgfffgSu5Wt^˕,\cfaffqfftffxffzf~f{f}fzff{f|f}fzf~f|f|f}fzf~f{f}f|f}fzffyffzffyf~f{f}f|f}fyffvffuffkff"fgWL oJ\yy;$MagLffpffwffxffzf}f}f|f{f~fyff{f|f}fzf~f|f{f}f|f{f~f{f|f}f{f~fzffxffzf~fzf~fzffyffxffmff-fg[ ϙe}~nI^Xg;ffmffvff}fyff{f|f}f{f|ffxffyf}f~fzf~fzf~f{f}f}fzf~f{f}f|f|f}f{f}f{f~fzffyffyffwffmff;fXg^Jm}{dϙ [g,fflffxf~f{f~fzf~f{f}f{f~fzf~f{f|f~fzf}f|f|f|f}f{f~fzf}f|f}f{f~fyff{f|f|f|f}fzffwffuffpffLfgQa7$"ᙐ|z^KnN Wgffdffqffvffyffzf~fzffxffxff{f{f~f{f|f}f{f}f}fzf~f{f|f~fzf}f|f|f|f}f{f}f|f|f|f|f}f}fxffaffZc,`}|x[5wSgffhfftffvffxf~f{f|f~fzf~fzf}f}f|f{f}f{f~f{f}f{f|f}f|f|f}fzffyffzf~f{f}fzffxffyffyffzfzffdz4kӧ"v~z^ߙINgffkffyf~fzffyffzf~f{f}f{f}f|f|f|f}f{f}f{f~fzf~f{f|f~fzf~f{f}f{f}f|f|f}f{f|f}f{f~fzffxffwfftff;۲瘲pz}{bՙqaHpg0ffnffvffvffvffzf}f|f|f}f{f}f|f{f~f{f}f|f{f~fzf~f|fzffxff{f|f~fyffzffyffyff{f}fyffpffJffB~Aęiz}wb˙E}BfJffpffvffxff{f}fzffyffzf~f{f|f|f|f}f|f{f~fzf~f{f}f{f~fzf~f{f|f~fyffzf}f}f{f|f~fxffuffjff1fmgH{gnי_x~~r蘰;foffuffxffyf~f|fzffyf~f{f}f{f~fzf~f{f}f{f}f{f~f{f|f}f{f}f|f|f|f}f|f|f}fzffzf~fzffxfftffiff"fgNH]x~~~{$i~4dfwfzffxffzf|f}f{f~f{f|f}f{f}f|f|f|f}f{f}f{f}f}fzf~fzffyffyffzf~fzf~fzffyffxffxffsffdfffgSt7Zx^˕,[cfdffzf{ffxffxffzf}f|f|f|f|f|f}fzffyffzf~f{f}f{f}f|f|f}f{f}f{f~fzf~f{f|f}f|f{ffwffsffffffgWN oHXt}~{9$PagMffrffyf~f{f|f}f{ffxffyf~f}fyffzffyffwffyf~f{f}f{f~fzf~f{f}f{f}f{f~f{f|f|f}f{ffwffnff.fg[֙]y|nJ^Vg;ffkfftffxffyffzf|f~fzffzf}f{f~f{f}f|f|f{ffyf~f|f{f~f{f{f~f{f}f|f{f~fzffzf}f|f}fyffmff7f]g^Em}cҙ~[g-ffkfftffwff|f{f}f|f{f~f{f}f|f{f}f|f|f~fyffzf}f~fyffwffyf~f|f{f}f|f{f~f{f}f|f{f~fyffqffKfgNa:$㙏|~x\LlP Wgffhffsffvffyf~f{f|f}f{f}f}fzf~fzf~f{f}f|f|f|f}fzffzffyffyff{f}f{f}f{f~f{f|f}f{f}f}fyffdffZc,[|]8telektroid-2.0/test/res/square_loop.wav000066400000000000000000002736541416764361200202100ustar00rootroot00000000000000RIFFwWAVEfmt wJUNK4smpl<aQ<C!datawSgffhffuff{f|f}f{f|f}f|f|f|f{ffyffzf~f{f|f}f{f~fzf~f{f|f}fzffxffxffyffzf~f{f|f}f{ffyffuffd|4lӤ&{{^GNg ffgfftffxffwffyf}f}fzffzf~fzf~f{f}f|f{f~fzf~f{f}f{f~fzf~f|fzffxffyffyffxffxffvffrffnff;۱昴o}zcԙqbHqg.ffkffxffyffzf}f}f{f}f|f{f~fyffzf}f|f|fzffyffzf}fzffxffyf}f}f{f}f{f~fzffyffyffvffpffJffBCǙew{}xhęB~BfJffpffwffyffxffyf~f|f|f|f}f{f}f|f|f|f}f{f}f|f{f~f{f}f{f~fyffyf~f{f}f{f~fzf}f|f|f}f}fwffhff+frgH~epԙdz|}}~~s昳;fqffwff{f|f}f{f}f|f|f}fzf~f{f}f|f|f|f|f}f{f}f|f{ffyffzf|f~fzffyffyffzf}f|f|f|f}fyffqffdfffgNH^z{%iӀ4dfrffwffyf}f}f{f}f{f}f{ffyf~f{f|f~fyffxffzf~f|fzffxffyf~fzf~f{f}f|f|f|f|f~fxffuffrffgfffgSu7^{_˖,Zcfaffwf~f|f|f}f{f~fyff{f|f~fyf~f|f|f}f{f}f{f~fzf~f{f|f~fyffxffzf|f~fzf}f}fzffyffxffsffgfffgWL pHZt}=$NagNffrff{f{f~f{f|f~fyffxffzf}f|f{f~f{f}f{f}f{f~f{f|f}fzffxff{f|f~fyff{f|f~fxffuffrffjff)fg[ ҙcz~lH^Zg:ffnffxf~f|f|f|f|f|f}f|f|f|f{ffyffzf}f}f{f}f{f}f|f}f{f}f{f}f|f|f}f{f|f}f{ffxffxfftffiff5f\g^Hn|}y`ԙ[g'ffiffuffxffyffzf}f|f|f|f}f|f|f{f~fzffyf~f{f~fyffxffwffzf}f}f{f|f~fxffvffwffwffrffLfgMa:$!ᙑz~~y[KnN Wgffefftff|fzffzf}f|f|f|f}f{f}f|f|f|f|f~fyffwffxf~f|fzffyffyffzf~f|fzffwffwffxffxffdffZc,\}{x\7tSgffifftffxf~f|f}fzffyffyf~f{f}f|f}fzffyffxffxffyf~f{f}f|f{f~fzffzf}f|f|f|f~fyffzf}f}fvffd|4jӦ%zuYFNg!ffjffxf}f~fxffxff{f{ffyffyffzffzf|f~fzf~f|fzffxffyf~f{f}f|f|f|f|f}f{f~fzf~f{f}f{f~fxffrff;۳嘵n~{dԙpdHrg.ffmffyf~f{f}f{f~fzf~f{f}f{f}f{f~f{f|f|f|f}f|f|f|f|f}f{f~fzf~f{f|f~fzf~f{f|f}f{f}f}fzffxffsffLffB?™j||hƙBBfHffoffyf~fzffyffzf}f|f}fzf~fzffzf~fzf}f}f{f~fzf~fzf~fzffyffzf}f|f{f~f|f{f~fzf~fzffvffkff/fqgHcoי`z{l㘴;fofftffzf}f|f}fzf~f|f{ffxff{f|f~fyffwffyf~f|f{f}f|f|f|f}f{f}f|f{ffxffyff{f{f~fyfftffhff fgNH[v}z$l|4dfrffzf}f}fyff{f|f}f{f|f~fzf~f{f|f}f{f~fzf~fzffyffzf~f{f|f}f{f}f}fyffyf~f{f~fxfftffsffhfffgSt7[y]˗,[cfbffvffyf~fzffyff{f|f}f{f}f|f|f}f{f}f|f{f~fzffyf~f|f{f~fzf}f}fzffyf~f|f{f~fzffxfftffhfffgWM nLߙ^yz=$JagJffrffzf~fzffyffzf}f}fzf~f|f{f~fyff{f}f{f}f{f~fzf~f{f}f|f|f{f~fzffwffwffyffxfftffjff)fg[ әazzkI^Wg;ffoffxf}f}f|f|f|f{f~f{f}f{f}f{ffyf~f{f|f~f{f{f~f{f|f~fyf~f}fyffzf|f}f|f{ffxffyffwffmff9fYg^Im|~~zt]ՙ [g+ffjffsffyffzf~fyff{f}f{f}f{f~f{f}fzffzf~f{f|f}f|f|f}fzf~f|f{ffyf~f{f}f|f|f}fzffvffoffJfgKa=$㙒x}w]LlP Wgffgffvffzf~f{f|f~fzf}f|f|f|f~fyffzf}f}f{f|f}f{f~fzf~fzf~f|fzffxff{f|f}f{f~fzf}f}fzfftff^ffXc,_x[6tSgfffffrfftffxf}f}f|f{f~f{f|f|f~fyffyf}f}f{f~fzf}f|f|f}f{f~fyffwffwffzf|f}f|f|f}fzffyffrffd|4mӢ({}uZENgffeffrffvffzf|f~f{f|f}f{f}f|f|f|f}f{f}f{f~fzf}f}f{f}f{f|f~fzf~fzf~f|f|f|f}fzffzf~f{f|f}fzffsff;ۯ蘳n|{bՙpcHpg.ffiffsffyf~fzffzf}f}fyffzf}f|f{f}f}f{f}f{f}f|f}fzf~f|f{f~fyff{f}f{f|f}f|f|f|f|f|ffvffmffGffBBǙh~ygǙC}BfMffsffyffyffzf~f{f|f}f{f}f|f|f|f|f}f{f~fzf}f}f{f~fzf}f|f}f{f~fyffzf~f|fzffzf}f}fyfftffiff-ftgH`rՙ`x|p䘵;fqffuffxffzf~fzffyffzf~fzffzf|f}f{f~f{f|f|f}f{f}f{f~fzf~fzf~f{f}f{f}f|f|f|f|f|f~fyffufflff%fgNG^x}z%k}4dfqffwff{f{f~f{f|f}f|f{f~f{f|f}f{f}f|f|f|f}f{f}f|f|f|f}f{f}f|f|f{ffyffzf~fzffwffxffvffhfffgSu7]}^˗,Ycf_ffvffzf}f}fzffzf}f|f|f|f}f|f|f{f~fzffzf}f|f}f{f}f|f{ffxffyffyffwffvffvffwffuffgfffgWN mK\v~~~}@$JagLffqffxf~f|f{f}f|f|f}f{f}f{f~f{f}f{f}f|f}f{f|f}f|f{ffxffzf}f|f}fzffyffxffyffyfftffjff,fg[ әa|{jD^Zg:ffnffuffwffzf}f}fzf~f{f}f|f|f}f{f}f{f}f|f}f{f|f}f{f~f{f}fzffzf~f|fzf~f|f|f|f}fzffwffnff;fXg^Dm|zbљ [g(ffhffuffyf~f{f}f{f~fzffyffzf~fzffyffzf}f}fzf~f{f}f|f|f|f}f{f}f{f~f{f|f}fzffzffwffpffLfgNa;$䙎}y\JoN Wgffiffvffwffwffzf}f|f|f|f|f}f{f}f|f{f~f{f|f~fzf}f|f}f|f|f{f}f}f{f}f{f}f|f|f|f}f{f~fxffsff]ffXc,]~xZ8rSgffeffpfftffyf~f{f}f{f}f|f|f|f}f{f}f|f{f~f{f|f~fyffzf}f~fyf~f|f{f~f{f|f}f{f}f}fzffxffyf~fvffd|4kӥ&{~x^FNgffjffwffyf~f|f{f}f|f|f}f{f}f{f~fzf~f{f|f}f{f~fzf~f{f}f|f{f~fzffyffyffzffxffyff{f{ffwffoff;۲嘴n}|b֙nf}Hmg1ffkffsffxf}f}f{f}f|f}fzffzf}f}fzffzf}f|f|f|f~fyff{f|f}f{f~fzf~f{f|f}f{f}f|f|f|f|f}fzffrffKffB{Gəd{}gǙD~BfGffoffyf~f{f|f}fzffyf}f~fxffyf~f{f|f}f|f{f~fyffyf~f{f}f|f|f|f}f{f~fzf}f}f{f~fxffwffsffhff-fpgH~dpԙdz~~r瘱;fnfftffxffzf}f|f{f~f{f}f{f~fyffzf|f~fyff{f}f{f}f{f~f{f|f}f{f}f|f{ffxffyf~f|fzffwffrffefffgNIߙ^y~{(ru4dfvf}f}f{f}f|f|f{ffxffzf}f|f{f~f|f{f}f{f}f}fzffyffzf~fzffyffzf}f|f|f}f{f}f|f{ffxfftffiff fgSw4[z^˖,Ycfbffzf|f|f}f{f}f{f}f|f}fzffyf~f|f{ffyf~f{f}f|f}fzffzf~fzf~f{f}f|f{f~fzffyffwffvfftffgfffgWJ qI^y{<$NagNffqffvffzf~fzf~fzffzf}f|f|f}f{f}f{f~fzf~f{f|f}f{f}f|f|f|f}f{f}f|f|f|f|f|f}f{f~fxffsffjff,fg[ ϙe{mG^Xg;ffmffwffyffyffzf~fzf~f|f{f}f|f|f}f{f}f|f|f|f|f}f|f{f~fzf~f|f{f~fzf~f{f}f{f}f|f|f}fxffofffUg^Hq{bҙ}[g0ffoffvffwffyff{f|f}fzffzf~f{f|f}f{f~fzf~f{f}f{f}f|f{ffxffzf}f|f|f|f}f|f{f~fzffyfftffMfgKa>$晎|y^ޙNkP Wgffeffpffuffxff{f}f{f}f{f~f{f}f{f}f|f|f|f}f{f~fyffyf~f|fzffxffyf}f}f{f~fzf~fyffvffrff^ffYc,]}uX5vSgffifftffuffwffxffxffzf}f|f|f|f}f|f{f~fzf~f|fzffxff{f|f|f}f|f{f~fzf}f}f{f}f}fzf}f~fxffpffd}4jӦ$zy^FNgffffftffyffzf~fzf}f|f}f{f~fyffzffxffwffyf}f}fzffyffyffyf~f{f|f}f}fzffyffzf~fzffxffrff;۱蘰s{t]ٙmeHrg.fflffwffxffwffyf}f}fzffzf~f{f|f}f{f}f}fzffyffzf~fzffyffyffzf}f|f|f|f}fzffxffuffnffMffB{DǙg}|fșCBfLfftffyffzf~f{f|f}f|f{ffxffyffzf}f{f~f{f|f}f{f}f|f{ffyffzf}f}fzffzf}f|f{ffyf~f}fvffeff)ftgHdoי`y}~|q꘮;fsffxffzf~f{f|f~fyffyf}f}f{f}f|f{f~fzffzf}f{ffxffxff{f|f}f|f|f}fzffzf~f{f|f}f|fzffuffiff!fgN D\yv!l{4dfuff{f}f{f}f|f{f~f{f}f|f{f}f}f{f}f|fzffyf~f{f|f|f}f|f|f|f|f|f}f|f|f|f|f}f|f|f|f|f}f{ffvffhfffgSv4Wv~_ˑ,_cfcffxf~f{f}f|f{f~f{f}f{f}f{f}f|f|f|f}f{f|f}f|f|f}f{f|f~fzffyffzf}f}f{f|f~fxffuffvfftffifffgWP mK^{x<$MagMffrffxffyffzf}f|f|f}f{f}f|f{f~fzf~f{f}fzffwffwff|fzffxffyffzf}f|f}f{f}f|f{ffwfflff*fg[ ҙbz}mF^[g7ffjffuffwffxffyf~f|fzffxffyf~fzffwffyf}f~fyff{f{ffyffzf}f|f|f|f}fzffzf}f~fvffkff8fZg^Gm~{cљ [g(ffiffvff{f|f~fyff{f|f}f{f}f}fzf~f{f}f|f|f{f~f{f}f|f{f~fzf~f{f}f|f|f|f|f}f|f{f~fzffxffpffJfgMa<$㙒w~~v[MmM Wg"ffjffvffzf~fzf~fzffyffzf~fzf~f{f}f|f{f}f}fzf~f{f|f~fyffyf~f{f}f|f|f|f|f}f{f}f{f~fzffxffdff\c, b~}zZ5wSgffhffuffxffyffzf}f|f}fzffyffzf~fzffyffzf~fzffyffzf}f|f}f{f}f{f}f}fzffzf}f|f|f|f}f}ftffd4iӦ%z~~x\ENgffhffvffzf}f|f|f|f}f|f|f|f|f}f{f~fyffxffyffyffzf~f{f|f}f|f{f~fzffyffxffwffyf~f{f~fxffpff;۳昱s{b֙oe~Hng2fflffuffxffyffyffzf}f|f}f{f}f|f|f}fzf~f{f}f|f{f~fzf~f{f}f|f|f|f|f}f{f~fzf~f{f}f{f~fyffrffNffB~Břgz~}fʙIyBfJffpffxf~f|f{f~f{f}f|f{f}f|f}f{f|f~fyffxffyf~f{f~fyffxffzf}f|f{ffxffxffzf}f|f|ffuffkff1fmgH~fmٙ]v~s蘱;fpffvff|f|f|f}f{f|f}f|f|f}fzf~f{f}f|f|f|f|f|f}f{f}f|f{f}f}fzffyf~f|f{f~fzf~f{f}fzffwfftffifffgNF[xz$i4dfuf}f~fzf~fzf~f{f~fzf~fzf~f|f|f|f|f|f}f|f|f|f|f}f|f{f~fzf~f|fzffzf}f}fyffzf}f|f{f~fzffuffefffgSu5Zx]˓,]cfbffvff|f{f~fzffyffzf}f}f{f~fzf~fzffzf}f|f|f|f}fzffyffyff{f}f{f|f}f}f{f}fzffzf~fyfflff fgWO mL\w|~x<$MagLffqffvffyf}f~fyf~f|f{ffxffyf~f|f{f}f}fyffvffxf~f|f|f|f}fzffyffxffzffyffwffvffmff/fg[}ԙ_w~oE^Xg=ffpffxf~f|f}f{f}f{f~fzffyf~f}fzf~f{f|f}f|f|f|f}f{f}f|f|f|f~fyffyffyffyf~f{f~fzffxffoff;fYg^Ckz~xaҙ~[g-fflffwff{f|f}f{f}f|f|f|f|f|f}f|f|f|f|f}f{f~fzf~f|f{f}f|f|f}f{f}f{f~f{f|f}f|f{f~fzf~f{ffuffOfgNa;$㙑yw[KmN Wg ffgffsffyf}f}f{f}f|f|f|f}f{f}f|f|f}f{f}fzff{f|f|f}fzffwffzf|f}fzff{f|f|f|f}f|f|f{ffwffaffYc,_wX4uSgffgffrffwf~f}f{f|f~fyff{f{f~f{f|f~fyf~f|f|f|f}fzffzf~fzffyffzf~f|fzffyff{f|f}fzffyfftffd{4lӤ%xy^ߙINgffhffsffxf}f~fyffyffyf~f|f|f}f{f}fzffvffwffzf~fzf~fzffyffyf}f}fzffyf}f}fzffzf}f}fxffnf f;ۯ蘱s|fљu^Htg-ffkffuffxffzf}f}fzf~f{f}f|f|f|f}f{f}f|f}f{f~fyffxffzf|f~fyff{f}f{f|f}f|f|f|f|f|f}fyffoffGffB}Eədx~}{hƙE|BfIffpffyffyffxff{f}f{f~fzf~f{f}f{f}f{f}f}fzffyffzf}f|f}f{f}f{f}f|f|f}fzffyff{f|f~fwffmff0fngH~enי`yp阯;foffwf~f}f{f|f~fyffyf~f{f}f|f}fzffyff{f|f|f}f{f}f|f{f}f|f|f|f}f{f}f{f}f|f|f}f{f|ffvffqffffffgNJ\w}x$j~4dfuf~f|f{f~fzffyf~f{f}f}fzf~fzffzf}f|f|f|f|f}f{f~fzf}f}f{f~fzf}f|f|f}f{f}f{f}f|f{ffxfftffhfffgSs8]{^˖,Ycf^ffwffzf}f{f~f{f|f}f{f~fzf~f{f}f|f|f}f{f}f{f}f|f|f|f|f|f}f|f{f~f{f|f~fyffzf~fzffwffrffiff"fgWK qGZw}9$PagNffsff{f}f|f}fzf~f{f}f|f{f~fzf~f{f|f~fzf~f{f|f}f|f|f|f|f}f{f}f|f|f}f{f|f}f|f|f{ffwfftffkff+fg[ љby{iK^Vg$㙑xx\LlP Wgffeffrffvff{f{f~f{f|f}f{f|f~fzf}f}fzffyf~f|f|f|f|f|f}f{f}f|f|f|f}fzffzf}f|f|f|f}fzffuff]ffYc,^~{v[9rSgfffffvffxffwffwff{f|f}f|f{f}f|f|f}f{f}f{f~f{f|f~fxffyf}f}fzffzf}f|f|f}f|f|f|f|f}f{f~f|fuffd}4kӥ$vy]HNgffgfftffyffzf}f|f|f}f{f}f|f|f}fzffyffyf~f{f}f{f}f|f|f}fzffxffvffwffzf~fzf~f{f}f{f~fxffqff;۶☷m|zb֙ng|Hmg2ffnffvffwff{f|f}f{f}f|f|f|f}f{f~fzf}f|f}f{f}fzffzffxffyffzf~fyffwffxffzf~f{f}f{ffrffKffB@l{~yhřB~BfKffpffwffxffyf~f{f}f|f{ffyf~f|f{f~f{f|f|f~fzf~fzf~f|f{f~fyffyf~f{f}f|f|f|f|f}f{f}f}fxffiff*fugHcnؙ`}~q蘱;foffsffwffzf}f|f|f}f{f}f|f|f|f}fzffyff{f{f~fzffzf|f~fzf~fzf~f{f}f|f{f~f{f|f~fzf~fzffxfflff"fgNFYv~y%l}4dfsff|f{f}f{f~fzf~f|fzffzf}f~fxffzf}f|f|f|f|f~fyff{f{ffzf|f~fyffyf~f{f}f{f~fyffvfftffjfffgSs7Xs|[˔,]cffff{f}fzffxff{f|f}f|f|f|f|f|f}f}fzf~f{f}f{f}f|f}f{f|f|f}f|f}fzffyffzf~f{f}f{f}f{ffwffkff!fgWJ qI]y|;$NagNfftffzf}f{f~f{f|f}fzffzf}f|f|f}fzf~f{f}f|f|f{ffyffzf}f}f{f}f|f{f~fzffyffyffxffuffkff*fg[Ιez~~ziK^Wg;ffoffwffzf|f}f{f~fzffxffyf~f|f|f|f}fzffzf~f{f|f}f|f{f~fzffzf}f|f{ffyffyffyffuffmff;fWg^Jn}|eЙ}[g*ffkffxf~fzffxffyf~f{f~fzffyffyff{f|f}fzf~f|f{f~fzf~f{f}f{f~fzffxffyffzf}f{ffxffpffLfgMa<$晏zx\JmN Wgffdffqffwff{f}f{f~fzf~f{f}f|f|f|f}f{f~fzf~fzffzf~fzf~f{f}f|f{ffyffzf}f}f{f}f{f~fzffwffaffZc,]{]6wSgfffffuffwffyffzf~f{f|f}f{f~f{f|f|f|f~fyffwffyf~f{f}f|f|f|f}f{f~fzf~f{f|f}f|f|f|f|f}f{ffrffd~4kӣ({~x[HNgffgffuff{f|f}fzffzf}f|f{ffyffzf}f|f}f{f}f|f{f~f{f}fzffwffzf{ffxffzf}f|fzffwffwffvffoff;۵䘴o|}~y^ڙlf~Hpg.ffiffxf}f}fzf~f{f~f{f{f~f{f|f~fyffxffyf~f|f|f|f}fyffwffyf}f|f|f}f|f|f{f~f{f|f}fzffyffqffIffBAșdw}fəGzBfLffpffyf~f{f}fzffzf~f{f}fzffyff{f|f}f{f}f|f|f|f}f{f}f|f{f~fzf~f{f}f{f~fyffzf|f~fxffuffjff.frgHbpי_yq蘯;fqffwffzf}f|f|f|f}f{f}f|f|f|f}f{f}f|f|f|f}f{f~fzf}f|f}f{f}f{f}f}f{f}f{f~fzffzf}f|f|f|f~fxfflff fgNF\y~w#j}4dfvf~f{f}f|f{ffyffzf}f|f|f}f|f{f}f{f~f|f{f|f}f|f}f{f|f~fzffyffzf~f|f{f~fzf~f{f}f|f{ffvffhfffgSt6[{`˕,Zcf`ffwff{f|f}f{f}f|f}fzf~f{f}f}fzf~f{f}f{f~fzf~f{f}f|f|f|f|f}f|f{ffxffwffwffvfftffpffefffgWI sG[x}y<$MagLffqffyf~f|f|f{f~fzffyffzf~f{f|f~fyff{f{ffyf~f}fyffyf~f|f{f}f}fzffyf~f|f{ffwffsffhff(fg[Йcx}kI^Xg:ffmffxffzf~f{f|f~fyff{f|f~fyffzf}f}f{f|f}f{f~f{f{f~f{f}f|f|f|f|f}f{f}f|f|f|f}fzffvffnff$噏|}w^ߙLmO Wgfffffvffzf}f{f~f{f|f}f{f}f}f{f|f~fyffzf|f}f{f}f}fyffxff{f{f~f{f}f{f}f{f~f{f}f{f~fyffwffcff\c,\}wY7tSgffhffuffxffxffwffzf}f}fzf~f{f}f|f{f~fzf~f|f{f}f{f~f{f}f{f|f~fzf~fzf~f{f~fzf~fzf~f|f|f|f}fvffdy4oӢ'z|`ޙINgffjffwffyf~f}fyff{f}f{f~fyffyf~f{f}f|f|f|f|f|f~fzf~fzf}f}f{f~fyffzf~f{f|f}f{f~fzffxffvffpff;۴☸jy}{b֙pcHog1ffkffuffyf~f{f}f{f}f|f|f}fzf~f{f}f|f|f|f|f}f{f}f|f|f}fzffzf}f}fzffzf}f|f{ffyffwffwffpffKffB{Eƙi}~~~jęD|BfKffrff{f~fxffvffyf~f{f~fzf~fzf~f|f|f|f|f|f}f|f|f{ffxffzf}f|f|f|f|f}f{f}f|f|f|f}fzffvffjff.fqgHdp֙az~~q昲;foffvffzf~f{f}f|f{f~f{f|f|f}f|f|f|f{ffyffzf}f|f|f|f~fyffzf}f|f|f|f}f{f|f}f{f~fzffwffsffffffgNG[u{}~y'm{4dfuff{f}f{f~fyffyf~f|f{f}f|f|f|f}fzffzf~fzf~fzf~f|f{f~fzf}f|f}f{f~fyff{f|f}f{f}f}fyfftffffffgSr8Yv` ˒,^cfcffvffyf~f|fzffzf~f{f}fzffzf~fzf~fzf~f{f}f{f}f|f{ffxffxffzf~f{f}f|f{f~f{f}f{f|ffwffkff"fgWM nJ[v~{=$LagMfftff|f|f|f}fzffyffxffxffzf}f|f{f~f{f}f{f}f|f}f{f|f}f|f|f|f|f|f~fzf}f{f~f{f}f{f~fwffiff)fg[~ؙ]w}}oE^\g6ffkffxf~f{f~fzf}f}f{f}f|f|f}f{f}f{f~f{f|f}f{f}f|f{f~f{f}f{f}f{f~f{f}f{f}f|f|f|f}fzfftffjff:fVg^~Ll|~~}dҙ[g*ffjffsffwffzf}f|f{ffxffzf|f~fyffyffzf}f|f|f}f|f|f{f~fzf~f{f}f{f}f{f~f{f|f}fzffwffrffOfgOa;$噏z{^HsI Wgffiffuffvffwffyf}f|f|f|f}f{f}f{f~fzf~f{f}f{f~fzf~f{f}f{f~fyffyf~f|f{f}f|f|f}fzffyffuffaff\c, avY6tSgffeffrffvffzf}f}f{f}f{f~fzf~f{f}f|f|f|f|f~fyffxffzf}f{f}f|f|f}fzffyffxff|f{f}f|f{ffzfxffd{4mӣ'|wZDNg!ffiffuffyf}f}f{f}f{f}f|f{ffyffzf|f~fzf~f{f|f~fyffyffzffzf}f{f}f|f}f{f}f{f}f}fyfftfftffpff;۰昵m|zbԙraHqg0ffnffyf}f}fzf~f{f}f|f|f|f|f}f{f~fzf~f{f|f}f|f|f|f|f|f~fzf}f|f|f}f|f{f~fzf~f{f~fyffxffxffpffJffB~BǙgz}l=BfHffpffuffxffyf}f|f}f{f~fyffzf~f{f|f}fzffyf}f|f|f}f|f|f|f{f~f{f}f|f{f~fzf~f|f{ffxffvfflff0fogHcrҙe}s䘵;fnffrffwff{f|f|f}f{f~fzf}f|f}f{f~fyffzf~f{f}f{f}f|f|f|f|f|f}f|f{f~fyffyffyffzffxffsffgfffgNDZxx#l{4dfuff{f|f}f{f}f|f|f}f{f}f|f{f~f{f|f~fzf}f}fzffzf}f}fzf~f|fzffxff{f|f|f}f|f|f{f}f|f|ffuffhfffgSq:[v~_ ˓,]cfdff|fzf~f{f}f|f|f|f|f}f{f}f|f{f~f{f|f}f{f}f|f|f|f|f}f{f}f|f{ffyffyffxffyffyffyffuffhfffgWM pI\z{=$KagKffsff|f|f|f|f|f}f|f|f|f{ffyffyffzf~f{f|f}f{f~fzf~f{f|f~fzf~fzf~f{f}f|f{f~fzffxffsffeff(fg[ ҙbx~}kK^UgfUg^Ip|xcϙ[g+ffifftff{f{f~fzf~f{f}f|f|f|f|f|f~fyffyff{f|f}f{f}f{f~f{f~fxffuffuffxf~f{f~fzffwffsffOfgNa;$♑zzt\ߙMlO Wgffdffsffxffxffyffzf~fzf~f{f|f~fyffxff{f|f}f|f{f}f|f|f~fxffzf}f|f|f{ffyffyffyffxffeff_c,^y[9rSgffgffxf~f{f|f|f}f}fzf~f{f|f~fyffyf~f{f}f{f~fyffxffyf~f|f|f{ffxffyf}f|f|f|f}f{f~fzf~fzffuffdw4qӠ)z}v]HNgffiffuffyf~f{f~fyffxffzf}f{f}f|f}fzffxffxffzf~f{f|f}f{f~fzf~fzf~f{f}f|f{f~fzf~f|f{f~fxffsff;۵䘴o~{`ؙmg|Hmg2ffjfftffyf}f|f|f|f}f{f~fyffxffzf|f}f{f}f}fzf~f{f}f|f|f|f|f}f|f{f~fzf~f|f{f}f|f|f|f~fwffkffEffBCǙfz}gșDBfEfflffvffzf}f}f{f}f{f}f}f{f}f{f~fzf~f{f|f~fyffzf~fzffyffzf}f}fzffzf}f|f|f}f{f~fyffuffjff-frgH`sәcys昴;fpffxf|f}f|f|f~fxffwffyf~f{f}f{f}f|f}fzffyff{f{ffxffwffxffyf~f{f~fzf~f{f|f~fzf}f}fxffiff!fgNDZw}}x"j}4dftff|f{f~fzf~f{f}f|f{f~f{f|f}f{f}f|f|f|f|f}f{f~fzf~fzffyff{f|f|f|f}f|f{f~fyffwffxffuffhfffgSu7\x}b ˕,Zcf`ffwff{f}f{f}f|f}fzffzf}f}fzf~f|f{f~f{f{ffyffzf}f|f|f}f{f}f{f}f}f{f}f|f{ffxffwffuffhfffgWL oKߙ_|~}w:$OagPffsffyffyffxffzf}fzffxffwffyffyf~f{f|f~fzf~fzffyffzf}f}f{f}f{f~fzffyffyffvffjff,fg[~ љby|lK^Wg;ffnffxf~f{f}f|f|f}fzf~f|fzffxffyf~f{f|f~fzf~f{f|f}f{f}f|f|f|f}f{f}f{f}f|f|f}f{f}f|fzffpff;fXg^Hnzbҙ}[g,ffhffrffxffzf~fzf~f{f}f|f|f{f~f{f}f|f{f}f}fzffyffzf~fzffyffzf~fzffzf}f{f~fzffvffqffLfgLa>$晎{z]KnN Wgffefftff{f|f}f|f{f~f{f|f}f{f}f}fyffyf~f|f{f~f{f|f}f{f~fzf}f|f}f{f}f{f}f|f|f|f}fzffyfftff^ffXc,]~w\8tSgffhffuffxffzffzf}f|f{f~f|f{f~fzf}f}f|f{f~fzffyffyffzf|f}f{f~fzf~f{f}f|f|f{ffyf~f|f{fftffd{4mӣ&x~~w]ߙJNg!ffiffuff{f|f|f}fzffzf}f|f|f}f|f{f}f|f|f}f{f}f|f|f|f}f{f~fzf}f}fzffyffzf}f|f|f}f{f~fxffsffnff;۶㘴p~y_ؙmf~Hng0ffkffvffwffwffyf~fzf~f{f}f|f|f|f|f}f{f}f{f}f|f|f|f}fzf~f{f~fzf~fzffyffyffyffxffwffoffIffBAęh}{hřC}BfKffqffwffwffxff{f}f{f}f|f|f}f{f|f}f{f}f|f|f|f|f|f|f~fzf~fzffyffyffxffyf~f{f}f{ffwffkff-fqgH|hlؙa|~~s瘳;fofftffyf~f{f}f{f}f|f{ffwffvffzf}f|f}fzffxffvffxf}f~fzf}f}f{f}f|f{f~f{f}f|f{f~fyffsfffff fgNI]zw$l|4dfuf}f}f{f}f|f|f|f}f|f{f~fzf~f|f{f}f|f{ffxffyffyffzf}f}fzf~f|f{f~fyffxffzf|f}f{f|ffuffhfffgSt8[x`˖,[cfbffvffwffzf}f|f}fzffyffyf~fzf~f{f}f|f|f|f|f}f{f~f{f|f}f{f~fzf~fzf~f{f}f{f}f{ffwffpffefffgWO lMߙ^y}}<$MagNffsffyffyffzf~f{f|f|f}f|f|f|f}fzffyffyf~f{f}f{f~fzf~f{f}f{f~fzf~fzffyffxffxffuffiff)fg[ ϙd{{kF^Zg:ffoffuffwff}fyf~f}fyffxf~f}fzf~f{f|f~f{f|f|f}fzffxff{f|f|f}f{f~fzf~fzffyffwffvffpffffpffwffxffxffwffxff{f|f}f|f|f{f~f{f}f|f{f}f}fzffyff{f{ffxffxffzf~fzffyffwffqff>fUg^Ji{z_ՙ [g*ffjffwf~f{f~f{f}f{f}f{f~f{f|f~fyffzf}f~fyffzf}f|f|f}f{f}f{f}f}fyffxffzf}f|f{f}f|f{ffrffMfgQa7$#ޙxy\KnM Wg!ffjfftfftffvffzf~fzffyf~f}fzffyf~f|f|f|f}fzffzf}f{f}f|f~fyffzf}f~fyffzf~fzffwffuffaff[c,_vZ6uSgffjffwff{f}f{f~fyffyf~f|f{f}f}fzf~f{f|f}f|f|f{f}f|f}f|f{f|f~f{f|f~fyff{f{f~f{f}f|f{f~fyffsffd{4mӤ%yw[FNg"ffkffvffxffzffzf}f|f{f~f|fzffzf}f|f|f}f{f}f{f}f}f{f}f{f~fzf~f{f|f~fzf~fzf~fzffwffvffsffmf f;۱昳p~~y`ؙne~Hog1ffmffvffxff{f|f|f}fzffxff{f|f~fyffxffzf}f|f|f{ffyffyf~f|f|f|f|f|f}fzffxffxffxffpffJffB}Eșf{}gǙE|BfKffpffxffzf~fzf~f{f}f|f|f{ffyffxff{f}f|f{f}f|f|f}f{f}f{f}f}fzffyffzf~fzf~fzffxffrffhff,fsgHemٙ^zo☵;frff{f|f|f|f|f}f{f~fyffxffzf}f|f|f|f}f{f}f{f~fzf~fzf~f{f}f|fzffxffxf}f~fyffzf{ffwffuffiff fgNI]z{&k~4dfsffyffzf~f{f}fzffwffwffzf|f~fzffxffyffzf}f|f|f|f}fzffzf}f|f|f|f}f{f|f~fzffxffuffffffgSs8[v}b ˑ,^cfbffuffyf}f~fxffvffyf~f{f}f{f}f|f}fzffyff{f|f}f|f{f~fzf~f|f{f~fzf~f{f}f|f}fzffvffrffhff fgWK pJ\w9$OagNffuf}f}f{f}f|f|f}fzffxffxff{f|f|f}f{f~fzf~f{f}f{f}f{f~f{f|f|f}fzffwffwffwffxfftffhff'fg[ Йd}mI^Zg8ffmffvffuffwffzf}f|f|f|f}f{f~fzf~fzf~f{f}f{f~fyffyf~f{f}f|f|f}f{f|f~fzffyf~f{ffxffmff7f\g^Gj}}~|eϙ [g'ffeffqffwff{f}fzffwffyf}f}f{f|f}f|f{f~fyffzf|f}f{f}f|f|f|f}f{f}f{f~f{f}f{f}f{ffxffrffMfgOa9$ ᙓw}~x]KnM Wg ffhfftffxffyf~f{f|f~fzf~f{f|f}f{f~fzffxffzf}f|f|f{ffyffzf}f|f|f~fyffzf}f}f{f|f}f|fyff_ffYc,]~wZ6uSgffhffsffwffyffzf|f~f{f|f~fyff{f|f}f|f{f~fzf}f}fzf~f{f|f}f{f~fyffxffzf|f}f{f~fzf~f{f|ffrffd~4kӤ&z}v_ݙJNgffgffuff{f|f}f{f|ffxff|fzffxff{f}f{f}f|f|f|f}f{f}f|f{f~fzf~f|fzffyff{f|f|f|f|f~fyffvffpff;۲嘴o~}cՙoe~Hng1ffmffvffxffxffyffzf}f|f}f{f}f|f{ffyf~f|fzffzf~f{f|f|f}f{f~f{f|f}f{f}f|f}f{f~fyffwffmffFffB~DǙf|~~~|iƙF{BfIffqff|f{f}f|f|f|f}f{f|f}f{f}f|f|f|f|f}f{f}f}fzf~f|f{f~fzf~f{f|f}f{f~fzf~f{f|f~fyffxffuffhff,frgHcpי_w}yk䘲;fqffyffyffzf}f|f|f|f|f~fyffzf~f{f}f|f{f~f{f}f|f|f{f~f|f|f|f{f~f{f}f{f}f|f{f~f{f|f~fxffsffgff fgNG\w~~{$k~4dfsff|f|f|f|f|f}f{f}f|f{f~f{f|f}fzffzf~fzf~f{f}f{f~fzffyf~f|f|f|f|f|f|f}f{f}f|f{f~fzffvffjfffgSs7[y^˕,Zcf`ffvf~f}f{f~fzf~fzffzf}f}fyffwffxf~f|f|f|f}fzffzf}f|f|f|f~fzf~fzf}f}f{f~fzf~f{f{ffvffiff fgWL pI\z|<$NagNfftffzf~fyffyffyffzf}f}fzffyf~f|f|f|f}f{f}f|f|f|f~fyff{f{ffyffzf~fzffyffxffvffmff+fg[ әaz~~oG^Xg;ffpffxffzf}f}f{f|f}f{f}f}fyffwffzf~fzf~fzffzf}f|f{ffyf~f|f{f~f{f}f{f}f|f|f}f{f~fyffnff;fWg^Kk|~~~~xcЙ [g(ffjffuffvffwffxffzf~f{f|f}f{f}f|f|f|f}f{f~fzf}f}fzffwffvffzf}f{f~fyffzf|f}f|fzfftffPfgNa<$♒xz\JnO Wg ffifftffxff{f|f|f}f{f~fzf}f|f|f|f|f|f}f|f{f~fyffxff{f|f}f{f~fzffxffzf}f}fzf~f{f|ffwffbff]c,^vW5uSgffeffrffwffzf}f|f|f|f|f|f}f{f}f{f}f|f|f|f}f{f~fzf~f{f~fyffxffzf|f}f{f}f|f}f{f}f{f}f}fzffsffd}4lӤ%v{~x\HNgffhffuffxff{f}f{f}f{f~fzffyffzf}f}fzffzf}f|f|f}f{f|f~fyffwff{f}f|f|f|f|f}f{f~fzf~f|fyffqff;۴䘵n|za֙odHpg.ffiffxf|f~fzf~f{f}f{f}f}fyffwffzf~fzffxff{f{f~fzffyffyffyf~f{f}f|f}fzffyffyffuffnffJffB}Dřj|~zgƙB~BfJffrffzf}f{f}f}fzffyffzf}f|f|f}f|f{f~fzf~f|fzffxff{f|f}f|f{f~fzffyffyffxff{f|ffuffkff0fogHarԙb{t꘯;fqfftffwff{f|f}f{f}f{f~fzf~f{f|f}f|f|f}fzffzf~f{f|f}f{f~fzf~f{f}f|f|f|f}f{f~fzf~f{f|f~fxffjff fgNG[xx!i}4dfwf|f}f|f|f}fzffzf}f|f|f|f~fyffzf~f{f}f{f~fzf~f{f|f}f|f|f|f}fzff{f|f}fzffzf~fzffxfftffifffgSs7Zuy|b ˑ,^cfaffvff|fzffxff{f|f}f|f{f}f}fzffwffxff|fzffyffzffyffyffzffyffzf|ffxffwffuffffffgWM oK]y}~}y:$PagQffsffvffwffzf}f}fzf~f{f|f~f{f|f|f|f}f|f}fzf~f{f}f}f{f|f}f{f~f{f|f|f}f|f{f~fyffwfftffiff*fg[ Йd{}mF^Yg:ffmffvffzf|f}f}fzffxffzf~fzf~f{f}f{f~fzf~f{f|f~fyffxff|f{f}f|f|f|f~fyffxffwffuffnff8f[g^Gk{zfΙ [g+ffjffuffyffzf|f~fzf~f{f}f{f}f|f{ffyf~f{f|f}f|f|f|f|f|f}f{f}f|f|f|f|f|f}f|f{f~fyffvffqffKfgJa?$䙐z~v[LlP Wgffgffsffwffyffzf}f|f|f|f}f{f}f{f~fzffyf~f|f{f~f{f{ffyffzf|f}f|f}f{f|f|f}f|f}f{f|f~fxffbff]c,_xY5vSgfffffrffwffyffyffxffyffyffzf~f{f|f}f|f|f|f|f}f|f|f{f~f{f~fyff{f|f}fzffzf}f|f{f~fzffuffdz4nӣ%x}w\INg"ffiffuffyffyffzf~f{f}f{f~fzf}f}f{f~fzf}f|f|f}f|f{f~fyffyffzf|f}f|f}f{f}f{f}f|f|f~fxfftffpff;۱嘶ky{_ٙmeHsg,ffhffrffvffzf~fzf~f{f}f|f{f}f|f}f{f}f{f~fyffyf~f{f}f{ffxff|fzffxf~f}fzffxfftffrffoffLffB}DǙf|~xdəFzBfMffpffvffyf~f|f{f~fzf~f{f|f~fyff{f{ffxffzf}f|f|f|f|f}f{f}f|f|f|f|f|f}f|f{f~fzffxffvffiff+ftgHbqԙc{~o㘶;fsffzf}f{ffwffxf~f{f}f|f|f|f}f{f}f|f|f}f{f|f}f|f|f|f}fzf~f{f}f|f|f}f{f}f{f}f|f~fyffyfftffefffgNDYz~y'ox4dfwf}f{f~fzf~f{f}f{f~fzf~fzffyffxffzf~f{f}f{f}f|f{f~fzf~f{f}f{f}f{f}f|f|f~fwffuffvffrffefffgSs8]x~]˒,^cfcffyf}f|f|f|f}f{f}f|f{ffyffzf}f}fzffxffzf~f{f~fyffyffyf~f{f|f~fzf}f}fyffwffxffvffhfffgWM oJ\wz!9$NagLffsff{f}f{f}f{f}f|f|f|f}f{f|f}f{f~f{f|f|f}f|f|f}fzffzf~fzf~f|f{f~fzf}f}f{f|f~fzf}f~fvffkff-fg[|ՙ`x|mE^\g7fflffvffvffuffxffxffyf~f|f{f~fzffyffyff{f|f}f{f}f|f|f|f}f{f}f|f|f|f|f|f}f{f~fxffnff:fXg^Imz}}dЙ [g(ffhffsffxf~f|f{f}f}fzffyf~f|f{f~f{f|f|f}fzffyf}f}fzffzf}f|f}f{f}f{f~f{f|f}f{f}f}fzfftffOfgNa;$ᙔu~x[IqJ Wg#ffmffzf}f|f}fzf~f{f|f~f{f{f~fzf~f{f}f{f~fzf~fzffyffyf~f{f}f|f}fzffyffxff{f|f}f|fzffvffaffZc,`~x[:qSgffkffxf~fzffxff{f{ffyffyff{f{ffxffyffyffxff{f|f}f|f{f~fzf~f|f{f~fyffzffyffyff|fuffd}4kӥ%x~}x]HNg!ffiffuffzf}f|f|f|f}f{f~fzf~f{f}f{f~fzf~f{f|f~fyffxff|fzffyffzf~f{f|f~fyffyf~f{f}f{fftfflf f;۴㘶m}y`ؙndHug*ffgffrffwffxffyf}f~fyff{f{ffzf}f|f{f}f}f{f}f|f{ffyf~f{f}f|f|f}fzf~f{f}f|f|f{f~f{f{ffoffHffB}Dři{}}ziřE{BfLffpffvffwffzf}f|f|f}f{f}f{f}f}fzf~fzf~f{f}f|f{f~fzf~f|f{f~fzf}f~fxffvff|fzf~f|fzffuffkff.fqgHcqԙc{}p꘮;fqffwffzf|f}f{f~fzffxffwffyf}f}f{f}f}fzf}f~fyffxff{f}f|f{f~fzf~f|fzffxff|fyffuffsffgfffgNH[v~}&l}4dfqffyffyffwffvffvffyf|f~fzffyf~f{f|f~fzf}f|f|f|f}f{f~fzf}f|f}f{f~fyffyf~f|f|f{ffvffgfffgSx4Xw^˖,Zcfaffyf|f}f{f}f|f|f|f|f}f{f}f|f|f}f{f}f{f~f{f}f{f}f{f~f{f|f}f{f~fzf}f}fzffxff{f{ffxfftffhff fgWJ rGZx}}z:$PagQfftffxff{f|f|f}f|f{f~f{f|f}f{f}f|f|f|f}f{f|f~fyffyf}f~fxffzf|f~fzf}f}fzffzf}f|f}fyfflff+fg[ ϙcx||ymI^Ug$LagLffofftffxf~fzffyff{f|f|f}fzffzf}f}fzf~f{f}f|f|f|f|f}f{f}f|f|f}f{f|f~fzf~fzffxfftffkff+fg[ Ιf|~kJ^Yg9fflffwffzf}f|f|f{ffyffzf}f}fzffzf}f}f{f}f|f|f|f}f|f{f~fzf~f{f|f}f{f~fzf~fzf~f{ffvfflff9fYg^Hk}{cљ [g-ffnffwffzf}f{f}f{f~f{f|f|f|f}f{f}f|f|f}fzf~f{f~fzf}f}fzf~f{f}f|f|f|f|f}f{f}f{ffxffvffqffOfgPa;$晎{}w\JoM Wgffhffuffyf~f{f}f{f}f|f{f~f{f{ffvffwff|fyffxffxff{f}f{f|f}f|f}fzf~f{f}f|f|f|f|f}f|fzffcff]c, a~{u[9rSgffiffwff{f|f}f{f}f|f|f}f{f}f|f|f}f{f}f|f{f~f{f|f~fxffxff{f{ffyffyffzf~f{f|f}f|f{f~fzffsffd4hӨ"w~z]FNg!ffgfftffxffzf~fzf~f{f}f{f}f{f}f|f{ffyf~f{f}f|f}fzf~fzffzf}f|f{f~f|f{f}f{f~f{f}f{f~fyfftffmff;۲昳p}}x`יocHpg0ffnffwffxffyffzf}f{f}f}f{f}f{f}f|f|f}f{f~fzf~fzffzf~fzf~f{f}f|f{f~f{f}f{f}f{ffyffxffqffJffB|Eșgz{ięB~BfMfftff|f|f|f~fxffxff{f{f~f{f|f~fyffyf}f}f{f~fzf~fzf~f{f}f{f~fzf~f{f}f|f}fzf~f|f{f~f{fzffiff-frgHbrԙb{o嘳;fqffxffyffzf}f|f}f{f}f{f}f|f|f|f}f{f~fzf}f|f|f}f|f{f}f|f|f}f{f}f|f|f}fzffyffyf~f{f|f~fxffhfffgNLݙ_x~}}|%i4dfvfzffuffwffzffzf}f|f{f~f|f{f}f|f{f~f{f}f|f{f}f{f~fzffyf~f{f}f|f|f|f|f~fyffxffyffvffffffgSu8]x]˖,Zcfafftffvffyf}f|f}fzffyffyffyf~f|f{f}f}fyffwffzf}f|f}fzf~f{f}f|f}fzf~f{f}f|f}fyffsffffffgWL pJ^z~}x 8$QagPffsffyff{f|f|f}fzffzf~fzffxffzf}f}fzf~f{f}f|f|f|f|f}f{f}f|f|f|f|f|f}f{f~fzf~fzffwffiff'fg[ љbwz|lG^Zg9ffnffxffxffwffzf}f}fzf~f{f}f|f|f|f|f}f{f}f|f|f|f|f|f~fyffzf}f|f}fzffyffzf~fyffvffnff=fVg^Fn|zbҙ [g)ffjffwf~f|f|f}fzffyff{f|f|f|f}f{f~fyffyf~f|fzffxff{f}f{f}f{f}f}fzffyffzf~f{f}fzffpffIfgMa:$㙐{~|w[GsI Wgfffffuff|f|f{f~fzf~f{f|f}f|f{f}f|f|f}f|f{f~fzf~f{f}f{f~fzf~f{f|f}f|f|f}f{f}f{f~f{f|f|f~fwff]ffWc, azZ6uSgffdffsffvffwffwffzf|f~fzf~fzf~f{f}f|f{f~fzf~f{f|f~fzf~fzf~f{f~fzf}f|f|f}f|f{f~fzf~f{f}f|fxffd{4lӤ&y~x\FNg!ffiffvffzf~fzf~fzffyffzf~f{f|f}f|f|f}f{f|f~fyffyf~f{f|f}f|f|f|f|f|f}f|f|f|f|f}fzffwffvffsff;۴䘵n|~}y`ؙmg|Hpg-ffgffsffzf}f}fzf~f{f}f|f{f}f|f|f}f{f|f}fzffyf~f{f|f}f|f}fzffxffxffxffxffwffwffvffoffIffBAęh{}yiřE{BfJffoffwffyf~f|f|f}fzf~f{f}f|f}fzf~f|f{f~fzf}f~fyffzf}f|f}f{f}f|f{ffxffzf}f|f|f}fzffvfflff1fpgHarՙaw}}p䘳;fmffvff{f{f~fyff|f{f~fyffzf~f{f|f}f{f}f|f|f|f|f}f{f~fzf~fzffyffzf~fzf}f}f{f~fyffyffvffjff$fgNF]x}~{'m{4dfyfzffzf~f{f}f{f~fzf~f{f}f{f}f{f~fzf~f{f|f}f{f~f{f|f|f}f{f~fzf}f|f|f|f}fzffyffwffxffvffhfffgSt6Yu|~[˗,Ycfaffwffzf~f{f}f{f~fzf~f{f}f{f~fzffxffzf}f|f{f~f{f}fzffzf}f}fzffyffzf~f{f|f|f~fxffrffefffgWL nLߙ^y~z<$MagNffrffwffxf~f|f|f}f|f{f}f|f|f}f{f~fzf}f}fzffwffxffyf~f|f{f~f{f}f|f|f|f}f{f}f|f|f}fyffnff,fg[ ҙc}}{lL~^Ug=ffnffuffyf~f{f}f|f{f~f{f|f~fzf}f}fzf~f|f{f~fzf}f|f}f{f}f{f}f|f}fzffyff{f|f}f|fzffwffoff$晏z}xZJmP Wgffiffvffyf~f{f}f{f~fyffzf|f~fyffyf~f{f}f|f{f~f{f}f{f}f|f|f}f{f}f|f|f}fzffzf~f{f|f{fftff^ff[c,\z]8tSgffgffvffzf~fzf~f{f|f~fyf~f|f{ffyffyff{f|f~fyffxffzf|f~fzf~f{f}f{f~fzf~f{f}f{f}f|f|f{ffrffd4jӥ%y~~~y^GNg"fffffsffwffyf~f|f{f~fzf~f|f{f~fzffyffzf}f}fzf~f{f|f~fyffxffyffyffxffzf|f~fyffxffvffqff;۲瘱r}xa֙pcHqg.ffifftffxff{f{ffxffzf}f|f{f}f}fzffvffvffyf}f}fzffyffzf~fzf~f{f}f|f{f~fzffyffvffoffJffB|Dęj}~}ziÙ@BfIffnffvff{f|f|f|f}f{f~fyff{f|f~fxff{f|f}fzffzf~fzffyffxff{f}f|f{f~fzf~f|f{f~fzf~fzffpff3fmgHbsҙd}o㘵;fpffuffxffzffzf}f{f~fzffyffzf}f|f|f}fzffzf}f|f|f|f}f{f}f|f|f{ffyffzf}f|f|f}f|fzfftffefffgNIߙ_yz"hӀ4dfrff{f|f~fzf}f|f}f{f}f|f{f~f{f|f}f{f}f}f{f}fzff{f|f~fxffxffzf~f{f}f{f}f{f~f{f}f{f|ffuffbfffgSv4Yx}~a˕,\cfeffzf|f}f{f~fzf~f{f}f{f~fyffzf|f}f|f{ffxff|fzffzf}f}fzf~f{f}f|f|f|f|f}fzffyffwffrffefffgWL oJ]x{8$QagOfftffzf}f|f|f}f{f}f|f|f|f|f}f|f|f{f~f{f}f{f}f{f~fzf}f}fzffyffyffzf~f|fzffyffwffsffhff(fg[ЙczyhF^Yg:fflffuffzf~fzffzf}f{f}f}fzffyffzf~fzf~f|f{f~fzf~fzffzf}f|f|f|f}f{f}f|f|f|f|f|f~fxffmff8fZg^Ioyaә [g)ffiffsffuffxff{f{f~f{f}f|f|f|f}f{f}f|f}f{f|f|f}f|f|f|f|f|f|f}f|f|f{f}f|f}f{f}f{f}f{ffsffLfgNa:$ ᙒxz_ݙOjQ Wgffjffvffwffxffxffyf~f}fyffyf~f|f|f{ffxffuffyf}f}f{f|f~fzf}f|f|f}f{f}f{f}f|f|f{ffuffaffZc,^z\9qSgffhffvffyffxffxffzf~f{f{f~fzffyffzf~fzf~f{f}f|f{f~f{f}f|f{f~fzffyffzf}f}fzffyffzfftffd}4kӥ%zxZGNg ffkffvffyf}f}f{f}f{f~f{f}f{f}f{ffyf~f{f}f|f}fzf~f{f}f}fyffzf~f|fzffyffxffwffxffxfftffpff;۰蘱rz`ؙne~Hog1ffmffxffzf~fzffyff{f{ffxffwffxffzffyffzf}f}f{f~fyffyffzf|f~fzf~f{f}f{f}fzffwffnffGffBAřhz|hƙD}BfKffrffzf~fzf~fzf~f{f|f}f{f}f{f}f{f}f|f|f|f|f|f}f{f}f{f~fzf~fzffzf~fzf}f|f~fyffwffxffuffjff/fpgHdoؙ^t}s阯;fqffzf}fzffzf}f~fxffzf}f|f|f|f}f{f~fzf~fzf~f{f}f|f{f~fzf}f~fyffxff{f}f|f{f}f|f|f~fxfftffiff!fgNIߙ]w|}~w#j4dfrff{f|f{ffwffvffyf~f{f}f{f~fyffzf~f{f}f{f}f|f|f}f|f{f~fzf~f{f}f{f}f{f~fyffuffrffnffcfffgSs8\x~^ˑ,]cf^ffsff|f{f}f|f|f|f}f{f|f}f{f}f|f|f{f~fzffyffzf~fzffxffvffyf}f}f{f|f~fyff{f{ffzf{ffkff fgWM oI[xx<$NagPfftffyf~f{f}f{f~fzf~f{f}f{f~fzf~f|fzffxff|fzffyff{f}f{f|f}f|f}f|fzffzf~f{f}fzfftffjff+fg[ ҙc{{oD^[g8fflffvffxffzf~f{f}f{f~fzf~f{f}f{f}f|f{ffxffxf~f{f~fzffyf~f|f{f~fzf~f{f}f{f~fyffvffoff;fYg^Fpzf̙[g*ffkffuffuffwff{f|f}f{f}f|f|f|f}fzffzf~f{f}f{f~f{f|f~fyffyf~f{f|f}f|f|f|f|f|f}f{f}f~fuffPfgQa9$噎||v\JpK Wg!ffgffrffyf|f~fzffyf~f{f~fzf~fzf~f|f{f}f|f|f}f|f|f{ffxffxffxffyf~f{f}f|f|f|f|f}f{f~fxffbffYc,`~zY3vSgffeffuff{f|f}f{f}f{f~fzf~f{f|f~fyff{f|f}f|fzffvffxffzf}f}f{f}f|fzffyf~f{f}fzffzf~f{f~ftffd|4mӢ(y~{\ENg ffhffsffwffxffyffxffzf|f~fyff{f|f}f{f|f}f}fyffvffzf}f|f|f{f~f{f}f|f{f~fzf~f{f}f{ffvffpff;۱瘲pxbԙqcHog0fflffsffsfftffxf~f{f}f{f~fzf~f{f}f{f}f{f~fzffyffyffzf~f{f|f|f}f{f}f|f{ffxffyffxffpffHffB?řgz|ięABfGffnffyffzf}f|f{ffyf~f|f{f~fzf~f{f~fzf~fzffzf}f}fyffwffyffzf~f{f|f}f{f}f}fzffwfftffkff0fogH}fn֙bzs昳;fofftffxf~f}fzf~f{f|f~fyffzf~f{f|f}f{f~fzffyffxff{f}f|f{f~f{f|f}f{f}f|f|f|f|f|f}f{f~fxfflff"fgNDZxx%n{4dfuf~f{f~fyffyf~f|f{f~fzf~f{f}f|f{f~fzf~f{f}f|f|f|f}fzffxffyf}f~fyffzf}f}fzffxffvfftffifffgSv3Vu}` ˓,\cf`ffxf~f{f~fyffzf~fzf~f{f}f{f~fyffyf~f|f{f~fzf~f{f|f~fzf~fzf~f{f~fyffzf~f{f}f{ffwffpff`fffgWJ qI]z}>$LagMffoffuffyffyffzf}f}f{f}f{f~fzf~f{f|f~fyffxffxffxffzf}f|f}fzffzf}f|f|f}f{f}f|f{ffoff0fg[~ љd||lI^Yg9ffmffwff{f|f|f}f{f~fzf~fzffyffzf~f{f|f}f|f|f|f|f|f}f|f{f~fzf}f}f{f}f|f{f~fzffxffuffmff;fXg^Hl}x_ԙ [g*ffkffuffwffvffxffzf}f|f{ffxffwffzf~fzf~fzf~f|f|f|f|f|f}f{f~fzf~f{f|f~fzf~fzffxffpffKfgMa;$噎|{xt]ޙOjP Wgffhffvffzf}f|f{ffxffxff{f{ffyffzf~f{f}fzffzf~fzf~fzf~f{f}f|f|f{ffyffzf}f}f{f|f~fxffdff^c,_}}w[7tSgffhffvffyff{f|f}f{f}f{f~fzffyf~f{f|f~fzf~fzf}f}f{f~fyffzf~f|f{f~fyffyff{f|f|f}f{f~fzffuffd{4mӣ'z~x]HNgffgffrffwff|fzf~f|f|f|f|f|f}f|f|f|f|f|f~fzf~fyffyffzf}f{f~fzffzf}f|f}f{f}f|f|f|f~fxffuffpff;۱瘳o~xaԙs`Hpg0ffjffrffwff{f|f}f{f}f|f{ffxffwffyf~f{f|f}f|f|f}f{f|f~fyffzf|f}f{f}f}fzf~f{f}f{f~fxffoffIffB~CǙgz|iƙFzBfIffoffxffzf~f|fzf~f{f}f|f{f~fzffyffzf}f|f|f~fyffyffyf~f|f{f~fzf~f|f{f~fyffxffwffvffmff/fqgHarԙcz~p嘴;fofftffyff{f{f~f{f|f}f|f{f~fzf~f|f{f}f|f|f}f{f}f{f~fzffyffyffxffwffzf~fzffxffuffsffhff!fgNE]z{'nz4dfwf|f~fzf~fyffyffyffzf~f{f|f}f|f|f}fzffyff{f|f}fzf~f|f|f|f|f|f}f{f}f{ffyf~f{f}f{ffvffhfffgSq8]{\˖,\cfeffyf~fzf~f{f}f|f|f{ffyffzf}f|f}f{f}f{f~f{f|f}f{f}f|f|f|f|f}fzffxffyf~f{f}f}fzf~f{f{ffkff fgWK pJ\w~~y<$KagKffqffzf}f}f{f}f{f}f}f{f|f}fzffxffzf~fzffyffyffyf~f|fzff{f{ffwffyf}f}fzffwfftffgff&fg[ ЙcznK^Zg9ffnffyf|f~f|f|f|f{f}f}f|f{f~fyff{f|f}f{f}f{f~fzffyffzf}f}fzffyffzf~fzf~fzffwffuffoff:fZg^Ify}wcϙ [g(ffiffvffyffyff{f|f|f}f{f~fzf}f}f{f}f|f{f~fzf~f{f}f{f}f|f{f~f{f|f~fyffyffxffyffxffqffOfgQa8$!ᙒx{aܙPjO Wg ffgffrffwffzf~fzffyf~f|f{f~fzf~f|fzffyffyf~f{f|f}f{f~fzf~fzf~f|f{f~fyffyf~f{f}f|f|f{fffff]c,a}tW5uSgffgffvf~f}f{f}f|f|f|f}f|f{ffwffuffvffxffwffyf}f}f{f}f}fzffzf}f|f|f}f{f~fzf}f}fzffzf}fxffdz4mӤ'|~xYB"Ngffeffsffyf~f{f}fzffzf~f{f|f|f}f{f}f}fzf~f{f|f~fzf~fzffyffzf}f}f{f|f}f{f}f|f|f|f|f}fzfftffnff;ۯ阱q}v]ٙncHrg.fflffwffzf}f|f}fzf~f|f{f~fzf}f~fxffvffvffzf|f}f{f}f|f|f|f|f}f{f}f}fyffyf~f{f~fyffuffnffIffB{FǙh{~kÙB}BfMfftff|f{f}f|f|f}fzffyffzf}f}fzffyff{f{f~f{f}f|f{f}f|f}f{f|f}f{f~fzf~fzffzf}f|f{ffxfflff/fpgHenי`x~~~n;fqffuffxf~f}fzf~f|fzff{f{ffxffzf}f|f|f|f}f{f}f|f|f}f{f}f{f~fzffyf~f{f}f}fyffxffwfftffffffgNG]v~x"j~4dfsffyffzf~f{f{f~f{f~fzf}f|f{ffxffxff|f{f~fzf~f{f}f{f}f|f|f}f{f}f{f~f{f}fzffxffxffwffgfffgSu5Wt^˕,\cfaffqfftffxffzf~f{f}fzff{f|f}fzf~f|f|f}fzf~f{f}f|f}fzffyffzffyf~f{f}f|f}fyffvffuffkff"fgWL oJ\yy;$MagLffpffwffxffzf}f}f|f{f~fyff{f|f}fzf~f|f{f}f|f{f~f{f|f}f{f~fzffxffzf~fzf~fzffyffxffmff-fg[ ϙe}~nI^Xg;ffmffvff}fyff{f|f}f{f|ffxffyf}f~fzf~fzf~f{f}f}fzf~f{f}f|f|f}f{f}f{f~fzffyffyffwffmff;fXg^Jm}{dϙ [g,fflffxf~f{f~fzf~f{f}f{f~fzf~f{f|f~fzf}f|f|f|f}f{f~fzf}f|f}f{f~fyff{f|f|f|f}fzffwffuffpffLfgQa7$"ᙐ|z^KnN Wgffdffqffvffyffzf~fzffxffxff{f{f~f{f|f}f{f}f}fzf~f{f|f~fzf}f|f|f|f}f{f}f|f|f|f|f}f}fxffaffZc,`}|x[5wSgffhfftffvffxf~f{f|f~fzf~fzf}f}f|f{f}f{f~f{f}f{f|f}f|f|f}fzffyffzf~f{f}fzffxffyffyffzfzffdz4kӧ"v~z^ߙINgffkffyf~fzffyffzf~f{f}f{f}f|f|f|f}f{f}f{f~fzf~f{f|f~fzf~f{f}f{f}f|f|f}f{f|f}f{f~fzffxffwfftff;۲瘲pz}{bՙqaHpg0ffnffvffvffvffzf}f|f|f}f{f}f|f{f~f{f}f|f{f~fzf~f|fzffxff{f|f~fyffzffyffyff{f}fyffpffJffB~Aęiz}wb˙E}BfJffpffvffxff{f}fzffyffzf~f{f|f|f|f}f|f{f~fzf~f{f}f{f~fzf~f{f|f~fyffzf}f}f{f|f~fxffuffjff1fmgH{gnי_x~~r蘰;foffuffxffyf~f|fzffyf~f{f}f{f~fzf~f{f}f{f}f{f~f{f|f}f{f}f|f|f|f}f|f|f}fzffzf~fzffxfftffiff"fgNH]x~~~{$i~4dfwfzffxffzf|f}f{f~f{f|f}f{f}f|f|f|f}f{f}f{f}f}fzf~fzffyffyffzf~fzf~fzffyffxffxffsffdfffgSt7Zx^˕,[cfdffzf{ffxffxffzf}f|f|f|f|f|f}fzffyffzf~f{f}f{f}f|f|f}f{f}f{f~fzf~f{f|f}f|f{ffwffsffffffgWN oHXt}~{9$PagMffrffyf~f{f|f}f{ffxffyf~f}fyffzffyffwffyf~f{f}f{f~fzf~f{f}f{f}f{f~f{f|f|f}f{ffwffnff.fg[֙]y|nJ^Vg;ffkfftffxffyffzf|f~fzffzf}f{f~f{f}f|f|f{ffyf~f|f{f~f{f{f~f{f}f|f{f~fzffzf}f|f}fyffmff7f]g^Em}cҙ~[g-ffkfftffwff|f{f}f|f{f~f{f}f|f{f}f|f|f~fyffzf}f~fyffwffyf~f|f{f}f|f{f~f{f}f|f{f~fyffqffKfgNa:$㙏|~x\LlP Wgffhffsffvffyf~f{f|f}f{f}f}fzf~fzf~f{f}f|f|f|f}fzffzffyffyff{f}f{f}f{f~f{f|f}f{f}f}fyffdffZc,[|]8telektroid-2.0/test/sample_fs_tests.sh000077500000000000000000000053021416764361200200600ustar00rootroot00000000000000#!/usr/bin/env bash TEST_NAME=auto-test echo "Getting devices..." DEVICE=$($ecli ld | head -n 1 | awk '{print $1}') [ -z "$DEVICE" ] && echo "No device found" && exit 0 echo "Using device $DEVICE..." echo "Testing info..." $ecli info $DEVICE:/ [ $? -ne 0 ] && exit 1 echo "Testing df..." $ecli df $DEVICE:/ [ $? -ne 0 ] && exit 1 echo "Testing ls..." $ecli ls $DEVICE:/ [ $? -ne 0 ] && exit 1 echo "Testing mkdir..." $ecli mkdir $DEVICE:/$TEST_NAME [ $? -ne 0 ] && exit 1 $ecli ls $DEVICE:/$TEST_NAME [ $? -ne 0 ] && exit 1 echo "Testing upload..." $ecli ul-sample $srcdir/res/square.wav $DEVICE:/$TEST_NAME [ $? -ne 0 ] && exit 1 echo "Testing upload (loop)..." $ecli ul-sample $srcdir/res/square_loop.wav $DEVICE:/$TEST_NAME [ $? -ne 0 ] && exit 1 output=$($ecli ls $DEVICE:/$TEST_NAME) echo $output type=$(echo $output | awk '{print $1}') size=$(echo $output | awk '{print $2}') name=$(echo $output | awk '{print $4}') [ "$type" != "F" ] || [ "$size" != "93.81KiB" ] || [ "$name" != "square" ] && exit 1 echo "Testing upload (nonexistent source)..." $ecli ul-sample $srcdir/res/foo $DEVICE:/$TEST_NAME [ $? -eq 0 ] && exit 1 echo "Testing download..." $ecli -v download $DEVICE:/$TEST_NAME/square [ $? -ne 0 ] && exit 1 actual_cksum="$(cksum square.wav | awk '{print $1}')" rm square.wav [ "$actual_cksum" != "$(cksum $srcdir/res/square.wav | awk '{print $1}')" ] && exit 1 echo "Testing download (loop)..." $ecli -v dl-sample $DEVICE:/$TEST_NAME/square_loop [ $? -ne 0 ] && exit 1 actual_cksum="$(cksum square_loop.wav | awk '{print $1}')" rm square_loop.wav [ "$actual_cksum" != "$(cksum $srcdir/res/square_loop.wav | awk '{print $1}')" ] && exit 1 echo "Testing download (nonexistent source)..." $ecli dl-sample $DEVICE:/$TEST_NAME/foo [ $? -eq 0 ] && exit 1 echo "Testing mv..." $ecli mv $DEVICE:/$TEST_NAME/square $DEVICE:/$TEST_NAME/sample [ $? -ne 0 ] && exit 1 echo "Testing mv..." $ecli mv $DEVICE:/$TEST_NAME/foo $DEVICE:/$TEST_NAME/sample [ $? -eq 0 ] && exit 1 echo "Testing rm..." $ecli rm $DEVICE:/$TEST_NAME/sample [ $? -ne 0 ] && exit 1 echo "Testing rm (nonexistent file)..." $ecli rm $DEVICE:/$TEST_NAME/sample [ $? -eq 0 ] && exit 1 echo "Testing rmdir..." $ecli rmdir $DEVICE:/$TEST_NAME [ $? -ne 0 ] && exit 1 echo "Testing rmdir (nonexistent dir)..." $ecli rmdir $DEVICE:/$TEST_NAME [ $? -eq 0 ] && exit 1 echo "Testing recursive mkdir..." $ecli mkdir $DEVICE:/$TEST_NAME/foo [ $? -ne 0 ] && exit 1 echo "Testing recursive rmdir..." $ecli rmdir $DEVICE:/$TEST_NAME [ $? -ne 0 ] && exit 1 echo "Testing ls (nonexistent dir)..." $ecli ls $DEVICE:/$TEST_NAME [ $? -eq 0 ] && exit 1 echo "Testing ls (nonexistent dir inside nonexistent dir)..." $ecli ls $DEVICE:/$TEST_NAME/foo [ $? -eq 0 ] && exit 1 exit 0